From b2b93d9f87f070ca46a407a8f3d755c9b227f7c1 Mon Sep 17 00:00:00 2001 From: Sotr Date: Wed, 10 Apr 2019 02:22:38 +0800 Subject: [PATCH] Ability to modify api --- .../aikar/timings/FullServerTickHandler.java | 84 + .../co/aikar/timings/NullTimingHandler.java | 68 + .../co/aikar/timings/ThreadAssertion.java | 0 .../co/aikar/timings/TimedEventExecutor.java | 83 + .../main/java/co/aikar/timings/Timing.java | 0 .../java/co/aikar/timings/TimingData.java | 122 + .../java/co/aikar/timings/TimingHandler.java | 0 .../java/co/aikar/timings/TimingHistory.java | 354 + .../co/aikar/timings/TimingHistoryEntry.java | 58 + .../co/aikar/timings/TimingIdentifier.java | 116 + .../main/java/co/aikar/timings/Timings.java | 293 + .../java/co/aikar/timings/TimingsCommand.java | 122 + .../java/co/aikar/timings/TimingsExport.java | 355 + .../java/co/aikar/timings/TimingsManager.java | 188 + .../aikar/timings/TimingsReportListener.java | 75 + .../co/aikar/timings/UnsafeTimingHandler.java | 53 + api/src/main/java/co/aikar/util/Counter.java | 38 + api/src/main/java/co/aikar/util/JSONUtil.java | 140 + .../java/co/aikar/util/LoadingIntMap.java | 76 + .../main/java/co/aikar/util/LoadingMap.java | 368 + .../main/java/co/aikar/util/MRUMapCache.java | 111 + .../destroystokyo/paper/MaterialSetTag.java | 190 + .../com/destroystokyo/paper/MaterialTags.java | 382 + .../com/destroystokyo/paper/Namespaced.java | 40 + .../destroystokyo/paper/NamespacedTag.java | 142 + .../destroystokyo/paper/ParticleBuilder.java | 478 ++ .../java/com/destroystokyo/paper/Title.java | 373 + .../paper/VersionHistoryManager.java | 144 + .../paper/block/TargetBlockInfo.java | 54 + .../paper/entity/Pathfinder.java | 170 + .../paper/entity/RangedEntity.java | 31 + .../paper/entity/TargetEntityInfo.java | 38 + .../paper/event/block/AnvilDamagedEvent.java | 148 + .../paper/event/block/BeaconEffectEvent.java | 86 + .../paper/event/block/BlockDestroyEvent.java | 92 + .../paper/event/block/TNTPrimeEvent.java | 114 + .../event/entity/CreeperIgniteEvent.java | 54 + .../entity/EnderDragonFireballHitEvent.java | 79 + .../event/entity/EnderDragonFlameEvent.java | 61 + .../entity/EnderDragonShootFireballEvent.java | 61 + .../entity/EndermanAttackPlayerEvent.java | 101 + .../event/entity/EndermanEscapeEvent.java | 87 + .../event/entity/EntityAddToWorldEvent.java | 32 + .../entity/EntityKnockbackByEntityEvent.java | 82 + .../event/entity/EntityPathfindEvent.java | 82 + .../entity/EntityRemoveFromWorldEvent.java | 29 + .../entity/EntityTeleportEndGatewayEvent.java | 31 + .../event/entity/EntityTransformedEvent.java | 92 + .../paper/event/entity/EntityZapEvent.java | 65 + .../event/entity/ExperienceOrbMergeEvent.java | 87 + .../event/entity/PhantomPreSpawnEvent.java | 31 + .../PlayerNaturallySpawnCreaturesEvent.java | 64 + .../event/entity/PreCreatureSpawnEvent.java | 104 + .../event/entity/PreSpawnerSpawnEvent.java | 29 + .../event/entity/ProjectileCollideEvent.java | 67 + .../event/entity/SkeletonHorseTrapEvent.java | 47 + .../entity/SlimeChangeDirectionEvent.java | 38 + .../event/entity/SlimePathfindEvent.java | 53 + .../paper/event/entity/SlimeSwimEvent.java | 17 + .../entity/SlimeTargetLivingEntityEvent.java | 31 + .../paper/event/entity/SlimeWanderEvent.java | 17 + .../paper/event/entity/TurtleGoHomeEvent.java | 49 + .../paper/event/entity/TurtleLayEggEvent.java | 87 + .../event/entity/TurtleStartDiggingEvent.java | 62 + .../event/entity/WitchConsumePotionEvent.java | 70 + .../event/entity/WitchReadyPotionEvent.java | 80 + .../event/entity/WitchThrowPotionEvent.java | 81 + .../executor/MethodHandleEventExecutor.java | 42 + .../StaticMethodHandleEventExecutor.java | 43 + .../asm/ASMEventExecutorGenerator.java | 47 + .../event/executor/asm/ClassDefiner.java | 35 + .../event/executor/asm/SafeClassDefiner.java | 66 + .../event/player/IllegalPacketEvent.java | 70 + .../PlayerAdvancementCriterionGrantEvent.java | 63 + .../event/player/PlayerArmorChangeEvent.java | 137 + .../player/PlayerConnectionCloseEvent.java | 95 + .../event/player/PlayerElytraBoostEvent.java | 85 + .../event/player/PlayerHandshakeEvent.java | 221 + .../event/player/PlayerInitialSpawnEvent.java | 47 + .../paper/event/player/PlayerJumpEvent.java | 106 + .../player/PlayerLaunchProjectileEvent.java | 83 + .../event/player/PlayerLocaleChangeEvent.java | 50 + .../player/PlayerPickupExperienceEvent.java | 80 + .../event/player/PlayerPostRespawnEvent.java | 52 + .../event/player/PlayerReadyArrowEvent.java | 93 + .../PlayerStartSpectatingEntityEvent.java | 67 + .../PlayerStopSpectatingEntityEvent.java | 54 + .../player/PlayerTeleportEndGatewayEvent.java | 29 + .../player/PlayerUseUnknownEntityEvent.java | 46 + .../paper/event/profile/FillProfileEvent.java | 75 + .../event/profile/LookupProfileEvent.java | 46 + .../event/profile/PreFillProfileEvent.java | 77 + .../event/profile/PreLookupProfileEvent.java | 108 + .../profile/ProfileWhitelistVerifyEvent.java | 117 + .../event/server/AsyncTabCompleteEvent.java | 177 + .../paper/event/server/GS4QueryEvent.java | 411 + .../server/PaperServerListPingEvent.java | 323 + .../event/server/ServerExceptionEvent.java | 41 + .../event/server/ServerTickEndEvent.java | 59 + .../event/server/ServerTickStartEvent.java | 32 + .../event/server/WhitelistToggleEvent.java | 40 + .../exception/ServerCommandException.java | 64 + .../paper/exception/ServerEventException.java | 52 + .../paper/exception/ServerException.java | 23 + .../exception/ServerInternalException.java | 35 + .../ServerPluginEnableDisableException.java | 20 + .../exception/ServerPluginException.java | 38 + .../ServerPluginMessageException.java | 64 + .../exception/ServerSchedulerException.java | 37 + .../exception/ServerTabCompleteException.java | 22 + .../inventory/ItemStackRecipeChoice.java | 51 + .../paper/inventory/meta/ArmorStandMeta.java | 78 + .../loottable/LootableBlockInventory.java | 17 + .../loottable/LootableEntityInventory.java | 17 + .../paper/loottable/LootableInventory.java | 116 + .../LootableInventoryReplenishEvent.java | 45 + .../paper/network/NetworkClient.java | 41 + .../paper/network/StatusClient.java | 25 + .../paper/profile/PlayerProfile.java | 145 + .../paper/profile/ProfileProperty.java | 72 + .../destroystokyo/paper/util/SneakyThrow.java | 16 + .../CachedSizeConcurrentLinkedQueue.java | 34 + .../paper/utils/PaperPluginLogger.java | 41 + .../paper/utils/UnsafeUtils.java | 35 + .../server/core/AkarinGlobalConfig.java | 0 api/src/main/java/org/bukkit/Achievement.java | 75 + api/src/main/java/org/bukkit/Art.java | 115 + api/src/main/java/org/bukkit/Axis.java | 21 + api/src/main/java/org/bukkit/BanEntry.java | 136 + api/src/main/java/org/bukkit/BanList.java | 78 + .../java/org/bukkit/BlockChangeDelegate.java | 51 + api/src/main/java/org/bukkit/Bukkit.java | 1641 ++++ api/src/main/java/org/bukkit/ChatColor.java | 404 + api/src/main/java/org/bukkit/Chunk.java | 201 + .../main/java/org/bukkit/ChunkSnapshot.java | 134 + api/src/main/java/org/bukkit/CoalType.java | 52 + api/src/main/java/org/bukkit/Color.java | 356 + api/src/main/java/org/bukkit/CropState.java | 83 + api/src/main/java/org/bukkit/Difficulty.java | 74 + api/src/main/java/org/bukkit/DyeColor.java | 230 + api/src/main/java/org/bukkit/Effect.java | 269 + .../main/java/org/bukkit/EntityEffect.java | 203 + .../main/java/org/bukkit/FireworkEffect.java | 440 + .../java/org/bukkit/FluidCollisionMode.java | 20 + api/src/main/java/org/bukkit/GameMode.java | 75 + api/src/main/java/org/bukkit/GameRule.java | 224 + .../main/java/org/bukkit/GrassSpecies.java | 63 + api/src/main/java/org/bukkit/Instrument.java | 90 + api/src/main/java/org/bukkit/Keyed.java | 17 + api/src/main/java/org/bukkit/Location.java | 1096 +++ api/src/main/java/org/bukkit/Material.java | 7218 +++++++++++++++++ api/src/main/java/org/bukkit/Nameable.java | 31 + .../main/java/org/bukkit/NamespacedKey.java | 144 + .../java/org/bukkit/NetherWartsState.java | 21 + api/src/main/java/org/bukkit/Note.java | 285 + .../main/java/org/bukkit/OfflinePlayer.java | 195 + api/src/main/java/org/bukkit/Particle.java | 132 + api/src/main/java/org/bukkit/PortalType.java | 22 + api/src/main/java/org/bukkit/Rotation.java | 67 + .../main/java/org/bukkit/SandstoneType.java | 53 + api/src/main/java/org/bukkit/Server.java | 1430 ++++ api/src/main/java/org/bukkit/SkullType.java | 15 + api/src/main/java/org/bukkit/Sound.java | 674 ++ .../main/java/org/bukkit/SoundCategory.java | 18 + api/src/main/java/org/bukkit/Statistic.java | 151 + .../main/java/org/bukkit/StructureType.java | 228 + api/src/main/java/org/bukkit/Tag.java | 211 + api/src/main/java/org/bukkit/TravelAgent.java | 100 + api/src/main/java/org/bukkit/TreeSpecies.java | 76 + api/src/main/java/org/bukkit/TreeType.java | 76 + .../java/org/bukkit/UndefinedNullability.java | 26 + .../main/java/org/bukkit/UnsafeValues.java | 76 + api/src/main/java/org/bukkit/Utility.java | 18 + api/src/main/java/org/bukkit/Warning.java | 112 + api/src/main/java/org/bukkit/WeatherType.java | 17 + api/src/main/java/org/bukkit/World.java | 2832 +++++++ api/src/main/java/org/bukkit/WorldBorder.java | 134 + .../main/java/org/bukkit/WorldCreator.java | 337 + api/src/main/java/org/bukkit/WorldType.java | 54 + .../org/bukkit/advancement/Advancement.java | 20 + .../advancement/AdvancementProgress.java | 71 + .../org/bukkit/attribute/Attributable.java | 20 + .../java/org/bukkit/attribute/Attribute.java | 56 + .../bukkit/attribute/AttributeInstance.java | 71 + .../bukkit/attribute/AttributeModifier.java | 166 + .../main/java/org/bukkit/block/Banner.java | 87 + .../main/java/org/bukkit/block/Beacon.java | 76 + api/src/main/java/org/bukkit/block/Bed.java | 10 + api/src/main/java/org/bukkit/block/Biome.java | 80 + api/src/main/java/org/bukkit/block/Block.java | 535 ++ .../main/java/org/bukkit/block/BlockFace.java | 150 + .../java/org/bukkit/block/BlockState.java | 224 + .../java/org/bukkit/block/BrewingStand.java | 47 + api/src/main/java/org/bukkit/block/Chest.java | 30 + .../java/org/bukkit/block/CommandBlock.java | 48 + .../java/org/bukkit/block/Comparator.java | 6 + .../main/java/org/bukkit/block/Conduit.java | 6 + .../main/java/org/bukkit/block/Container.java | 39 + .../org/bukkit/block/CreatureSpawner.java | 202 + .../org/bukkit/block/DaylightDetector.java | 6 + .../main/java/org/bukkit/block/Dispenser.java | 36 + .../java/org/bukkit/block/DoubleChest.java | 57 + .../main/java/org/bukkit/block/Dropper.java | 33 + .../org/bukkit/block/EnchantingTable.java | 8 + .../java/org/bukkit/block/EndGateway.java | 68 + .../java/org/bukkit/block/EnderChest.java | 6 + .../main/java/org/bukkit/block/FlowerPot.java | 30 + .../main/java/org/bukkit/block/Furnace.java | 92 + .../main/java/org/bukkit/block/Hopper.java | 10 + .../main/java/org/bukkit/block/Jukebox.java | 60 + .../main/java/org/bukkit/block/Lockable.java | 35 + .../main/java/org/bukkit/block/NoteBlock.java | 84 + .../org/bukkit/block/PistonMoveReaction.java | 69 + .../java/org/bukkit/block/ShulkerBox.java | 21 + api/src/main/java/org/bukkit/block/Sign.java | 63 + api/src/main/java/org/bukkit/block/Skull.java | 119 + .../main/java/org/bukkit/block/Structure.java | 243 + .../java/org/bukkit/block/banner/Pattern.java | 98 + .../org/bukkit/block/banner/PatternType.java | 87 + .../java/org/bukkit/block/data/Ageable.java | 33 + .../bukkit/block/data/AnaloguePowerable.java | 31 + .../org/bukkit/block/data/Attachable.java | 27 + .../java/org/bukkit/block/data/Bisected.java | 40 + .../java/org/bukkit/block/data/BlockData.java | 96 + .../org/bukkit/block/data/Directional.java | 37 + .../java/org/bukkit/block/data/Levelled.java | 39 + .../java/org/bukkit/block/data/Lightable.java | 22 + .../org/bukkit/block/data/MultipleFacing.java | 49 + .../java/org/bukkit/block/data/Openable.java | 21 + .../org/bukkit/block/data/Orientable.java | 38 + .../java/org/bukkit/block/data/Powerable.java | 22 + .../main/java/org/bukkit/block/data/Rail.java | 88 + .../java/org/bukkit/block/data/Rotatable.java | 25 + .../java/org/bukkit/block/data/Snowable.java | 22 + .../org/bukkit/block/data/Waterlogged.java | 21 + .../java/org/bukkit/block/data/type/Bed.java | 52 + .../bukkit/block/data/type/BrewingStand.java | 45 + .../bukkit/block/data/type/BubbleColumn.java | 25 + .../java/org/bukkit/block/data/type/Cake.java | 34 + .../org/bukkit/block/data/type/Chest.java | 51 + .../org/bukkit/block/data/type/Cocoa.java | 7 + .../bukkit/block/data/type/CommandBlock.java | 24 + .../bukkit/block/data/type/Comparator.java | 43 + .../bukkit/block/data/type/CoralWallFan.java | 7 + .../block/data/type/DaylightDetector.java | 24 + .../org/bukkit/block/data/type/Dispenser.java | 25 + .../java/org/bukkit/block/data/type/Door.java | 43 + .../block/data/type/EndPortalFrame.java | 24 + .../bukkit/block/data/type/EnderChest.java | 7 + .../org/bukkit/block/data/type/Farmland.java | 34 + .../org/bukkit/block/data/type/Fence.java | 7 + .../java/org/bukkit/block/data/type/Fire.java | 10 + .../org/bukkit/block/data/type/Furnace.java | 7 + .../java/org/bukkit/block/data/type/Gate.java | 26 + .../org/bukkit/block/data/type/GlassPane.java | 7 + .../org/bukkit/block/data/type/Hopper.java | 28 + .../org/bukkit/block/data/type/Jukebox.java | 17 + .../org/bukkit/block/data/type/Ladder.java | 7 + .../org/bukkit/block/data/type/Leaves.java | 42 + .../org/bukkit/block/data/type/NoteBlock.java | 44 + .../org/bukkit/block/data/type/Observer.java | 7 + .../org/bukkit/block/data/type/Piston.java | 23 + .../bukkit/block/data/type/PistonHead.java | 22 + .../bukkit/block/data/type/RedstoneRail.java | 10 + .../block/data/type/RedstoneWallTorch.java | 7 + .../bukkit/block/data/type/RedstoneWire.java | 56 + .../org/bukkit/block/data/type/Repeater.java | 62 + .../org/bukkit/block/data/type/Sapling.java | 33 + .../org/bukkit/block/data/type/SeaPickle.java | 37 + .../java/org/bukkit/block/data/type/Sign.java | 7 + .../java/org/bukkit/block/data/type/Slab.java | 44 + .../java/org/bukkit/block/data/type/Snow.java | 41 + .../org/bukkit/block/data/type/Stairs.java | 53 + .../block/data/type/StructureBlock.java | 49 + .../org/bukkit/block/data/type/Switch.java | 47 + .../java/org/bukkit/block/data/type/TNT.java | 23 + .../block/data/type/TechnicalPiston.java | 41 + .../org/bukkit/block/data/type/TrapDoor.java | 10 + .../org/bukkit/block/data/type/Tripwire.java | 26 + .../bukkit/block/data/type/TripwireHook.java | 8 + .../org/bukkit/block/data/type/TurtleEgg.java | 60 + .../org/bukkit/block/data/type/WallSign.java | 7 + .../org/bukkit/block/structure/Mirror.java | 27 + .../block/structure/StructureRotation.java | 26 + .../org/bukkit/block/structure/UsageMode.java | 29 + .../main/java/org/bukkit/boss/BarColor.java | 11 + .../main/java/org/bukkit/boss/BarFlag.java | 17 + .../main/java/org/bukkit/boss/BarStyle.java | 24 + .../main/java/org/bukkit/boss/BossBar.java | 151 + .../java/org/bukkit/boss/KeyedBossBar.java | 9 + .../bukkit/command/BlockCommandSender.java | 15 + .../bukkit/command/BufferedCommandSender.java | 21 + .../main/java/org/bukkit/command/Command.java | 451 + .../org/bukkit/command/CommandException.java | 28 + .../org/bukkit/command/CommandExecutor.java | 23 + .../java/org/bukkit/command/CommandMap.java | 141 + .../org/bukkit/command/CommandSender.java | 91 + .../bukkit/command/ConsoleCommandSender.java | 6 + .../bukkit/command/FormattedCommandAlias.java | 130 + .../bukkit/command/MessageCommandSender.java | 114 + .../bukkit/command/MultipleCommandAlias.java | 36 + .../org/bukkit/command/PluginCommand.java | 167 + .../command/PluginCommandYamlParser.java | 78 + .../command/PluginIdentifiableCommand.java | 21 + .../bukkit/command/ProxiedCommandSender.java | 24 + .../command/RemoteConsoleCommandSender.java | 4 + .../org/bukkit/command/SimpleCommandMap.java | 295 + .../java/org/bukkit/command/TabCompleter.java | 28 + .../java/org/bukkit/command/TabExecutor.java | 8 + .../command/defaults/BukkitCommand.java | 16 + .../bukkit/command/defaults/HelpCommand.java | 232 + .../command/defaults/PluginsCommand.java | 67 + .../command/defaults/ReloadCommand.java | 66 + .../command/defaults/VersionCommand.java | 0 .../bukkit/configuration/Configuration.java | 89 + .../configuration/ConfigurationOptions.java | 95 + .../configuration/ConfigurationSection.java | 946 +++ .../InvalidConfigurationException.java | 45 + .../configuration/MemoryConfiguration.java | 84 + .../MemoryConfigurationOptions.java | 33 + .../bukkit/configuration/MemorySection.java | 889 ++ .../configuration/file/FileConfiguration.java | 229 + .../file/FileConfigurationOptions.java | 126 + .../configuration/file/YamlConfiguration.java | 222 + .../file/YamlConfigurationOptions.java | 79 + .../configuration/file/YamlConstructor.java | 53 + .../configuration/file/YamlRepresenter.java | 43 + .../ConfigurationSerializable.java | 38 + .../ConfigurationSerialization.java | 300 + .../DelegateDeserialization.java | 25 + .../serialization/SerializableAs.java | 37 + .../bukkit/conversations/BooleanPrompt.java | 41 + .../org/bukkit/conversations/Conversable.java | 57 + .../bukkit/conversations/Conversation.java | 304 + .../ConversationAbandonedEvent.java | 58 + .../ConversationAbandonedListener.java | 17 + .../conversations/ConversationCanceller.java | 37 + .../conversations/ConversationContext.java | 88 + .../conversations/ConversationFactory.java | 240 + .../conversations/ConversationPrefix.java | 20 + .../ExactMatchConversationCanceller.java | 32 + .../bukkit/conversations/FixedSetPrompt.java | 49 + .../InactivityConversationCanceller.java | 80 + ...anuallyAbandonedConversationCanceller.java | 23 + .../bukkit/conversations/MessagePrompt.java | 47 + .../conversations/NullConversationPrefix.java | 21 + .../bukkit/conversations/NumericPrompt.java | 89 + .../conversations/PlayerNamePrompt.java | 41 + .../PluginNameConversationPrefix.java | 41 + .../java/org/bukkit/conversations/Prompt.java | 50 + .../org/bukkit/conversations/RegexPrompt.java | 31 + .../bukkit/conversations/StringPrompt.java | 20 + .../conversations/ValidatingPrompt.java | 83 + .../org/bukkit/enchantments/Enchantment.java | 373 + .../bukkit/enchantments/EnchantmentOffer.java | 83 + .../enchantments/EnchantmentTarget.java | 213 + .../enchantments/EnchantmentWrapper.java | 66 + .../java/org/bukkit/entity/AbstractHorse.java | 108 + .../main/java/org/bukkit/entity/Ageable.java | 67 + .../main/java/org/bukkit/entity/Ambient.java | 6 + .../java/org/bukkit/entity/AnimalTamer.java | 25 + .../main/java/org/bukkit/entity/Animals.java | 54 + .../org/bukkit/entity/AreaEffectCloud.java | 242 + .../java/org/bukkit/entity/ArmorStand.java | 385 + .../main/java/org/bukkit/entity/Arrow.java | 186 + api/src/main/java/org/bukkit/entity/Bat.java | 27 + .../main/java/org/bukkit/entity/Blaze.java | 8 + api/src/main/java/org/bukkit/entity/Boat.java | 106 + api/src/main/java/org/bukkit/entity/Boss.java | 18 + .../java/org/bukkit/entity/CaveSpider.java | 6 + .../java/org/bukkit/entity/ChestedHorse.java | 22 + .../main/java/org/bukkit/entity/Chicken.java | 6 + api/src/main/java/org/bukkit/entity/Cod.java | 7 + .../org/bukkit/entity/ComplexEntityPart.java | 17 + .../bukkit/entity/ComplexLivingEntity.java | 19 + api/src/main/java/org/bukkit/entity/Cow.java | 6 + .../main/java/org/bukkit/entity/Creature.java | 7 + .../main/java/org/bukkit/entity/Creeper.java | 81 + .../java/org/bukkit/entity/Damageable.java | 73 + .../main/java/org/bukkit/entity/Dolphin.java | 3 + .../main/java/org/bukkit/entity/Donkey.java | 6 + .../org/bukkit/entity/DragonFireball.java | 3 + .../main/java/org/bukkit/entity/Drowned.java | 6 + api/src/main/java/org/bukkit/entity/Egg.java | 6 + .../java/org/bukkit/entity/ElderGuardian.java | 6 + .../java/org/bukkit/entity/EnderCrystal.java | 43 + .../java/org/bukkit/entity/EnderDragon.java | 81 + .../org/bukkit/entity/EnderDragonPart.java | 11 + .../java/org/bukkit/entity/EnderPearl.java | 8 + .../java/org/bukkit/entity/EnderSignal.java | 64 + .../main/java/org/bukkit/entity/Enderman.java | 53 + .../java/org/bukkit/entity/Endermite.java | 22 + .../main/java/org/bukkit/entity/Entity.java | 655 ++ .../java/org/bukkit/entity/EntityType.java | 400 + .../main/java/org/bukkit/entity/Evoker.java | 67 + .../java/org/bukkit/entity/EvokerFangs.java | 24 + .../java/org/bukkit/entity/ExperienceOrb.java | 115 + .../java/org/bukkit/entity/Explosive.java | 35 + .../java/org/bukkit/entity/FallingBlock.java | 68 + .../main/java/org/bukkit/entity/Fireball.java | 26 + .../main/java/org/bukkit/entity/Firework.java | 42 + api/src/main/java/org/bukkit/entity/Fish.java | 6 + .../main/java/org/bukkit/entity/FishHook.java | 32 + .../main/java/org/bukkit/entity/Flying.java | 6 + .../main/java/org/bukkit/entity/Ghast.java | 6 + .../main/java/org/bukkit/entity/Giant.java | 6 + .../main/java/org/bukkit/entity/Golem.java | 8 + .../main/java/org/bukkit/entity/Guardian.java | 20 + .../main/java/org/bukkit/entity/Hanging.java | 23 + .../main/java/org/bukkit/entity/Horse.java | 166 + .../java/org/bukkit/entity/HumanEntity.java | 486 ++ api/src/main/java/org/bukkit/entity/Husk.java | 41 + .../main/java/org/bukkit/entity/Illager.java | 6 + .../java/org/bukkit/entity/Illusioner.java | 10 + .../java/org/bukkit/entity/IronGolem.java | 22 + api/src/main/java/org/bukkit/entity/Item.java | 90 + .../java/org/bukkit/entity/ItemFrame.java | 51 + .../java/org/bukkit/entity/LargeFireball.java | 7 + .../java/org/bukkit/entity/LeashHitch.java | 7 + .../org/bukkit/entity/LightningStrike.java | 36 + .../org/bukkit/entity/LingeringPotion.java | 6 + .../java/org/bukkit/entity/LivingEntity.java | 652 ++ .../main/java/org/bukkit/entity/Llama.java | 70 + .../java/org/bukkit/entity/LlamaSpit.java | 6 + .../java/org/bukkit/entity/MagmaCube.java | 7 + .../main/java/org/bukkit/entity/Minecart.java | 146 + api/src/main/java/org/bukkit/entity/Mob.java | 45 + .../main/java/org/bukkit/entity/Monster.java | 6 + api/src/main/java/org/bukkit/entity/Mule.java | 6 + .../java/org/bukkit/entity/MushroomCow.java | 8 + api/src/main/java/org/bukkit/entity/NPC.java | 8 + .../main/java/org/bukkit/entity/Ocelot.java | 73 + .../main/java/org/bukkit/entity/Painting.java | 41 + .../main/java/org/bukkit/entity/Parrot.java | 50 + .../main/java/org/bukkit/entity/Phantom.java | 30 + api/src/main/java/org/bukkit/entity/Pig.java | 21 + .../java/org/bukkit/entity/PigZombie.java | 60 + .../main/java/org/bukkit/entity/Player.java | 2064 +++++ .../java/org/bukkit/entity/PolarBear.java | 6 + .../java/org/bukkit/entity/Projectile.java | 42 + .../java/org/bukkit/entity/PufferFish.java | 21 + .../main/java/org/bukkit/entity/Rabbit.java | 52 + .../main/java/org/bukkit/entity/Salmon.java | 7 + .../main/java/org/bukkit/entity/Sheep.java | 19 + .../main/java/org/bukkit/entity/Shulker.java | 5 + .../java/org/bukkit/entity/ShulkerBullet.java | 21 + .../java/org/bukkit/entity/Silverfish.java | 6 + .../main/java/org/bukkit/entity/Sittable.java | 23 + .../main/java/org/bukkit/entity/Skeleton.java | 49 + .../java/org/bukkit/entity/SkeletonHorse.java | 14 + .../main/java/org/bukkit/entity/Slime.java | 33 + .../java/org/bukkit/entity/SmallFireball.java | 8 + .../main/java/org/bukkit/entity/Snowball.java | 6 + .../main/java/org/bukkit/entity/Snowman.java | 26 + .../java/org/bukkit/entity/SpectralArrow.java | 22 + .../java/org/bukkit/entity/Spellcaster.java | 55 + .../main/java/org/bukkit/entity/Spider.java | 6 + .../java/org/bukkit/entity/SplashPotion.java | 6 + .../main/java/org/bukkit/entity/Squid.java | 6 + .../main/java/org/bukkit/entity/Stray.java | 6 + .../java/org/bukkit/entity/TNTPrimed.java | 53 + .../main/java/org/bukkit/entity/Tameable.java | 61 + .../org/bukkit/entity/ThrownExpBottle.java | 8 + .../java/org/bukkit/entity/ThrownPotion.java | 44 + .../java/org/bukkit/entity/TippedArrow.java | 98 + .../main/java/org/bukkit/entity/Trident.java | 6 + .../java/org/bukkit/entity/TropicalFish.java | 76 + .../main/java/org/bukkit/entity/Turtle.java | 55 + .../main/java/org/bukkit/entity/Vehicle.java | 25 + api/src/main/java/org/bukkit/entity/Vex.java | 44 + .../main/java/org/bukkit/entity/Villager.java | 272 + .../java/org/bukkit/entity/Vindicator.java | 32 + .../main/java/org/bukkit/entity/WaterMob.java | 6 + .../main/java/org/bukkit/entity/Weather.java | 6 + .../main/java/org/bukkit/entity/Witch.java | 44 + .../main/java/org/bukkit/entity/Wither.java | 9 + .../org/bukkit/entity/WitherSkeleton.java | 6 + .../java/org/bukkit/entity/WitherSkull.java | 21 + api/src/main/java/org/bukkit/entity/Wolf.java | 44 + .../main/java/org/bukkit/entity/Zombie.java | 140 + .../java/org/bukkit/entity/ZombieHorse.java | 6 + .../org/bukkit/entity/ZombieVillager.java | 58 + .../entity/minecart/CommandMinecart.java | 38 + .../entity/minecart/ExplosiveMinecart.java | 9 + .../entity/minecart/HopperMinecart.java | 27 + .../entity/minecart/PoweredMinecart.java | 10 + .../entity/minecart/RideableMinecart.java | 14 + .../entity/minecart/SpawnerMinecart.java | 10 + .../entity/minecart/StorageMinecart.java | 14 + .../java/org/bukkit/event/Cancellable.java | 20 + api/src/main/java/org/bukkit/event/Event.java | 118 + .../java/org/bukkit/event/EventException.java | 53 + .../java/org/bukkit/event/EventHandler.java | 41 + .../java/org/bukkit/event/EventPriority.java | 47 + .../java/org/bukkit/event/HandlerList.java | 0 .../main/java/org/bukkit/event/Listener.java | 6 + .../java/org/bukkit/event/block/Action.java | 33 + .../bukkit/event/block/BlockBreakEvent.java | 76 + .../bukkit/event/block/BlockBurnEvent.java | 59 + .../event/block/BlockCanBuildEvent.java | 112 + .../bukkit/event/block/BlockDamageEvent.java | 88 + .../event/block/BlockDispenseArmorEvent.java | 34 + .../event/block/BlockDispenseEvent.java | 89 + .../event/block/BlockDropItemEvent.java | 110 + .../org/bukkit/event/block/BlockEvent.java | 26 + .../org/bukkit/event/block/BlockExpEvent.java | 48 + .../bukkit/event/block/BlockExplodeEvent.java | 73 + .../bukkit/event/block/BlockFadeEvent.java | 65 + .../event/block/BlockFertilizeEvent.java | 73 + .../bukkit/event/block/BlockFormEvent.java | 43 + .../bukkit/event/block/BlockFromToEvent.java | 76 + .../bukkit/event/block/BlockGrowEvent.java | 61 + .../bukkit/event/block/BlockIgniteEvent.java | 137 + .../event/block/BlockMultiPlaceEvent.java | 38 + .../bukkit/event/block/BlockPhysicsEvent.java | 92 + .../bukkit/event/block/BlockPistonEvent.java | 50 + .../event/block/BlockPistonExtendEvent.java | 74 + .../event/block/BlockPistonRetractEvent.java | 56 + .../bukkit/event/block/BlockPlaceEvent.java | 146 + .../event/block/BlockRedstoneEvent.java | 58 + .../bukkit/event/block/BlockSpreadEvent.java | 53 + .../event/block/CauldronLevelChangeEvent.java | 116 + .../event/block/EntityBlockFormEvent.java | 35 + .../event/block/FluidLevelChangeEvent.java | 69 + .../bukkit/event/block/LeavesDecayEvent.java | 39 + .../event/block/MoistureChangeEvent.java | 54 + .../org/bukkit/event/block/NotePlayEvent.java | 91 + .../bukkit/event/block/SignChangeEvent.java | 91 + .../bukkit/event/block/SpongeAbsorbEvent.java | 64 + .../event/command/UnknownCommandEvent.java | 82 + .../event/enchantment/EnchantItemEvent.java | 128 + .../enchantment/PrepareItemEnchantEvent.java | 122 + .../entity/AreaEffectCloudApplyEvent.java | 66 + .../event/entity/BatToggleSleepEvent.java | 55 + .../event/entity/CreatureSpawnEvent.java | 161 + .../event/entity/CreeperPowerEvent.java | 100 + .../entity/EnderDragonChangePhaseEvent.java | 83 + .../event/entity/EntityAirChangeEvent.java | 62 + .../event/entity/EntityBreakDoorEvent.java | 24 + .../bukkit/event/entity/EntityBreedEvent.java | 128 + .../event/entity/EntityChangeBlockEvent.java | 75 + .../entity/EntityCombustByBlockEvent.java | 30 + .../entity/EntityCombustByEntityEvent.java | 26 + .../event/entity/EntityCombustEvent.java | 62 + .../event/entity/EntityCreatePortalEvent.java | 71 + .../entity/EntityDamageByBlockEvent.java | 36 + .../entity/EntityDamageByEntityEvent.java | 34 + .../event/entity/EntityDamageEvent.java | 434 + .../bukkit/event/entity/EntityDeathEvent.java | 217 + .../event/entity/EntityDropItemEvent.java | 53 + .../org/bukkit/event/entity/EntityEvent.java | 37 + .../event/entity/EntityExplodeEvent.java | 91 + .../event/entity/EntityInteractEvent.java | 50 + .../event/entity/EntityPickupItemEvent.java | 67 + .../bukkit/event/entity/EntityPlaceEvent.java | 89 + .../event/entity/EntityPortalEnterEvent.java | 40 + .../event/entity/EntityPortalEvent.java | 87 + .../event/entity/EntityPortalExitEvent.java | 67 + .../event/entity/EntityPotionEffectEvent.java | 249 + .../event/entity/EntityRegainHealthEvent.java | 136 + .../event/entity/EntityResurrectEvent.java | 49 + .../event/entity/EntityShootBowEvent.java | 112 + .../bukkit/event/entity/EntitySpawnEvent.java | 53 + .../bukkit/event/entity/EntityTameEvent.java | 56 + .../event/entity/EntityTargetEvent.java | 164 + .../entity/EntityTargetLivingEntityEvent.java | 37 + .../event/entity/EntityTeleportEvent.java | 85 + .../event/entity/EntityToggleGlideEvent.java | 53 + .../event/entity/EntityToggleSwimEvent.java | 46 + .../event/entity/EntityTransformEvent.java | 108 + .../event/entity/EntityUnleashEvent.java | 56 + .../bukkit/event/entity/ExpBottleEvent.java | 79 + .../event/entity/ExplosionPrimeEvent.java | 83 + .../event/entity/FireworkExplodeEvent.java | 53 + .../event/entity/FoodLevelChangeEvent.java | 70 + .../bukkit/event/entity/HorseJumpEvent.java | 88 + .../bukkit/event/entity/ItemDespawnEvent.java | 60 + .../bukkit/event/entity/ItemMergeEvent.java | 55 + .../bukkit/event/entity/ItemSpawnEvent.java | 27 + .../entity/LingeringPotionSplashEvent.java | 57 + .../org/bukkit/event/entity/PigZapEvent.java | 79 + .../event/entity/PigZombieAngerEvent.java | 84 + .../bukkit/event/entity/PlayerDeathEvent.java | 199 + .../event/entity/PlayerLeashEntityEvent.java | 74 + .../event/entity/PotionSplashEvent.java | 100 + .../event/entity/ProjectileHitEvent.java | 91 + .../event/entity/ProjectileLaunchEvent.java | 31 + .../event/entity/SheepDyeWoolEvent.java | 67 + .../event/entity/SheepRegrowWoolEvent.java | 45 + .../bukkit/event/entity/SlimeSplitEvent.java | 63 + .../event/entity/SpawnerSpawnEvent.java | 24 + .../entity/VillagerAcquireTradeEvent.java | 69 + .../entity/VillagerReplenishTradeEvent.java | 94 + .../hanging/HangingBreakByEntityEvent.java | 33 + .../event/hanging/HangingBreakEvent.java | 75 + .../bukkit/event/hanging/HangingEvent.java | 26 + .../event/hanging/HangingPlaceEvent.java | 77 + .../org/bukkit/event/inventory/BrewEvent.java | 63 + .../inventory/BrewingStandFuelEvent.java | 96 + .../org/bukkit/event/inventory/ClickType.java | 115 + .../event/inventory/CraftItemEvent.java | 38 + .../org/bukkit/event/inventory/DragType.java | 17 + .../event/inventory/FurnaceBurnEvent.java | 92 + .../event/inventory/FurnaceExtractEvent.java | 52 + .../event/inventory/FurnaceSmeltEvent.java | 73 + .../event/inventory/InventoryAction.java | 91 + .../event/inventory/InventoryClickEvent.java | 244 + .../event/inventory/InventoryCloseEvent.java | 90 + .../inventory/InventoryCreativeEvent.java | 29 + .../event/inventory/InventoryDragEvent.java | 174 + .../event/inventory/InventoryEvent.java | 65 + .../inventory/InventoryInteractEvent.java | 79 + .../inventory/InventoryMoveItemEvent.java | 119 + .../event/inventory/InventoryOpenEvent.java | 67 + .../inventory/InventoryPickupItemEvent.java | 63 + .../bukkit/event/inventory/InventoryType.java | 164 + .../event/inventory/PrepareAnvilEvent.java | 53 + .../inventory/PrepareItemCraftEvent.java | 62 + .../event/player/AsyncPlayerChatEvent.java | 146 + .../player/AsyncPlayerPreLoginEvent.java | 238 + .../player/PlayerAchievementAwardedEvent.java | 50 + .../player/PlayerAdvancementDoneEvent.java | 42 + .../event/player/PlayerAnimationEvent.java | 56 + .../event/player/PlayerAnimationType.java | 8 + .../PlayerArmorStandManipulateEvent.java | 82 + .../player/PlayerAttemptPickupItemEvent.java | 89 + .../event/player/PlayerBedEnterEvent.java | 160 + .../event/player/PlayerBedLeaveEvent.java | 75 + .../event/player/PlayerBucketEmptyEvent.java | 38 + .../event/player/PlayerBucketEvent.java | 107 + .../event/player/PlayerBucketFillEvent.java | 39 + .../player/PlayerChangedMainHandEvent.java | 43 + .../event/player/PlayerChangedWorldEvent.java | 40 + .../event/player/PlayerChannelEvent.java | 35 + .../bukkit/event/player/PlayerChatEvent.java | 131 + .../player/PlayerChatTabCompleteEvent.java | 81 + .../player/PlayerCommandPreprocessEvent.java | 144 + .../event/player/PlayerCommandSendEvent.java | 50 + .../event/player/PlayerDropItemEvent.java | 50 + .../event/player/PlayerEditBookEvent.java | 132 + .../event/player/PlayerEggThrowEvent.java | 114 + .../org/bukkit/event/player/PlayerEvent.java | 32 + .../event/player/PlayerExpChangeEvent.java | 69 + .../bukkit/event/player/PlayerFishEvent.java | 142 + .../player/PlayerGameModeChangeEvent.java | 50 + .../player/PlayerInteractAtEntityEvent.java | 45 + .../player/PlayerInteractEntityEvent.java | 67 + .../event/player/PlayerInteractEvent.java | 226 + .../event/player/PlayerItemBreakEvent.java | 43 + .../event/player/PlayerItemConsumeEvent.java | 103 + .../event/player/PlayerItemDamageEvent.java | 69 + .../event/player/PlayerItemHeldEvent.java | 59 + .../event/player/PlayerItemMendEvent.java | 97 + .../bukkit/event/player/PlayerJoinEvent.java | 48 + .../bukkit/event/player/PlayerKickEvent.java | 80 + .../event/player/PlayerLevelChangeEvent.java | 49 + .../event/player/PlayerLocaleChangeEvent.java | 41 + .../bukkit/event/player/PlayerLoginEvent.java | 196 + .../bukkit/event/player/PlayerMoveEvent.java | 109 + .../event/player/PlayerPickupArrowEvent.java | 29 + .../event/player/PlayerPickupItemEvent.java | 89 + .../event/player/PlayerPortalEvent.java | 92 + .../event/player/PlayerPreLoginEvent.java | 167 + .../bukkit/event/player/PlayerQuitEvent.java | 48 + .../player/PlayerRecipeDiscoverEvent.java | 54 + .../player/PlayerRegisterChannelEvent.java | 14 + .../player/PlayerResourcePackStatusEvent.java | 84 + .../event/player/PlayerRespawnEvent.java | 64 + .../event/player/PlayerRiptideEvent.java | 45 + .../event/player/PlayerShearEntityEvent.java | 52 + .../player/PlayerStatisticIncrementEvent.java | 123 + .../player/PlayerSwapHandItemsEvent.java | 87 + .../event/player/PlayerTeleportEvent.java | 93 + .../event/player/PlayerToggleFlightEvent.java | 48 + .../event/player/PlayerToggleSneakEvent.java | 48 + .../event/player/PlayerToggleSprintEvent.java | 48 + .../player/PlayerUnleashEntityEvent.java | 38 + .../player/PlayerUnregisterChannelEvent.java | 14 + .../event/player/PlayerVelocityEvent.java | 59 + .../event/server/BroadcastMessageEvent.java | 82 + .../event/server/MapInitializeEvent.java | 38 + .../event/server/PluginDisableEvent.java | 27 + .../event/server/PluginEnableEvent.java | 27 + .../org/bukkit/event/server/PluginEvent.java | 25 + .../server/RemoteServerCommandEvent.java | 28 + .../event/server/ServerCommandEvent.java | 103 + .../org/bukkit/event/server/ServerEvent.java | 19 + .../event/server/ServerListPingEvent.java | 155 + .../bukkit/event/server/ServerLoadEvent.java | 50 + .../org/bukkit/event/server/ServiceEvent.java | 21 + .../event/server/ServiceRegisterEvent.java | 30 + .../event/server/ServiceUnregisterEvent.java | 30 + .../bukkit/event/server/TabCompleteEvent.java | 132 + .../vehicle/VehicleBlockCollisionEvent.java | 40 + .../event/vehicle/VehicleCollisionEvent.java | 13 + .../event/vehicle/VehicleCreateEvent.java | 39 + .../event/vehicle/VehicleDamageEvent.java | 71 + .../event/vehicle/VehicleDestroyEvent.java | 53 + .../event/vehicle/VehicleEnterEvent.java | 50 + .../vehicle/VehicleEntityCollisionEvent.java | 63 + .../bukkit/event/vehicle/VehicleEvent.java | 26 + .../event/vehicle/VehicleExitEvent.java | 67 + .../event/vehicle/VehicleMoveEvent.java | 54 + .../event/vehicle/VehicleUpdateEvent.java | 27 + .../event/weather/LightningStrikeEvent.java | 90 + .../event/weather/ThunderChangeEvent.java | 48 + .../event/weather/WeatherChangeEvent.java | 48 + .../bukkit/event/weather/WeatherEvent.java | 26 + .../org/bukkit/event/world/ChunkEvent.java | 26 + .../bukkit/event/world/ChunkLoadEvent.java | 40 + .../event/world/ChunkPopulateEvent.java | 31 + .../bukkit/event/world/ChunkUnloadEvent.java | 61 + .../bukkit/event/world/PortalCreateEvent.java | 82 + .../bukkit/event/world/SpawnChangeEvent.java | 41 + .../event/world/StructureGrowEvent.java | 104 + .../org/bukkit/event/world/WorldEvent.java | 26 + .../bukkit/event/world/WorldInitEvent.java | 27 + .../bukkit/event/world/WorldLoadEvent.java | 27 + .../bukkit/event/world/WorldSaveEvent.java | 27 + .../bukkit/event/world/WorldUnloadEvent.java | 37 + .../org/bukkit/generator/BlockPopulator.java | 30 + .../org/bukkit/generator/ChunkGenerator.java | 291 + .../bukkit/help/GenericCommandHelpTopic.java | 78 + .../main/java/org/bukkit/help/HelpMap.java | 85 + .../main/java/org/bukkit/help/HelpTopic.java | 127 + .../org/bukkit/help/HelpTopicComparator.java | 50 + .../org/bukkit/help/HelpTopicFactory.java | 45 + .../java/org/bukkit/help/IndexHelpTopic.java | 117 + .../inventory/AbstractHorseInventory.java | 25 + .../org/bukkit/inventory/AnvilInventory.java | 52 + .../inventory/ArmoredHorseInventory.java | 21 + .../org/bukkit/inventory/BeaconInventory.java | 24 + .../org/bukkit/inventory/BrewerInventory.java | 45 + .../bukkit/inventory/CraftingInventory.java | 51 + .../inventory/DoubleChestInventory.java | 30 + .../bukkit/inventory/EnchantingInventory.java | 39 + .../org/bukkit/inventory/EntityEquipment.java | 334 + .../org/bukkit/inventory/EquipmentSlot.java | 11 + .../bukkit/inventory/FurnaceInventory.java | 58 + .../org/bukkit/inventory/FurnaceRecipe.java | 222 + .../org/bukkit/inventory/HorseInventory.java | 8 + .../java/org/bukkit/inventory/Inventory.java | 427 + .../org/bukkit/inventory/InventoryHolder.java | 14 + .../org/bukkit/inventory/InventoryView.java | 433 + .../org/bukkit/inventory/ItemFactory.java | 169 + .../java/org/bukkit/inventory/ItemFlag.java | 32 + .../java/org/bukkit/inventory/ItemStack.java | 752 ++ .../org/bukkit/inventory/LlamaInventory.java | 25 + .../java/org/bukkit/inventory/MainHand.java | 9 + .../java/org/bukkit/inventory/Merchant.java | 74 + .../bukkit/inventory/MerchantInventory.java | 33 + .../org/bukkit/inventory/MerchantRecipe.java | 130 + .../org/bukkit/inventory/PlayerInventory.java | 236 + .../java/org/bukkit/inventory/Recipe.java | 17 + .../org/bukkit/inventory/RecipeChoice.java | 229 + .../inventory/SaddledHorseInventory.java | 3 + .../org/bukkit/inventory/ShapedRecipe.java | 235 + .../org/bukkit/inventory/ShapelessRecipe.java | 349 + .../org/bukkit/inventory/meta/BannerMeta.java | 89 + .../bukkit/inventory/meta/BlockStateMeta.java | 37 + .../org/bukkit/inventory/meta/BookMeta.java | 263 + .../org/bukkit/inventory/meta/Damageable.java | 33 + .../meta/EnchantmentStorageMeta.java | 82 + .../inventory/meta/FireworkEffectMeta.java | 38 + .../bukkit/inventory/meta/FireworkMeta.java | 97 + .../org/bukkit/inventory/meta/ItemMeta.java | 461 ++ .../inventory/meta/KnowledgeBookMeta.java | 42 + .../inventory/meta/LeatherArmorMeta.java | 35 + .../org/bukkit/inventory/meta/MapMeta.java | 161 + .../org/bukkit/inventory/meta/PotionMeta.java | 125 + .../org/bukkit/inventory/meta/Repairable.java | 34 + .../org/bukkit/inventory/meta/SkullMeta.java | 76 + .../bukkit/inventory/meta/SpawnEggMeta.java | 36 + .../meta/TropicalFishBucketMeta.java | 85 + .../meta/tags/CustomItemTagContainer.java | 106 + .../meta/tags/ItemTagAdapterContext.java | 18 + .../inventory/meta/tags/ItemTagType.java | 153 + .../java/org/bukkit/loot/LootContext.java | 178 + .../main/java/org/bukkit/loot/LootTable.java | 39 + .../main/java/org/bukkit/loot/LootTables.java | 142 + .../main/java/org/bukkit/loot/Lootable.java | 82 + .../main/java/org/bukkit/map/MapCanvas.java | 88 + .../main/java/org/bukkit/map/MapCursor.java | 289 + .../org/bukkit/map/MapCursorCollection.java | 121 + api/src/main/java/org/bukkit/map/MapFont.java | 150 + .../main/java/org/bukkit/map/MapPalette.java | 264 + .../main/java/org/bukkit/map/MapRenderer.java | 57 + api/src/main/java/org/bukkit/map/MapView.java | 176 + .../java/org/bukkit/map/MinecraftFont.java | 331 + .../java/org/bukkit/material/Attachable.java | 18 + .../main/java/org/bukkit/material/Banner.java | 216 + .../main/java/org/bukkit/material/Bed.java | 125 + .../main/java/org/bukkit/material/Button.java | 123 + .../main/java/org/bukkit/material/Cake.java | 74 + .../java/org/bukkit/material/Cauldron.java | 64 + .../main/java/org/bukkit/material/Chest.java | 43 + .../main/java/org/bukkit/material/Coal.java | 60 + .../java/org/bukkit/material/CocoaPlant.java | 124 + .../java/org/bukkit/material/Colorable.java | 34 + .../java/org/bukkit/material/Command.java | 56 + .../java/org/bukkit/material/Comparator.java | 181 + .../main/java/org/bukkit/material/Crops.java | 132 + .../org/bukkit/material/DetectorRail.java | 39 + .../main/java/org/bukkit/material/Diode.java | 190 + .../java/org/bukkit/material/Directional.java | 22 + .../bukkit/material/DirectionalContainer.java | 77 + .../java/org/bukkit/material/Dispenser.java | 95 + .../main/java/org/bukkit/material/Door.java | 319 + .../main/java/org/bukkit/material/Dye.java | 62 + .../java/org/bukkit/material/EnderChest.java | 43 + .../org/bukkit/material/ExtendedRails.java | 57 + .../java/org/bukkit/material/FlowerPot.java | 122 + .../java/org/bukkit/material/Furnace.java | 43 + .../bukkit/material/FurnaceAndDispenser.java | 28 + .../main/java/org/bukkit/material/Gate.java | 97 + .../main/java/org/bukkit/material/Hopper.java | 162 + .../main/java/org/bukkit/material/Ladder.java | 85 + .../main/java/org/bukkit/material/Leaves.java | 137 + .../main/java/org/bukkit/material/Lever.java | 143 + .../java/org/bukkit/material/LongGrass.java | 60 + .../org/bukkit/material/MaterialData.java | 115 + .../java/org/bukkit/material/MonsterEggs.java | 50 + .../java/org/bukkit/material/Mushroom.java | 283 + .../java/org/bukkit/material/NetherWarts.java | 82 + .../java/org/bukkit/material/Observer.java | 98 + .../java/org/bukkit/material/Openable.java | 18 + .../bukkit/material/PistonBaseMaterial.java | 100 + .../material/PistonExtensionMaterial.java | 95 + .../java/org/bukkit/material/PoweredRail.java | 44 + .../org/bukkit/material/PressurePlate.java | 40 + .../org/bukkit/material/PressureSensor.java | 5 + .../java/org/bukkit/material/Pumpkin.java | 95 + .../main/java/org/bukkit/material/Rails.java | 159 + .../java/org/bukkit/material/Redstone.java | 15 + .../org/bukkit/material/RedstoneTorch.java | 46 + .../org/bukkit/material/RedstoneWire.java | 46 + .../java/org/bukkit/material/Sandstone.java | 60 + .../java/org/bukkit/material/Sapling.java | 111 + .../main/java/org/bukkit/material/Sign.java | 233 + .../SimpleAttachableMaterialData.java | 44 + .../main/java/org/bukkit/material/Skull.java | 97 + .../java/org/bukkit/material/SmoothBrick.java | 51 + .../java/org/bukkit/material/SpawnEgg.java | 73 + .../main/java/org/bukkit/material/Stairs.java | 121 + .../main/java/org/bukkit/material/Step.java | 102 + .../org/bukkit/material/TexturedMaterial.java | 93 + .../main/java/org/bukkit/material/Torch.java | 87 + .../java/org/bukkit/material/TrapDoor.java | 114 + .../main/java/org/bukkit/material/Tree.java | 150 + .../java/org/bukkit/material/Tripwire.java | 77 + .../org/bukkit/material/TripwireHook.java | 120 + .../main/java/org/bukkit/material/Vine.java | 197 + .../main/java/org/bukkit/material/Wood.java | 177 + .../java/org/bukkit/material/WoodenStep.java | 87 + .../main/java/org/bukkit/material/Wool.java | 60 + .../material/types/MushroomBlockTexture.java | 135 + .../bukkit/metadata/FixedMetadataValue.java | 44 + .../bukkit/metadata/LazyMetadataValue.java | 125 + .../metadata/MetadataConversionException.java | 13 + .../metadata/MetadataEvaluationException.java | 13 + .../org/bukkit/metadata/MetadataStore.java | 62 + .../bukkit/metadata/MetadataStoreBase.java | 164 + .../org/bukkit/metadata/MetadataValue.java | 87 + .../bukkit/metadata/MetadataValueAdapter.java | 82 + .../java/org/bukkit/metadata/Metadatable.java | 54 + .../org/bukkit/permissions/Permissible.java | 129 + .../bukkit/permissions/PermissibleBase.java | 259 + .../org/bukkit/permissions/Permission.java | 357 + .../permissions/PermissionAttachment.java | 145 + .../permissions/PermissionAttachmentInfo.java | 68 + .../bukkit/permissions/PermissionDefault.java | 70 + .../PermissionRemovedExecutor.java | 18 + .../bukkit/permissions/ServerOperator.java | 24 + .../org/bukkit/plugin/AuthorNagException.java | 20 + .../java/org/bukkit/plugin/EventExecutor.java | 79 + .../plugin/IllegalPluginAccessException.java | 25 + .../plugin/InvalidDescriptionException.java | 45 + .../bukkit/plugin/InvalidPluginException.java | 49 + .../main/java/org/bukkit/plugin/Plugin.java | 187 + .../org/bukkit/plugin/PluginAwareness.java | 28 + .../java/org/bukkit/plugin/PluginBase.java | 35 + .../bukkit/plugin/PluginDescriptionFile.java | 1144 +++ .../org/bukkit/plugin/PluginLoadOrder.java | 17 + .../java/org/bukkit/plugin/PluginLoader.java | 95 + .../java/org/bukkit/plugin/PluginLogger.java | 38 + .../java/org/bukkit/plugin/PluginManager.java | 319 + .../org/bukkit/plugin/RegisteredListener.java | 77 + .../plugin/RegisteredServiceProvider.java | 51 + .../org/bukkit/plugin/ServicePriority.java | 12 + .../org/bukkit/plugin/ServicesManager.java | 114 + .../bukkit/plugin/SimplePluginManager.java | 0 .../bukkit/plugin/SimpleServicesManager.java | 313 + .../plugin/TimedRegisteredListener.java | 101 + .../plugin/UnknownDependencyException.java | 46 + .../org/bukkit/plugin/java/JavaPlugin.java | 431 + .../bukkit/plugin/java/JavaPluginLoader.java | 395 + .../bukkit/plugin/java/PluginClassLoader.java | 181 + .../ChannelNameTooLongException.java | 15 + .../ChannelNotRegisteredException.java | 15 + .../messaging/MessageTooLargeException.java | 23 + .../bukkit/plugin/messaging/Messenger.java | 233 + .../messaging/PluginChannelDirection.java | 17 + .../messaging/PluginMessageListener.java | 21 + .../PluginMessageListenerRegistration.java | 108 + .../messaging/PluginMessageRecipient.java | 40 + .../messaging/ReservedChannelException.java | 16 + .../plugin/messaging/StandardMessenger.java | 532 ++ .../main/java/org/bukkit/potion/Potion.java | 404 + .../java/org/bukkit/potion/PotionBrewer.java | 47 + .../java/org/bukkit/potion/PotionData.java | 87 + .../java/org/bukkit/potion/PotionEffect.java | 276 + .../org/bukkit/potion/PotionEffectType.java | 322 + .../potion/PotionEffectTypeWrapper.java | 42 + .../java/org/bukkit/potion/PotionType.java | 115 + .../projectiles/BlockProjectileSource.java | 15 + .../bukkit/projectiles/ProjectileSource.java | 34 + .../org/bukkit/scheduler/BukkitRunnable.java | 171 + .../org/bukkit/scheduler/BukkitScheduler.java | 462 ++ .../java/org/bukkit/scheduler/BukkitTask.java | 44 + .../org/bukkit/scheduler/BukkitWorker.java | 37 + .../java/org/bukkit/scoreboard/Criterias.java | 20 + .../org/bukkit/scoreboard/DisplaySlot.java | 10 + .../bukkit/scoreboard/NameTagVisibility.java | 25 + .../java/org/bukkit/scoreboard/Objective.java | 137 + .../org/bukkit/scoreboard/RenderType.java | 16 + .../java/org/bukkit/scoreboard/Score.java | 76 + .../org/bukkit/scoreboard/Scoreboard.java | 233 + .../bukkit/scoreboard/ScoreboardManager.java | 33 + .../main/java/org/bukkit/scoreboard/Team.java | 340 + .../java/org/bukkit/util/BlockIterator.java | 370 + .../java/org/bukkit/util/BlockVector.java | 130 + .../java/org/bukkit/util/BoundingBox.java | 1064 +++ .../org/bukkit/util/CachedServerIcon.java | 26 + .../java/org/bukkit/util/ChatPaginator.java | 178 + .../main/java/org/bukkit/util/Consumer.java | 17 + .../main/java/org/bukkit/util/EulerAngle.java | 154 + .../main/java/org/bukkit/util/FileUtil.java | 59 + .../org/bukkit/util/NumberConversions.java | 127 + .../java/org/bukkit/util/RayTraceResult.java | 163 + .../main/java/org/bukkit/util/StringUtil.java | 60 + api/src/main/java/org/bukkit/util/Vector.java | 878 ++ .../util/io/BukkitObjectInputStream.java | 62 + .../util/io/BukkitObjectOutputStream.java | 52 + .../main/java/org/bukkit/util/io/Wrapper.java | 24 + .../org/bukkit/util/noise/NoiseGenerator.java | 176 + .../bukkit/util/noise/OctaveGenerator.java | 203 + .../util/noise/PerlinNoiseGenerator.java | 219 + .../util/noise/PerlinOctaveGenerator.java | 52 + .../util/noise/SimplexNoiseGenerator.java | 522 ++ .../util/noise/SimplexOctaveGenerator.java | 131 + .../permissions/BroadcastPermissions.java | 24 + .../util/permissions/CommandPermissions.java | 25 + .../util/permissions/DefaultPermissions.java | 94 + .../org/spigotmc/CustomTimingsHandler.java | 111 + .../event/entity/EntityDismountEvent.java | 74 + .../event/entity/EntityMountEvent.java | 56 + .../player/PlayerSpawnLocationEvent.java | 53 + .../org/bukkit/block/package-info.java | 6 + .../bukkit/command/defaults/package-info.java | 6 + .../org/bukkit/command/package-info.java | 5 + .../configuration/file/package-info.java | 7 + .../bukkit/configuration/package-info.java | 5 + .../serialization/package-info.java | 7 + .../bukkit/conversations/package-info.java | 5 + .../org/bukkit/enchantments/package-info.java | 7 + .../bukkit/entity/minecart/package-info.java | 5 + .../org/bukkit/entity/package-info.java | 6 + .../org/bukkit/event/block/package-info.java | 7 + .../event/enchantment/package-info.java | 6 + .../org/bukkit/event/entity/package-info.java | 7 + .../bukkit/event/hanging/package-info.java | 6 + .../bukkit/event/inventory/package-info.java | 6 + .../org/bukkit/event/package-info.java | 5 + .../org/bukkit/event/player/package-info.java | 6 + .../org/bukkit/event/server/package-info.java | 6 + .../bukkit/event/vehicle/package-info.java | 6 + .../bukkit/event/weather/package-info.java | 5 + .../org/bukkit/event/world/package-info.java | 6 + .../org/bukkit/generator/package-info.java | 6 + .../javadoc/org/bukkit/help/package-info.java | 5 + .../bukkit/inventory/meta/package-info.java | 6 + .../org/bukkit/inventory/package-info.java | 5 + .../javadoc/org/bukkit/map/package-info.java | 6 + .../org/bukkit/material/package-info.java | 5 + .../org/bukkit/metadata/package-info.java | 6 + .../main/javadoc/org/bukkit/package-info.java | 5 + .../org/bukkit/permissions/package-info.java | 5 + .../doc-files/permissions-example_plugin.yml | 64 + .../org/bukkit/plugin/java/package-info.java | 6 + .../bukkit/plugin/messaging/package-info.java | 5 + .../org/bukkit/plugin/package-info.java | 5 + .../org/bukkit/potion/package-info.java | 6 + .../org/bukkit/projectiles/package-info.java | 5 + .../org/bukkit/scheduler/package-info.java | 6 + .../org/bukkit/scoreboard/package-info.java | 5 + .../org/bukkit/util/io/package-info.java | 5 + .../org/bukkit/util/noise/package-info.java | 5 + .../javadoc/org/bukkit/util/package-info.java | 6 + .../bukkit/util/permissions/package-info.java | 6 + api/src/main/javadoc/overview.html | 17 + {src/api => api/src}/pom.xml | 12 +- .../destroystokyo/paper/MaterialTagsTest.java | 25 + .../test/java/org/bukkit/AnnotationTest.java | 257 + api/src/test/java/org/bukkit/ArtTest.java | 45 + .../java/org/bukkit/BukkitMirrorTest.java | 75 + .../test/java/org/bukkit/ChatColorTest.java | 83 + .../java/org/bukkit/ChatPaginatorTest.java | 174 + .../test/java/org/bukkit/CoalTypeTest.java | 15 + api/src/test/java/org/bukkit/ColorTest.java | 365 + .../test/java/org/bukkit/CropStateTest.java | 15 + .../test/java/org/bukkit/DifficultyTest.java | 15 + .../test/java/org/bukkit/DyeColorTest.java | 71 + api/src/test/java/org/bukkit/EffectTest.java | 15 + .../java/org/bukkit/EntityEffectTest.java | 15 + .../test/java/org/bukkit/GameModeTest.java | 15 + .../java/org/bukkit/GrassSpeciesTest.java | 15 + .../test/java/org/bukkit/InstrumentTest.java | 15 + .../test/java/org/bukkit/LocationTest.java | 196 + .../test/java/org/bukkit/MaterialTest.java | 73 + .../java/org/bukkit/NamespacedKeyTest.java | 59 + api/src/test/java/org/bukkit/NoteTest.java | 156 + api/src/test/java/org/bukkit/TestServer.java | 110 + .../test/java/org/bukkit/TreeSpeciesTest.java | 15 + .../test/java/org/bukkit/WorldTypeTest.java | 15 + .../ConfigurationSectionTest.java | 600 ++ .../configuration/ConfigurationTest.java | 156 + .../MemoryConfigurationTest.java | 8 + .../configuration/MemorySectionTest.java | 8 + .../file/FileConfigurationTest.java | 209 + .../file/YamlConfigurationTest.java | 56 + .../ConversationContextTest.java | 34 + .../conversations/ConversationTest.java | 116 + .../bukkit/conversations/FakeConversable.java | 105 + .../conversations/ValidatingPromptTest.java | 115 + .../event/PlayerChatTabCompleteEventTest.java | 28 + .../org/bukkit/event/SyntheticEventTest.java | 48 + .../test/java/org/bukkit/event/TestEvent.java | 19 + .../bukkit/materials/MaterialDataTest.java | 433 + .../metadata/FixedMetadataValueTest.java | 42 + .../metadata/LazyMetadataValueTest.java | 135 + .../metadata/MetadataConversionTest.java | 103 + .../bukkit/metadata/MetadataStoreTest.java | 143 + .../metadata/MetadataValueAdapterTest.java | 97 + .../org/bukkit/plugin/PluginManagerTest.java | 176 + .../java/org/bukkit/plugin/TestPlugin.java | 105 + .../plugin/TimedRegisteredListenerTest.java | 56 + .../messaging/StandardMessengerTest.java | 305 + .../plugin/messaging/TestMessageListener.java | 29 + .../bukkit/plugin/messaging/TestPlayer.java | 50 + .../java/org/bukkit/util/BoundingBoxTest.java | 206 + .../bukkit/util/StringUtilStartsWithTest.java | 86 + .../java/org/bukkit/util/StringUtilTest.java | 61 + .../test/java/org/bukkit/util/VectorTest.java | 117 + .../util/io/BukkitObjectStreamTest.java | 173 + scripts/build.sh | 14 +- 1055 files changed, 107079 insertions(+), 12 deletions(-) create mode 100644 api/src/main/java/co/aikar/timings/FullServerTickHandler.java create mode 100644 api/src/main/java/co/aikar/timings/NullTimingHandler.java rename {src/api => api/src}/main/java/co/aikar/timings/ThreadAssertion.java (100%) create mode 100644 api/src/main/java/co/aikar/timings/TimedEventExecutor.java rename {src/api => api/src}/main/java/co/aikar/timings/Timing.java (100%) create mode 100644 api/src/main/java/co/aikar/timings/TimingData.java rename {src/api => api/src}/main/java/co/aikar/timings/TimingHandler.java (100%) create mode 100644 api/src/main/java/co/aikar/timings/TimingHistory.java create mode 100644 api/src/main/java/co/aikar/timings/TimingHistoryEntry.java create mode 100644 api/src/main/java/co/aikar/timings/TimingIdentifier.java create mode 100644 api/src/main/java/co/aikar/timings/Timings.java create mode 100644 api/src/main/java/co/aikar/timings/TimingsCommand.java create mode 100644 api/src/main/java/co/aikar/timings/TimingsExport.java create mode 100644 api/src/main/java/co/aikar/timings/TimingsManager.java create mode 100644 api/src/main/java/co/aikar/timings/TimingsReportListener.java create mode 100644 api/src/main/java/co/aikar/timings/UnsafeTimingHandler.java create mode 100644 api/src/main/java/co/aikar/util/Counter.java create mode 100644 api/src/main/java/co/aikar/util/JSONUtil.java create mode 100644 api/src/main/java/co/aikar/util/LoadingIntMap.java create mode 100644 api/src/main/java/co/aikar/util/LoadingMap.java create mode 100644 api/src/main/java/co/aikar/util/MRUMapCache.java create mode 100644 api/src/main/java/com/destroystokyo/paper/MaterialSetTag.java create mode 100644 api/src/main/java/com/destroystokyo/paper/MaterialTags.java create mode 100644 api/src/main/java/com/destroystokyo/paper/Namespaced.java create mode 100644 api/src/main/java/com/destroystokyo/paper/NamespacedTag.java create mode 100644 api/src/main/java/com/destroystokyo/paper/ParticleBuilder.java create mode 100644 api/src/main/java/com/destroystokyo/paper/Title.java create mode 100644 api/src/main/java/com/destroystokyo/paper/VersionHistoryManager.java create mode 100644 api/src/main/java/com/destroystokyo/paper/block/TargetBlockInfo.java create mode 100644 api/src/main/java/com/destroystokyo/paper/entity/Pathfinder.java create mode 100644 api/src/main/java/com/destroystokyo/paper/entity/RangedEntity.java create mode 100644 api/src/main/java/com/destroystokyo/paper/entity/TargetEntityInfo.java create mode 100644 api/src/main/java/com/destroystokyo/paper/event/block/AnvilDamagedEvent.java create mode 100644 api/src/main/java/com/destroystokyo/paper/event/block/BeaconEffectEvent.java create mode 100644 api/src/main/java/com/destroystokyo/paper/event/block/BlockDestroyEvent.java create mode 100644 api/src/main/java/com/destroystokyo/paper/event/block/TNTPrimeEvent.java create mode 100644 api/src/main/java/com/destroystokyo/paper/event/entity/CreeperIgniteEvent.java create mode 100644 api/src/main/java/com/destroystokyo/paper/event/entity/EnderDragonFireballHitEvent.java create mode 100644 api/src/main/java/com/destroystokyo/paper/event/entity/EnderDragonFlameEvent.java create mode 100644 api/src/main/java/com/destroystokyo/paper/event/entity/EnderDragonShootFireballEvent.java create mode 100644 api/src/main/java/com/destroystokyo/paper/event/entity/EndermanAttackPlayerEvent.java create mode 100644 api/src/main/java/com/destroystokyo/paper/event/entity/EndermanEscapeEvent.java create mode 100644 api/src/main/java/com/destroystokyo/paper/event/entity/EntityAddToWorldEvent.java create mode 100644 api/src/main/java/com/destroystokyo/paper/event/entity/EntityKnockbackByEntityEvent.java create mode 100644 api/src/main/java/com/destroystokyo/paper/event/entity/EntityPathfindEvent.java create mode 100644 api/src/main/java/com/destroystokyo/paper/event/entity/EntityRemoveFromWorldEvent.java create mode 100644 api/src/main/java/com/destroystokyo/paper/event/entity/EntityTeleportEndGatewayEvent.java create mode 100644 api/src/main/java/com/destroystokyo/paper/event/entity/EntityTransformedEvent.java create mode 100644 api/src/main/java/com/destroystokyo/paper/event/entity/EntityZapEvent.java create mode 100644 api/src/main/java/com/destroystokyo/paper/event/entity/ExperienceOrbMergeEvent.java create mode 100644 api/src/main/java/com/destroystokyo/paper/event/entity/PhantomPreSpawnEvent.java create mode 100644 api/src/main/java/com/destroystokyo/paper/event/entity/PlayerNaturallySpawnCreaturesEvent.java create mode 100644 api/src/main/java/com/destroystokyo/paper/event/entity/PreCreatureSpawnEvent.java create mode 100644 api/src/main/java/com/destroystokyo/paper/event/entity/PreSpawnerSpawnEvent.java create mode 100644 api/src/main/java/com/destroystokyo/paper/event/entity/ProjectileCollideEvent.java create mode 100644 api/src/main/java/com/destroystokyo/paper/event/entity/SkeletonHorseTrapEvent.java create mode 100644 api/src/main/java/com/destroystokyo/paper/event/entity/SlimeChangeDirectionEvent.java create mode 100644 api/src/main/java/com/destroystokyo/paper/event/entity/SlimePathfindEvent.java create mode 100644 api/src/main/java/com/destroystokyo/paper/event/entity/SlimeSwimEvent.java create mode 100644 api/src/main/java/com/destroystokyo/paper/event/entity/SlimeTargetLivingEntityEvent.java create mode 100644 api/src/main/java/com/destroystokyo/paper/event/entity/SlimeWanderEvent.java create mode 100644 api/src/main/java/com/destroystokyo/paper/event/entity/TurtleGoHomeEvent.java create mode 100644 api/src/main/java/com/destroystokyo/paper/event/entity/TurtleLayEggEvent.java create mode 100644 api/src/main/java/com/destroystokyo/paper/event/entity/TurtleStartDiggingEvent.java create mode 100644 api/src/main/java/com/destroystokyo/paper/event/entity/WitchConsumePotionEvent.java create mode 100644 api/src/main/java/com/destroystokyo/paper/event/entity/WitchReadyPotionEvent.java create mode 100644 api/src/main/java/com/destroystokyo/paper/event/entity/WitchThrowPotionEvent.java create mode 100644 api/src/main/java/com/destroystokyo/paper/event/executor/MethodHandleEventExecutor.java create mode 100644 api/src/main/java/com/destroystokyo/paper/event/executor/StaticMethodHandleEventExecutor.java create mode 100644 api/src/main/java/com/destroystokyo/paper/event/executor/asm/ASMEventExecutorGenerator.java create mode 100644 api/src/main/java/com/destroystokyo/paper/event/executor/asm/ClassDefiner.java create mode 100644 api/src/main/java/com/destroystokyo/paper/event/executor/asm/SafeClassDefiner.java create mode 100644 api/src/main/java/com/destroystokyo/paper/event/player/IllegalPacketEvent.java create mode 100644 api/src/main/java/com/destroystokyo/paper/event/player/PlayerAdvancementCriterionGrantEvent.java create mode 100644 api/src/main/java/com/destroystokyo/paper/event/player/PlayerArmorChangeEvent.java create mode 100644 api/src/main/java/com/destroystokyo/paper/event/player/PlayerConnectionCloseEvent.java create mode 100644 api/src/main/java/com/destroystokyo/paper/event/player/PlayerElytraBoostEvent.java create mode 100644 api/src/main/java/com/destroystokyo/paper/event/player/PlayerHandshakeEvent.java create mode 100644 api/src/main/java/com/destroystokyo/paper/event/player/PlayerInitialSpawnEvent.java create mode 100644 api/src/main/java/com/destroystokyo/paper/event/player/PlayerJumpEvent.java create mode 100644 api/src/main/java/com/destroystokyo/paper/event/player/PlayerLaunchProjectileEvent.java create mode 100644 api/src/main/java/com/destroystokyo/paper/event/player/PlayerLocaleChangeEvent.java create mode 100644 api/src/main/java/com/destroystokyo/paper/event/player/PlayerPickupExperienceEvent.java create mode 100644 api/src/main/java/com/destroystokyo/paper/event/player/PlayerPostRespawnEvent.java create mode 100644 api/src/main/java/com/destroystokyo/paper/event/player/PlayerReadyArrowEvent.java create mode 100644 api/src/main/java/com/destroystokyo/paper/event/player/PlayerStartSpectatingEntityEvent.java create mode 100644 api/src/main/java/com/destroystokyo/paper/event/player/PlayerStopSpectatingEntityEvent.java create mode 100644 api/src/main/java/com/destroystokyo/paper/event/player/PlayerTeleportEndGatewayEvent.java create mode 100644 api/src/main/java/com/destroystokyo/paper/event/player/PlayerUseUnknownEntityEvent.java create mode 100644 api/src/main/java/com/destroystokyo/paper/event/profile/FillProfileEvent.java create mode 100644 api/src/main/java/com/destroystokyo/paper/event/profile/LookupProfileEvent.java create mode 100644 api/src/main/java/com/destroystokyo/paper/event/profile/PreFillProfileEvent.java create mode 100644 api/src/main/java/com/destroystokyo/paper/event/profile/PreLookupProfileEvent.java create mode 100644 api/src/main/java/com/destroystokyo/paper/event/profile/ProfileWhitelistVerifyEvent.java create mode 100644 api/src/main/java/com/destroystokyo/paper/event/server/AsyncTabCompleteEvent.java create mode 100644 api/src/main/java/com/destroystokyo/paper/event/server/GS4QueryEvent.java create mode 100644 api/src/main/java/com/destroystokyo/paper/event/server/PaperServerListPingEvent.java create mode 100644 api/src/main/java/com/destroystokyo/paper/event/server/ServerExceptionEvent.java create mode 100644 api/src/main/java/com/destroystokyo/paper/event/server/ServerTickEndEvent.java create mode 100644 api/src/main/java/com/destroystokyo/paper/event/server/ServerTickStartEvent.java create mode 100644 api/src/main/java/com/destroystokyo/paper/event/server/WhitelistToggleEvent.java create mode 100644 api/src/main/java/com/destroystokyo/paper/exception/ServerCommandException.java create mode 100644 api/src/main/java/com/destroystokyo/paper/exception/ServerEventException.java create mode 100644 api/src/main/java/com/destroystokyo/paper/exception/ServerException.java create mode 100644 api/src/main/java/com/destroystokyo/paper/exception/ServerInternalException.java create mode 100644 api/src/main/java/com/destroystokyo/paper/exception/ServerPluginEnableDisableException.java create mode 100644 api/src/main/java/com/destroystokyo/paper/exception/ServerPluginException.java create mode 100644 api/src/main/java/com/destroystokyo/paper/exception/ServerPluginMessageException.java create mode 100644 api/src/main/java/com/destroystokyo/paper/exception/ServerSchedulerException.java create mode 100644 api/src/main/java/com/destroystokyo/paper/exception/ServerTabCompleteException.java create mode 100644 api/src/main/java/com/destroystokyo/paper/inventory/ItemStackRecipeChoice.java create mode 100644 api/src/main/java/com/destroystokyo/paper/inventory/meta/ArmorStandMeta.java create mode 100644 api/src/main/java/com/destroystokyo/paper/loottable/LootableBlockInventory.java create mode 100644 api/src/main/java/com/destroystokyo/paper/loottable/LootableEntityInventory.java create mode 100644 api/src/main/java/com/destroystokyo/paper/loottable/LootableInventory.java create mode 100644 api/src/main/java/com/destroystokyo/paper/loottable/LootableInventoryReplenishEvent.java create mode 100644 api/src/main/java/com/destroystokyo/paper/network/NetworkClient.java create mode 100644 api/src/main/java/com/destroystokyo/paper/network/StatusClient.java create mode 100644 api/src/main/java/com/destroystokyo/paper/profile/PlayerProfile.java create mode 100644 api/src/main/java/com/destroystokyo/paper/profile/ProfileProperty.java create mode 100644 api/src/main/java/com/destroystokyo/paper/util/SneakyThrow.java create mode 100644 api/src/main/java/com/destroystokyo/paper/utils/CachedSizeConcurrentLinkedQueue.java create mode 100644 api/src/main/java/com/destroystokyo/paper/utils/PaperPluginLogger.java create mode 100644 api/src/main/java/com/destroystokyo/paper/utils/UnsafeUtils.java rename {src/api => api/src}/main/java/io/akarin/server/core/AkarinGlobalConfig.java (100%) create mode 100644 api/src/main/java/org/bukkit/Achievement.java create mode 100644 api/src/main/java/org/bukkit/Art.java create mode 100644 api/src/main/java/org/bukkit/Axis.java create mode 100644 api/src/main/java/org/bukkit/BanEntry.java create mode 100644 api/src/main/java/org/bukkit/BanList.java create mode 100644 api/src/main/java/org/bukkit/BlockChangeDelegate.java create mode 100644 api/src/main/java/org/bukkit/Bukkit.java create mode 100644 api/src/main/java/org/bukkit/ChatColor.java create mode 100644 api/src/main/java/org/bukkit/Chunk.java create mode 100644 api/src/main/java/org/bukkit/ChunkSnapshot.java create mode 100644 api/src/main/java/org/bukkit/CoalType.java create mode 100644 api/src/main/java/org/bukkit/Color.java create mode 100644 api/src/main/java/org/bukkit/CropState.java create mode 100644 api/src/main/java/org/bukkit/Difficulty.java create mode 100644 api/src/main/java/org/bukkit/DyeColor.java create mode 100644 api/src/main/java/org/bukkit/Effect.java create mode 100644 api/src/main/java/org/bukkit/EntityEffect.java create mode 100644 api/src/main/java/org/bukkit/FireworkEffect.java create mode 100644 api/src/main/java/org/bukkit/FluidCollisionMode.java create mode 100644 api/src/main/java/org/bukkit/GameMode.java create mode 100644 api/src/main/java/org/bukkit/GameRule.java create mode 100644 api/src/main/java/org/bukkit/GrassSpecies.java create mode 100644 api/src/main/java/org/bukkit/Instrument.java create mode 100644 api/src/main/java/org/bukkit/Keyed.java create mode 100644 api/src/main/java/org/bukkit/Location.java create mode 100644 api/src/main/java/org/bukkit/Material.java create mode 100644 api/src/main/java/org/bukkit/Nameable.java create mode 100644 api/src/main/java/org/bukkit/NamespacedKey.java create mode 100644 api/src/main/java/org/bukkit/NetherWartsState.java create mode 100644 api/src/main/java/org/bukkit/Note.java create mode 100644 api/src/main/java/org/bukkit/OfflinePlayer.java create mode 100644 api/src/main/java/org/bukkit/Particle.java create mode 100644 api/src/main/java/org/bukkit/PortalType.java create mode 100644 api/src/main/java/org/bukkit/Rotation.java create mode 100644 api/src/main/java/org/bukkit/SandstoneType.java create mode 100644 api/src/main/java/org/bukkit/Server.java create mode 100644 api/src/main/java/org/bukkit/SkullType.java create mode 100644 api/src/main/java/org/bukkit/Sound.java create mode 100644 api/src/main/java/org/bukkit/SoundCategory.java create mode 100644 api/src/main/java/org/bukkit/Statistic.java create mode 100644 api/src/main/java/org/bukkit/StructureType.java create mode 100644 api/src/main/java/org/bukkit/Tag.java create mode 100644 api/src/main/java/org/bukkit/TravelAgent.java create mode 100644 api/src/main/java/org/bukkit/TreeSpecies.java create mode 100644 api/src/main/java/org/bukkit/TreeType.java create mode 100644 api/src/main/java/org/bukkit/UndefinedNullability.java create mode 100644 api/src/main/java/org/bukkit/UnsafeValues.java create mode 100644 api/src/main/java/org/bukkit/Utility.java create mode 100644 api/src/main/java/org/bukkit/Warning.java create mode 100644 api/src/main/java/org/bukkit/WeatherType.java create mode 100644 api/src/main/java/org/bukkit/World.java create mode 100644 api/src/main/java/org/bukkit/WorldBorder.java create mode 100644 api/src/main/java/org/bukkit/WorldCreator.java create mode 100644 api/src/main/java/org/bukkit/WorldType.java create mode 100644 api/src/main/java/org/bukkit/advancement/Advancement.java create mode 100644 api/src/main/java/org/bukkit/advancement/AdvancementProgress.java create mode 100644 api/src/main/java/org/bukkit/attribute/Attributable.java create mode 100644 api/src/main/java/org/bukkit/attribute/Attribute.java create mode 100644 api/src/main/java/org/bukkit/attribute/AttributeInstance.java create mode 100644 api/src/main/java/org/bukkit/attribute/AttributeModifier.java create mode 100644 api/src/main/java/org/bukkit/block/Banner.java create mode 100644 api/src/main/java/org/bukkit/block/Beacon.java create mode 100644 api/src/main/java/org/bukkit/block/Bed.java create mode 100644 api/src/main/java/org/bukkit/block/Biome.java create mode 100644 api/src/main/java/org/bukkit/block/Block.java create mode 100644 api/src/main/java/org/bukkit/block/BlockFace.java create mode 100644 api/src/main/java/org/bukkit/block/BlockState.java create mode 100644 api/src/main/java/org/bukkit/block/BrewingStand.java create mode 100644 api/src/main/java/org/bukkit/block/Chest.java create mode 100644 api/src/main/java/org/bukkit/block/CommandBlock.java create mode 100644 api/src/main/java/org/bukkit/block/Comparator.java create mode 100644 api/src/main/java/org/bukkit/block/Conduit.java create mode 100644 api/src/main/java/org/bukkit/block/Container.java create mode 100644 api/src/main/java/org/bukkit/block/CreatureSpawner.java create mode 100644 api/src/main/java/org/bukkit/block/DaylightDetector.java create mode 100644 api/src/main/java/org/bukkit/block/Dispenser.java create mode 100644 api/src/main/java/org/bukkit/block/DoubleChest.java create mode 100644 api/src/main/java/org/bukkit/block/Dropper.java create mode 100644 api/src/main/java/org/bukkit/block/EnchantingTable.java create mode 100644 api/src/main/java/org/bukkit/block/EndGateway.java create mode 100644 api/src/main/java/org/bukkit/block/EnderChest.java create mode 100644 api/src/main/java/org/bukkit/block/FlowerPot.java create mode 100644 api/src/main/java/org/bukkit/block/Furnace.java create mode 100644 api/src/main/java/org/bukkit/block/Hopper.java create mode 100644 api/src/main/java/org/bukkit/block/Jukebox.java create mode 100644 api/src/main/java/org/bukkit/block/Lockable.java create mode 100644 api/src/main/java/org/bukkit/block/NoteBlock.java create mode 100644 api/src/main/java/org/bukkit/block/PistonMoveReaction.java create mode 100644 api/src/main/java/org/bukkit/block/ShulkerBox.java create mode 100644 api/src/main/java/org/bukkit/block/Sign.java create mode 100644 api/src/main/java/org/bukkit/block/Skull.java create mode 100644 api/src/main/java/org/bukkit/block/Structure.java create mode 100644 api/src/main/java/org/bukkit/block/banner/Pattern.java create mode 100644 api/src/main/java/org/bukkit/block/banner/PatternType.java create mode 100644 api/src/main/java/org/bukkit/block/data/Ageable.java create mode 100644 api/src/main/java/org/bukkit/block/data/AnaloguePowerable.java create mode 100644 api/src/main/java/org/bukkit/block/data/Attachable.java create mode 100644 api/src/main/java/org/bukkit/block/data/Bisected.java create mode 100644 api/src/main/java/org/bukkit/block/data/BlockData.java create mode 100644 api/src/main/java/org/bukkit/block/data/Directional.java create mode 100644 api/src/main/java/org/bukkit/block/data/Levelled.java create mode 100644 api/src/main/java/org/bukkit/block/data/Lightable.java create mode 100644 api/src/main/java/org/bukkit/block/data/MultipleFacing.java create mode 100644 api/src/main/java/org/bukkit/block/data/Openable.java create mode 100644 api/src/main/java/org/bukkit/block/data/Orientable.java create mode 100644 api/src/main/java/org/bukkit/block/data/Powerable.java create mode 100644 api/src/main/java/org/bukkit/block/data/Rail.java create mode 100644 api/src/main/java/org/bukkit/block/data/Rotatable.java create mode 100644 api/src/main/java/org/bukkit/block/data/Snowable.java create mode 100644 api/src/main/java/org/bukkit/block/data/Waterlogged.java create mode 100644 api/src/main/java/org/bukkit/block/data/type/Bed.java create mode 100644 api/src/main/java/org/bukkit/block/data/type/BrewingStand.java create mode 100644 api/src/main/java/org/bukkit/block/data/type/BubbleColumn.java create mode 100644 api/src/main/java/org/bukkit/block/data/type/Cake.java create mode 100644 api/src/main/java/org/bukkit/block/data/type/Chest.java create mode 100644 api/src/main/java/org/bukkit/block/data/type/Cocoa.java create mode 100644 api/src/main/java/org/bukkit/block/data/type/CommandBlock.java create mode 100644 api/src/main/java/org/bukkit/block/data/type/Comparator.java create mode 100644 api/src/main/java/org/bukkit/block/data/type/CoralWallFan.java create mode 100644 api/src/main/java/org/bukkit/block/data/type/DaylightDetector.java create mode 100644 api/src/main/java/org/bukkit/block/data/type/Dispenser.java create mode 100644 api/src/main/java/org/bukkit/block/data/type/Door.java create mode 100644 api/src/main/java/org/bukkit/block/data/type/EndPortalFrame.java create mode 100644 api/src/main/java/org/bukkit/block/data/type/EnderChest.java create mode 100644 api/src/main/java/org/bukkit/block/data/type/Farmland.java create mode 100644 api/src/main/java/org/bukkit/block/data/type/Fence.java create mode 100644 api/src/main/java/org/bukkit/block/data/type/Fire.java create mode 100644 api/src/main/java/org/bukkit/block/data/type/Furnace.java create mode 100644 api/src/main/java/org/bukkit/block/data/type/Gate.java create mode 100644 api/src/main/java/org/bukkit/block/data/type/GlassPane.java create mode 100644 api/src/main/java/org/bukkit/block/data/type/Hopper.java create mode 100644 api/src/main/java/org/bukkit/block/data/type/Jukebox.java create mode 100644 api/src/main/java/org/bukkit/block/data/type/Ladder.java create mode 100644 api/src/main/java/org/bukkit/block/data/type/Leaves.java create mode 100644 api/src/main/java/org/bukkit/block/data/type/NoteBlock.java create mode 100644 api/src/main/java/org/bukkit/block/data/type/Observer.java create mode 100644 api/src/main/java/org/bukkit/block/data/type/Piston.java create mode 100644 api/src/main/java/org/bukkit/block/data/type/PistonHead.java create mode 100644 api/src/main/java/org/bukkit/block/data/type/RedstoneRail.java create mode 100644 api/src/main/java/org/bukkit/block/data/type/RedstoneWallTorch.java create mode 100644 api/src/main/java/org/bukkit/block/data/type/RedstoneWire.java create mode 100644 api/src/main/java/org/bukkit/block/data/type/Repeater.java create mode 100644 api/src/main/java/org/bukkit/block/data/type/Sapling.java create mode 100644 api/src/main/java/org/bukkit/block/data/type/SeaPickle.java create mode 100644 api/src/main/java/org/bukkit/block/data/type/Sign.java create mode 100644 api/src/main/java/org/bukkit/block/data/type/Slab.java create mode 100644 api/src/main/java/org/bukkit/block/data/type/Snow.java create mode 100644 api/src/main/java/org/bukkit/block/data/type/Stairs.java create mode 100644 api/src/main/java/org/bukkit/block/data/type/StructureBlock.java create mode 100644 api/src/main/java/org/bukkit/block/data/type/Switch.java create mode 100644 api/src/main/java/org/bukkit/block/data/type/TNT.java create mode 100644 api/src/main/java/org/bukkit/block/data/type/TechnicalPiston.java create mode 100644 api/src/main/java/org/bukkit/block/data/type/TrapDoor.java create mode 100644 api/src/main/java/org/bukkit/block/data/type/Tripwire.java create mode 100644 api/src/main/java/org/bukkit/block/data/type/TripwireHook.java create mode 100644 api/src/main/java/org/bukkit/block/data/type/TurtleEgg.java create mode 100644 api/src/main/java/org/bukkit/block/data/type/WallSign.java create mode 100644 api/src/main/java/org/bukkit/block/structure/Mirror.java create mode 100644 api/src/main/java/org/bukkit/block/structure/StructureRotation.java create mode 100644 api/src/main/java/org/bukkit/block/structure/UsageMode.java create mode 100644 api/src/main/java/org/bukkit/boss/BarColor.java create mode 100644 api/src/main/java/org/bukkit/boss/BarFlag.java create mode 100644 api/src/main/java/org/bukkit/boss/BarStyle.java create mode 100644 api/src/main/java/org/bukkit/boss/BossBar.java create mode 100644 api/src/main/java/org/bukkit/boss/KeyedBossBar.java create mode 100644 api/src/main/java/org/bukkit/command/BlockCommandSender.java create mode 100644 api/src/main/java/org/bukkit/command/BufferedCommandSender.java create mode 100644 api/src/main/java/org/bukkit/command/Command.java create mode 100644 api/src/main/java/org/bukkit/command/CommandException.java create mode 100644 api/src/main/java/org/bukkit/command/CommandExecutor.java create mode 100644 api/src/main/java/org/bukkit/command/CommandMap.java create mode 100644 api/src/main/java/org/bukkit/command/CommandSender.java create mode 100644 api/src/main/java/org/bukkit/command/ConsoleCommandSender.java create mode 100644 api/src/main/java/org/bukkit/command/FormattedCommandAlias.java create mode 100644 api/src/main/java/org/bukkit/command/MessageCommandSender.java create mode 100644 api/src/main/java/org/bukkit/command/MultipleCommandAlias.java create mode 100644 api/src/main/java/org/bukkit/command/PluginCommand.java create mode 100644 api/src/main/java/org/bukkit/command/PluginCommandYamlParser.java create mode 100644 api/src/main/java/org/bukkit/command/PluginIdentifiableCommand.java create mode 100644 api/src/main/java/org/bukkit/command/ProxiedCommandSender.java create mode 100644 api/src/main/java/org/bukkit/command/RemoteConsoleCommandSender.java create mode 100644 api/src/main/java/org/bukkit/command/SimpleCommandMap.java create mode 100644 api/src/main/java/org/bukkit/command/TabCompleter.java create mode 100644 api/src/main/java/org/bukkit/command/TabExecutor.java create mode 100644 api/src/main/java/org/bukkit/command/defaults/BukkitCommand.java create mode 100644 api/src/main/java/org/bukkit/command/defaults/HelpCommand.java create mode 100644 api/src/main/java/org/bukkit/command/defaults/PluginsCommand.java create mode 100644 api/src/main/java/org/bukkit/command/defaults/ReloadCommand.java rename {src/api => api/src}/main/java/org/bukkit/command/defaults/VersionCommand.java (100%) create mode 100644 api/src/main/java/org/bukkit/configuration/Configuration.java create mode 100644 api/src/main/java/org/bukkit/configuration/ConfigurationOptions.java create mode 100644 api/src/main/java/org/bukkit/configuration/ConfigurationSection.java create mode 100644 api/src/main/java/org/bukkit/configuration/InvalidConfigurationException.java create mode 100644 api/src/main/java/org/bukkit/configuration/MemoryConfiguration.java create mode 100644 api/src/main/java/org/bukkit/configuration/MemoryConfigurationOptions.java create mode 100644 api/src/main/java/org/bukkit/configuration/MemorySection.java create mode 100644 api/src/main/java/org/bukkit/configuration/file/FileConfiguration.java create mode 100644 api/src/main/java/org/bukkit/configuration/file/FileConfigurationOptions.java create mode 100644 api/src/main/java/org/bukkit/configuration/file/YamlConfiguration.java create mode 100644 api/src/main/java/org/bukkit/configuration/file/YamlConfigurationOptions.java create mode 100644 api/src/main/java/org/bukkit/configuration/file/YamlConstructor.java create mode 100644 api/src/main/java/org/bukkit/configuration/file/YamlRepresenter.java create mode 100644 api/src/main/java/org/bukkit/configuration/serialization/ConfigurationSerializable.java create mode 100644 api/src/main/java/org/bukkit/configuration/serialization/ConfigurationSerialization.java create mode 100644 api/src/main/java/org/bukkit/configuration/serialization/DelegateDeserialization.java create mode 100644 api/src/main/java/org/bukkit/configuration/serialization/SerializableAs.java create mode 100644 api/src/main/java/org/bukkit/conversations/BooleanPrompt.java create mode 100644 api/src/main/java/org/bukkit/conversations/Conversable.java create mode 100644 api/src/main/java/org/bukkit/conversations/Conversation.java create mode 100644 api/src/main/java/org/bukkit/conversations/ConversationAbandonedEvent.java create mode 100644 api/src/main/java/org/bukkit/conversations/ConversationAbandonedListener.java create mode 100644 api/src/main/java/org/bukkit/conversations/ConversationCanceller.java create mode 100644 api/src/main/java/org/bukkit/conversations/ConversationContext.java create mode 100644 api/src/main/java/org/bukkit/conversations/ConversationFactory.java create mode 100644 api/src/main/java/org/bukkit/conversations/ConversationPrefix.java create mode 100644 api/src/main/java/org/bukkit/conversations/ExactMatchConversationCanceller.java create mode 100644 api/src/main/java/org/bukkit/conversations/FixedSetPrompt.java create mode 100644 api/src/main/java/org/bukkit/conversations/InactivityConversationCanceller.java create mode 100644 api/src/main/java/org/bukkit/conversations/ManuallyAbandonedConversationCanceller.java create mode 100644 api/src/main/java/org/bukkit/conversations/MessagePrompt.java create mode 100644 api/src/main/java/org/bukkit/conversations/NullConversationPrefix.java create mode 100644 api/src/main/java/org/bukkit/conversations/NumericPrompt.java create mode 100644 api/src/main/java/org/bukkit/conversations/PlayerNamePrompt.java create mode 100644 api/src/main/java/org/bukkit/conversations/PluginNameConversationPrefix.java create mode 100644 api/src/main/java/org/bukkit/conversations/Prompt.java create mode 100644 api/src/main/java/org/bukkit/conversations/RegexPrompt.java create mode 100644 api/src/main/java/org/bukkit/conversations/StringPrompt.java create mode 100644 api/src/main/java/org/bukkit/conversations/ValidatingPrompt.java create mode 100644 api/src/main/java/org/bukkit/enchantments/Enchantment.java create mode 100644 api/src/main/java/org/bukkit/enchantments/EnchantmentOffer.java create mode 100644 api/src/main/java/org/bukkit/enchantments/EnchantmentTarget.java create mode 100644 api/src/main/java/org/bukkit/enchantments/EnchantmentWrapper.java create mode 100644 api/src/main/java/org/bukkit/entity/AbstractHorse.java create mode 100644 api/src/main/java/org/bukkit/entity/Ageable.java create mode 100644 api/src/main/java/org/bukkit/entity/Ambient.java create mode 100644 api/src/main/java/org/bukkit/entity/AnimalTamer.java create mode 100644 api/src/main/java/org/bukkit/entity/Animals.java create mode 100644 api/src/main/java/org/bukkit/entity/AreaEffectCloud.java create mode 100644 api/src/main/java/org/bukkit/entity/ArmorStand.java create mode 100644 api/src/main/java/org/bukkit/entity/Arrow.java create mode 100644 api/src/main/java/org/bukkit/entity/Bat.java create mode 100644 api/src/main/java/org/bukkit/entity/Blaze.java create mode 100644 api/src/main/java/org/bukkit/entity/Boat.java create mode 100644 api/src/main/java/org/bukkit/entity/Boss.java create mode 100644 api/src/main/java/org/bukkit/entity/CaveSpider.java create mode 100644 api/src/main/java/org/bukkit/entity/ChestedHorse.java create mode 100644 api/src/main/java/org/bukkit/entity/Chicken.java create mode 100644 api/src/main/java/org/bukkit/entity/Cod.java create mode 100644 api/src/main/java/org/bukkit/entity/ComplexEntityPart.java create mode 100644 api/src/main/java/org/bukkit/entity/ComplexLivingEntity.java create mode 100644 api/src/main/java/org/bukkit/entity/Cow.java create mode 100644 api/src/main/java/org/bukkit/entity/Creature.java create mode 100644 api/src/main/java/org/bukkit/entity/Creeper.java create mode 100644 api/src/main/java/org/bukkit/entity/Damageable.java create mode 100644 api/src/main/java/org/bukkit/entity/Dolphin.java create mode 100644 api/src/main/java/org/bukkit/entity/Donkey.java create mode 100644 api/src/main/java/org/bukkit/entity/DragonFireball.java create mode 100644 api/src/main/java/org/bukkit/entity/Drowned.java create mode 100644 api/src/main/java/org/bukkit/entity/Egg.java create mode 100644 api/src/main/java/org/bukkit/entity/ElderGuardian.java create mode 100644 api/src/main/java/org/bukkit/entity/EnderCrystal.java create mode 100644 api/src/main/java/org/bukkit/entity/EnderDragon.java create mode 100644 api/src/main/java/org/bukkit/entity/EnderDragonPart.java create mode 100644 api/src/main/java/org/bukkit/entity/EnderPearl.java create mode 100644 api/src/main/java/org/bukkit/entity/EnderSignal.java create mode 100644 api/src/main/java/org/bukkit/entity/Enderman.java create mode 100644 api/src/main/java/org/bukkit/entity/Endermite.java create mode 100644 api/src/main/java/org/bukkit/entity/Entity.java create mode 100644 api/src/main/java/org/bukkit/entity/EntityType.java create mode 100644 api/src/main/java/org/bukkit/entity/Evoker.java create mode 100644 api/src/main/java/org/bukkit/entity/EvokerFangs.java create mode 100644 api/src/main/java/org/bukkit/entity/ExperienceOrb.java create mode 100644 api/src/main/java/org/bukkit/entity/Explosive.java create mode 100644 api/src/main/java/org/bukkit/entity/FallingBlock.java create mode 100644 api/src/main/java/org/bukkit/entity/Fireball.java create mode 100644 api/src/main/java/org/bukkit/entity/Firework.java create mode 100644 api/src/main/java/org/bukkit/entity/Fish.java create mode 100644 api/src/main/java/org/bukkit/entity/FishHook.java create mode 100644 api/src/main/java/org/bukkit/entity/Flying.java create mode 100644 api/src/main/java/org/bukkit/entity/Ghast.java create mode 100644 api/src/main/java/org/bukkit/entity/Giant.java create mode 100644 api/src/main/java/org/bukkit/entity/Golem.java create mode 100644 api/src/main/java/org/bukkit/entity/Guardian.java create mode 100644 api/src/main/java/org/bukkit/entity/Hanging.java create mode 100644 api/src/main/java/org/bukkit/entity/Horse.java create mode 100644 api/src/main/java/org/bukkit/entity/HumanEntity.java create mode 100644 api/src/main/java/org/bukkit/entity/Husk.java create mode 100644 api/src/main/java/org/bukkit/entity/Illager.java create mode 100644 api/src/main/java/org/bukkit/entity/Illusioner.java create mode 100644 api/src/main/java/org/bukkit/entity/IronGolem.java create mode 100644 api/src/main/java/org/bukkit/entity/Item.java create mode 100644 api/src/main/java/org/bukkit/entity/ItemFrame.java create mode 100644 api/src/main/java/org/bukkit/entity/LargeFireball.java create mode 100644 api/src/main/java/org/bukkit/entity/LeashHitch.java create mode 100644 api/src/main/java/org/bukkit/entity/LightningStrike.java create mode 100644 api/src/main/java/org/bukkit/entity/LingeringPotion.java create mode 100644 api/src/main/java/org/bukkit/entity/LivingEntity.java create mode 100644 api/src/main/java/org/bukkit/entity/Llama.java create mode 100644 api/src/main/java/org/bukkit/entity/LlamaSpit.java create mode 100644 api/src/main/java/org/bukkit/entity/MagmaCube.java create mode 100644 api/src/main/java/org/bukkit/entity/Minecart.java create mode 100644 api/src/main/java/org/bukkit/entity/Mob.java create mode 100644 api/src/main/java/org/bukkit/entity/Monster.java create mode 100644 api/src/main/java/org/bukkit/entity/Mule.java create mode 100644 api/src/main/java/org/bukkit/entity/MushroomCow.java create mode 100644 api/src/main/java/org/bukkit/entity/NPC.java create mode 100644 api/src/main/java/org/bukkit/entity/Ocelot.java create mode 100644 api/src/main/java/org/bukkit/entity/Painting.java create mode 100644 api/src/main/java/org/bukkit/entity/Parrot.java create mode 100644 api/src/main/java/org/bukkit/entity/Phantom.java create mode 100644 api/src/main/java/org/bukkit/entity/Pig.java create mode 100644 api/src/main/java/org/bukkit/entity/PigZombie.java create mode 100644 api/src/main/java/org/bukkit/entity/Player.java create mode 100644 api/src/main/java/org/bukkit/entity/PolarBear.java create mode 100644 api/src/main/java/org/bukkit/entity/Projectile.java create mode 100644 api/src/main/java/org/bukkit/entity/PufferFish.java create mode 100644 api/src/main/java/org/bukkit/entity/Rabbit.java create mode 100644 api/src/main/java/org/bukkit/entity/Salmon.java create mode 100644 api/src/main/java/org/bukkit/entity/Sheep.java create mode 100644 api/src/main/java/org/bukkit/entity/Shulker.java create mode 100644 api/src/main/java/org/bukkit/entity/ShulkerBullet.java create mode 100644 api/src/main/java/org/bukkit/entity/Silverfish.java create mode 100644 api/src/main/java/org/bukkit/entity/Sittable.java create mode 100644 api/src/main/java/org/bukkit/entity/Skeleton.java create mode 100644 api/src/main/java/org/bukkit/entity/SkeletonHorse.java create mode 100644 api/src/main/java/org/bukkit/entity/Slime.java create mode 100644 api/src/main/java/org/bukkit/entity/SmallFireball.java create mode 100644 api/src/main/java/org/bukkit/entity/Snowball.java create mode 100644 api/src/main/java/org/bukkit/entity/Snowman.java create mode 100644 api/src/main/java/org/bukkit/entity/SpectralArrow.java create mode 100644 api/src/main/java/org/bukkit/entity/Spellcaster.java create mode 100644 api/src/main/java/org/bukkit/entity/Spider.java create mode 100644 api/src/main/java/org/bukkit/entity/SplashPotion.java create mode 100644 api/src/main/java/org/bukkit/entity/Squid.java create mode 100644 api/src/main/java/org/bukkit/entity/Stray.java create mode 100644 api/src/main/java/org/bukkit/entity/TNTPrimed.java create mode 100644 api/src/main/java/org/bukkit/entity/Tameable.java create mode 100644 api/src/main/java/org/bukkit/entity/ThrownExpBottle.java create mode 100644 api/src/main/java/org/bukkit/entity/ThrownPotion.java create mode 100644 api/src/main/java/org/bukkit/entity/TippedArrow.java create mode 100644 api/src/main/java/org/bukkit/entity/Trident.java create mode 100644 api/src/main/java/org/bukkit/entity/TropicalFish.java create mode 100644 api/src/main/java/org/bukkit/entity/Turtle.java create mode 100644 api/src/main/java/org/bukkit/entity/Vehicle.java create mode 100644 api/src/main/java/org/bukkit/entity/Vex.java create mode 100644 api/src/main/java/org/bukkit/entity/Villager.java create mode 100644 api/src/main/java/org/bukkit/entity/Vindicator.java create mode 100644 api/src/main/java/org/bukkit/entity/WaterMob.java create mode 100644 api/src/main/java/org/bukkit/entity/Weather.java create mode 100644 api/src/main/java/org/bukkit/entity/Witch.java create mode 100644 api/src/main/java/org/bukkit/entity/Wither.java create mode 100644 api/src/main/java/org/bukkit/entity/WitherSkeleton.java create mode 100644 api/src/main/java/org/bukkit/entity/WitherSkull.java create mode 100644 api/src/main/java/org/bukkit/entity/Wolf.java create mode 100644 api/src/main/java/org/bukkit/entity/Zombie.java create mode 100644 api/src/main/java/org/bukkit/entity/ZombieHorse.java create mode 100644 api/src/main/java/org/bukkit/entity/ZombieVillager.java create mode 100644 api/src/main/java/org/bukkit/entity/minecart/CommandMinecart.java create mode 100644 api/src/main/java/org/bukkit/entity/minecart/ExplosiveMinecart.java create mode 100644 api/src/main/java/org/bukkit/entity/minecart/HopperMinecart.java create mode 100644 api/src/main/java/org/bukkit/entity/minecart/PoweredMinecart.java create mode 100644 api/src/main/java/org/bukkit/entity/minecart/RideableMinecart.java create mode 100644 api/src/main/java/org/bukkit/entity/minecart/SpawnerMinecart.java create mode 100644 api/src/main/java/org/bukkit/entity/minecart/StorageMinecart.java create mode 100644 api/src/main/java/org/bukkit/event/Cancellable.java create mode 100644 api/src/main/java/org/bukkit/event/Event.java create mode 100644 api/src/main/java/org/bukkit/event/EventException.java create mode 100644 api/src/main/java/org/bukkit/event/EventHandler.java create mode 100644 api/src/main/java/org/bukkit/event/EventPriority.java rename {src/api => api/src}/main/java/org/bukkit/event/HandlerList.java (100%) create mode 100644 api/src/main/java/org/bukkit/event/Listener.java create mode 100644 api/src/main/java/org/bukkit/event/block/Action.java create mode 100644 api/src/main/java/org/bukkit/event/block/BlockBreakEvent.java create mode 100644 api/src/main/java/org/bukkit/event/block/BlockBurnEvent.java create mode 100644 api/src/main/java/org/bukkit/event/block/BlockCanBuildEvent.java create mode 100644 api/src/main/java/org/bukkit/event/block/BlockDamageEvent.java create mode 100644 api/src/main/java/org/bukkit/event/block/BlockDispenseArmorEvent.java create mode 100644 api/src/main/java/org/bukkit/event/block/BlockDispenseEvent.java create mode 100644 api/src/main/java/org/bukkit/event/block/BlockDropItemEvent.java create mode 100644 api/src/main/java/org/bukkit/event/block/BlockEvent.java create mode 100644 api/src/main/java/org/bukkit/event/block/BlockExpEvent.java create mode 100644 api/src/main/java/org/bukkit/event/block/BlockExplodeEvent.java create mode 100644 api/src/main/java/org/bukkit/event/block/BlockFadeEvent.java create mode 100644 api/src/main/java/org/bukkit/event/block/BlockFertilizeEvent.java create mode 100644 api/src/main/java/org/bukkit/event/block/BlockFormEvent.java create mode 100644 api/src/main/java/org/bukkit/event/block/BlockFromToEvent.java create mode 100644 api/src/main/java/org/bukkit/event/block/BlockGrowEvent.java create mode 100644 api/src/main/java/org/bukkit/event/block/BlockIgniteEvent.java create mode 100644 api/src/main/java/org/bukkit/event/block/BlockMultiPlaceEvent.java create mode 100644 api/src/main/java/org/bukkit/event/block/BlockPhysicsEvent.java create mode 100644 api/src/main/java/org/bukkit/event/block/BlockPistonEvent.java create mode 100644 api/src/main/java/org/bukkit/event/block/BlockPistonExtendEvent.java create mode 100644 api/src/main/java/org/bukkit/event/block/BlockPistonRetractEvent.java create mode 100644 api/src/main/java/org/bukkit/event/block/BlockPlaceEvent.java create mode 100644 api/src/main/java/org/bukkit/event/block/BlockRedstoneEvent.java create mode 100644 api/src/main/java/org/bukkit/event/block/BlockSpreadEvent.java create mode 100644 api/src/main/java/org/bukkit/event/block/CauldronLevelChangeEvent.java create mode 100644 api/src/main/java/org/bukkit/event/block/EntityBlockFormEvent.java create mode 100644 api/src/main/java/org/bukkit/event/block/FluidLevelChangeEvent.java create mode 100644 api/src/main/java/org/bukkit/event/block/LeavesDecayEvent.java create mode 100644 api/src/main/java/org/bukkit/event/block/MoistureChangeEvent.java create mode 100644 api/src/main/java/org/bukkit/event/block/NotePlayEvent.java create mode 100644 api/src/main/java/org/bukkit/event/block/SignChangeEvent.java create mode 100644 api/src/main/java/org/bukkit/event/block/SpongeAbsorbEvent.java create mode 100644 api/src/main/java/org/bukkit/event/command/UnknownCommandEvent.java create mode 100644 api/src/main/java/org/bukkit/event/enchantment/EnchantItemEvent.java create mode 100644 api/src/main/java/org/bukkit/event/enchantment/PrepareItemEnchantEvent.java create mode 100644 api/src/main/java/org/bukkit/event/entity/AreaEffectCloudApplyEvent.java create mode 100644 api/src/main/java/org/bukkit/event/entity/BatToggleSleepEvent.java create mode 100644 api/src/main/java/org/bukkit/event/entity/CreatureSpawnEvent.java create mode 100644 api/src/main/java/org/bukkit/event/entity/CreeperPowerEvent.java create mode 100644 api/src/main/java/org/bukkit/event/entity/EnderDragonChangePhaseEvent.java create mode 100644 api/src/main/java/org/bukkit/event/entity/EntityAirChangeEvent.java create mode 100644 api/src/main/java/org/bukkit/event/entity/EntityBreakDoorEvent.java create mode 100644 api/src/main/java/org/bukkit/event/entity/EntityBreedEvent.java create mode 100644 api/src/main/java/org/bukkit/event/entity/EntityChangeBlockEvent.java create mode 100644 api/src/main/java/org/bukkit/event/entity/EntityCombustByBlockEvent.java create mode 100644 api/src/main/java/org/bukkit/event/entity/EntityCombustByEntityEvent.java create mode 100644 api/src/main/java/org/bukkit/event/entity/EntityCombustEvent.java create mode 100644 api/src/main/java/org/bukkit/event/entity/EntityCreatePortalEvent.java create mode 100644 api/src/main/java/org/bukkit/event/entity/EntityDamageByBlockEvent.java create mode 100644 api/src/main/java/org/bukkit/event/entity/EntityDamageByEntityEvent.java create mode 100644 api/src/main/java/org/bukkit/event/entity/EntityDamageEvent.java create mode 100644 api/src/main/java/org/bukkit/event/entity/EntityDeathEvent.java create mode 100644 api/src/main/java/org/bukkit/event/entity/EntityDropItemEvent.java create mode 100644 api/src/main/java/org/bukkit/event/entity/EntityEvent.java create mode 100644 api/src/main/java/org/bukkit/event/entity/EntityExplodeEvent.java create mode 100644 api/src/main/java/org/bukkit/event/entity/EntityInteractEvent.java create mode 100644 api/src/main/java/org/bukkit/event/entity/EntityPickupItemEvent.java create mode 100644 api/src/main/java/org/bukkit/event/entity/EntityPlaceEvent.java create mode 100644 api/src/main/java/org/bukkit/event/entity/EntityPortalEnterEvent.java create mode 100644 api/src/main/java/org/bukkit/event/entity/EntityPortalEvent.java create mode 100644 api/src/main/java/org/bukkit/event/entity/EntityPortalExitEvent.java create mode 100644 api/src/main/java/org/bukkit/event/entity/EntityPotionEffectEvent.java create mode 100644 api/src/main/java/org/bukkit/event/entity/EntityRegainHealthEvent.java create mode 100644 api/src/main/java/org/bukkit/event/entity/EntityResurrectEvent.java create mode 100644 api/src/main/java/org/bukkit/event/entity/EntityShootBowEvent.java create mode 100644 api/src/main/java/org/bukkit/event/entity/EntitySpawnEvent.java create mode 100644 api/src/main/java/org/bukkit/event/entity/EntityTameEvent.java create mode 100644 api/src/main/java/org/bukkit/event/entity/EntityTargetEvent.java create mode 100644 api/src/main/java/org/bukkit/event/entity/EntityTargetLivingEntityEvent.java create mode 100644 api/src/main/java/org/bukkit/event/entity/EntityTeleportEvent.java create mode 100644 api/src/main/java/org/bukkit/event/entity/EntityToggleGlideEvent.java create mode 100644 api/src/main/java/org/bukkit/event/entity/EntityToggleSwimEvent.java create mode 100644 api/src/main/java/org/bukkit/event/entity/EntityTransformEvent.java create mode 100644 api/src/main/java/org/bukkit/event/entity/EntityUnleashEvent.java create mode 100644 api/src/main/java/org/bukkit/event/entity/ExpBottleEvent.java create mode 100644 api/src/main/java/org/bukkit/event/entity/ExplosionPrimeEvent.java create mode 100644 api/src/main/java/org/bukkit/event/entity/FireworkExplodeEvent.java create mode 100644 api/src/main/java/org/bukkit/event/entity/FoodLevelChangeEvent.java create mode 100644 api/src/main/java/org/bukkit/event/entity/HorseJumpEvent.java create mode 100644 api/src/main/java/org/bukkit/event/entity/ItemDespawnEvent.java create mode 100644 api/src/main/java/org/bukkit/event/entity/ItemMergeEvent.java create mode 100644 api/src/main/java/org/bukkit/event/entity/ItemSpawnEvent.java create mode 100644 api/src/main/java/org/bukkit/event/entity/LingeringPotionSplashEvent.java create mode 100644 api/src/main/java/org/bukkit/event/entity/PigZapEvent.java create mode 100644 api/src/main/java/org/bukkit/event/entity/PigZombieAngerEvent.java create mode 100644 api/src/main/java/org/bukkit/event/entity/PlayerDeathEvent.java create mode 100644 api/src/main/java/org/bukkit/event/entity/PlayerLeashEntityEvent.java create mode 100644 api/src/main/java/org/bukkit/event/entity/PotionSplashEvent.java create mode 100644 api/src/main/java/org/bukkit/event/entity/ProjectileHitEvent.java create mode 100644 api/src/main/java/org/bukkit/event/entity/ProjectileLaunchEvent.java create mode 100644 api/src/main/java/org/bukkit/event/entity/SheepDyeWoolEvent.java create mode 100644 api/src/main/java/org/bukkit/event/entity/SheepRegrowWoolEvent.java create mode 100644 api/src/main/java/org/bukkit/event/entity/SlimeSplitEvent.java create mode 100644 api/src/main/java/org/bukkit/event/entity/SpawnerSpawnEvent.java create mode 100644 api/src/main/java/org/bukkit/event/entity/VillagerAcquireTradeEvent.java create mode 100644 api/src/main/java/org/bukkit/event/entity/VillagerReplenishTradeEvent.java create mode 100644 api/src/main/java/org/bukkit/event/hanging/HangingBreakByEntityEvent.java create mode 100644 api/src/main/java/org/bukkit/event/hanging/HangingBreakEvent.java create mode 100644 api/src/main/java/org/bukkit/event/hanging/HangingEvent.java create mode 100644 api/src/main/java/org/bukkit/event/hanging/HangingPlaceEvent.java create mode 100644 api/src/main/java/org/bukkit/event/inventory/BrewEvent.java create mode 100644 api/src/main/java/org/bukkit/event/inventory/BrewingStandFuelEvent.java create mode 100644 api/src/main/java/org/bukkit/event/inventory/ClickType.java create mode 100644 api/src/main/java/org/bukkit/event/inventory/CraftItemEvent.java create mode 100644 api/src/main/java/org/bukkit/event/inventory/DragType.java create mode 100644 api/src/main/java/org/bukkit/event/inventory/FurnaceBurnEvent.java create mode 100644 api/src/main/java/org/bukkit/event/inventory/FurnaceExtractEvent.java create mode 100644 api/src/main/java/org/bukkit/event/inventory/FurnaceSmeltEvent.java create mode 100644 api/src/main/java/org/bukkit/event/inventory/InventoryAction.java create mode 100644 api/src/main/java/org/bukkit/event/inventory/InventoryClickEvent.java create mode 100644 api/src/main/java/org/bukkit/event/inventory/InventoryCloseEvent.java create mode 100644 api/src/main/java/org/bukkit/event/inventory/InventoryCreativeEvent.java create mode 100644 api/src/main/java/org/bukkit/event/inventory/InventoryDragEvent.java create mode 100644 api/src/main/java/org/bukkit/event/inventory/InventoryEvent.java create mode 100644 api/src/main/java/org/bukkit/event/inventory/InventoryInteractEvent.java create mode 100644 api/src/main/java/org/bukkit/event/inventory/InventoryMoveItemEvent.java create mode 100644 api/src/main/java/org/bukkit/event/inventory/InventoryOpenEvent.java create mode 100644 api/src/main/java/org/bukkit/event/inventory/InventoryPickupItemEvent.java create mode 100644 api/src/main/java/org/bukkit/event/inventory/InventoryType.java create mode 100644 api/src/main/java/org/bukkit/event/inventory/PrepareAnvilEvent.java create mode 100644 api/src/main/java/org/bukkit/event/inventory/PrepareItemCraftEvent.java create mode 100644 api/src/main/java/org/bukkit/event/player/AsyncPlayerChatEvent.java create mode 100644 api/src/main/java/org/bukkit/event/player/AsyncPlayerPreLoginEvent.java create mode 100644 api/src/main/java/org/bukkit/event/player/PlayerAchievementAwardedEvent.java create mode 100644 api/src/main/java/org/bukkit/event/player/PlayerAdvancementDoneEvent.java create mode 100644 api/src/main/java/org/bukkit/event/player/PlayerAnimationEvent.java create mode 100644 api/src/main/java/org/bukkit/event/player/PlayerAnimationType.java create mode 100644 api/src/main/java/org/bukkit/event/player/PlayerArmorStandManipulateEvent.java create mode 100644 api/src/main/java/org/bukkit/event/player/PlayerAttemptPickupItemEvent.java create mode 100644 api/src/main/java/org/bukkit/event/player/PlayerBedEnterEvent.java create mode 100644 api/src/main/java/org/bukkit/event/player/PlayerBedLeaveEvent.java create mode 100644 api/src/main/java/org/bukkit/event/player/PlayerBucketEmptyEvent.java create mode 100644 api/src/main/java/org/bukkit/event/player/PlayerBucketEvent.java create mode 100644 api/src/main/java/org/bukkit/event/player/PlayerBucketFillEvent.java create mode 100644 api/src/main/java/org/bukkit/event/player/PlayerChangedMainHandEvent.java create mode 100644 api/src/main/java/org/bukkit/event/player/PlayerChangedWorldEvent.java create mode 100644 api/src/main/java/org/bukkit/event/player/PlayerChannelEvent.java create mode 100644 api/src/main/java/org/bukkit/event/player/PlayerChatEvent.java create mode 100644 api/src/main/java/org/bukkit/event/player/PlayerChatTabCompleteEvent.java create mode 100644 api/src/main/java/org/bukkit/event/player/PlayerCommandPreprocessEvent.java create mode 100644 api/src/main/java/org/bukkit/event/player/PlayerCommandSendEvent.java create mode 100644 api/src/main/java/org/bukkit/event/player/PlayerDropItemEvent.java create mode 100644 api/src/main/java/org/bukkit/event/player/PlayerEditBookEvent.java create mode 100644 api/src/main/java/org/bukkit/event/player/PlayerEggThrowEvent.java create mode 100644 api/src/main/java/org/bukkit/event/player/PlayerEvent.java create mode 100644 api/src/main/java/org/bukkit/event/player/PlayerExpChangeEvent.java create mode 100644 api/src/main/java/org/bukkit/event/player/PlayerFishEvent.java create mode 100644 api/src/main/java/org/bukkit/event/player/PlayerGameModeChangeEvent.java create mode 100644 api/src/main/java/org/bukkit/event/player/PlayerInteractAtEntityEvent.java create mode 100644 api/src/main/java/org/bukkit/event/player/PlayerInteractEntityEvent.java create mode 100644 api/src/main/java/org/bukkit/event/player/PlayerInteractEvent.java create mode 100644 api/src/main/java/org/bukkit/event/player/PlayerItemBreakEvent.java create mode 100644 api/src/main/java/org/bukkit/event/player/PlayerItemConsumeEvent.java create mode 100644 api/src/main/java/org/bukkit/event/player/PlayerItemDamageEvent.java create mode 100644 api/src/main/java/org/bukkit/event/player/PlayerItemHeldEvent.java create mode 100644 api/src/main/java/org/bukkit/event/player/PlayerItemMendEvent.java create mode 100644 api/src/main/java/org/bukkit/event/player/PlayerJoinEvent.java create mode 100644 api/src/main/java/org/bukkit/event/player/PlayerKickEvent.java create mode 100644 api/src/main/java/org/bukkit/event/player/PlayerLevelChangeEvent.java create mode 100644 api/src/main/java/org/bukkit/event/player/PlayerLocaleChangeEvent.java create mode 100644 api/src/main/java/org/bukkit/event/player/PlayerLoginEvent.java create mode 100644 api/src/main/java/org/bukkit/event/player/PlayerMoveEvent.java create mode 100644 api/src/main/java/org/bukkit/event/player/PlayerPickupArrowEvent.java create mode 100644 api/src/main/java/org/bukkit/event/player/PlayerPickupItemEvent.java create mode 100644 api/src/main/java/org/bukkit/event/player/PlayerPortalEvent.java create mode 100644 api/src/main/java/org/bukkit/event/player/PlayerPreLoginEvent.java create mode 100644 api/src/main/java/org/bukkit/event/player/PlayerQuitEvent.java create mode 100644 api/src/main/java/org/bukkit/event/player/PlayerRecipeDiscoverEvent.java create mode 100644 api/src/main/java/org/bukkit/event/player/PlayerRegisterChannelEvent.java create mode 100644 api/src/main/java/org/bukkit/event/player/PlayerResourcePackStatusEvent.java create mode 100644 api/src/main/java/org/bukkit/event/player/PlayerRespawnEvent.java create mode 100644 api/src/main/java/org/bukkit/event/player/PlayerRiptideEvent.java create mode 100644 api/src/main/java/org/bukkit/event/player/PlayerShearEntityEvent.java create mode 100644 api/src/main/java/org/bukkit/event/player/PlayerStatisticIncrementEvent.java create mode 100644 api/src/main/java/org/bukkit/event/player/PlayerSwapHandItemsEvent.java create mode 100644 api/src/main/java/org/bukkit/event/player/PlayerTeleportEvent.java create mode 100644 api/src/main/java/org/bukkit/event/player/PlayerToggleFlightEvent.java create mode 100644 api/src/main/java/org/bukkit/event/player/PlayerToggleSneakEvent.java create mode 100644 api/src/main/java/org/bukkit/event/player/PlayerToggleSprintEvent.java create mode 100644 api/src/main/java/org/bukkit/event/player/PlayerUnleashEntityEvent.java create mode 100644 api/src/main/java/org/bukkit/event/player/PlayerUnregisterChannelEvent.java create mode 100644 api/src/main/java/org/bukkit/event/player/PlayerVelocityEvent.java create mode 100644 api/src/main/java/org/bukkit/event/server/BroadcastMessageEvent.java create mode 100644 api/src/main/java/org/bukkit/event/server/MapInitializeEvent.java create mode 100644 api/src/main/java/org/bukkit/event/server/PluginDisableEvent.java create mode 100644 api/src/main/java/org/bukkit/event/server/PluginEnableEvent.java create mode 100644 api/src/main/java/org/bukkit/event/server/PluginEvent.java create mode 100644 api/src/main/java/org/bukkit/event/server/RemoteServerCommandEvent.java create mode 100644 api/src/main/java/org/bukkit/event/server/ServerCommandEvent.java create mode 100644 api/src/main/java/org/bukkit/event/server/ServerEvent.java create mode 100644 api/src/main/java/org/bukkit/event/server/ServerListPingEvent.java create mode 100644 api/src/main/java/org/bukkit/event/server/ServerLoadEvent.java create mode 100644 api/src/main/java/org/bukkit/event/server/ServiceEvent.java create mode 100644 api/src/main/java/org/bukkit/event/server/ServiceRegisterEvent.java create mode 100644 api/src/main/java/org/bukkit/event/server/ServiceUnregisterEvent.java create mode 100644 api/src/main/java/org/bukkit/event/server/TabCompleteEvent.java create mode 100644 api/src/main/java/org/bukkit/event/vehicle/VehicleBlockCollisionEvent.java create mode 100644 api/src/main/java/org/bukkit/event/vehicle/VehicleCollisionEvent.java create mode 100644 api/src/main/java/org/bukkit/event/vehicle/VehicleCreateEvent.java create mode 100644 api/src/main/java/org/bukkit/event/vehicle/VehicleDamageEvent.java create mode 100644 api/src/main/java/org/bukkit/event/vehicle/VehicleDestroyEvent.java create mode 100644 api/src/main/java/org/bukkit/event/vehicle/VehicleEnterEvent.java create mode 100644 api/src/main/java/org/bukkit/event/vehicle/VehicleEntityCollisionEvent.java create mode 100644 api/src/main/java/org/bukkit/event/vehicle/VehicleEvent.java create mode 100644 api/src/main/java/org/bukkit/event/vehicle/VehicleExitEvent.java create mode 100644 api/src/main/java/org/bukkit/event/vehicle/VehicleMoveEvent.java create mode 100644 api/src/main/java/org/bukkit/event/vehicle/VehicleUpdateEvent.java create mode 100644 api/src/main/java/org/bukkit/event/weather/LightningStrikeEvent.java create mode 100644 api/src/main/java/org/bukkit/event/weather/ThunderChangeEvent.java create mode 100644 api/src/main/java/org/bukkit/event/weather/WeatherChangeEvent.java create mode 100644 api/src/main/java/org/bukkit/event/weather/WeatherEvent.java create mode 100644 api/src/main/java/org/bukkit/event/world/ChunkEvent.java create mode 100644 api/src/main/java/org/bukkit/event/world/ChunkLoadEvent.java create mode 100644 api/src/main/java/org/bukkit/event/world/ChunkPopulateEvent.java create mode 100644 api/src/main/java/org/bukkit/event/world/ChunkUnloadEvent.java create mode 100644 api/src/main/java/org/bukkit/event/world/PortalCreateEvent.java create mode 100644 api/src/main/java/org/bukkit/event/world/SpawnChangeEvent.java create mode 100644 api/src/main/java/org/bukkit/event/world/StructureGrowEvent.java create mode 100644 api/src/main/java/org/bukkit/event/world/WorldEvent.java create mode 100644 api/src/main/java/org/bukkit/event/world/WorldInitEvent.java create mode 100644 api/src/main/java/org/bukkit/event/world/WorldLoadEvent.java create mode 100644 api/src/main/java/org/bukkit/event/world/WorldSaveEvent.java create mode 100644 api/src/main/java/org/bukkit/event/world/WorldUnloadEvent.java create mode 100644 api/src/main/java/org/bukkit/generator/BlockPopulator.java create mode 100644 api/src/main/java/org/bukkit/generator/ChunkGenerator.java create mode 100644 api/src/main/java/org/bukkit/help/GenericCommandHelpTopic.java create mode 100644 api/src/main/java/org/bukkit/help/HelpMap.java create mode 100644 api/src/main/java/org/bukkit/help/HelpTopic.java create mode 100644 api/src/main/java/org/bukkit/help/HelpTopicComparator.java create mode 100644 api/src/main/java/org/bukkit/help/HelpTopicFactory.java create mode 100644 api/src/main/java/org/bukkit/help/IndexHelpTopic.java create mode 100644 api/src/main/java/org/bukkit/inventory/AbstractHorseInventory.java create mode 100644 api/src/main/java/org/bukkit/inventory/AnvilInventory.java create mode 100644 api/src/main/java/org/bukkit/inventory/ArmoredHorseInventory.java create mode 100644 api/src/main/java/org/bukkit/inventory/BeaconInventory.java create mode 100644 api/src/main/java/org/bukkit/inventory/BrewerInventory.java create mode 100644 api/src/main/java/org/bukkit/inventory/CraftingInventory.java create mode 100644 api/src/main/java/org/bukkit/inventory/DoubleChestInventory.java create mode 100644 api/src/main/java/org/bukkit/inventory/EnchantingInventory.java create mode 100644 api/src/main/java/org/bukkit/inventory/EntityEquipment.java create mode 100644 api/src/main/java/org/bukkit/inventory/EquipmentSlot.java create mode 100644 api/src/main/java/org/bukkit/inventory/FurnaceInventory.java create mode 100644 api/src/main/java/org/bukkit/inventory/FurnaceRecipe.java create mode 100644 api/src/main/java/org/bukkit/inventory/HorseInventory.java create mode 100644 api/src/main/java/org/bukkit/inventory/Inventory.java create mode 100644 api/src/main/java/org/bukkit/inventory/InventoryHolder.java create mode 100644 api/src/main/java/org/bukkit/inventory/InventoryView.java create mode 100644 api/src/main/java/org/bukkit/inventory/ItemFactory.java create mode 100644 api/src/main/java/org/bukkit/inventory/ItemFlag.java create mode 100644 api/src/main/java/org/bukkit/inventory/ItemStack.java create mode 100644 api/src/main/java/org/bukkit/inventory/LlamaInventory.java create mode 100644 api/src/main/java/org/bukkit/inventory/MainHand.java create mode 100644 api/src/main/java/org/bukkit/inventory/Merchant.java create mode 100644 api/src/main/java/org/bukkit/inventory/MerchantInventory.java create mode 100644 api/src/main/java/org/bukkit/inventory/MerchantRecipe.java create mode 100644 api/src/main/java/org/bukkit/inventory/PlayerInventory.java create mode 100644 api/src/main/java/org/bukkit/inventory/Recipe.java create mode 100644 api/src/main/java/org/bukkit/inventory/RecipeChoice.java create mode 100644 api/src/main/java/org/bukkit/inventory/SaddledHorseInventory.java create mode 100644 api/src/main/java/org/bukkit/inventory/ShapedRecipe.java create mode 100644 api/src/main/java/org/bukkit/inventory/ShapelessRecipe.java create mode 100644 api/src/main/java/org/bukkit/inventory/meta/BannerMeta.java create mode 100644 api/src/main/java/org/bukkit/inventory/meta/BlockStateMeta.java create mode 100644 api/src/main/java/org/bukkit/inventory/meta/BookMeta.java create mode 100644 api/src/main/java/org/bukkit/inventory/meta/Damageable.java create mode 100644 api/src/main/java/org/bukkit/inventory/meta/EnchantmentStorageMeta.java create mode 100644 api/src/main/java/org/bukkit/inventory/meta/FireworkEffectMeta.java create mode 100644 api/src/main/java/org/bukkit/inventory/meta/FireworkMeta.java create mode 100644 api/src/main/java/org/bukkit/inventory/meta/ItemMeta.java create mode 100644 api/src/main/java/org/bukkit/inventory/meta/KnowledgeBookMeta.java create mode 100644 api/src/main/java/org/bukkit/inventory/meta/LeatherArmorMeta.java create mode 100644 api/src/main/java/org/bukkit/inventory/meta/MapMeta.java create mode 100644 api/src/main/java/org/bukkit/inventory/meta/PotionMeta.java create mode 100644 api/src/main/java/org/bukkit/inventory/meta/Repairable.java create mode 100644 api/src/main/java/org/bukkit/inventory/meta/SkullMeta.java create mode 100644 api/src/main/java/org/bukkit/inventory/meta/SpawnEggMeta.java create mode 100644 api/src/main/java/org/bukkit/inventory/meta/TropicalFishBucketMeta.java create mode 100644 api/src/main/java/org/bukkit/inventory/meta/tags/CustomItemTagContainer.java create mode 100644 api/src/main/java/org/bukkit/inventory/meta/tags/ItemTagAdapterContext.java create mode 100644 api/src/main/java/org/bukkit/inventory/meta/tags/ItemTagType.java create mode 100644 api/src/main/java/org/bukkit/loot/LootContext.java create mode 100644 api/src/main/java/org/bukkit/loot/LootTable.java create mode 100644 api/src/main/java/org/bukkit/loot/LootTables.java create mode 100644 api/src/main/java/org/bukkit/loot/Lootable.java create mode 100644 api/src/main/java/org/bukkit/map/MapCanvas.java create mode 100644 api/src/main/java/org/bukkit/map/MapCursor.java create mode 100644 api/src/main/java/org/bukkit/map/MapCursorCollection.java create mode 100644 api/src/main/java/org/bukkit/map/MapFont.java create mode 100644 api/src/main/java/org/bukkit/map/MapPalette.java create mode 100644 api/src/main/java/org/bukkit/map/MapRenderer.java create mode 100644 api/src/main/java/org/bukkit/map/MapView.java create mode 100644 api/src/main/java/org/bukkit/map/MinecraftFont.java create mode 100644 api/src/main/java/org/bukkit/material/Attachable.java create mode 100644 api/src/main/java/org/bukkit/material/Banner.java create mode 100644 api/src/main/java/org/bukkit/material/Bed.java create mode 100644 api/src/main/java/org/bukkit/material/Button.java create mode 100644 api/src/main/java/org/bukkit/material/Cake.java create mode 100644 api/src/main/java/org/bukkit/material/Cauldron.java create mode 100644 api/src/main/java/org/bukkit/material/Chest.java create mode 100644 api/src/main/java/org/bukkit/material/Coal.java create mode 100644 api/src/main/java/org/bukkit/material/CocoaPlant.java create mode 100644 api/src/main/java/org/bukkit/material/Colorable.java create mode 100644 api/src/main/java/org/bukkit/material/Command.java create mode 100644 api/src/main/java/org/bukkit/material/Comparator.java create mode 100644 api/src/main/java/org/bukkit/material/Crops.java create mode 100644 api/src/main/java/org/bukkit/material/DetectorRail.java create mode 100644 api/src/main/java/org/bukkit/material/Diode.java create mode 100644 api/src/main/java/org/bukkit/material/Directional.java create mode 100644 api/src/main/java/org/bukkit/material/DirectionalContainer.java create mode 100644 api/src/main/java/org/bukkit/material/Dispenser.java create mode 100644 api/src/main/java/org/bukkit/material/Door.java create mode 100644 api/src/main/java/org/bukkit/material/Dye.java create mode 100644 api/src/main/java/org/bukkit/material/EnderChest.java create mode 100644 api/src/main/java/org/bukkit/material/ExtendedRails.java create mode 100644 api/src/main/java/org/bukkit/material/FlowerPot.java create mode 100644 api/src/main/java/org/bukkit/material/Furnace.java create mode 100644 api/src/main/java/org/bukkit/material/FurnaceAndDispenser.java create mode 100644 api/src/main/java/org/bukkit/material/Gate.java create mode 100644 api/src/main/java/org/bukkit/material/Hopper.java create mode 100644 api/src/main/java/org/bukkit/material/Ladder.java create mode 100644 api/src/main/java/org/bukkit/material/Leaves.java create mode 100644 api/src/main/java/org/bukkit/material/Lever.java create mode 100644 api/src/main/java/org/bukkit/material/LongGrass.java create mode 100644 api/src/main/java/org/bukkit/material/MaterialData.java create mode 100644 api/src/main/java/org/bukkit/material/MonsterEggs.java create mode 100644 api/src/main/java/org/bukkit/material/Mushroom.java create mode 100644 api/src/main/java/org/bukkit/material/NetherWarts.java create mode 100644 api/src/main/java/org/bukkit/material/Observer.java create mode 100644 api/src/main/java/org/bukkit/material/Openable.java create mode 100644 api/src/main/java/org/bukkit/material/PistonBaseMaterial.java create mode 100644 api/src/main/java/org/bukkit/material/PistonExtensionMaterial.java create mode 100644 api/src/main/java/org/bukkit/material/PoweredRail.java create mode 100644 api/src/main/java/org/bukkit/material/PressurePlate.java create mode 100644 api/src/main/java/org/bukkit/material/PressureSensor.java create mode 100644 api/src/main/java/org/bukkit/material/Pumpkin.java create mode 100644 api/src/main/java/org/bukkit/material/Rails.java create mode 100644 api/src/main/java/org/bukkit/material/Redstone.java create mode 100644 api/src/main/java/org/bukkit/material/RedstoneTorch.java create mode 100644 api/src/main/java/org/bukkit/material/RedstoneWire.java create mode 100644 api/src/main/java/org/bukkit/material/Sandstone.java create mode 100644 api/src/main/java/org/bukkit/material/Sapling.java create mode 100644 api/src/main/java/org/bukkit/material/Sign.java create mode 100644 api/src/main/java/org/bukkit/material/SimpleAttachableMaterialData.java create mode 100644 api/src/main/java/org/bukkit/material/Skull.java create mode 100644 api/src/main/java/org/bukkit/material/SmoothBrick.java create mode 100644 api/src/main/java/org/bukkit/material/SpawnEgg.java create mode 100644 api/src/main/java/org/bukkit/material/Stairs.java create mode 100644 api/src/main/java/org/bukkit/material/Step.java create mode 100644 api/src/main/java/org/bukkit/material/TexturedMaterial.java create mode 100644 api/src/main/java/org/bukkit/material/Torch.java create mode 100644 api/src/main/java/org/bukkit/material/TrapDoor.java create mode 100644 api/src/main/java/org/bukkit/material/Tree.java create mode 100644 api/src/main/java/org/bukkit/material/Tripwire.java create mode 100644 api/src/main/java/org/bukkit/material/TripwireHook.java create mode 100644 api/src/main/java/org/bukkit/material/Vine.java create mode 100644 api/src/main/java/org/bukkit/material/Wood.java create mode 100644 api/src/main/java/org/bukkit/material/WoodenStep.java create mode 100644 api/src/main/java/org/bukkit/material/Wool.java create mode 100644 api/src/main/java/org/bukkit/material/types/MushroomBlockTexture.java create mode 100644 api/src/main/java/org/bukkit/metadata/FixedMetadataValue.java create mode 100644 api/src/main/java/org/bukkit/metadata/LazyMetadataValue.java create mode 100644 api/src/main/java/org/bukkit/metadata/MetadataConversionException.java create mode 100644 api/src/main/java/org/bukkit/metadata/MetadataEvaluationException.java create mode 100644 api/src/main/java/org/bukkit/metadata/MetadataStore.java create mode 100644 api/src/main/java/org/bukkit/metadata/MetadataStoreBase.java create mode 100644 api/src/main/java/org/bukkit/metadata/MetadataValue.java create mode 100644 api/src/main/java/org/bukkit/metadata/MetadataValueAdapter.java create mode 100644 api/src/main/java/org/bukkit/metadata/Metadatable.java create mode 100644 api/src/main/java/org/bukkit/permissions/Permissible.java create mode 100644 api/src/main/java/org/bukkit/permissions/PermissibleBase.java create mode 100644 api/src/main/java/org/bukkit/permissions/Permission.java create mode 100644 api/src/main/java/org/bukkit/permissions/PermissionAttachment.java create mode 100644 api/src/main/java/org/bukkit/permissions/PermissionAttachmentInfo.java create mode 100644 api/src/main/java/org/bukkit/permissions/PermissionDefault.java create mode 100644 api/src/main/java/org/bukkit/permissions/PermissionRemovedExecutor.java create mode 100644 api/src/main/java/org/bukkit/permissions/ServerOperator.java create mode 100644 api/src/main/java/org/bukkit/plugin/AuthorNagException.java create mode 100644 api/src/main/java/org/bukkit/plugin/EventExecutor.java create mode 100644 api/src/main/java/org/bukkit/plugin/IllegalPluginAccessException.java create mode 100644 api/src/main/java/org/bukkit/plugin/InvalidDescriptionException.java create mode 100644 api/src/main/java/org/bukkit/plugin/InvalidPluginException.java create mode 100644 api/src/main/java/org/bukkit/plugin/Plugin.java create mode 100644 api/src/main/java/org/bukkit/plugin/PluginAwareness.java create mode 100644 api/src/main/java/org/bukkit/plugin/PluginBase.java create mode 100644 api/src/main/java/org/bukkit/plugin/PluginDescriptionFile.java create mode 100644 api/src/main/java/org/bukkit/plugin/PluginLoadOrder.java create mode 100644 api/src/main/java/org/bukkit/plugin/PluginLoader.java create mode 100644 api/src/main/java/org/bukkit/plugin/PluginLogger.java create mode 100644 api/src/main/java/org/bukkit/plugin/PluginManager.java create mode 100644 api/src/main/java/org/bukkit/plugin/RegisteredListener.java create mode 100644 api/src/main/java/org/bukkit/plugin/RegisteredServiceProvider.java create mode 100644 api/src/main/java/org/bukkit/plugin/ServicePriority.java create mode 100644 api/src/main/java/org/bukkit/plugin/ServicesManager.java rename {src/api => api/src}/main/java/org/bukkit/plugin/SimplePluginManager.java (100%) create mode 100644 api/src/main/java/org/bukkit/plugin/SimpleServicesManager.java create mode 100644 api/src/main/java/org/bukkit/plugin/TimedRegisteredListener.java create mode 100644 api/src/main/java/org/bukkit/plugin/UnknownDependencyException.java create mode 100644 api/src/main/java/org/bukkit/plugin/java/JavaPlugin.java create mode 100644 api/src/main/java/org/bukkit/plugin/java/JavaPluginLoader.java create mode 100644 api/src/main/java/org/bukkit/plugin/java/PluginClassLoader.java create mode 100644 api/src/main/java/org/bukkit/plugin/messaging/ChannelNameTooLongException.java create mode 100644 api/src/main/java/org/bukkit/plugin/messaging/ChannelNotRegisteredException.java create mode 100644 api/src/main/java/org/bukkit/plugin/messaging/MessageTooLargeException.java create mode 100644 api/src/main/java/org/bukkit/plugin/messaging/Messenger.java create mode 100644 api/src/main/java/org/bukkit/plugin/messaging/PluginChannelDirection.java create mode 100644 api/src/main/java/org/bukkit/plugin/messaging/PluginMessageListener.java create mode 100644 api/src/main/java/org/bukkit/plugin/messaging/PluginMessageListenerRegistration.java create mode 100644 api/src/main/java/org/bukkit/plugin/messaging/PluginMessageRecipient.java create mode 100644 api/src/main/java/org/bukkit/plugin/messaging/ReservedChannelException.java create mode 100644 api/src/main/java/org/bukkit/plugin/messaging/StandardMessenger.java create mode 100644 api/src/main/java/org/bukkit/potion/Potion.java create mode 100644 api/src/main/java/org/bukkit/potion/PotionBrewer.java create mode 100644 api/src/main/java/org/bukkit/potion/PotionData.java create mode 100644 api/src/main/java/org/bukkit/potion/PotionEffect.java create mode 100644 api/src/main/java/org/bukkit/potion/PotionEffectType.java create mode 100644 api/src/main/java/org/bukkit/potion/PotionEffectTypeWrapper.java create mode 100644 api/src/main/java/org/bukkit/potion/PotionType.java create mode 100644 api/src/main/java/org/bukkit/projectiles/BlockProjectileSource.java create mode 100644 api/src/main/java/org/bukkit/projectiles/ProjectileSource.java create mode 100644 api/src/main/java/org/bukkit/scheduler/BukkitRunnable.java create mode 100644 api/src/main/java/org/bukkit/scheduler/BukkitScheduler.java create mode 100644 api/src/main/java/org/bukkit/scheduler/BukkitTask.java create mode 100644 api/src/main/java/org/bukkit/scheduler/BukkitWorker.java create mode 100644 api/src/main/java/org/bukkit/scoreboard/Criterias.java create mode 100644 api/src/main/java/org/bukkit/scoreboard/DisplaySlot.java create mode 100644 api/src/main/java/org/bukkit/scoreboard/NameTagVisibility.java create mode 100644 api/src/main/java/org/bukkit/scoreboard/Objective.java create mode 100644 api/src/main/java/org/bukkit/scoreboard/RenderType.java create mode 100644 api/src/main/java/org/bukkit/scoreboard/Score.java create mode 100644 api/src/main/java/org/bukkit/scoreboard/Scoreboard.java create mode 100644 api/src/main/java/org/bukkit/scoreboard/ScoreboardManager.java create mode 100644 api/src/main/java/org/bukkit/scoreboard/Team.java create mode 100644 api/src/main/java/org/bukkit/util/BlockIterator.java create mode 100644 api/src/main/java/org/bukkit/util/BlockVector.java create mode 100644 api/src/main/java/org/bukkit/util/BoundingBox.java create mode 100644 api/src/main/java/org/bukkit/util/CachedServerIcon.java create mode 100644 api/src/main/java/org/bukkit/util/ChatPaginator.java create mode 100644 api/src/main/java/org/bukkit/util/Consumer.java create mode 100644 api/src/main/java/org/bukkit/util/EulerAngle.java create mode 100644 api/src/main/java/org/bukkit/util/FileUtil.java create mode 100644 api/src/main/java/org/bukkit/util/NumberConversions.java create mode 100644 api/src/main/java/org/bukkit/util/RayTraceResult.java create mode 100644 api/src/main/java/org/bukkit/util/StringUtil.java create mode 100644 api/src/main/java/org/bukkit/util/Vector.java create mode 100644 api/src/main/java/org/bukkit/util/io/BukkitObjectInputStream.java create mode 100644 api/src/main/java/org/bukkit/util/io/BukkitObjectOutputStream.java create mode 100644 api/src/main/java/org/bukkit/util/io/Wrapper.java create mode 100644 api/src/main/java/org/bukkit/util/noise/NoiseGenerator.java create mode 100644 api/src/main/java/org/bukkit/util/noise/OctaveGenerator.java create mode 100644 api/src/main/java/org/bukkit/util/noise/PerlinNoiseGenerator.java create mode 100644 api/src/main/java/org/bukkit/util/noise/PerlinOctaveGenerator.java create mode 100644 api/src/main/java/org/bukkit/util/noise/SimplexNoiseGenerator.java create mode 100644 api/src/main/java/org/bukkit/util/noise/SimplexOctaveGenerator.java create mode 100644 api/src/main/java/org/bukkit/util/permissions/BroadcastPermissions.java create mode 100644 api/src/main/java/org/bukkit/util/permissions/CommandPermissions.java create mode 100644 api/src/main/java/org/bukkit/util/permissions/DefaultPermissions.java create mode 100644 api/src/main/java/org/spigotmc/CustomTimingsHandler.java create mode 100644 api/src/main/java/org/spigotmc/event/entity/EntityDismountEvent.java create mode 100644 api/src/main/java/org/spigotmc/event/entity/EntityMountEvent.java create mode 100644 api/src/main/java/org/spigotmc/event/player/PlayerSpawnLocationEvent.java create mode 100644 api/src/main/javadoc/org/bukkit/block/package-info.java create mode 100644 api/src/main/javadoc/org/bukkit/command/defaults/package-info.java create mode 100644 api/src/main/javadoc/org/bukkit/command/package-info.java create mode 100644 api/src/main/javadoc/org/bukkit/configuration/file/package-info.java create mode 100644 api/src/main/javadoc/org/bukkit/configuration/package-info.java create mode 100644 api/src/main/javadoc/org/bukkit/configuration/serialization/package-info.java create mode 100644 api/src/main/javadoc/org/bukkit/conversations/package-info.java create mode 100644 api/src/main/javadoc/org/bukkit/enchantments/package-info.java create mode 100644 api/src/main/javadoc/org/bukkit/entity/minecart/package-info.java create mode 100644 api/src/main/javadoc/org/bukkit/entity/package-info.java create mode 100644 api/src/main/javadoc/org/bukkit/event/block/package-info.java create mode 100644 api/src/main/javadoc/org/bukkit/event/enchantment/package-info.java create mode 100644 api/src/main/javadoc/org/bukkit/event/entity/package-info.java create mode 100644 api/src/main/javadoc/org/bukkit/event/hanging/package-info.java create mode 100644 api/src/main/javadoc/org/bukkit/event/inventory/package-info.java create mode 100644 api/src/main/javadoc/org/bukkit/event/package-info.java create mode 100644 api/src/main/javadoc/org/bukkit/event/player/package-info.java create mode 100644 api/src/main/javadoc/org/bukkit/event/server/package-info.java create mode 100644 api/src/main/javadoc/org/bukkit/event/vehicle/package-info.java create mode 100644 api/src/main/javadoc/org/bukkit/event/weather/package-info.java create mode 100644 api/src/main/javadoc/org/bukkit/event/world/package-info.java create mode 100644 api/src/main/javadoc/org/bukkit/generator/package-info.java create mode 100644 api/src/main/javadoc/org/bukkit/help/package-info.java create mode 100644 api/src/main/javadoc/org/bukkit/inventory/meta/package-info.java create mode 100644 api/src/main/javadoc/org/bukkit/inventory/package-info.java create mode 100644 api/src/main/javadoc/org/bukkit/map/package-info.java create mode 100644 api/src/main/javadoc/org/bukkit/material/package-info.java create mode 100644 api/src/main/javadoc/org/bukkit/metadata/package-info.java create mode 100644 api/src/main/javadoc/org/bukkit/package-info.java create mode 100644 api/src/main/javadoc/org/bukkit/permissions/package-info.java create mode 100644 api/src/main/javadoc/org/bukkit/plugin/doc-files/permissions-example_plugin.yml create mode 100644 api/src/main/javadoc/org/bukkit/plugin/java/package-info.java create mode 100644 api/src/main/javadoc/org/bukkit/plugin/messaging/package-info.java create mode 100644 api/src/main/javadoc/org/bukkit/plugin/package-info.java create mode 100644 api/src/main/javadoc/org/bukkit/potion/package-info.java create mode 100644 api/src/main/javadoc/org/bukkit/projectiles/package-info.java create mode 100644 api/src/main/javadoc/org/bukkit/scheduler/package-info.java create mode 100644 api/src/main/javadoc/org/bukkit/scoreboard/package-info.java create mode 100644 api/src/main/javadoc/org/bukkit/util/io/package-info.java create mode 100644 api/src/main/javadoc/org/bukkit/util/noise/package-info.java create mode 100644 api/src/main/javadoc/org/bukkit/util/package-info.java create mode 100644 api/src/main/javadoc/org/bukkit/util/permissions/package-info.java create mode 100644 api/src/main/javadoc/overview.html rename {src/api => api/src}/pom.xml (98%) create mode 100644 api/src/test/java/com/destroystokyo/paper/MaterialTagsTest.java create mode 100644 api/src/test/java/org/bukkit/AnnotationTest.java create mode 100644 api/src/test/java/org/bukkit/ArtTest.java create mode 100644 api/src/test/java/org/bukkit/BukkitMirrorTest.java create mode 100644 api/src/test/java/org/bukkit/ChatColorTest.java create mode 100644 api/src/test/java/org/bukkit/ChatPaginatorTest.java create mode 100644 api/src/test/java/org/bukkit/CoalTypeTest.java create mode 100644 api/src/test/java/org/bukkit/ColorTest.java create mode 100644 api/src/test/java/org/bukkit/CropStateTest.java create mode 100644 api/src/test/java/org/bukkit/DifficultyTest.java create mode 100644 api/src/test/java/org/bukkit/DyeColorTest.java create mode 100644 api/src/test/java/org/bukkit/EffectTest.java create mode 100644 api/src/test/java/org/bukkit/EntityEffectTest.java create mode 100644 api/src/test/java/org/bukkit/GameModeTest.java create mode 100644 api/src/test/java/org/bukkit/GrassSpeciesTest.java create mode 100644 api/src/test/java/org/bukkit/InstrumentTest.java create mode 100644 api/src/test/java/org/bukkit/LocationTest.java create mode 100644 api/src/test/java/org/bukkit/MaterialTest.java create mode 100644 api/src/test/java/org/bukkit/NamespacedKeyTest.java create mode 100644 api/src/test/java/org/bukkit/NoteTest.java create mode 100644 api/src/test/java/org/bukkit/TestServer.java create mode 100644 api/src/test/java/org/bukkit/TreeSpeciesTest.java create mode 100644 api/src/test/java/org/bukkit/WorldTypeTest.java create mode 100644 api/src/test/java/org/bukkit/configuration/ConfigurationSectionTest.java create mode 100644 api/src/test/java/org/bukkit/configuration/ConfigurationTest.java create mode 100644 api/src/test/java/org/bukkit/configuration/MemoryConfigurationTest.java create mode 100644 api/src/test/java/org/bukkit/configuration/MemorySectionTest.java create mode 100644 api/src/test/java/org/bukkit/configuration/file/FileConfigurationTest.java create mode 100644 api/src/test/java/org/bukkit/configuration/file/YamlConfigurationTest.java create mode 100644 api/src/test/java/org/bukkit/conversations/ConversationContextTest.java create mode 100644 api/src/test/java/org/bukkit/conversations/ConversationTest.java create mode 100644 api/src/test/java/org/bukkit/conversations/FakeConversable.java create mode 100644 api/src/test/java/org/bukkit/conversations/ValidatingPromptTest.java create mode 100644 api/src/test/java/org/bukkit/event/PlayerChatTabCompleteEventTest.java create mode 100644 api/src/test/java/org/bukkit/event/SyntheticEventTest.java create mode 100644 api/src/test/java/org/bukkit/event/TestEvent.java create mode 100644 api/src/test/java/org/bukkit/materials/MaterialDataTest.java create mode 100644 api/src/test/java/org/bukkit/metadata/FixedMetadataValueTest.java create mode 100644 api/src/test/java/org/bukkit/metadata/LazyMetadataValueTest.java create mode 100644 api/src/test/java/org/bukkit/metadata/MetadataConversionTest.java create mode 100644 api/src/test/java/org/bukkit/metadata/MetadataStoreTest.java create mode 100644 api/src/test/java/org/bukkit/metadata/MetadataValueAdapterTest.java create mode 100644 api/src/test/java/org/bukkit/plugin/PluginManagerTest.java create mode 100644 api/src/test/java/org/bukkit/plugin/TestPlugin.java create mode 100644 api/src/test/java/org/bukkit/plugin/TimedRegisteredListenerTest.java create mode 100644 api/src/test/java/org/bukkit/plugin/messaging/StandardMessengerTest.java create mode 100644 api/src/test/java/org/bukkit/plugin/messaging/TestMessageListener.java create mode 100644 api/src/test/java/org/bukkit/plugin/messaging/TestPlayer.java create mode 100644 api/src/test/java/org/bukkit/util/BoundingBoxTest.java create mode 100644 api/src/test/java/org/bukkit/util/StringUtilStartsWithTest.java create mode 100644 api/src/test/java/org/bukkit/util/StringUtilTest.java create mode 100644 api/src/test/java/org/bukkit/util/VectorTest.java create mode 100644 api/src/test/java/org/bukkit/util/io/BukkitObjectStreamTest.java diff --git a/api/src/main/java/co/aikar/timings/FullServerTickHandler.java b/api/src/main/java/co/aikar/timings/FullServerTickHandler.java new file mode 100644 index 000000000..64531fcce --- /dev/null +++ b/api/src/main/java/co/aikar/timings/FullServerTickHandler.java @@ -0,0 +1,84 @@ +package co.aikar.timings; + +import static co.aikar.timings.TimingsManager.*; + +import org.jetbrains.annotations.NotNull; + +public class FullServerTickHandler extends TimingHandler { + private static final TimingIdentifier IDENTITY = new TimingIdentifier("Minecraft", "Full Server Tick", null); + final TimingData minuteData; + double avgFreeMemory = -1D; + double avgUsedMemory = -1D; + FullServerTickHandler() { + super(IDENTITY); + minuteData = new TimingData(id); + + TIMING_MAP.put(IDENTITY, this); + } + + @NotNull + @Override + public Timing startTiming() { + if (TimingsManager.needsFullReset) { + TimingsManager.resetTimings(); + } else if (TimingsManager.needsRecheckEnabled) { + TimingsManager.recheckEnabled(); + } + return super.startTiming(); + } + + @Override + public void stopTiming() { + super.stopTiming(); + if (!isEnabled()) { + return; + } + if (TimingHistory.timedTicks % 20 == 0) { + final Runtime runtime = Runtime.getRuntime(); + double usedMemory = runtime.totalMemory() - runtime.freeMemory(); + double freeMemory = runtime.maxMemory() - usedMemory; + if (this.avgFreeMemory == -1) { + this.avgFreeMemory = freeMemory; + } else { + this.avgFreeMemory = (this.avgFreeMemory * (59 / 60D)) + (freeMemory * (1 / 60D)); + } + + if (this.avgUsedMemory == -1) { + this.avgUsedMemory = usedMemory; + } else { + this.avgUsedMemory = (this.avgUsedMemory * (59 / 60D)) + (usedMemory * (1 / 60D)); + } + } + + long start = System.nanoTime(); + TimingsManager.tick(); + long diff = System.nanoTime() - start; + TIMINGS_TICK.addDiff(diff, null); + // addDiff for TIMINGS_TICK incremented this, bring it back down to 1 per tick. + record.setCurTickCount(record.getCurTickCount()-1); + + minuteData.setCurTickTotal(record.getCurTickTotal()); + minuteData.setCurTickCount(1); + + boolean violated = isViolated(); + minuteData.processTick(violated); + TIMINGS_TICK.processTick(violated); + processTick(violated); + + + if (TimingHistory.timedTicks % 1200 == 0) { + MINUTE_REPORTS.add(new TimingHistory.MinuteReport()); + TimingHistory.resetTicks(false); + minuteData.reset(); + } + if (TimingHistory.timedTicks % Timings.getHistoryInterval() == 0) { + TimingsManager.HISTORY.add(new TimingHistory()); + TimingsManager.resetTimings(); + } + TimingsExport.reportTimings(); + } + + boolean isViolated() { + return record.getCurTickTotal() > 50000000; + } +} diff --git a/api/src/main/java/co/aikar/timings/NullTimingHandler.java b/api/src/main/java/co/aikar/timings/NullTimingHandler.java new file mode 100644 index 000000000..9b45ce887 --- /dev/null +++ b/api/src/main/java/co/aikar/timings/NullTimingHandler.java @@ -0,0 +1,68 @@ +/* + * This file is licensed under the MIT License (MIT). + * + * Copyright (c) 2014 Daniel Ennis + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +package co.aikar.timings; + +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +public final class NullTimingHandler implements Timing { + public static final Timing NULL = new NullTimingHandler(); + @NotNull + @Override + public Timing startTiming() { + return this; + } + + @Override + public void stopTiming() { + + } + + @NotNull + @Override + public Timing startTimingIfSync() { + return this; + } + + @Override + public void stopTimingIfSync() { + + } + + @Override + public void abort() { + + } + + @Nullable + @Override + public TimingHandler getTimingHandler() { + return null; + } + + @Override + public void close() { + + } +} diff --git a/src/api/main/java/co/aikar/timings/ThreadAssertion.java b/api/src/main/java/co/aikar/timings/ThreadAssertion.java similarity index 100% rename from src/api/main/java/co/aikar/timings/ThreadAssertion.java rename to api/src/main/java/co/aikar/timings/ThreadAssertion.java diff --git a/api/src/main/java/co/aikar/timings/TimedEventExecutor.java b/api/src/main/java/co/aikar/timings/TimedEventExecutor.java new file mode 100644 index 000000000..933ecf9bd --- /dev/null +++ b/api/src/main/java/co/aikar/timings/TimedEventExecutor.java @@ -0,0 +1,83 @@ +/* + * This file is licensed under the MIT License (MIT). + * + * Copyright (c) 2014 Daniel Ennis + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +package co.aikar.timings; + +import org.bukkit.Bukkit; +import org.bukkit.event.Event; +import org.bukkit.event.EventException; +import org.bukkit.event.Listener; +import org.bukkit.plugin.EventExecutor; +import org.bukkit.plugin.Plugin; + +import java.lang.reflect.Method; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +public class TimedEventExecutor implements EventExecutor { + + private final EventExecutor executor; + private final Timing timings; + + /** + * Wraps an event executor and associates a timing handler to it. + * + * @param executor Executor to wrap + * @param plugin Owning plugin + * @param method EventHandler method + * @param eventClass Owning class + */ + public TimedEventExecutor(@NotNull EventExecutor executor, @NotNull Plugin plugin, @Nullable Method method, @NotNull Class eventClass) { + this.executor = executor; + String id; + + if (method == null) { + if (executor.getClass().getEnclosingClass() != null) { // Oh Skript, how we love you + method = executor.getClass().getEnclosingMethod(); + } + } + + if (method != null) { + id = method.getDeclaringClass().getName(); + } else { + id = executor.getClass().getName(); + } + + + final String eventName = eventClass.getSimpleName(); + boolean verbose = "BlockPhysicsEvent".equals(eventName); + this.timings = Timings.ofSafe(plugin.getName(), (verbose ? "## " : "") + + "Event: " + id + " (" + eventName + ")", null); + } + + @Override + public void execute(@NotNull Listener listener, @NotNull Event event) throws EventException { + if (event.isAsynchronous() || !Timings.timingsEnabled || !Bukkit.isPrimaryThread()) { + executor.execute(listener, event); + return; + } + try (Timing ignored = timings.startTiming()){ + executor.execute(listener, event); + } + } +} diff --git a/src/api/main/java/co/aikar/timings/Timing.java b/api/src/main/java/co/aikar/timings/Timing.java similarity index 100% rename from src/api/main/java/co/aikar/timings/Timing.java rename to api/src/main/java/co/aikar/timings/Timing.java diff --git a/api/src/main/java/co/aikar/timings/TimingData.java b/api/src/main/java/co/aikar/timings/TimingData.java new file mode 100644 index 000000000..a5d13a1e4 --- /dev/null +++ b/api/src/main/java/co/aikar/timings/TimingData.java @@ -0,0 +1,122 @@ +/* + * This file is licensed under the MIT License (MIT). + * + * Copyright (c) 2014 Daniel Ennis + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +package co.aikar.timings; + +import java.util.List; +import org.jetbrains.annotations.NotNull; + +import static co.aikar.util.JSONUtil.toArray; + +/** + *

Lightweight object for tracking timing data

+ * + * This is broken out to reduce memory usage + */ +class TimingData { + private final int id; + private int count = 0; + private int lagCount = 0; + private long totalTime = 0; + private long lagTotalTime = 0; + private int curTickCount = 0; + private long curTickTotal = 0; + + TimingData(int id) { + this.id = id; + } + + private TimingData(TimingData data) { + this.id = data.id; + this.totalTime = data.totalTime; + this.lagTotalTime = data.lagTotalTime; + this.count = data.count; + this.lagCount = data.lagCount; + } + + void add(long diff) { + ++curTickCount; + curTickTotal += diff; + } + + void processTick(boolean violated) { + totalTime += curTickTotal; + count += curTickCount; + if (violated) { + lagTotalTime += curTickTotal; + lagCount += curTickCount; + } + curTickTotal = 0; + curTickCount = 0; + } + + void reset() { + count = 0; + lagCount = 0; + curTickTotal = 0; + curTickCount = 0; + totalTime = 0; + lagTotalTime = 0; + } + + protected TimingData clone() { + return new TimingData(this); + } + + @NotNull + List export() { + List list = toArray( + id, + count, + totalTime); + if (lagCount > 0) { + list.add(lagCount); + list.add(lagTotalTime); + } + return list; + } + + boolean hasData() { + return count > 0; + } + + long getTotalTime() { + return totalTime; + } + + int getCurTickCount() { + return curTickCount; + } + + void setCurTickCount(int curTickCount) { + this.curTickCount = curTickCount; + } + + long getCurTickTotal() { + return curTickTotal; + } + + void setCurTickTotal(long curTickTotal) { + this.curTickTotal = curTickTotal; + } +} diff --git a/src/api/main/java/co/aikar/timings/TimingHandler.java b/api/src/main/java/co/aikar/timings/TimingHandler.java similarity index 100% rename from src/api/main/java/co/aikar/timings/TimingHandler.java rename to api/src/main/java/co/aikar/timings/TimingHandler.java diff --git a/api/src/main/java/co/aikar/timings/TimingHistory.java b/api/src/main/java/co/aikar/timings/TimingHistory.java new file mode 100644 index 000000000..99815866b --- /dev/null +++ b/api/src/main/java/co/aikar/timings/TimingHistory.java @@ -0,0 +1,354 @@ +/* + * This file is licensed under the MIT License (MIT). + * + * Copyright (c) 2014 Daniel Ennis + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +package co.aikar.timings; + +import co.aikar.timings.TimingHistory.RegionData.RegionId; +import com.google.common.base.Function; +import com.google.common.collect.Sets; +import org.bukkit.Bukkit; +import org.bukkit.Chunk; +import org.bukkit.Material; +import org.bukkit.World; +import org.bukkit.block.BlockState; +import org.bukkit.entity.Entity; +import org.bukkit.entity.EntityType; +import org.bukkit.entity.Player; +import co.aikar.util.LoadingMap; +import co.aikar.util.MRUMapCache; + +import java.lang.management.ManagementFactory; +import java.util.Collection; +import java.util.EnumMap; +import java.util.List; +import java.util.Map; +import java.util.Set; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +import static co.aikar.timings.TimingsManager.FULL_SERVER_TICK; +import static co.aikar.timings.TimingsManager.MINUTE_REPORTS; +import static co.aikar.util.JSONUtil.*; + +@SuppressWarnings({"deprecation", "SuppressionAnnotation", "Convert2Lambda", "Anonymous2MethodRef"}) +public class TimingHistory { + public static long lastMinuteTime; + public static long timedTicks; + public static long playerTicks; + public static long entityTicks; + public static long tileEntityTicks; + public static long activatedEntityTicks; + private static int worldIdPool = 1; + static Map worldMap = LoadingMap.newHashMap(new Function() { + @NotNull + @Override + public Integer apply(@Nullable String input) { + return worldIdPool++; + } + }); + private final long endTime; + private final long startTime; + private final long totalTicks; + private final long totalTime; // Represents all time spent running the server this history + private final MinuteReport[] minuteReports; + + private final TimingHistoryEntry[] entries; + final Set tileEntityTypeSet = Sets.newHashSet(); + final Set entityTypeSet = Sets.newHashSet(); + private final Map worlds; + + TimingHistory() { + this.endTime = System.currentTimeMillis() / 1000; + this.startTime = TimingsManager.historyStart / 1000; + if (timedTicks % 1200 != 0 || MINUTE_REPORTS.isEmpty()) { + this.minuteReports = MINUTE_REPORTS.toArray(new MinuteReport[MINUTE_REPORTS.size() + 1]); + this.minuteReports[this.minuteReports.length - 1] = new MinuteReport(); + } else { + this.minuteReports = MINUTE_REPORTS.toArray(new MinuteReport[MINUTE_REPORTS.size()]); + } + long ticks = 0; + for (MinuteReport mp : this.minuteReports) { + ticks += mp.ticksRecord.timed; + } + this.totalTicks = ticks; + this.totalTime = FULL_SERVER_TICK.record.getTotalTime(); + this.entries = new TimingHistoryEntry[TimingsManager.HANDLERS.size()]; + + int i = 0; + for (TimingHandler handler : TimingsManager.HANDLERS) { + entries[i++] = new TimingHistoryEntry(handler); + } + + // Information about all loaded chunks/entities + //noinspection unchecked + this.worlds = toObjectMapper(Bukkit.getWorlds(), new Function() { + @NotNull + @Override + public JSONPair apply(World world) { + Map regions = LoadingMap.newHashMap(RegionData.LOADER); + + for (Chunk chunk : world.getLoadedChunks()) { + RegionData data = regions.get(new RegionId(chunk.getX(), chunk.getZ())); + + for (Entity entity : chunk.getEntities()) { + if (entity == null) { + Bukkit.getLogger().warning("Null entity detected in chunk at position x: " + chunk.getX() + ", z: " + chunk.getZ()); + continue; + } + + data.entityCounts.get(entity.getType()).increment(); + } + + for (BlockState tileEntity : chunk.getTileEntities(false)) { + if (tileEntity == null) { + Bukkit.getLogger().warning("Null tileentity detected in chunk at position x: " + chunk.getX() + ", z: " + chunk.getZ()); + continue; + } + + data.tileEntityCounts.get(tileEntity.getBlock().getType()).increment(); + } + } + return pair( + worldMap.get(world.getName()), + toArrayMapper(regions.values(),new Function() { + @NotNull + @Override + public Object apply(RegionData input) { + return toArray( + input.regionId.x, + input.regionId.z, + toObjectMapper(input.entityCounts.entrySet(), + new Function, JSONPair>() { + @NotNull + @Override + public JSONPair apply(Map.Entry entry) { + entityTypeSet.add(entry.getKey()); + return pair( + String.valueOf(entry.getKey().getTypeId()), + entry.getValue().count() + ); + } + } + ), + toObjectMapper(input.tileEntityCounts.entrySet(), + new Function, JSONPair>() { + @NotNull + @Override + public JSONPair apply(Map.Entry entry) { + tileEntityTypeSet.add(entry.getKey()); + return pair( + String.valueOf(entry.getKey().getId()), + entry.getValue().count() + ); + } + } + ) + ); + } + }) + ); + } + }); + } + static class RegionData { + final RegionId regionId; + @SuppressWarnings("Guava") + static Function LOADER = new Function() { + @NotNull + @Override + public RegionData apply(@NotNull RegionId id) { + return new RegionData(id); + } + }; + RegionData(@NotNull RegionId id) { + this.regionId = id; + } + + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } + + RegionData that = (RegionData) o; + + return regionId.equals(that.regionId); + + } + + @Override + public int hashCode() { + return regionId.hashCode(); + } + + @SuppressWarnings("unchecked") + final Map entityCounts = MRUMapCache.of(LoadingMap.of( + new EnumMap(EntityType.class), k -> new Counter() + )); + @SuppressWarnings("unchecked") + final Map tileEntityCounts = MRUMapCache.of(LoadingMap.of( + new EnumMap(Material.class), k -> new Counter() + )); + + static class RegionId { + final int x, z; + final long regionId; + RegionId(int x, int z) { + this.x = x >> 5 << 5; + this.z = z >> 5 << 5; + this.regionId = ((long) (this.x) << 32) + (this.z >> 5 << 5) - Integer.MIN_VALUE; + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + + RegionId regionId1 = (RegionId) o; + + return regionId == regionId1.regionId; + + } + + @Override + public int hashCode() { + return (int) (regionId ^ (regionId >>> 32)); + } + } + } + static void resetTicks(boolean fullReset) { + if (fullReset) { + // Non full is simply for 1 minute reports + timedTicks = 0; + } + lastMinuteTime = System.nanoTime(); + playerTicks = 0; + tileEntityTicks = 0; + entityTicks = 0; + activatedEntityTicks = 0; + } + + @NotNull + Object export() { + return createObject( + pair("s", startTime), + pair("e", endTime), + pair("tk", totalTicks), + pair("tm", totalTime), + pair("w", worlds), + pair("h", toArrayMapper(entries, new Function() { + @Nullable + @Override + public Object apply(TimingHistoryEntry entry) { + TimingData record = entry.data; + if (!record.hasData()) { + return null; + } + return entry.export(); + } + })), + pair("mp", toArrayMapper(minuteReports, new Function() { + @NotNull + @Override + public Object apply(MinuteReport input) { + return input.export(); + } + })) + ); + } + + static class MinuteReport { + final long time = System.currentTimeMillis() / 1000; + + final TicksRecord ticksRecord = new TicksRecord(); + final PingRecord pingRecord = new PingRecord(); + final TimingData fst = TimingsManager.FULL_SERVER_TICK.minuteData.clone(); + final double tps = 1E9 / ( System.nanoTime() - lastMinuteTime ) * ticksRecord.timed; + final double usedMemory = TimingsManager.FULL_SERVER_TICK.avgUsedMemory; + final double freeMemory = TimingsManager.FULL_SERVER_TICK.avgFreeMemory; + final double loadAvg = ManagementFactory.getOperatingSystemMXBean().getSystemLoadAverage(); + + @NotNull + List export() { + return toArray( + time, + Math.round(tps * 100D) / 100D, + Math.round(pingRecord.avg * 100D) / 100D, + fst.export(), + toArray(ticksRecord.timed, + ticksRecord.player, + ticksRecord.entity, + ticksRecord.activatedEntity, + ticksRecord.tileEntity + ), + usedMemory, + freeMemory, + loadAvg + ); + } + } + + private static class TicksRecord { + final long timed; + final long player; + final long entity; + final long tileEntity; + final long activatedEntity; + + TicksRecord() { + timed = timedTicks - (TimingsManager.MINUTE_REPORTS.size() * 1200); + player = playerTicks; + entity = entityTicks; + tileEntity = tileEntityTicks; + activatedEntity = activatedEntityTicks; + } + + } + + private static class PingRecord { + final double avg; + + PingRecord() { + final Collection onlinePlayers = Bukkit.getOnlinePlayers(); + int totalPing = 0; + for (Player player : onlinePlayers) { + totalPing += player.spigot().getPing(); + } + avg = onlinePlayers.isEmpty() ? 0 : totalPing / onlinePlayers.size(); + } + } + + + private static class Counter { + private int count = 0; + public int increment() { + return ++count; + } + public int count() { + return count; + } + } +} diff --git a/api/src/main/java/co/aikar/timings/TimingHistoryEntry.java b/api/src/main/java/co/aikar/timings/TimingHistoryEntry.java new file mode 100644 index 000000000..86d5ac6bd --- /dev/null +++ b/api/src/main/java/co/aikar/timings/TimingHistoryEntry.java @@ -0,0 +1,58 @@ +/* + * This file is licensed under the MIT License (MIT). + * + * Copyright (c) 2014 Daniel Ennis + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +package co.aikar.timings; + +import com.google.common.base.Function; + +import java.util.List; +import org.jetbrains.annotations.NotNull; + +import static co.aikar.util.JSONUtil.toArrayMapper; + +class TimingHistoryEntry { + final TimingData data; + private final TimingData[] children; + + TimingHistoryEntry(@NotNull TimingHandler handler) { + this.data = handler.record.clone(); + children = handler.cloneChildren(); + } + + @NotNull + List export() { + List result = data.export(); + if (children.length > 0) { + result.add( + toArrayMapper(children, new Function() { + @NotNull + @Override + public Object apply(TimingData child) { + return child.export(); + } + }) + ); + } + return result; + } +} diff --git a/api/src/main/java/co/aikar/timings/TimingIdentifier.java b/api/src/main/java/co/aikar/timings/TimingIdentifier.java new file mode 100644 index 000000000..df142a89b --- /dev/null +++ b/api/src/main/java/co/aikar/timings/TimingIdentifier.java @@ -0,0 +1,116 @@ +/* + * This file is licensed under the MIT License (MIT). + * + * Copyright (c) 2014 Daniel Ennis + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +package co.aikar.timings; + +import co.aikar.util.LoadingMap; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; +import java.util.Map; +import java.util.Objects; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.atomic.AtomicInteger; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +/** + *

Used as a basis for fast HashMap key comparisons for the Timing Map.

+ * + * This class uses interned strings giving us the ability to do an identity check instead of equals() on the strings + */ +final class TimingIdentifier { + /** + * Holds all groups. Autoloads on request for a group by name. + */ + static final Map GROUP_MAP = LoadingMap.of(new ConcurrentHashMap<>(64, .5F), TimingGroup::new); + private static final TimingGroup DEFAULT_GROUP = getGroup("Minecraft"); + final String group; + final String name; + final TimingHandler groupHandler; + private final int hashCode; + + TimingIdentifier(@Nullable String group, @NotNull String name, @Nullable Timing groupHandler) { + this.group = group != null ? group: DEFAULT_GROUP.name; + this.name = name; + this.groupHandler = groupHandler != null ? groupHandler.getTimingHandler() : null; + this.hashCode = (31 * this.group.hashCode()) + this.name.hashCode(); + } + + @NotNull + static TimingGroup getGroup(@Nullable String groupName) { + if (groupName == null) { + //noinspection ConstantConditions + return DEFAULT_GROUP; + } + + return GROUP_MAP.get(groupName); + } + + @Override + public boolean equals(Object o) { + if (o == null) { + return false; + } + + TimingIdentifier that = (TimingIdentifier) o; + return Objects.equals(group, that.group) && Objects.equals(name, that.name); + } + + @Override + public int hashCode() { + return hashCode; + } + + @Override + public String toString() { + return "TimingIdentifier{id=" + group + ":" + name +'}'; + } + + static class TimingGroup { + + private static AtomicInteger idPool = new AtomicInteger(1); + final int id = idPool.getAndIncrement(); + + final String name; + final List handlers = Collections.synchronizedList(new ArrayList<>(64)); + + private TimingGroup(String name) { + this.name = name; + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + TimingGroup that = (TimingGroup) o; + return id == that.id; + } + + @Override + public int hashCode() { + return id; + } + } +} diff --git a/api/src/main/java/co/aikar/timings/Timings.java b/api/src/main/java/co/aikar/timings/Timings.java new file mode 100644 index 000000000..0b34e0d01 --- /dev/null +++ b/api/src/main/java/co/aikar/timings/Timings.java @@ -0,0 +1,293 @@ +/* + * This file is licensed under the MIT License (MIT). + * + * Copyright (c) 2014 Daniel Ennis + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +package co.aikar.timings; + +import com.google.common.base.Preconditions; +import com.google.common.collect.EvictingQueue; +import org.apache.commons.lang.Validate; +import org.bukkit.Bukkit; +import org.bukkit.command.CommandSender; +import org.bukkit.plugin.Plugin; + +import java.util.Queue; +import java.util.logging.Level; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +@SuppressWarnings({"UnusedDeclaration", "WeakerAccess", "SameParameterValue"}) +public final class Timings { + + private static final int MAX_HISTORY_FRAMES = 12; + public static final Timing NULL_HANDLER = new NullTimingHandler(); + static boolean timingsEnabled = false; + static boolean verboseEnabled = false; + private static int historyInterval = -1; + private static int historyLength = -1; + + private Timings() {} + + /** + * Returns a Timing for a plugin corresponding to a name. + * + * @param plugin Plugin to own the Timing + * @param name Name of Timing + * @return Handler + */ + @NotNull + public static Timing of(@NotNull Plugin plugin, @NotNull String name) { + Timing pluginHandler = null; + if (plugin != null) { + pluginHandler = ofSafe(plugin.getName(), "Combined Total", TimingsManager.PLUGIN_GROUP_HANDLER); + } + return of(plugin, name, pluginHandler); + } + + /** + *

Returns a handler that has a groupHandler timer handler. Parent timers should not have their + * start/stop methods called directly, as the children will call it for you.

+ * + * Parent Timers are used to group multiple subsections together and get a summary of them combined + * Parent Handler can not be changed after first call + * + * @param plugin Plugin to own the Timing + * @param name Name of Timing + * @param groupHandler Parent handler to mirror .start/stop calls to + * @return Timing Handler + */ + @NotNull + public static Timing of(@NotNull Plugin plugin, @NotNull String name, @Nullable Timing groupHandler) { + Preconditions.checkNotNull(plugin, "Plugin can not be null"); + return TimingsManager.getHandler(plugin.getName(), name, groupHandler); + } + + /** + * Returns a Timing object after starting it, useful for Java7 try-with-resources. + * + * try (Timing ignored = Timings.ofStart(plugin, someName)) { + * // timed section + * } + * + * @param plugin Plugin to own the Timing + * @param name Name of Timing + * @return Timing Handler + */ + @NotNull + public static Timing ofStart(@NotNull Plugin plugin, @NotNull String name) { + return ofStart(plugin, name, null); + } + + /** + * Returns a Timing object after starting it, useful for Java7 try-with-resources. + * + * try (Timing ignored = Timings.ofStart(plugin, someName, groupHandler)) { + * // timed section + * } + * + * @param plugin Plugin to own the Timing + * @param name Name of Timing + * @param groupHandler Parent handler to mirror .start/stop calls to + * @return Timing Handler + */ + @NotNull + public static Timing ofStart(@NotNull Plugin plugin, @NotNull String name, @Nullable Timing groupHandler) { + Timing timing = of(plugin, name, groupHandler); + timing.startTiming(); + return timing; + } + + /** + * Gets whether or not the Spigot Timings system is enabled + * + * @return Enabled or not + */ + public static boolean isTimingsEnabled() { + return timingsEnabled; + } + + /** + *

Sets whether or not the Spigot Timings system should be enabled

+ * + * Calling this will reset timing data. + * + * @param enabled Should timings be reported + */ + public static void setTimingsEnabled(boolean enabled) { + timingsEnabled = enabled; + reset(); + } + + /** + *

Sets whether or not the Timings should monitor at Verbose level.

+ * + *

When Verbose is disabled, high-frequency timings will not be available.

+ * + * @return Enabled or not + */ + public static boolean isVerboseTimingsEnabled() { + return verboseEnabled; + } + + /** + *

Sets whether or not the Timings should monitor at Verbose level.

+ * + * When Verbose is disabled, high-frequency timings will not be available. + * Calling this will reset timing data. + * + * @param enabled Should high-frequency timings be reported + */ + public static void setVerboseTimingsEnabled(boolean enabled) { + verboseEnabled = enabled; + TimingsManager.needsRecheckEnabled = true; + } + + /** + *

Gets the interval between Timing History report generation.

+ * + * Defaults to 5 minutes (6000 ticks) + * + * @return Interval in ticks + */ + public static int getHistoryInterval() { + return historyInterval; + } + + /** + *

Sets the interval between Timing History report generations.

+ * + *

Defaults to 5 minutes (6000 ticks)

+ * + * This will recheck your history length, so lowering this value will lower your + * history length if you need more than 60 history windows. + * + * @param interval Interval in ticks + */ + public static void setHistoryInterval(int interval) { + historyInterval = Math.max(20*60, interval); + // Recheck the history length with the new Interval + if (historyLength != -1) { + setHistoryLength(historyLength); + } + } + + /** + * Gets how long in ticks Timings history is kept for the server. + * + * Defaults to 1 hour (72000 ticks) + * + * @return Duration in Ticks + */ + public static int getHistoryLength() { + return historyLength; + } + + /** + * Sets how long Timing History reports are kept for the server. + * + * Defaults to 1 hours(72000 ticks) + * + * This value is capped at a maximum of getHistoryInterval() * MAX_HISTORY_FRAMES (12) + * + * Will not reset Timing Data but may truncate old history if the new length is less than old length. + * + * @param length Duration in ticks + */ + public static void setHistoryLength(int length) { + // Cap at 12 History Frames, 1 hour at 5 minute frames. + int maxLength = historyInterval * MAX_HISTORY_FRAMES; + // For special cases of servers with special permission to bypass the max. + // This max helps keep data file sizes reasonable for processing on Aikar's Timing parser side. + // Setting this will not help you bypass the max unless Aikar has added an exception on the API side. + if (System.getProperty("timings.bypassMax") != null) { + maxLength = Integer.MAX_VALUE; + } + historyLength = Math.max(Math.min(maxLength, length), historyInterval); + Queue oldQueue = TimingsManager.HISTORY; + int frames = (getHistoryLength() / getHistoryInterval()); + if (length > maxLength) { + Bukkit.getLogger().log(Level.WARNING, "Timings Length too high. Requested " + length + ", max is " + maxLength + ". To get longer history, you must increase your interval. Set Interval to " + Math.ceil(length / MAX_HISTORY_FRAMES) + " to achieve this length."); + } + TimingsManager.HISTORY = EvictingQueue.create(frames); + TimingsManager.HISTORY.addAll(oldQueue); + } + + /** + * Resets all Timing Data + */ + public static void reset() { + TimingsManager.reset(); + } + + /** + * Generates a report and sends it to the specified command sender. + * + * If sender is null, ConsoleCommandSender will be used. + * @param sender The sender to send to, or null to use the ConsoleCommandSender + */ + public static void generateReport(@Nullable CommandSender sender) { + if (sender == null) { + sender = Bukkit.getConsoleSender(); + } + TimingsExport.requestingReport.add(sender); + } + + /** + * Generates a report and sends it to the specified listener. + * Use with {@link org.bukkit.command.BufferedCommandSender} to get full response when done! + * @param sender The listener to send responses too. + */ + public static void generateReport(@NotNull TimingsReportListener sender) { + Validate.notNull(sender); + TimingsExport.requestingReport.add(sender); + } + + /* + ================= + Protected API: These are for internal use only in Bukkit/CraftBukkit + These do not have isPrimaryThread() checks in the startTiming/stopTiming + ================= + */ + @NotNull + static TimingHandler ofSafe(@NotNull String name) { + return ofSafe(null, name, null); + } + + @NotNull + static Timing ofSafe(@Nullable Plugin plugin, @NotNull String name) { + Timing pluginHandler = null; + if (plugin != null) { + pluginHandler = ofSafe(plugin.getName(), "Combined Total", TimingsManager.PLUGIN_GROUP_HANDLER); + } + return ofSafe(plugin != null ? plugin.getName() : "Minecraft - Invalid Plugin", name, pluginHandler); + } + + @NotNull + static TimingHandler ofSafe(@NotNull String name, @Nullable Timing groupHandler) { + return ofSafe(null, name, groupHandler); + } + + @NotNull + static TimingHandler ofSafe(@Nullable String groupName, @NotNull String name, @Nullable Timing groupHandler) { + return TimingsManager.getHandler(groupName, name, groupHandler); + } +} diff --git a/api/src/main/java/co/aikar/timings/TimingsCommand.java b/api/src/main/java/co/aikar/timings/TimingsCommand.java new file mode 100644 index 000000000..c0d8f2016 --- /dev/null +++ b/api/src/main/java/co/aikar/timings/TimingsCommand.java @@ -0,0 +1,122 @@ +/* + * This file is licensed under the MIT License (MIT). + * + * Copyright (c) 2014 Daniel Ennis + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +package co.aikar.timings; + +import com.google.common.collect.ImmutableList; +import org.apache.commons.lang.Validate; +import org.bukkit.ChatColor; +import org.bukkit.command.CommandSender; +import org.bukkit.command.defaults.BukkitCommand; +import org.bukkit.util.StringUtil; + +import java.util.ArrayList; +import java.util.List; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + + +public class TimingsCommand extends BukkitCommand { + private static final List TIMINGS_SUBCOMMANDS = ImmutableList.of("report", "reset", "on", "off", "paste", "verbon", "verboff"); + private long lastResetAttempt = 0; + + public TimingsCommand(@NotNull String name) { + super(name); + this.description = "Manages Spigot Timings data to see performance of the server."; + this.usageMessage = "/timings "; + this.setPermission("bukkit.command.timings"); + } + + @Override + public boolean execute(@NotNull CommandSender sender, @NotNull String currentAlias, @NotNull String[] args) { + if (!testPermission(sender)) { + return true; + } + if (args.length < 1) { + sender.sendMessage(ChatColor.RED + "Usage: " + usageMessage); + return true; + } + final String arg = args[0]; + if ("on".equalsIgnoreCase(arg)) { + Timings.setTimingsEnabled(true); + sender.sendMessage("Enabled Timings & Reset"); + return true; + } else if ("off".equalsIgnoreCase(arg)) { + Timings.setTimingsEnabled(false); + sender.sendMessage("Disabled Timings"); + return true; + } + + if (!Timings.isTimingsEnabled()) { + sender.sendMessage("Please enable timings by typing /timings on"); + return true; + } + + long now = System.currentTimeMillis(); + if ("verbon".equalsIgnoreCase(arg)) { + Timings.setVerboseTimingsEnabled(true); + sender.sendMessage("Enabled Verbose Timings"); + return true; + } else if ("verboff".equalsIgnoreCase(arg)) { + Timings.setVerboseTimingsEnabled(false); + sender.sendMessage("Disabled Verbose Timings"); + return true; + } else if ("reset".equalsIgnoreCase(arg)) { + if (now - lastResetAttempt < 30000) { + TimingsManager.reset(); + sender.sendMessage(ChatColor.RED + "Timings reset. Please wait 5-10 minutes before using /timings report."); + } else { + lastResetAttempt = now; + sender.sendMessage(ChatColor.RED + "WARNING: Timings v2 should not be reset. If you are encountering lag, please wait 3 minutes and then issue a report. The best timings will include 10+ minutes, with data before and after your lag period. If you really want to reset, run this command again within 30 seconds."); + } + + } else if ("cost".equals(arg)) { + sender.sendMessage("Timings cost: " + TimingsExport.getCost()); + } else if ( + "paste".equalsIgnoreCase(arg) || + "report".equalsIgnoreCase(arg) || + "get".equalsIgnoreCase(arg) || + "merged".equalsIgnoreCase(arg) || + "separate".equalsIgnoreCase(arg) + ) { + Timings.generateReport(sender); + } else { + sender.sendMessage(ChatColor.RED + "Usage: " + usageMessage); + } + return true; + } + + @NotNull + @Override + public List tabComplete(@NotNull CommandSender sender, @NotNull String alias, @NotNull String[] args) { + Validate.notNull(sender, "Sender cannot be null"); + Validate.notNull(args, "Arguments cannot be null"); + Validate.notNull(alias, "Alias cannot be null"); + + if (args.length == 1) { + return StringUtil.copyPartialMatches(args[0], TIMINGS_SUBCOMMANDS, + new ArrayList(TIMINGS_SUBCOMMANDS.size())); + } + return ImmutableList.of(); + } +} diff --git a/api/src/main/java/co/aikar/timings/TimingsExport.java b/api/src/main/java/co/aikar/timings/TimingsExport.java new file mode 100644 index 000000000..65d312b02 --- /dev/null +++ b/api/src/main/java/co/aikar/timings/TimingsExport.java @@ -0,0 +1,355 @@ +/* + * This file is licensed under the MIT License (MIT). + * + * Copyright (c) 2014 Daniel Ennis + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +package co.aikar.timings; + +import com.google.common.collect.Lists; +import com.google.common.collect.Sets; +import org.apache.commons.lang.StringUtils; +import org.bukkit.Bukkit; +import org.bukkit.ChatColor; +import org.bukkit.Material; +import org.bukkit.command.CommandSender; +import org.bukkit.configuration.ConfigurationSection; +import org.bukkit.configuration.MemorySection; +import org.bukkit.entity.EntityType; +import org.json.simple.JSONObject; +import org.json.simple.JSONValue; + +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import java.lang.management.ManagementFactory; +import java.lang.management.RuntimeMXBean; +import java.net.HttpURLConnection; +import java.net.InetAddress; +import java.net.URL; +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.logging.Level; +import java.util.zip.GZIPOutputStream; + +import static co.aikar.timings.TimingsManager.HISTORY; +import static co.aikar.util.JSONUtil.appendObjectData; +import static co.aikar.util.JSONUtil.createObject; +import static co.aikar.util.JSONUtil.pair; +import static co.aikar.util.JSONUtil.toArray; +import static co.aikar.util.JSONUtil.toArrayMapper; +import static co.aikar.util.JSONUtil.toObjectMapper; + +@SuppressWarnings({"rawtypes", "SuppressionAnnotation"}) +class TimingsExport extends Thread { + + private final TimingsReportListener listeners; + private final Map out; + private final TimingHistory[] history; + private static long lastReport = 0; + final static List requestingReport = Lists.newArrayList(); + + private TimingsExport(TimingsReportListener listeners, Map out, TimingHistory[] history) { + super("Timings paste thread"); + this.listeners = listeners; + this.out = out; + this.history = history; + } + + /** + * Checks if any pending reports are being requested, and builds one if needed. + */ + static void reportTimings() { + if (requestingReport.isEmpty()) { + return; + } + TimingsReportListener listeners = new TimingsReportListener(requestingReport); + listeners.addConsoleIfNeeded(); + + requestingReport.clear(); + long now = System.currentTimeMillis(); + final long lastReportDiff = now - lastReport; + if (lastReportDiff < 60000) { + listeners.sendMessage(ChatColor.RED + "Please wait at least 1 minute in between Timings reports. (" + (int)((60000 - lastReportDiff) / 1000) + " seconds)"); + listeners.done(); + return; + } + final long lastStartDiff = now - TimingsManager.timingStart; + if (lastStartDiff < 180000) { + listeners.sendMessage(ChatColor.RED + "Please wait at least 3 minutes before generating a Timings report. Unlike Timings v1, v2 benefits from longer timings and is not as useful with short timings. (" + (int)((180000 - lastStartDiff) / 1000) + " seconds)"); + listeners.done(); + return; + } + listeners.sendMessage(ChatColor.GREEN + "Preparing Timings Report..."); + lastReport = now; + Map parent = createObject( + // Get some basic system details about the server + pair("version", Bukkit.getVersion()), + pair("maxplayers", Bukkit.getMaxPlayers()), + pair("start", TimingsManager.timingStart / 1000), + pair("end", System.currentTimeMillis() / 1000), + pair("sampletime", (System.currentTimeMillis() - TimingsManager.timingStart) / 1000) + ); + if (!TimingsManager.privacy) { + appendObjectData(parent, + pair("server", Bukkit.getServerName()), + pair("motd", Bukkit.getServer().getMotd()), + pair("online-mode", Bukkit.getServer().getOnlineMode()), + pair("icon", Bukkit.getServer().getServerIcon().getData()) + ); + } + + final Runtime runtime = Runtime.getRuntime(); + RuntimeMXBean runtimeBean = ManagementFactory.getRuntimeMXBean(); + + parent.put("system", createObject( + pair("timingcost", getCost()), + pair("name", System.getProperty("os.name")), + pair("version", System.getProperty("os.version")), + pair("jvmversion", System.getProperty("java.version")), + pair("arch", System.getProperty("os.arch")), + pair("maxmem", runtime.maxMemory()), + pair("cpu", runtime.availableProcessors()), + pair("runtime", ManagementFactory.getRuntimeMXBean().getUptime()), + pair("flags", StringUtils.join(runtimeBean.getInputArguments(), " ")), + pair("gc", toObjectMapper(ManagementFactory.getGarbageCollectorMXBeans(), input -> pair(input.getName(), toArray(input.getCollectionCount(), input.getCollectionTime())))) + ) + ); + + Set tileEntityTypeSet = Sets.newHashSet(); + Set entityTypeSet = Sets.newHashSet(); + + int size = HISTORY.size(); + TimingHistory[] history = new TimingHistory[size + 1]; + int i = 0; + for (TimingHistory timingHistory : HISTORY) { + tileEntityTypeSet.addAll(timingHistory.tileEntityTypeSet); + entityTypeSet.addAll(timingHistory.entityTypeSet); + history[i++] = timingHistory; + } + + history[i] = new TimingHistory(); // Current snapshot + tileEntityTypeSet.addAll(history[i].tileEntityTypeSet); + entityTypeSet.addAll(history[i].entityTypeSet); + + + Map handlers = createObject(); + Map groupData; + synchronized (TimingIdentifier.GROUP_MAP) { + for (TimingIdentifier.TimingGroup group : TimingIdentifier.GROUP_MAP.values()) { + synchronized (group.handlers) { + for (TimingHandler id : group.handlers) { + + if (!id.isTimed() && !id.isSpecial()) { + continue; + } + + String name = id.identifier.name; + if (name.startsWith("##")) { + name = name.substring(3); + } + handlers.put(id.id, toArray( + group.id, + name + )); + } + } + } + + groupData = toObjectMapper( + TimingIdentifier.GROUP_MAP.values(), group -> pair(group.id, group.name)); + } + + parent.put("idmap", createObject( + pair("groups", groupData), + pair("handlers", handlers), + pair("worlds", toObjectMapper(TimingHistory.worldMap.entrySet(), input -> pair(input.getValue(), input.getKey()))), + pair("tileentity", + toObjectMapper(tileEntityTypeSet, input -> pair(input.getId(), input.name()))), + pair("entity", + toObjectMapper(entityTypeSet, input -> pair(input.getTypeId(), input.name()))) + )); + + // Information about loaded plugins + + parent.put("plugins", toObjectMapper(Bukkit.getPluginManager().getPlugins(), + plugin -> pair(plugin.getName(), createObject( + pair("version", plugin.getDescription().getVersion()), + pair("description", String.valueOf(plugin.getDescription().getDescription()).trim()), + pair("website", plugin.getDescription().getWebsite()), + pair("authors", StringUtils.join(plugin.getDescription().getAuthors(), ", ")) + )))); + + + + // Information on the users Config + + parent.put("config", createObject( + pair("spigot", mapAsJSON(Bukkit.spigot().getSpigotConfig(), null)), + pair("bukkit", mapAsJSON(Bukkit.spigot().getBukkitConfig(), null)), + pair("paper", mapAsJSON(Bukkit.spigot().getPaperConfig(), null)) + )); + + new TimingsExport(listeners, parent, history).start(); + } + + static long getCost() { + // Benchmark the users System.nanotime() for cost basis + int passes = 100; + TimingHandler SAMPLER1 = Timings.ofSafe("Timings Sampler 1"); + TimingHandler SAMPLER2 = Timings.ofSafe("Timings Sampler 2"); + TimingHandler SAMPLER3 = Timings.ofSafe("Timings Sampler 3"); + TimingHandler SAMPLER4 = Timings.ofSafe("Timings Sampler 4"); + TimingHandler SAMPLER5 = Timings.ofSafe("Timings Sampler 5"); + TimingHandler SAMPLER6 = Timings.ofSafe("Timings Sampler 6"); + + long start = System.nanoTime(); + for (int i = 0; i < passes; i++) { + SAMPLER1.startTiming(); + SAMPLER2.startTiming(); + SAMPLER3.startTiming(); + SAMPLER3.stopTiming(); + SAMPLER4.startTiming(); + SAMPLER5.startTiming(); + SAMPLER6.startTiming(); + SAMPLER6.stopTiming(); + SAMPLER5.stopTiming(); + SAMPLER4.stopTiming(); + SAMPLER2.stopTiming(); + SAMPLER1.stopTiming(); + } + long timingsCost = (System.nanoTime() - start) / passes / 6; + SAMPLER1.reset(true); + SAMPLER2.reset(true); + SAMPLER3.reset(true); + SAMPLER4.reset(true); + SAMPLER5.reset(true); + SAMPLER6.reset(true); + return timingsCost; + } + + private static JSONObject mapAsJSON(ConfigurationSection config, String parentKey) { + + JSONObject object = new JSONObject(); + for (String key : config.getKeys(false)) { + String fullKey = (parentKey != null ? parentKey + "." + key : key); + if (fullKey.equals("database") || fullKey.equals("settings.bungeecord-addresses") || TimingsManager.hiddenConfigs.contains(fullKey)) { + continue; + } + final Object val = config.get(key); + + object.put(key, valAsJSON(val, fullKey)); + } + return object; + } + + private static Object valAsJSON(Object val, final String parentKey) { + if (!(val instanceof MemorySection)) { + if (val instanceof List) { + Iterable v = (Iterable) val; + return toArrayMapper(v, input -> valAsJSON(input, parentKey)); + } else { + return val.toString(); + } + } else { + return mapAsJSON((ConfigurationSection) val, parentKey); + } + } + + @Override + public void run() { + out.put("data", toArrayMapper(history, TimingHistory::export)); + + + String response = null; + String timingsURL = null; + try { + HttpURLConnection con = (HttpURLConnection) new URL("http://timings.aikar.co/post").openConnection(); + con.setDoOutput(true); + String hostName = "BrokenHost"; + try { + hostName = InetAddress.getLocalHost().getHostName(); + } catch(Exception ignored) {} + con.setRequestProperty("User-Agent", "Paper/" + Bukkit.getServerName() + "/" + hostName); + con.setRequestMethod("POST"); + con.setInstanceFollowRedirects(false); + + OutputStream request = new GZIPOutputStream(con.getOutputStream()) {{ + this.def.setLevel(7); + }}; + + request.write(JSONValue.toJSONString(out).getBytes("UTF-8")); + request.close(); + + response = getResponse(con); + + if (con.getResponseCode() != 302) { + listeners.sendMessage( + ChatColor.RED + "Upload Error: " + con.getResponseCode() + ": " + con.getResponseMessage()); + listeners.sendMessage(ChatColor.RED + "Check your logs for more information"); + if (response != null) { + Bukkit.getLogger().log(Level.SEVERE, response); + } + return; + } + + timingsURL = con.getHeaderField("Location"); + listeners.sendMessage(ChatColor.GREEN + "View Timings Report: " + timingsURL); + + if (response != null && !response.isEmpty()) { + Bukkit.getLogger().log(Level.INFO, "Timing Response: " + response); + } + } catch (IOException ex) { + listeners.sendMessage(ChatColor.RED + "Error uploading timings, check your logs for more information"); + if (response != null) { + Bukkit.getLogger().log(Level.SEVERE, response); + } + Bukkit.getLogger().log(Level.SEVERE, "Could not paste timings", ex); + } finally { + this.listeners.done(timingsURL); + } + } + + private String getResponse(HttpURLConnection con) throws IOException { + InputStream is = null; + try { + is = con.getInputStream(); + ByteArrayOutputStream bos = new ByteArrayOutputStream(); + + byte[] b = new byte[1024]; + int bytesRead; + while ((bytesRead = is.read(b)) != -1) { + bos.write(b, 0, bytesRead); + } + return bos.toString(); + + } catch (IOException ex) { + listeners.sendMessage(ChatColor.RED + "Error uploading timings, check your logs for more information"); + Bukkit.getLogger().log(Level.WARNING, con.getResponseMessage(), ex); + return null; + } finally { + if (is != null) { + is.close(); + } + } + } +} diff --git a/api/src/main/java/co/aikar/timings/TimingsManager.java b/api/src/main/java/co/aikar/timings/TimingsManager.java new file mode 100644 index 000000000..ef824d701 --- /dev/null +++ b/api/src/main/java/co/aikar/timings/TimingsManager.java @@ -0,0 +1,188 @@ +/* + * This file is licensed under the MIT License (MIT). + * + * Copyright (c) 2014 Daniel Ennis + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +package co.aikar.timings; + +import co.aikar.util.LoadingMap; +import com.google.common.collect.EvictingQueue; +import org.bukkit.Bukkit; +import org.bukkit.Server; +import org.bukkit.command.Command; +import org.bukkit.plugin.Plugin; +import org.bukkit.plugin.java.PluginClassLoader; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; +import java.util.logging.Level; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +public final class TimingsManager { + static final Map TIMING_MAP = LoadingMap.of( + new ConcurrentHashMap<>(4096, .5F), TimingHandler::new + ); + public static final FullServerTickHandler FULL_SERVER_TICK = new FullServerTickHandler(); + public static final TimingHandler TIMINGS_TICK = Timings.ofSafe("Timings Tick", FULL_SERVER_TICK); + public static final Timing PLUGIN_GROUP_HANDLER = Timings.ofSafe("Plugins"); + public static List hiddenConfigs = new ArrayList(); + public static boolean privacy = false; + + static final List HANDLERS = new ArrayList<>(1024); + static final List MINUTE_REPORTS = new ArrayList<>(64); + + static EvictingQueue HISTORY = EvictingQueue.create(12); + static long timingStart = 0; + static long historyStart = 0; + static boolean needsFullReset = false; + static boolean needsRecheckEnabled = false; + + private TimingsManager() {} + + /** + * Resets all timing data on the next tick + */ + static void reset() { + needsFullReset = true; + } + + /** + * Ticked every tick by CraftBukkit to count the number of times a timer + * caused TPS loss. + */ + static void tick() { + if (Timings.timingsEnabled) { + boolean violated = FULL_SERVER_TICK.isViolated(); + + for (TimingHandler handler : HANDLERS) { + if (handler.isSpecial()) { + // We manually call this + continue; + } + handler.processTick(violated); + } + + TimingHistory.playerTicks += Bukkit.getOnlinePlayers().size(); + TimingHistory.timedTicks++; + // Generate TPS/Ping/Tick reports every minute + } + } + static void stopServer() { + Timings.timingsEnabled = false; + recheckEnabled(); + } + static void recheckEnabled() { + synchronized (TIMING_MAP) { + for (TimingHandler timings : TIMING_MAP.values()) { + timings.checkEnabled(); + } + } + needsRecheckEnabled = false; + } + static void resetTimings() { + if (needsFullReset) { + // Full resets need to re-check every handlers enabled state + // Timing map can be modified from async so we must sync on it. + synchronized (TIMING_MAP) { + for (TimingHandler timings : TIMING_MAP.values()) { + timings.reset(true); + } + } + Bukkit.getLogger().log(Level.INFO, "Timings Reset"); + HISTORY.clear(); + needsFullReset = false; + needsRecheckEnabled = false; + timingStart = System.currentTimeMillis(); + } else { + // Soft resets only need to act on timings that have done something + // Handlers can only be modified on main thread. + for (TimingHandler timings : HANDLERS) { + timings.reset(false); + } + } + + HANDLERS.clear(); + MINUTE_REPORTS.clear(); + + TimingHistory.resetTicks(true); + historyStart = System.currentTimeMillis(); + } + + @NotNull + static TimingHandler getHandler(@Nullable String group, @NotNull String name, @Nullable Timing parent) { + return TIMING_MAP.get(new TimingIdentifier(group, name, parent)); + } + + + /** + *

Due to access restrictions, we need a helper method to get a Command TimingHandler with String group

+ * + * Plugins should never call this + * + * @param pluginName Plugin this command is associated with + * @param command Command to get timings for + * @return TimingHandler + */ + @NotNull + public static Timing getCommandTiming(@Nullable String pluginName, @NotNull Command command) { + Plugin plugin = null; + final Server server = Bukkit.getServer(); + if (!( server == null || pluginName == null || + "minecraft".equals(pluginName) || "bukkit".equals(pluginName) || + "spigot".equalsIgnoreCase(pluginName) || "paper".equals(pluginName) + )) { + plugin = server.getPluginManager().getPlugin(pluginName); + } + if (plugin == null) { + // Plugin is passing custom fallback prefix, try to look up by class loader + plugin = getPluginByClassloader(command.getClass()); + } + if (plugin == null) { + return Timings.ofSafe("Command: " + pluginName + ":" + command.getTimingName()); + } + + return Timings.ofSafe(plugin, "Command: " + pluginName + ":" + command.getTimingName()); + } + + /** + * Looks up the class loader for the specified class, and if it is a PluginClassLoader, return the + * Plugin that created this class. + * + * @param clazz Class to check + * @return Plugin if created by a plugin + */ + @Nullable + public static Plugin getPluginByClassloader(@Nullable Class clazz) { + if (clazz == null) { + return null; + } + final ClassLoader classLoader = clazz.getClassLoader(); + if (classLoader instanceof PluginClassLoader) { + PluginClassLoader pluginClassLoader = (PluginClassLoader) classLoader; + return pluginClassLoader.getPlugin(); + } + return null; + } +} diff --git a/api/src/main/java/co/aikar/timings/TimingsReportListener.java b/api/src/main/java/co/aikar/timings/TimingsReportListener.java new file mode 100644 index 000000000..bf3e059fe --- /dev/null +++ b/api/src/main/java/co/aikar/timings/TimingsReportListener.java @@ -0,0 +1,75 @@ +package co.aikar.timings; + +import com.google.common.collect.Lists; +import org.apache.commons.lang.Validate; +import org.bukkit.Bukkit; +import org.bukkit.command.CommandSender; +import org.bukkit.command.ConsoleCommandSender; +import org.bukkit.command.MessageCommandSender; +import org.bukkit.command.RemoteConsoleCommandSender; + +import java.util.List; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +@SuppressWarnings("WeakerAccess") +public class TimingsReportListener implements MessageCommandSender { + private final List senders; + private final Runnable onDone; + private String timingsURL; + + public TimingsReportListener(@NotNull CommandSender senders) { + this(senders, null); + } + public TimingsReportListener(@NotNull CommandSender sender, @Nullable Runnable onDone) { + this(Lists.newArrayList(sender), onDone); + } + public TimingsReportListener(@NotNull List senders) { + this(senders, null); + } + public TimingsReportListener(@NotNull List senders, @Nullable Runnable onDone) { + Validate.notNull(senders); + Validate.notEmpty(senders); + + this.senders = Lists.newArrayList(senders); + this.onDone = onDone; + } + + @Nullable + public String getTimingsURL() { + return timingsURL; + } + + public void done() { + done(null); + } + + public void done(@Nullable String url) { + this.timingsURL = url; + if (onDone != null) { + onDone.run(); + } + for (CommandSender sender : senders) { + if (sender instanceof TimingsReportListener) { + ((TimingsReportListener) sender).done(); + } + } + } + + @Override + public void sendMessage(@NotNull String message) { + senders.forEach((sender) -> sender.sendMessage(message)); + } + + public void addConsoleIfNeeded() { + boolean hasConsole = false; + for (CommandSender sender : this.senders) { + if (sender instanceof ConsoleCommandSender || sender instanceof RemoteConsoleCommandSender) { + hasConsole = true; + } + } + if (!hasConsole) { + this.senders.add(Bukkit.getConsoleSender()); + } + } +} diff --git a/api/src/main/java/co/aikar/timings/UnsafeTimingHandler.java b/api/src/main/java/co/aikar/timings/UnsafeTimingHandler.java new file mode 100644 index 000000000..632c49615 --- /dev/null +++ b/api/src/main/java/co/aikar/timings/UnsafeTimingHandler.java @@ -0,0 +1,53 @@ +/* + * This file is licensed under the MIT License (MIT). + * + * Copyright (c) 2014 Daniel Ennis + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +package co.aikar.timings; + +import org.bukkit.Bukkit; +import org.jetbrains.annotations.NotNull; + +class UnsafeTimingHandler extends TimingHandler { + + UnsafeTimingHandler(@NotNull TimingIdentifier id) { + super(id); + } + + private static void checkThread() { + if (!Bukkit.isPrimaryThread()) { + throw new IllegalStateException("Calling Timings from Async Operation"); + } + } + + @NotNull + @Override + public Timing startTiming() { + checkThread(); + return super.startTiming(); + } + + @Override + public void stopTiming() { + checkThread(); + super.stopTiming(); + } +} diff --git a/api/src/main/java/co/aikar/util/Counter.java b/api/src/main/java/co/aikar/util/Counter.java new file mode 100644 index 000000000..80155072d --- /dev/null +++ b/api/src/main/java/co/aikar/util/Counter.java @@ -0,0 +1,38 @@ +package co.aikar.util; + +import com.google.common.collect.ForwardingMap; + +import java.util.HashMap; +import java.util.Map; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +public class Counter extends ForwardingMap { + private final Map counts = new HashMap<>(); + + public long decrement(@Nullable T key) { + return increment(key, -1); + } + public long increment(@Nullable T key) { + return increment(key, 1); + } + public long decrement(@Nullable T key, long amount) { + return decrement(key, -amount); + } + public long increment(@Nullable T key, long amount) { + Long count = this.getCount(key); + count += amount; + this.counts.put(key, count); + return count; + } + + public long getCount(@Nullable T key) { + return this.counts.getOrDefault(key, 0L); + } + + @NotNull + @Override + protected Map delegate() { + return this.counts; + } +} diff --git a/api/src/main/java/co/aikar/util/JSONUtil.java b/api/src/main/java/co/aikar/util/JSONUtil.java new file mode 100644 index 000000000..190bf0598 --- /dev/null +++ b/api/src/main/java/co/aikar/util/JSONUtil.java @@ -0,0 +1,140 @@ +package co.aikar.util; + +import com.google.common.base.Function; +import com.google.common.collect.Lists; +import com.google.common.collect.Maps; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; +import org.json.simple.JSONArray; +import org.json.simple.JSONObject; + +import java.util.ArrayList; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Map; + +/** + * Provides Utility methods that assist with generating JSON Objects + */ +@SuppressWarnings({"rawtypes", "SuppressionAnnotation"}) +public final class JSONUtil { + private JSONUtil() {} + + /** + * Creates a key/value "JSONPair" object + * + * @param key Key to use + * @param obj Value to use + * @return JSONPair + */ + @NotNull + public static JSONPair pair(@NotNull String key, @Nullable Object obj) { + return new JSONPair(key, obj); + } + + @NotNull + public static JSONPair pair(long key, @Nullable Object obj) { + return new JSONPair(String.valueOf(key), obj); + } + + /** + * Creates a new JSON object from multiple JSONPair key/value pairs + * + * @param data JSONPairs + * @return Map + */ + @NotNull + public static Map createObject(@NotNull JSONPair... data) { + return appendObjectData(new LinkedHashMap(), data); + } + + /** + * This appends multiple key/value Obj pairs into a JSON Object + * + * @param parent Map to be appended to + * @param data Data to append + * @return Map + */ + @NotNull + public static Map appendObjectData(@NotNull Map parent, @NotNull JSONPair... data) { + for (JSONPair JSONPair : data) { + parent.put(JSONPair.key, JSONPair.val); + } + return parent; + } + + /** + * This builds a JSON array from a set of data + * + * @param data Data to build JSON array from + * @return List + */ + @NotNull + public static List toArray(@NotNull Object... data) { + return Lists.newArrayList(data); + } + + /** + * These help build a single JSON array using a mapper function + * + * @param collection Collection to apply to + * @param mapper Mapper to apply + * @param Element Type + * @return List + */ + @NotNull + public static List toArrayMapper(@NotNull E[] collection, @NotNull Function mapper) { + return toArrayMapper(Lists.newArrayList(collection), mapper); + } + + @NotNull + public static List toArrayMapper(@NotNull Iterable collection, @NotNull Function mapper) { + List array = Lists.newArrayList(); + for (E e : collection) { + Object object = mapper.apply(e); + if (object != null) { + array.add(object); + } + } + return array; + } + + /** + * These help build a single JSON Object from a collection, using a mapper function + * + * @param collection Collection to apply to + * @param mapper Mapper to apply + * @param Element Type + * @return Map + */ + @NotNull + public static Map toObjectMapper(@NotNull E[] collection, @NotNull Function mapper) { + return toObjectMapper(Lists.newArrayList(collection), mapper); + } + + @NotNull + public static Map toObjectMapper(@NotNull Iterable collection, @NotNull Function mapper) { + Map object = Maps.newLinkedHashMap(); + for (E e : collection) { + JSONPair JSONPair = mapper.apply(e); + if (JSONPair != null) { + object.put(JSONPair.key, JSONPair.val); + } + } + return object; + } + + /** + * Simply stores a key and a value, used internally by many methods below. + */ + @SuppressWarnings("PublicInnerClass") + public static class JSONPair { + final String key; + final Object val; + + JSONPair(@NotNull String key, @NotNull Object val) { + this.key = key; + this.val = val; + } + } +} diff --git a/api/src/main/java/co/aikar/util/LoadingIntMap.java b/api/src/main/java/co/aikar/util/LoadingIntMap.java new file mode 100644 index 000000000..63a899c7d --- /dev/null +++ b/api/src/main/java/co/aikar/util/LoadingIntMap.java @@ -0,0 +1,76 @@ +/* + * Copyright (c) 2015. Starlis LLC / dba Empire Minecraft + * + * This source code is proprietary software and must not be redistributed without Starlis LLC's approval + * + */ +package co.aikar.util; + + +import com.google.common.base.Function; +import it.unimi.dsi.fastutil.ints.Int2ObjectOpenHashMap; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +/** + * Allows you to pass a Loader function that when a key is accessed that doesn't exist, + * automatically loads the entry into the map by calling the loader Function. + * + * .get() Will only return null if the Loader can return null. + * + * You may pass any backing Map to use. + * + * This class is not thread safe and should be wrapped with Collections.synchronizedMap on the OUTSIDE of the LoadingMap if needed. + * + * Do not wrap the backing map with Collections.synchronizedMap. + * + * @param Value + */ +public class LoadingIntMap extends Int2ObjectOpenHashMap { + private final Function loader; + + public LoadingIntMap(@NotNull Function loader) { + super(); + this.loader = loader; + } + + public LoadingIntMap(int expectedSize, @NotNull Function loader) { + super(expectedSize); + this.loader = loader; + } + + public LoadingIntMap(int expectedSize, float loadFactor, @NotNull Function loader) { + super(expectedSize, loadFactor); + this.loader = loader; + } + + + @Nullable + @Override + public V get(int key) { + V res = super.get(key); + if (res == null) { + res = loader.apply(key); + if (res != null) { + put(key, res); + } + } + return res; + } + + /** + * Due to java stuff, you will need to cast it to (Function) for some cases + * + * @param Type + */ + public abstract static class Feeder implements Function { + @Nullable + @Override + public T apply(@Nullable Object input) { + return apply(); + } + + @Nullable + public abstract T apply(); + } +} diff --git a/api/src/main/java/co/aikar/util/LoadingMap.java b/api/src/main/java/co/aikar/util/LoadingMap.java new file mode 100644 index 000000000..aedbb0332 --- /dev/null +++ b/api/src/main/java/co/aikar/util/LoadingMap.java @@ -0,0 +1,368 @@ +/* + * This file is licensed under the MIT License (MIT). + * + * Copyright (c) 2014 Daniel Ennis + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +package co.aikar.util; + +import com.google.common.base.Preconditions; +import java.lang.reflect.Constructor; +import java.util.AbstractMap; +import java.util.Collection; +import java.util.HashMap; +import java.util.IdentityHashMap; +import java.util.Map; +import java.util.Set; +import java.util.function.Function; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +/** + * Allows you to pass a Loader function that when a key is accessed that doesn't exists, + * automatically loads the entry into the map by calling the loader Function. + * + * .get() Will only return null if the Loader can return null. + * + * You may pass any backing Map to use. + * + * This class is not thread safe and should be wrapped with Collections.synchronizedMap on the OUTSIDE of the LoadingMap if needed. + * + * Do not wrap the backing map with Collections.synchronizedMap. + * + * @param Key + * @param Value + */ +public class LoadingMap extends AbstractMap { + private final Map backingMap; + private final java.util.function.Function loader; + + /** + * Initializes an auto loading map using specified loader and backing map + * @param backingMap Map to wrap + * @param loader Loader + */ + public LoadingMap(@NotNull Map backingMap, @NotNull java.util.function.Function loader) { + this.backingMap = backingMap; + this.loader = loader; + } + + /** + * Creates a new LoadingMap with the specified map and loader + * + * @param backingMap Actual map being used. + * @param loader Loader to use + * @param Key Type of the Map + * @param Value Type of the Map + * @return Map + */ + @NotNull + public static Map of(@NotNull Map backingMap, @NotNull Function loader) { + return new LoadingMap<>(backingMap, loader); + } + + /** + * Creates a LoadingMap with an auto instantiating loader. + * + * Will auto construct class of of Value when not found + * + * Since this uses Reflection, It is more effecient to define your own static loader + * than using this helper, but if performance is not critical, this is easier. + * + * @param backingMap Actual map being used. + * @param keyClass Class used for the K generic + * @param valueClass Class used for the V generic + * @param Key Type of the Map + * @param Value Type of the Map + * @return Map that auto instantiates on .get() + */ + @NotNull + public static Map newAutoMap(@NotNull Map backingMap, @Nullable final Class keyClass, + @NotNull final Class valueClass) { + return new LoadingMap<>(backingMap, new AutoInstantiatingLoader<>(keyClass, valueClass)); + } + /** + * Creates a LoadingMap with an auto instantiating loader. + * + * Will auto construct class of of Value when not found + * + * Since this uses Reflection, It is more effecient to define your own static loader + * than using this helper, but if performance is not critical, this is easier. + * + * @param backingMap Actual map being used. + * @param valueClass Class used for the V generic + * @param Key Type of the Map + * @param Value Type of the Map + * @return Map that auto instantiates on .get() + */ + @NotNull + public static Map newAutoMap(@NotNull Map backingMap, + @NotNull final Class valueClass) { + return newAutoMap(backingMap, null, valueClass); + } + + /** + * @see #newAutoMap + * + * new Auto initializing map using a HashMap. + * + * @param keyClass Class used for the K generic + * @param valueClass Class used for the V generic + * @param Key Type of the Map + * @param Value Type of the Map + * @return Map that auto instantiates on .get() + */ + @NotNull + public static Map newHashAutoMap(@Nullable final Class keyClass, @NotNull final Class valueClass) { + return newAutoMap(new HashMap<>(), keyClass, valueClass); + } + + /** + * @see #newAutoMap + * + * new Auto initializing map using a HashMap. + * + * @param valueClass Class used for the V generic + * @param Key Type of the Map + * @param Value Type of the Map + * @return Map that auto instantiates on .get() + */ + @NotNull + public static Map newHashAutoMap(@NotNull final Class valueClass) { + return newHashAutoMap(null, valueClass); + } + + /** + * @see #newAutoMap + * + * new Auto initializing map using a HashMap. + * + * @param keyClass Class used for the K generic + * @param valueClass Class used for the V generic + * @param initialCapacity Initial capacity to use + * @param loadFactor Load factor to use + * @param Key Type of the Map + * @param Value Type of the Map + * @return Map that auto instantiates on .get() + */ + @NotNull + public static Map newHashAutoMap(@Nullable final Class keyClass, @NotNull final Class valueClass, int initialCapacity, float loadFactor) { + return newAutoMap(new HashMap<>(initialCapacity, loadFactor), keyClass, valueClass); + } + + /** + * @see #newAutoMap + * + * new Auto initializing map using a HashMap. + * + * @param valueClass Class used for the V generic + * @param initialCapacity Initial capacity to use + * @param loadFactor Load factor to use + * @param Key Type of the Map + * @param Value Type of the Map + * @return Map that auto instantiates on .get() + */ + @NotNull + public static Map newHashAutoMap(@NotNull final Class valueClass, int initialCapacity, float loadFactor) { + return newHashAutoMap(null, valueClass, initialCapacity, loadFactor); + } + + /** + * Initializes an auto loading map using a HashMap + * + * @param loader Loader to use + * @param Key Type of the Map + * @param Value Type of the Map + * @return Map + */ + @NotNull + public static Map newHashMap(@NotNull Function loader) { + return new LoadingMap<>(new HashMap<>(), loader); + } + + /** + * Initializes an auto loading map using a HashMap + * + * @param loader Loader to use + * @param initialCapacity Initial capacity to use + * @param Key Type of the Map + * @param Value Type of the Map + * @return Map + */ + @NotNull + public static Map newHashMap(@NotNull Function loader, int initialCapacity) { + return new LoadingMap<>(new HashMap<>(initialCapacity), loader); + } + /** + * Initializes an auto loading map using a HashMap + * + * @param loader Loader to use + * @param initialCapacity Initial capacity to use + * @param loadFactor Load factor to use + * @param Key Type of the Map + * @param Value Type of the Map + * @return Map + */ + @NotNull + public static Map newHashMap(@NotNull Function loader, int initialCapacity, float loadFactor) { + return new LoadingMap<>(new HashMap<>(initialCapacity, loadFactor), loader); + } + + /** + * Initializes an auto loading map using an Identity HashMap + * + * @param loader Loader to use + * @param Key Type of the Map + * @param Value Type of the Map + * @return Map + */ + @NotNull + public static Map newIdentityHashMap(@NotNull Function loader) { + return new LoadingMap<>(new IdentityHashMap<>(), loader); + } + + /** + * Initializes an auto loading map using an Identity HashMap + * + * @param loader Loader to use + * @param initialCapacity Initial capacity to use + * @param Key Type of the Map + * @param Value Type of the Map + * @return Map + */ + @NotNull + public static Map newIdentityHashMap(@NotNull Function loader, int initialCapacity) { + return new LoadingMap<>(new IdentityHashMap<>(initialCapacity), loader); + } + + @Override + public int size() {return backingMap.size();} + + @Override + public boolean isEmpty() {return backingMap.isEmpty();} + + @Override + public boolean containsKey(@Nullable Object key) {return backingMap.containsKey(key);} + + @Override + public boolean containsValue(@Nullable Object value) {return backingMap.containsValue(value);} + + @Nullable + @Override + public V get(@Nullable Object key) { + V v = backingMap.get(key); + if (v != null) { + return v; + } + return backingMap.computeIfAbsent((K) key, loader); + } + + @Nullable + public V put(@Nullable K key, @Nullable V value) {return backingMap.put(key, value);} + + @Nullable + @Override + public V remove(@Nullable Object key) {return backingMap.remove(key);} + + public void putAll(@NotNull Map m) {backingMap.putAll(m);} + + @Override + public void clear() {backingMap.clear();} + + @NotNull + @Override + public Set keySet() {return backingMap.keySet();} + + @NotNull + @Override + public Collection values() {return backingMap.values();} + + @Override + public boolean equals(@Nullable Object o) {return backingMap.equals(o);} + + @Override + public int hashCode() {return backingMap.hashCode();} + + @NotNull + @Override + public Set> entrySet() { + return backingMap.entrySet(); + } + + @NotNull + public LoadingMap clone() { + return new LoadingMap<>(backingMap, loader); + } + + private static class AutoInstantiatingLoader implements Function { + final Constructor constructor; + private final Class valueClass; + + AutoInstantiatingLoader(@Nullable Class keyClass, @NotNull Class valueClass) { + try { + this.valueClass = valueClass; + if (keyClass != null) { + constructor = valueClass.getConstructor(keyClass); + } else { + constructor = null; + } + } catch (NoSuchMethodException e) { + throw new IllegalStateException( + valueClass.getName() + " does not have a constructor for " + (keyClass != null ? keyClass.getName() : null)); + } + } + + @NotNull + @Override + public V apply(@Nullable K input) { + try { + return (constructor != null ? constructor.newInstance(input) : valueClass.newInstance()); + } catch (Exception e) { + throw new ExceptionInInitializerError(e); + } + } + + @Override + public int hashCode() { + return super.hashCode(); + } + + @Override + public boolean equals(Object object) { + return false; + } + } + + /** + * Due to java stuff, you will need to cast it to (Function) for some cases + * + * @param Type + */ + public abstract static class Feeder implements Function { + @Nullable + @Override + public T apply(@Nullable Object input) { + return apply(); + } + + @Nullable + public abstract T apply(); + } +} diff --git a/api/src/main/java/co/aikar/util/MRUMapCache.java b/api/src/main/java/co/aikar/util/MRUMapCache.java new file mode 100644 index 000000000..5989ee212 --- /dev/null +++ b/api/src/main/java/co/aikar/util/MRUMapCache.java @@ -0,0 +1,111 @@ +/* + * This file is licensed under the MIT License (MIT). + * + * Copyright (c) 2014 Daniel Ennis + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +package co.aikar.util; + +import java.util.AbstractMap; +import java.util.Collection; +import java.util.Map; +import java.util.Set; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +/** + * Implements a Most Recently Used cache in front of a backing map, to quickly access the last accessed result. + * + * @param Key Type of the Map + * @param Value Type of the Map + */ +public class MRUMapCache extends AbstractMap { + final Map backingMap; + Object cacheKey; + V cacheValue; + public MRUMapCache(@NotNull final Map backingMap) { + this.backingMap = backingMap; + } + + public int size() {return backingMap.size();} + + public boolean isEmpty() {return backingMap.isEmpty();} + + public boolean containsKey(@Nullable Object key) { + return key != null && key.equals(cacheKey) || backingMap.containsKey(key); + } + + public boolean containsValue(@Nullable Object value) { + return value != null && value == cacheValue || backingMap.containsValue(value); + } + + @Nullable + public V get(@Nullable Object key) { + if (cacheKey != null && cacheKey.equals(key)) { + return cacheValue; + } + cacheKey = key; + return cacheValue = backingMap.get(key); + } + + @Nullable + public V put(@Nullable K key, @Nullable V value) { + cacheKey = key; + return cacheValue = backingMap.put(key, value); + } + + @Nullable + public V remove(@Nullable Object key) { + if (key != null && key.equals(cacheKey)) { + cacheKey = null; + } + return backingMap.remove(key); + } + + public void putAll(@NotNull Map m) {backingMap.putAll(m);} + + public void clear() { + cacheKey = null; + cacheValue = null; + backingMap.clear(); + } + + @NotNull + public Set keySet() {return backingMap.keySet();} + + @NotNull + public Collection values() {return backingMap.values();} + + @NotNull + public Set> entrySet() {return backingMap.entrySet();} + + /** + * Wraps the specified map with a most recently used cache + * + * @param map Map to be wrapped + * @param Key Type of the Map + * @param Value Type of the Map + * @return Map + */ + @NotNull + public static Map of(@NotNull Map map) { + return new MRUMapCache(map); + } +} diff --git a/api/src/main/java/com/destroystokyo/paper/MaterialSetTag.java b/api/src/main/java/com/destroystokyo/paper/MaterialSetTag.java new file mode 100644 index 000000000..c91ea2a06 --- /dev/null +++ b/api/src/main/java/com/destroystokyo/paper/MaterialSetTag.java @@ -0,0 +1,190 @@ +/* + * Copyright (c) 2018 Daniel Ennis (Aikar) MIT License + */ + +package com.destroystokyo.paper; + +import com.google.common.collect.Lists; +import com.google.common.collect.Sets; +import org.bukkit.Material; +import org.bukkit.NamespacedKey; +import org.bukkit.Tag; +import org.bukkit.block.Block; +import org.bukkit.block.BlockState; +import org.bukkit.block.data.BlockData; +import org.bukkit.inventory.ItemStack; + +import java.util.Collection; +import java.util.Set; +import java.util.function.Predicate; +import java.util.stream.Collectors; +import java.util.stream.Stream; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +public class MaterialSetTag implements Tag { + + private final NamespacedKey key; + private final Set materials; + + /** + * @deprecated Use NamespacedKey version of constructor + */ + @Deprecated + public MaterialSetTag(@NotNull Predicate filter) { + this(null, Stream.of(Material.values()).filter(filter).collect(Collectors.toList())); + } + + /** + * @deprecated Use NamespacedKey version of constructor + */ + @Deprecated + public MaterialSetTag(@NotNull Collection materials) { + this(null, materials); + } + + /** + * @deprecated Use NamespacedKey version of constructor + */ + @Deprecated + public MaterialSetTag(@NotNull Material... materials) { + this(null, materials); + } + + public MaterialSetTag(@Nullable NamespacedKey key, @NotNull Predicate filter) { + this(key, Stream.of(Material.values()).filter(filter).collect(Collectors.toList())); + } + + public MaterialSetTag(@Nullable NamespacedKey key, @NotNull Material... materials) { + this(key, Lists.newArrayList(materials)); + } + + public MaterialSetTag(@Nullable NamespacedKey key, @NotNull Collection materials) { + this.key = key != null ? key : NamespacedKey.randomKey(); + this.materials = Sets.newEnumSet(materials, Material.class); + } + + @NotNull + @Override + public NamespacedKey getKey() { + return key; + } + + @NotNull + public MaterialSetTag add(@NotNull Tag... tags) { + for (Tag tag : tags) { + add(tag.getValues()); + } + return this; + } + + @NotNull + public MaterialSetTag add(@NotNull MaterialSetTag... tags) { + for (Tag tag : tags) { + add(tag.getValues()); + } + return this; + } + + @NotNull + public MaterialSetTag add(@NotNull Material... material) { + this.materials.addAll(Lists.newArrayList(material)); + return this; + } + + @NotNull + public MaterialSetTag add(@NotNull Collection materials) { + this.materials.addAll(materials); + return this; + } + + @NotNull + public MaterialSetTag contains(@NotNull String with) { + return add(mat -> mat.name().contains(with)); + } + + @NotNull + public MaterialSetTag endsWith(@NotNull String with) { + return add(mat -> mat.name().endsWith(with)); + } + + + @NotNull + public MaterialSetTag startsWith(@NotNull String with) { + return add(mat -> mat.name().startsWith(with)); + } + @NotNull + public MaterialSetTag add(@NotNull Predicate filter) { + add(Stream.of(Material.values()).filter(((Predicate) Material::isLegacy).negate()).filter(filter).collect(Collectors.toList())); + return this; + } + + @NotNull + public MaterialSetTag not(@NotNull MaterialSetTag tags) { + not(tags.getValues()); + return this; + } + + @NotNull + public MaterialSetTag not(@NotNull Material... material) { + this.materials.removeAll(Lists.newArrayList(material)); + return this; + } + + @NotNull + public MaterialSetTag not(@NotNull Collection materials) { + this.materials.removeAll(materials); + return this; + } + + @NotNull + public MaterialSetTag not(@NotNull Predicate filter) { + not(Stream.of(Material.values()).filter(((Predicate) Material::isLegacy).negate()).filter(filter).collect(Collectors.toList())); + return this; + } + + @NotNull + public MaterialSetTag notEndsWith(@NotNull String with) { + return not(mat -> mat.name().endsWith(with)); + } + + + @NotNull + public MaterialSetTag notStartsWith(@NotNull String with) { + return not(mat -> mat.name().startsWith(with)); + } + + @NotNull + public Set getValues() { + return this.materials; + } + + public boolean isTagged(@NotNull BlockData block) { + return isTagged(block.getMaterial()); + } + + public boolean isTagged(@NotNull BlockState block) { + return isTagged(block.getType()); + } + + public boolean isTagged(@NotNull Block block) { + return isTagged(block.getType()); + } + + public boolean isTagged(@NotNull ItemStack item) { + return isTagged(item.getType()); + } + + public boolean isTagged(@NotNull Material material) { + return this.materials.contains(material); + } + + @NotNull + public MaterialSetTag ensureSize(@NotNull String label, int size) { + long actual = this.materials.stream().filter(((Predicate) Material::isLegacy).negate()).count(); + if (size != actual) { + throw new IllegalStateException(label + " - Expected " + size + " materials, got " + actual); + } + return this; + } +} diff --git a/api/src/main/java/com/destroystokyo/paper/MaterialTags.java b/api/src/main/java/com/destroystokyo/paper/MaterialTags.java new file mode 100644 index 000000000..660191c24 --- /dev/null +++ b/api/src/main/java/com/destroystokyo/paper/MaterialTags.java @@ -0,0 +1,382 @@ +/* + * Copyright (c) 2018 Daniel Ennis (Aikar) MIT License + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +package com.destroystokyo.paper; + +import org.bukkit.Material; +import org.bukkit.NamespacedKey; +import org.bukkit.Tag; + +/** + * Represents a collection tags to identify materials that share common properties. + * Will map to minecraft for missing tags, as well as custom ones that may be useful. + */ +@SuppressWarnings({"NonFinalUtilityClass", "unused", "WeakerAccess"}) +public class MaterialTags { + + private static NamespacedKey keyFor(String key) { + //noinspection deprecation + return new NamespacedKey("paper", key + "_settag"); + } + public static final MaterialSetTag ARROWS = new MaterialSetTag(keyFor("arrows")) + .endsWith("ARROW") + .ensureSize("ARROWS", 3); + + /** + * Cover all 16 colors of beds. + */ + public static final MaterialSetTag BEDS = new MaterialSetTag(keyFor("beds")) + .endsWith("_BED") + .ensureSize("BEDS", 16); + + /** + * Covers all bucket items. + */ + public static final MaterialSetTag BUCKETS = new MaterialSetTag(keyFor("buckets")) + .endsWith("BUCKET") + .ensureSize("BUCKETS", 8); + + /** + * Covers coal and charcoal. + */ + public static final MaterialSetTag COALS = new MaterialSetTag(keyFor("coals")) + .add(Material.COAL, Material.CHARCOAL); + + /** + * Covers both cobblestone wall variants. + */ + public static final MaterialSetTag COBBLESTONE_WALLS = new MaterialSetTag(keyFor("cobblestone_walls")) + .endsWith("COBBLESTONE_WALL") + .ensureSize("COBBLESTONE_WALLS", 2); + + /** + * Covers both cobblestone and mossy Cobblestone. + */ + public static final MaterialSetTag COBBLESTONES = new MaterialSetTag(keyFor("cobblestones")) + .add(Material.COBBLESTONE, Material.MOSSY_COBBLESTONE); + + /** + * Covers all 16 colors of concrete. + */ + public static final MaterialSetTag CONCRETES = new MaterialSetTag(keyFor("concretes")) + .endsWith("_CONCRETE") + .ensureSize("CONCRETES", 16); + + /** + * Covers all 16 colors of concrete powder. + */ + public static final MaterialSetTag CONCRETE_POWDER = new MaterialSetTag(keyFor("concrete_powder")) + .endsWith("_CONCRETE_POWDER") + .ensureSize("CONCRETE_POWDER", 16); + + /** + * Covers the two types of cooked fish. + */ + public static final MaterialSetTag COOKED_FISH = new MaterialSetTag(keyFor("cooked_fish")) + .add(Material.COOKED_COD, Material.COOKED_SALMON); + + /** + * Covers all 16 dyes. + */ + public static final MaterialSetTag DYES = new MaterialSetTag(keyFor("dyes")) + .endsWith("_DYE") + .add(Material.BONE_MEAL, + Material.CACTUS_GREEN, + Material.COCOA_BEANS, + Material.DANDELION_YELLOW, + Material.INK_SAC, + Material.LAPIS_LAZULI, + Material.ROSE_RED + ) + .ensureSize("DYES", 16); + + /** + * Covers all 6 wood variants of gates. + */ + public static final MaterialSetTag FENCE_GATES = new MaterialSetTag(keyFor("fence_gates")) + .endsWith("_GATE") + .ensureSize("FENCE_GATES", 6); + + /** + * Covers all 6 wood variants and nether brick fence. + */ + public static final MaterialSetTag FENCES = new MaterialSetTag(keyFor("fences")) + .endsWith("_FENCE") + .ensureSize("FENCES", 7); + + /** + * Covers all 4 variants of fish buckets. + */ + public static final MaterialSetTag FISH_BUCKETS = new MaterialSetTag(keyFor("fish_buckets")) + .add(Material.COD_BUCKET, Material.PUFFERFISH_BUCKET, Material.SALMON_BUCKET, Material.TROPICAL_FISH_BUCKET); + + /** + * Covers the non-colored glass and 16 stained glass (not panes). + */ + public static final MaterialSetTag GLASS = new MaterialSetTag(keyFor("glass")) + .endsWith("_GLASS") + .add(Material.GLASS) + .ensureSize("GLASS", 17); + + /** + * Covers the non-colored glass panes and 16 stained glass panes (panes only). + */ + public static final MaterialSetTag GLASS_PANES = new MaterialSetTag(keyFor("glass_panes")) + .endsWith("GLASS_PANE") + .ensureSize("GLASS_PANES", 17); + + /** + * Covers all 16 glazed terracotta blocks. + */ + public static final MaterialSetTag GLAZED_TERRACOTTA = new MaterialSetTag(keyFor("glazed_terracotta")) + .endsWith("GLAZED_TERRACOTTA") + .ensureSize("GLAZED_TERRACOTTA", 16); + + /** + * Covers the 16 colors of stained terracotta. + */ + public static final MaterialSetTag STAINED_TERRACOTTA = new MaterialSetTag(keyFor("stained_terracotta")) + .endsWith("TERRACOTTA") + .not(Material.TERRACOTTA) + .notEndsWith("GLAZED_TERRACOTTA") + .ensureSize("STAINED_TERRACOTTA", 16); + + /** + * Covers terracotta along with the 16 stained variants. + */ + public static final MaterialSetTag TERRACOTTA = new MaterialSetTag(keyFor("terracotta")) + .endsWith("TERRACOTTA") + .ensureSize("TERRACOTTA", 33); + + + /** + * Covers both golden apples. + */ + public static final MaterialSetTag GOLDEN_APPLES = new MaterialSetTag(keyFor("golden_apples")) + .endsWith("GOLDEN_APPLE") + .ensureSize("GOLDEN_APPLES", 2); + + /** + * Covers the 3 variants of horse armor. + */ + public static final MaterialSetTag HORSE_ARMORS = new MaterialSetTag(keyFor("horse_armors")) + .endsWith("_HORSE_ARMOR") + .ensureSize("HORSE_ARMORS", 3); + + /** + * Covers the 6 variants of infested blocks. + */ + public static final MaterialSetTag INFESTED_BLOCKS = new MaterialSetTag(keyFor("infested_blocks")) + .startsWith("INFESTED_") + .ensureSize("INFESTED_BLOCKS", 6); + + /** + * Covers the 3 variants of mushroom blocks. + */ + public static final MaterialSetTag MUSHROOM_BLOCKS = new MaterialSetTag(keyFor("mushroom_blocks")) + .endsWith("MUSHROOM_BLOCK") + .add(Material.MUSHROOM_STEM) + .ensureSize("MUSHROOM_BLOCKS", 3); + + /** + * Covers both mushrooms. + */ + public static final MaterialSetTag MUSHROOMS = new MaterialSetTag(keyFor("mushrooms")) + .add(Material.BROWN_MUSHROOM, Material.RED_MUSHROOM); + + /** + * Covers all 12 music disc items. + */ + public static final MaterialSetTag MUSIC_DISCS = new MaterialSetTag(keyFor("music_discs")) + .startsWith("MUSIC_DISC_") + .ensureSize("MUSIC_DISCS", 12); + + /** + * Covers all 8 ores. + */ + public static final MaterialSetTag ORES = new MaterialSetTag(keyFor("ores")) + .endsWith("_ORE") + .ensureSize("ORES", 8); + + /** + * Covers all piston typed items and blocks including the piston head and moving piston. + */ + public static final MaterialSetTag PISTONS = new MaterialSetTag(keyFor("pistons")) + .contains("PISTON") + .ensureSize("PISTONS", 4); + + /** + * Covers all potato items. + */ + public static final MaterialSetTag POTATOES = new MaterialSetTag(keyFor("potatoes")) + .endsWith("POTATO") + .ensureSize("POTATOES", 3); + + /** + * Covers all 6 wooden pressure plates and the 2 weighted pressure plates and 1 stone pressure plate. + */ + public static final MaterialSetTag PRESSURE_PLATES = new MaterialSetTag(keyFor("pressure_plates")) + .endsWith("_PRESSURE_PLATE") + .ensureSize("PRESSURE_PLATES", 9); + + /** + * Covers the 3 variants of prismarine blocks. + */ + public static final MaterialSetTag PRISMARINE = new MaterialSetTag(keyFor("prismarine")) + .add(Material.PRISMARINE, Material.PRISMARINE_BRICKS, Material.DARK_PRISMARINE); + + /** + * Covers the 3 variants of prismarine slabs. + */ + public static final MaterialSetTag PRISMARINE_SLABS = new MaterialSetTag(keyFor("prismarine_slabs")) + .add(Material.PRISMARINE_SLAB, Material.PRISMARINE_BRICK_SLAB, Material.DARK_PRISMARINE_SLAB); + + /** + * Covers the 3 variants of prismarine stairs. + */ + public static final MaterialSetTag PRISMARINE_STAIRS = new MaterialSetTag(keyFor("prismarine_stairs")) + .add(Material.PRISMARINE_STAIRS, Material.PRISMARINE_BRICK_STAIRS, Material.DARK_PRISMARINE_STAIRS); + + /** + * Covers the 3 variants of pumpkins. + */ + public static final MaterialSetTag PUMPKINS = new MaterialSetTag(keyFor("pumpkins")) + .add(Material.CARVED_PUMPKIN, Material.JACK_O_LANTERN, Material.PUMPKIN); + + /** + * Covers the 4 variants of quartz blocks. + */ + public static final MaterialSetTag QUARTZ_BLOCKS = new MaterialSetTag(keyFor("quartz_blocks")) + .add(Material.QUARTZ_BLOCK, Material.QUARTZ_PILLAR, Material.CHISELED_QUARTZ_BLOCK, Material.SMOOTH_QUARTZ); + + /** + * Covers all uncooked fish items. + */ + public static final MaterialSetTag RAW_FISH = new MaterialSetTag(keyFor("raw_fish")) + .add(Material.COD, Material.PUFFERFISH, Material.SALMON, Material.TROPICAL_FISH); + + /** + * Covers the 4 variants of red sandstone blocks. + */ + public static final MaterialSetTag RED_SANDSTONES = new MaterialSetTag(keyFor("red_sandstones")) + .endsWith("RED_SANDSTONE") + .ensureSize("RED_SANDSTONES", 4); + + /** + * Covers the 4 variants of sandstone blocks. + */ + public static final MaterialSetTag SANDSTONES = new MaterialSetTag(keyFor("sandstones")) + .add(Material.SANDSTONE, Material.CHISELED_SANDSTONE, Material.CUT_SANDSTONE, Material.SMOOTH_SANDSTONE); + + /** + * Covers sponge and wet sponge. + */ + public static final MaterialSetTag SPONGES = new MaterialSetTag(keyFor("sponges")) + .endsWith("SPONGE") + .ensureSize("SPONGES", 2); + + /** + * Covers the non-colored and 16 colored shulker boxes. + */ + public static final MaterialSetTag SHULKER_BOXES = new MaterialSetTag(keyFor("shulker_boxes")) + .endsWith("SHULKER_BOX") + .ensureSize("SHULKER_BOXES", 17); + + /** + * Covers zombie, creeper, skeleton, dragon, and player heads. + */ + public static final MaterialSetTag SKULLS = new MaterialSetTag(keyFor("skulls")) + .endsWith("_HEAD") + .endsWith("_SKULL") + .not(Material.PISTON_HEAD) + .ensureSize("SKULLS", 12); + + /** + * Covers all spawn egg items. + */ + public static final MaterialSetTag SPAWN_EGGS = new MaterialSetTag(keyFor("spawn_eggs")) + .endsWith("_SPAWN_EGG") + .ensureSize("SPAWN_EGGS", 51); + + /** + * Covers all 16 colors of stained glass. + */ + public static final MaterialSetTag STAINED_GLASS = new MaterialSetTag(keyFor("stained_glass")) + .endsWith("_STAINED_GLASS") + .ensureSize("STAINED_GLASS", 16); + + /** + * Covers all 16 colors of stained glass panes. + */ + public static final MaterialSetTag STAINED_GLASS_PANES = new MaterialSetTag(keyFor("stained_glass_panes")) + .endsWith("STAINED_GLASS_PANE") + .ensureSize("STAINED_GLASS_PANES", 16); + + /** + * Covers all 7 variants of trapdoors. + */ + public static final MaterialSetTag TRAPDOORS = new MaterialSetTag(keyFor("trapdoors")) + .endsWith("_TRAPDOOR") + .ensureSize("TRAPDOORS", 7); + + /** + * Covers all 6 wood variants of fences. + */ + public static final MaterialSetTag WOODEN_FENCES = new MaterialSetTag(keyFor("wooden_fences")) + .endsWith("_FENCE") + .not(Material.NETHER_BRICK_FENCE) + .ensureSize("WOODEN_FENCES", 6); + + /** + * Covers all 6 wood variants of trapdoors. + */ + public static final MaterialSetTag WOODEN_TRAPDOORS = new MaterialSetTag(keyFor("wooden_trapdoors")) + .endsWith("_TRAPDOOR") + .not(Material.IRON_TRAPDOOR) + .ensureSize("WOODEN_TRAPDOORS", 6); + + public static final MaterialSetTag WOODEN_GATES = new MaterialSetTag(keyFor("wooden_gates")) + .endsWith("_GATE") + .ensureSize("WOODEN_GATES", 6); + + public static final MaterialSetTag PURPUR = new MaterialSetTag(keyFor("purpur")) + .startsWith("PURPUR_") + .ensureSize("PURPUR", 4); + + public static final MaterialSetTag SIGNS = new MaterialSetTag(keyFor("signs")) + .add(Material.SIGN, Material.WALL_SIGN) + .ensureSize("SIGNS", 2); + + public static final MaterialSetTag TORCH = new MaterialSetTag(keyFor("torch")) + .add(Material.TORCH, Material.WALL_TORCH) + .ensureSize("TORCH", 2); + + public static final MaterialSetTag REDSTONE_TORCH = new MaterialSetTag(keyFor("restone_torch")) + .add(Material.REDSTONE_TORCH, Material.REDSTONE_WALL_TORCH) + .ensureSize("REDSTONE_TORCH", 2); + + @SuppressWarnings("unchecked") + public static final MaterialSetTag COLORABLE = new MaterialSetTag(keyFor("colorable")) + .add(Tag.WOOL, Tag.CARPETS).add(SHULKER_BOXES, STAINED_GLASS, STAINED_GLASS_PANES, CONCRETES, BEDS); + //.ensureSize("COLORABLE", 81); unit test don't have the vanilla item tags, so counts don't line up for real +} diff --git a/api/src/main/java/com/destroystokyo/paper/Namespaced.java b/api/src/main/java/com/destroystokyo/paper/Namespaced.java new file mode 100644 index 000000000..cd1a34b82 --- /dev/null +++ b/api/src/main/java/com/destroystokyo/paper/Namespaced.java @@ -0,0 +1,40 @@ +package com.destroystokyo.paper; + +import org.jetbrains.annotations.NotNull; + +/** + * Represents a namespaced resource, see {@link org.bukkit.NamespacedKey} for single elements + * or {@link com.destroystokyo.paper.NamespacedTag} for a collection of elements + * + * Namespaces may only contain lowercase alphanumeric characters, periods, + * underscores, and hyphens. + *

+ * Keys may only contain lowercase alphanumeric characters, periods, + * underscores, hyphens, and forward slashes. + *

+ * You should not be implementing this interface yourself, use {@link org.bukkit.NamespacedKey} + * or {@link com.destroystokyo.paper.NamespacedTag} as needed instead. + */ +public interface Namespaced { + /** + * Gets the namespace this resource is a part of + *

+ * This is contractually obligated to only contain lowercase alphanumeric characters, + * periods, underscores, and hyphens. + * + * @return resource namespace + */ + @NotNull + String getNamespace(); + + /** + * Gets the key corresponding to this resource + *

+ * This is contractually obligated to only contain lowercase alphanumeric characters, + * periods, underscores, hyphens, and forward slashes. + * + * @return resource key + */ + @NotNull + String getKey(); +} diff --git a/api/src/main/java/com/destroystokyo/paper/NamespacedTag.java b/api/src/main/java/com/destroystokyo/paper/NamespacedTag.java new file mode 100644 index 000000000..28f3fda95 --- /dev/null +++ b/api/src/main/java/com/destroystokyo/paper/NamespacedTag.java @@ -0,0 +1,142 @@ +package com.destroystokyo.paper; + +import com.google.common.base.Preconditions; +import java.util.Locale; +import java.util.UUID; +import java.util.regex.Pattern; +import org.bukkit.plugin.Plugin; +import org.jetbrains.annotations.NotNull; + +/** + * Represents a String based key pertaining to a tagged entry. Consists of two components - a namespace + * and a key. + *

+ * Namespaces may only contain lowercase alphanumeric characters, periods, + * underscores, and hyphens. + *

+ * Keys may only contain lowercase alphanumeric characters, periods, + * underscores, hyphens, and forward slashes. + * + */ +// Paper - entire class, based on org.bukkit.NamespacedKey +public final class NamespacedTag implements com.destroystokyo.paper.Namespaced { + + /** + * The namespace representing all inbuilt keys. + */ + public static final String MINECRAFT = "minecraft"; + /** + * The namespace representing all keys generated by Bukkit for backwards + * compatibility measures. + */ + public static final String BUKKIT = "bukkit"; + // + private static final Pattern VALID_NAMESPACE = Pattern.compile("[a-z0-9._-]+"); + private static final Pattern VALID_KEY = Pattern.compile("[a-z0-9/._-]+"); + // + private final String namespace; + private final String key; + + /** + * Create a key in a specific namespace. + * + * @param namespace String representing a grouping of keys + * @param key Name for this specific key + * @deprecated should never be used by plugins, for internal use only!! + */ + @Deprecated + public NamespacedTag(@NotNull String namespace, @NotNull String key) { + Preconditions.checkArgument(namespace != null && VALID_NAMESPACE.matcher(namespace).matches(), "Invalid namespace. Must be [a-z0-9._-]: %s", namespace); + Preconditions.checkArgument(key != null && VALID_KEY.matcher(key).matches(), "Invalid key. Must be [a-z0-9/._-]: %s", key); + + this.namespace = namespace; + this.key = key; + + String string = toString(); + Preconditions.checkArgument(string.length() < 256, "NamespacedTag must be less than 256 characters", string); + } + + /** + * Create a key in the plugin's namespace. + *

+ * Namespaces may only contain lowercase alphanumeric characters, periods, + * underscores, and hyphens. + *

+ * Keys may only contain lowercase alphanumeric characters, periods, + * underscores, hyphens, and forward slashes. + * + * @param plugin the plugin to use for the namespace + * @param key the key to create + */ + public NamespacedTag(@NotNull Plugin plugin, @NotNull String key) { + Preconditions.checkArgument(plugin != null, "Plugin cannot be null"); + Preconditions.checkArgument(key != null, "Key cannot be null"); + + this.namespace = plugin.getName().toLowerCase(Locale.ROOT); + this.key = key.toLowerCase().toLowerCase(Locale.ROOT); + + // Check validity after normalization + Preconditions.checkArgument(VALID_NAMESPACE.matcher(this.namespace).matches(), "Invalid namespace. Must be [a-z0-9._-]: %s", this.namespace); + Preconditions.checkArgument(VALID_KEY.matcher(this.key).matches(), "Invalid key. Must be [a-z0-9/._-]: %s", this.key); + + String string = toString(); + Preconditions.checkArgument(string.length() < 256, "NamespacedTag must be less than 256 characters (%s)", string); + } + + @NotNull + public String getNamespace() { + return namespace; + } + + @NotNull + public String getKey() { + return key; + } + + @Override + public int hashCode() { + int hash = 7; + hash = 47 * hash + this.namespace.hashCode(); + hash = 47 * hash + this.key.hashCode(); + return hash; + } + + @Override + public boolean equals(Object obj) { + if (obj == null) { + return false; + } + if (getClass() != obj.getClass()) { + return false; + } + final NamespacedTag other = (NamespacedTag) obj; + return this.namespace.equals(other.namespace) && this.key.equals(other.key); + } + + @Override + public String toString() { + return "#" + this.namespace + ":" + this.key; + } + + /** + * Return a new random key in the {@link #BUKKIT} namespace. + * + * @return new key + * @deprecated should never be used by plugins, for internal use only!! + */ + @Deprecated + public static NamespacedTag randomKey() { + return new NamespacedTag(BUKKIT, UUID.randomUUID().toString()); + } + + /** + * Get a key in the Minecraft namespace. + * + * @param key the key to use + * @return new key in the Minecraft namespace + */ + @NotNull + public static NamespacedTag minecraft(@NotNull String key) { + return new NamespacedTag(MINECRAFT, key); + } +} diff --git a/api/src/main/java/com/destroystokyo/paper/ParticleBuilder.java b/api/src/main/java/com/destroystokyo/paper/ParticleBuilder.java new file mode 100644 index 000000000..06f1602f5 --- /dev/null +++ b/api/src/main/java/com/destroystokyo/paper/ParticleBuilder.java @@ -0,0 +1,478 @@ +package com.destroystokyo.paper; + +import com.google.common.collect.Lists; +import org.bukkit.Color; +import org.bukkit.Location; +import org.bukkit.Particle; +import org.bukkit.World; +import org.bukkit.entity.Player; +import org.bukkit.util.NumberConversions; + +import java.util.Collection; +import java.util.List; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +/** + * Helps prepare a particle to be sent to players. + * + * Usage of the builder is preferred over the super long {@link World#spawnParticle(Particle, Location, int, double, double, double, double, Object)} API + */ +public class ParticleBuilder { + + private Particle particle; + private List receivers; + private Player source; + private Location location; + private int count = 1; + private double offsetX = 0, offsetY = 0, offsetZ = 0; + private double extra = 1; + private Object data; + private boolean force = true; + + public ParticleBuilder(@NotNull Particle particle) { + this.particle = particle; + } + + /** + * Sends the particle to all receiving players (or all). This method is safe to use + * Asynchronously + * + * @return a reference to this object. + */ + @NotNull + public ParticleBuilder spawn() { + if (this.location == null) { + throw new IllegalStateException("Please specify location for this particle"); + } + location.getWorld().spawnParticle(particle, receivers, source, + location.getX(), location.getY(), location.getZ(), + count, offsetX, offsetY, offsetZ, extra, data, force + ); + return this; + } + + /** + * @return The particle going to be sent + */ + @NotNull + public Particle particle() { + return particle; + } + + /** + * Changes what particle will be sent + * + * @param particle The particle + * @return a reference to this object. + */ + @NotNull + public ParticleBuilder particle(@NotNull Particle particle) { + this.particle = particle; + return this; + } + + /** + * @return List of players who will receive the particle, or null for all in world + */ + @Nullable + public List receivers() { + return receivers; + } + + /** + * Example use: + * + * builder.receivers(16); if (builder.hasReceivers()) { sendParticleAsync(builder); } + * + * @return If this particle is going to be sent to someone + */ + public boolean hasReceivers() { + return (receivers == null && !location.getWorld().getPlayers().isEmpty()) || ( + receivers != null && !receivers.isEmpty()); + } + + /** + * Sends this particle to all players in the world. This is rather silly and you should likely not + * be doing this. + * + * Just be a logical person and use receivers by radius or collection. + * + * @return a reference to this object. + */ + @NotNull + public ParticleBuilder allPlayers() { + this.receivers = null; + return this; + } + + /** + * @param receivers List of players to receive this particle, or null for all players in the + * world + * @return a reference to this object. + */ + @NotNull + public ParticleBuilder receivers(@Nullable List receivers) { + // Had to keep this as we first made API List<> and not Collection, but removing this may break plugins compiled on older jars + // TODO: deprecate? + this.receivers = receivers != null ? Lists.newArrayList(receivers) : null; + return this; + } + + /** + * @param receivers List of players to receive this particle, or null for all players in the + * world + * @return a reference to this object. + */ + @NotNull + public ParticleBuilder receivers(@Nullable Collection receivers) { + this.receivers = receivers != null ? Lists.newArrayList(receivers) : null; + return this; + } + + /** + * @param receivers List of players to be receive this particle, or null for all players in the + * world + * @return a reference to this object. + */ + @NotNull + public ParticleBuilder receivers(@Nullable Player... receivers) { + this.receivers = receivers != null ? Lists.newArrayList(receivers) : null; + return this; + } + + /** + * Selects all players within a cuboid selection around the particle location, within the + * specified bounding box. If you want a more spherical check, see {@link #receivers(int, + * boolean)} + * + * @param radius amount to add on all axis + * @return a reference to this object. + */ + @NotNull + public ParticleBuilder receivers(int radius) { + return receivers(radius, radius); + } + + /** + * Selects all players within the specified radius around the particle location. If byDistance is + * false, behavior uses cuboid selection the same as {@link #receivers(int, int)} If byDistance is + * true, radius is tested by distance in a spherical shape + * + * @param radius amount to add on each axis + * @param byDistance true to use a spherical radius, false to use a cuboid + * @return a reference to this object. + */ + @NotNull + public ParticleBuilder receivers(int radius, boolean byDistance) { + if (!byDistance) { + return receivers(radius, radius, radius); + } else { + this.receivers = Lists.newArrayList(); + for (Player nearbyPlayer : location.getWorld() + .getNearbyPlayers(location, radius, radius, radius)) { + Location loc = nearbyPlayer.getLocation(); + double x = NumberConversions.square(location.getX() - loc.getX()); + double y = NumberConversions.square(location.getY() - loc.getY()); + double z = NumberConversions.square(location.getZ() - loc.getZ()); + if (Math.sqrt(x + y + z) > radius) { + continue; + } + this.receivers.add(nearbyPlayer); + } + return this; + } + } + + /** + * Selects all players within a cuboid selection around the particle location, within the + * specified bounding box. Allows specifying a different Y size than X and Z If you want a more + * cylinder check, see {@link #receivers(int, int, boolean)} If you want a more spherical check, + * see {@link #receivers(int, boolean)} + * + * @param xzRadius amount to add on the x/z axis + * @param yRadius amount to add on the y axis + * @return a reference to this object. + */ + @NotNull + public ParticleBuilder receivers(int xzRadius, int yRadius) { + return receivers(xzRadius, yRadius, xzRadius); + } + + /** + * Selects all players within the specified radius around the particle location. If byDistance is + * false, behavior uses cuboid selection the same as {@link #receivers(int, int)} If byDistance is + * true, radius is tested by distance on the y plane and on the x/z plane, in a cylinder shape. + * + * @param xzRadius amount to add on the x/z axis + * @param yRadius amount to add on the y axis + * @param byDistance true to use a cylinder shape, false to use cuboid + * @return a reference to this object. + */ + @NotNull + public ParticleBuilder receivers(int xzRadius, int yRadius, boolean byDistance) { + if (!byDistance) { + return receivers(xzRadius, yRadius, xzRadius); + } else { + this.receivers = Lists.newArrayList(); + for (Player nearbyPlayer : location.getWorld() + .getNearbyPlayers(location, xzRadius, yRadius, xzRadius)) { + Location loc = nearbyPlayer.getLocation(); + if (Math.abs(loc.getY() - this.location.getY()) > yRadius) { + continue; + } + double x = NumberConversions.square(location.getX() - loc.getX()); + double z = NumberConversions.square(location.getZ() - loc.getZ()); + if (x + z > NumberConversions.square(xzRadius)) { + continue; + } + this.receivers.add(nearbyPlayer); + } + return this; + } + } + + /** + * Selects all players within a cuboid selection around the particle location, within the + * specified bounding box. If you want a more cylinder check, see {@link #receivers(int, int, + * boolean)} If you want a more spherical check, see {@link #receivers(int, boolean)} + * + * @param xRadius amount to add on the x axis + * @param yRadius amount to add on the y axis + * @param zRadius amount to add on the z axis + * @return a reference to this object. + */ + @NotNull + public ParticleBuilder receivers(int xRadius, int yRadius, int zRadius) { + if (location == null) { + throw new IllegalStateException("Please set location first"); + } + return receivers(location.getWorld().getNearbyPlayers(location, xRadius, yRadius, zRadius)); + } + + /** + * @return The player considered the source of this particle (for Visibility concerns), or null + */ + @Nullable + public Player source() { + return source; + } + + /** + * Sets the source of this particle for visibility concerns (Vanish API) + * + * @param source The player who is considered the source + * @return a reference to this object. + */ + @NotNull + public ParticleBuilder source(@Nullable Player source) { + this.source = source; + return this; + } + + /** + * @return Location of where the particle will spawn + */ + @Nullable + public Location location() { + return location; + } + + /** + * Sets the location of where to spawn the particle + * + * @param location The location of the particle + * @return a reference to this object. + */ + @NotNull + public ParticleBuilder location(@NotNull Location location) { + this.location = location.clone(); + return this; + } + + /** + * Sets the location of where to spawn the particle + * + * @param world World to spawn particle in + * @param x X location + * @param y Y location + * @param z Z location + * @return a reference to this object. + */ + @NotNull + public ParticleBuilder location(@NotNull World world, double x, double y, double z) { + this.location = new Location(world, x, y, z); + return this; + } + + /** + * @return Number of particles to spawn + */ + public int count() { + return count; + } + + /** + * Sets the number of particles to spawn + * + * @param count Number of particles + * @return a reference to this object. + */ + @NotNull + public ParticleBuilder count(int count) { + this.count = count; + return this; + } + + /** + * Particle offset X. Varies by particle on how this is used + * + * @return Particle offset X. + */ + public double offsetX() { + return offsetX; + } + + /** + * Particle offset Y. Varies by particle on how this is used + * + * @return Particle offset Y. + */ + public double offsetY() { + return offsetY; + } + + /** + * Particle offset Z. Varies by particle on how this is used + * + * @return Particle offset Z. + */ + public double offsetZ() { + return offsetZ; + } + + /** + * Sets the particle offset. Varies by particle on how this is used + * + * @param offsetX Particle offset X + * @param offsetY Particle offset Y + * @param offsetZ Particle offset Z + * @return a reference to this object. + */ + @NotNull + public ParticleBuilder offset(double offsetX, double offsetY, double offsetZ) { + this.offsetX = offsetX; + this.offsetY = offsetY; + this.offsetZ = offsetZ; + return this; + } + + /** + * Gets the Particle extra data. Varies by particle on how this is used + * + * @return the extra particle data + */ + public double extra() { + return extra; + } + + /** + * Sets the particle extra data. Varies by particle on how this is used + * + * @param extra the extra particle data + * @return a reference to this object. + */ + @NotNull + public ParticleBuilder extra(double extra) { + this.extra = extra; + return this; + } + + /** + * Gets the particle custom data. Varies by particle on how this is used + * + * @param The Particle data type + * @return the ParticleData for this particle + */ + @Nullable + public T data() { + //noinspection unchecked + return (T) data; + } + + /** + * Sets the particle custom data. Varies by particle on how this is used + * + * @param data The new particle data + * @param The Particle data type + * @return a reference to this object. + */ + @NotNull + public ParticleBuilder data(@Nullable T data) { + this.data = data; + return this; + } + + /** + * Sets whether the particle is forcefully shown to the player. If forced, the particle will show + * faraway, as far as the player's view distance allows. If false, the particle will show + * according to the client's particle settings. + * + * @param force true to force, false for normal + * @return a reference to this object. + */ + @NotNull + public ParticleBuilder force(boolean force) { + this.force = force; + return this; + } + + /** + * Sets the particle Color. Only valid for REDSTONE. + * + * @param color the new particle color + * @return a reference to this object. + */ + @NotNull + public ParticleBuilder color(@Nullable Color color) { + return color(color, 1); + } + + /** + * Sets the particle Color and size. Only valid for REDSTONE. + * + * @param color the new particle color + * @param size the size of the particle + * @return a reference to this object. + */ + @NotNull + public ParticleBuilder color(@Nullable Color color, float size) { + if (particle != Particle.REDSTONE && color != null) { + throw new IllegalStateException("Color may only be set on REDSTONE"); + } + + // We don't officially support reusing these objects, but here we go + if (color == null) { + if (data instanceof Particle.DustOptions) { + return data(null); + } else { + return this; + } + } + + return data(new Particle.DustOptions(color, size)); + } + + /** + * Sets the particle Color. + * Only valid for REDSTONE. + * @param r red color component + * @param g green color component + * @param b blue color component + * @return a reference to this object. + */ + @NotNull + public ParticleBuilder color(int r, int g, int b) { + return color(Color.fromRGB(r, g, b)); + } +} diff --git a/api/src/main/java/com/destroystokyo/paper/Title.java b/api/src/main/java/com/destroystokyo/paper/Title.java new file mode 100644 index 000000000..4fe18540f --- /dev/null +++ b/api/src/main/java/com/destroystokyo/paper/Title.java @@ -0,0 +1,373 @@ +package com.destroystokyo.paper; + +import net.md_5.bungee.api.chat.BaseComponent; +import net.md_5.bungee.api.chat.TextComponent; + +import org.bukkit.entity.Player; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +import static com.google.common.base.Preconditions.checkArgument; +import static com.google.common.base.Preconditions.checkNotNull; +import static com.google.common.base.Preconditions.checkState; + +/** + * Represents a title to may be sent to a {@link Player}. + * + *

A title can be sent without subtitle text.

+ */ +public final class Title { + + /** + * The default number of ticks for the title to fade in. + */ + public static final int DEFAULT_FADE_IN = 20; + /** + * The default number of ticks for the title to stay. + */ + public static final int DEFAULT_STAY = 200; + /** + * The default number of ticks for the title to fade out. + */ + public static final int DEFAULT_FADE_OUT = 20; + + private final BaseComponent[] title; + private final BaseComponent[] subtitle; + private final int fadeIn; + private final int stay; + private final int fadeOut; + + /** + * Create a title with the default time values and no subtitle. + * + *

Times use default values.

+ * + * @param title the main text of the title + * @throws NullPointerException if the title is null + */ + public Title(@NotNull BaseComponent title) { + this(title, null); + } + + /** + * Create a title with the default time values and no subtitle. + * + *

Times use default values.

+ * + * @param title the main text of the title + * @throws NullPointerException if the title is null + */ + public Title(@NotNull BaseComponent[] title) { + this(title, null); + } + + /** + * Create a title with the default time values and no subtitle. + * + *

Times use default values.

+ * + * @param title the main text of the title + * @throws NullPointerException if the title is null + */ + public Title(@NotNull String title) { + this(title, null); + } + + /** + * Create a title with the default time values. + * + *

Times use default values.

+ * + * @param title the main text of the title + * @param subtitle the secondary text of the title + */ + public Title(@NotNull BaseComponent title, @Nullable BaseComponent subtitle) { + this(title, subtitle, DEFAULT_FADE_IN, DEFAULT_STAY, DEFAULT_FADE_OUT); + } + + /** + * Create a title with the default time values. + * + *

Times use default values.

+ * + * @param title the main text of the title + * @param subtitle the secondary text of the title + */ + public Title(@NotNull BaseComponent[] title, @Nullable BaseComponent[] subtitle) { + this(title, subtitle, DEFAULT_FADE_IN, DEFAULT_STAY, DEFAULT_FADE_OUT); + } + + /** + * Create a title with the default time values. + * + *

Times use default values.

+ * + * @param title the main text of the title + * @param subtitle the secondary text of the title + */ + public Title(@NotNull String title, @Nullable String subtitle) { + this(title, subtitle, DEFAULT_FADE_IN, DEFAULT_STAY, DEFAULT_FADE_OUT); + } + + /** + * Creates a new title. + * + * @param title the main text of the title + * @param subtitle the secondary text of the title + * @param fadeIn the number of ticks for the title to fade in + * @param stay the number of ticks for the title to stay on screen + * @param fadeOut the number of ticks for the title to fade out + * @throws IllegalArgumentException if any of the times are negative + */ + public Title(@NotNull BaseComponent title, @Nullable BaseComponent subtitle, int fadeIn, int stay, int fadeOut) { + this( + new BaseComponent[]{checkNotNull(title, "title")}, + subtitle == null ? null : new BaseComponent[]{subtitle}, + fadeIn, + stay, + fadeOut + ); + } + + /** + * Creates a new title. + * + * @param title the main text of the title + * @param subtitle the secondary text of the title + * @param fadeIn the number of ticks for the title to fade in + * @param stay the number of ticks for the title to stay on screen + * @param fadeOut the number of ticks for the title to fade out + * @throws IllegalArgumentException if any of the times are negative + */ + public Title(@Nullable BaseComponent[] title, @NotNull BaseComponent[] subtitle, int fadeIn, int stay, int fadeOut) { + checkArgument(fadeIn >= 0, "Negative fadeIn: %s", fadeIn); + checkArgument(stay >= 0, "Negative stay: %s", stay); + checkArgument(fadeOut >= 0, "Negative fadeOut: %s", fadeOut); + this.title = checkNotNull(title, "title"); + this.subtitle = subtitle; + this.fadeIn = fadeIn; + this.stay = stay; + this.fadeOut = fadeOut; + } + + /** + * Creates a new title. + * + *

It is recommended to the {@link BaseComponent} constrctors.

+ * + * @param title the main text of the title + * @param subtitle the secondary text of the title + * @param fadeIn the number of ticks for the title to fade in + * @param stay the number of ticks for the title to stay on screen + * @param fadeOut the number of ticks for the title to fade out + */ + public Title(@NotNull String title, @Nullable String subtitle, int fadeIn, int stay, int fadeOut) { + this( + TextComponent.fromLegacyText(checkNotNull(title, "title")), + subtitle == null ? null : TextComponent.fromLegacyText(subtitle), + fadeIn, + stay, + fadeOut + ); + } + + /** + * Gets the text of this title + * + * @return the text + */ + @NotNull + public BaseComponent[] getTitle() { + return this.title; + } + + /** + * Gets the text of this title's subtitle + * + * @return the text + */ + @Nullable + public BaseComponent[] getSubtitle() { + return this.subtitle; + } + + /** + * Gets the number of ticks to fade in. + * + *

The returned value is never negative.

+ * + * @return the number of ticks to fade in + */ + public int getFadeIn() { + return this.fadeIn; + } + + /** + * Gets the number of ticks to stay. + * + *

The returned value is never negative.

+ * + * @return the number of ticks to stay + */ + public int getStay() { + return this.stay; + } + + /** + * Gets the number of ticks to fade out. + * + *

The returned value is never negative.

+ * + * @return the number of ticks to fade out + */ + public int getFadeOut() { + return this.fadeOut; + } + + @NotNull + public static Builder builder() { + return new Builder(); + } + + /** + * A builder for creating titles + */ + public static final class Builder { + + private BaseComponent[] title; + private BaseComponent[] subtitle; + private int fadeIn = DEFAULT_FADE_IN; + private int stay = DEFAULT_STAY; + private int fadeOut = DEFAULT_FADE_OUT; + + /** + * Sets the title to the given text. + * + * @param title the title text + * @return this builder instance + * @throws NullPointerException if the title is null + */ + @NotNull + public Builder title(@NotNull BaseComponent title) { + return this.title(new BaseComponent[]{checkNotNull(title, "title")}); + } + + /** + * Sets the title to the given text. + * + * @param title the title text + * @return this builder instance + * @throws NullPointerException if the title is null + */ + @NotNull + public Builder title(@NotNull BaseComponent[] title) { + this.title = checkNotNull(title, "title"); + return this; + } + + /** + * Sets the title to the given text. + * + *

It is recommended to the {@link BaseComponent} methods.

+ * + * @param title the title text + * @return this builder instance + * @throws NullPointerException if the title is null + */ + @NotNull + public Builder title(@NotNull String title) { + return this.title(TextComponent.fromLegacyText(checkNotNull(title, "title"))); + } + + /** + * Sets the subtitle to the given text. + * + * @param subtitle the title text + * @return this builder instance + */ + @NotNull + public Builder subtitle(@Nullable BaseComponent subtitle) { + return this.subtitle(subtitle == null ? null : new BaseComponent[]{subtitle}); + } + + /** + * Sets the subtitle to the given text. + * + * @param subtitle the title text + * @return this builder instance + */ + @NotNull + public Builder subtitle(@Nullable BaseComponent[] subtitle) { + this.subtitle = subtitle; + return this; + } + + /** + * Sets the subtitle to the given text. + * + *

It is recommended to the {@link BaseComponent} methods.

+ * + * @param subtitle the title text + * @return this builder instance + */ + @NotNull + public Builder subtitle(@Nullable String subtitle) { + return this.subtitle(subtitle == null ? null : TextComponent.fromLegacyText(subtitle)); + } + + /** + * Sets the number of ticks for the title to fade in + * + * @param fadeIn the number of ticks to fade in + * @return this builder instance + * @throws IllegalArgumentException if it is negative + */ + @NotNull + public Builder fadeIn(int fadeIn) { + checkArgument(fadeIn >= 0, "Negative fadeIn: %s", fadeIn); + this.fadeIn = fadeIn; + return this; + } + + + /** + * Sets the number of ticks for the title to stay. + * + * @param stay the number of ticks to stay + * @return this builder instance + * @throws IllegalArgumentException if it is negative + */ + @NotNull + public Builder stay(int stay) { + checkArgument(stay >= 0, "Negative stay: %s", stay); + this.stay = stay; + return this; + } + + /** + * Sets the number of ticks for the title to fade out. + * + * @param fadeOut the number of ticks to fade out + * @return this builder instance + * @throws IllegalArgumentException if it is negative + */ + @NotNull + public Builder fadeOut(int fadeOut) { + checkArgument(fadeOut >= 0, "Negative fadeOut: %s", fadeOut); + this.fadeOut = fadeOut; + return this; + } + + /** + * Create a title based on the values in the builder. + * + * @return a title from the values in this builder + * @throws IllegalStateException if title isn't specified + */ + @NotNull + public Title build() { + checkState(title != null, "Title not specified"); + return new Title(this.title, this.subtitle, this.fadeIn, this.stay, this.fadeOut); + } + } +} diff --git a/api/src/main/java/com/destroystokyo/paper/VersionHistoryManager.java b/api/src/main/java/com/destroystokyo/paper/VersionHistoryManager.java new file mode 100644 index 000000000..648b247ef --- /dev/null +++ b/api/src/main/java/com/destroystokyo/paper/VersionHistoryManager.java @@ -0,0 +1,144 @@ +package com.destroystokyo.paper; + +import com.google.common.base.MoreObjects; +import com.google.gson.Gson; +import com.google.gson.JsonSyntaxException; +import java.io.BufferedReader; +import java.io.BufferedWriter; +import java.io.IOException; +import java.nio.charset.StandardCharsets; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.nio.file.StandardOpenOption; +import java.util.Objects; +import java.util.logging.Level; +import java.util.logging.Logger; +import org.bukkit.Bukkit; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +public enum VersionHistoryManager { + INSTANCE; + + private final Gson gson = new Gson(); + + private final Logger logger = Bukkit.getLogger(); + + private VersionData currentData = null; + + VersionHistoryManager() { + final Path path = Paths.get("version_history.json"); + + if (Files.exists(path)) { + // Basic file santiy checks + if (!Files.isRegularFile(path)) { + if (Files.isDirectory(path)) { + logger.severe(path + " is a directory, cannot be used for version history"); + } else { + logger.severe(path + " is not a regular file, cannot be used for version history"); + } + // We can't continue + return; + } + + try (final BufferedReader reader = Files.newBufferedReader(path, StandardCharsets.UTF_8)) { + currentData = gson.fromJson(reader, VersionData.class); + } catch (final IOException e) { + logger.log(Level.SEVERE, "Failed to read version history file '" + path + "'", e); + return; + } catch (final JsonSyntaxException e) { + logger.log(Level.SEVERE, "Invalid json syntax for file '" + path + "'", e); + return; + } + + final String version = Bukkit.getVersion(); + if (version == null) { + logger.severe("Failed to retrieve current version"); + return; + } + + if (!version.equals(currentData.getCurrentVersion())) { + // The version appears to have changed + currentData.setOldVersion(currentData.getCurrentVersion()); + currentData.setCurrentVersion(version); + writeFile(path); + } + } else { + // File doesn't exist, start fresh + currentData = new VersionData(); + // oldVersion is null + currentData.setCurrentVersion(Bukkit.getVersion()); + writeFile(path); + } + } + + private void writeFile(@NotNull final Path path) { + try (final BufferedWriter writer = Files.newBufferedWriter( + path, + StandardCharsets.UTF_8, + StandardOpenOption.WRITE, + StandardOpenOption.CREATE, + StandardOpenOption.TRUNCATE_EXISTING + )) { + gson.toJson(currentData, writer); + } catch (final IOException e) { + logger.log(Level.SEVERE, "Failed to write to version history file", e); + } + } + + @Nullable + public VersionData getVersionData() { + return currentData; + } + + public static class VersionData { + private String oldVersion; + + private String currentVersion; + + @Nullable + public String getOldVersion() { + return oldVersion; + } + + public void setOldVersion(@Nullable String oldVersion) { + this.oldVersion = oldVersion; + } + + @Nullable + public String getCurrentVersion() { + return currentVersion; + } + + public void setCurrentVersion(@Nullable String currentVersion) { + this.currentVersion = currentVersion; + } + + @Override + public String toString() { + return MoreObjects.toStringHelper(this) + .add("oldVersion", oldVersion) + .add("currentVersion", currentVersion) + .toString(); + } + + @Override + public boolean equals(@Nullable Object o) { + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } + final VersionData versionData = (VersionData) o; + return Objects.equals(oldVersion, versionData.oldVersion) && + Objects.equals(currentVersion, versionData.currentVersion); + } + + @Override + public int hashCode() { + return Objects.hash(oldVersion, currentVersion); + } + } +} diff --git a/api/src/main/java/com/destroystokyo/paper/block/TargetBlockInfo.java b/api/src/main/java/com/destroystokyo/paper/block/TargetBlockInfo.java new file mode 100644 index 000000000..18a96dbb0 --- /dev/null +++ b/api/src/main/java/com/destroystokyo/paper/block/TargetBlockInfo.java @@ -0,0 +1,54 @@ +package com.destroystokyo.paper.block; + +import org.bukkit.block.Block; +import org.bukkit.block.BlockFace; +import org.jetbrains.annotations.NotNull; + +/** + * Represents information about a targeted block + */ +public class TargetBlockInfo { + private final Block block; + private final BlockFace blockFace; + + public TargetBlockInfo(@NotNull Block block, @NotNull BlockFace blockFace) { + this.block = block; + this.blockFace = blockFace; + } + + /** + * Get the block that is targeted + * + * @return Targeted block + */ + @NotNull + public Block getBlock() { + return block; + } + + /** + * Get the targeted BlockFace + * + * @return Targeted blockface + */ + @NotNull + public BlockFace getBlockFace() { + return blockFace; + } + + /** + * Get the relative Block to the targeted block on the side it is targeted at + * + * @return Block relative to targeted block + */ + @NotNull + public Block getRelativeBlock() { + return block.getRelative(blockFace); + } + + public enum FluidMode { + NEVER, + SOURCE_ONLY, + ALWAYS + } +} diff --git a/api/src/main/java/com/destroystokyo/paper/entity/Pathfinder.java b/api/src/main/java/com/destroystokyo/paper/entity/Pathfinder.java new file mode 100644 index 000000000..8b90a9053 --- /dev/null +++ b/api/src/main/java/com/destroystokyo/paper/entity/Pathfinder.java @@ -0,0 +1,170 @@ +package com.destroystokyo.paper.entity; + +import org.bukkit.Location; +import org.bukkit.entity.LivingEntity; +import org.bukkit.entity.Mob; + +import java.util.List; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +/** + * Handles pathfinding operations for an Entity + */ +public interface Pathfinder { + + /** + * + * @return The entity that is controlled by this pathfinder + */ + @NotNull + Mob getEntity(); + + /** + * Instructs the Entity to stop trying to navigate to its current desired location + */ + void stopPathfinding(); + + /** + * If the entity is currently trying to navigate to a destination, this will return true + * @return true if the entity is navigating to a destination + */ + boolean hasPath(); + + /** + * @return The location the entity is trying to navigate to, or null if there is no destination + */ + @Nullable + PathResult getCurrentPath(); + + /** + * Calculates a destination for the Entity to navigate to, but does not set it + * as the current target. Useful for calculating what would happen before setting it. + * @param loc Location to navigate to + * @return The closest Location the Entity can get to for this navigation, or null if no path could be calculated + */ + @Nullable PathResult findPath(@NotNull Location loc); + + /** + * Calculates a destination for the Entity to navigate to to reach the target entity, + * but does not set it as the current target. + * Useful for calculating what would happen before setting it. + * + * The behavior of this PathResult is subject to the games pathfinding rules, and may + * result in the pathfinding automatically updating to follow the target Entity. + * + * However, this behavior is not guaranteed, and is subject to the games behavior. + * + * @param target the Entity to navigate to + * @return The closest Location the Entity can get to for this navigation, or null if no path could be calculated + */ + @Nullable PathResult findPath(@NotNull LivingEntity target); + + /** + * Calculates a destination for the Entity to navigate to, and sets it with default speed + * as the current target. + * @param loc Location to navigate to + * @return If the pathfinding was successfully started + */ + default boolean moveTo(@NotNull Location loc) { + return moveTo(loc, 1); + } + + /** + * Calculates a destination for the Entity to navigate to, with desired speed + * as the current target. + * @param loc Location to navigate to + * @param speed Speed multiplier to navigate at, where 1 is 'normal' + * @return If the pathfinding was successfully started + */ + default boolean moveTo(@NotNull Location loc, double speed) { + PathResult path = findPath(loc); + return path != null && moveTo(path, speed); + } + + /** + * Calculates a destination for the Entity to navigate to to reach the target entity, + * and sets it with default speed. + * + * The behavior of this PathResult is subject to the games pathfinding rules, and may + * result in the pathfinding automatically updating to follow the target Entity. + * + * However, this behavior is not guaranteed, and is subject to the games behavior. + * + * @param target the Entity to navigate to + * @return If the pathfinding was successfully started + */ + default boolean moveTo(@NotNull LivingEntity target) { + return moveTo(target, 1); + } + + /** + * Calculates a destination for the Entity to navigate to to reach the target entity, + * and sets it with specified speed. + * + * The behavior of this PathResult is subject to the games pathfinding rules, and may + * result in the pathfinding automatically updating to follow the target Entity. + * + * However, this behavior is not guaranteed, and is subject to the games behavior. + * + * @param target the Entity to navigate to + * @param speed Speed multiplier to navigate at, where 1 is 'normal' + * @return If the pathfinding was successfully started + */ + default boolean moveTo(@NotNull LivingEntity target, double speed) { + PathResult path = findPath(target); + return path != null && moveTo(path, speed); + } + + /** + * Takes the result of a previous pathfinding calculation and sets it + * as the active pathfinding with default speed. + * + * @param path The Path to start following + * @return If the pathfinding was successfully started + */ + default boolean moveTo(@NotNull PathResult path) { + return moveTo(path, 1); + } + + /** + * Takes the result of a previous pathfinding calculation and sets it + * as the active pathfinding, + * + * @param path The Path to start following + * @param speed Speed multiplier to navigate at, where 1 is 'normal' + * @return If the pathfinding was successfully started + */ + boolean moveTo(@NotNull PathResult path, double speed); + + /** + * Represents the result of a pathfinding calculation + */ + interface PathResult { + + /** + * All currently calculated points to follow along the path to reach the destination location + * + * Will return points the entity has already moved past, see {@link #getNextPointIndex()} + * @return List of points + */ + @NotNull + List getPoints(); + + /** + * @return Returns the index of the current point along the points returned in {@link #getPoints()} the entity + * is trying to reach, or null if we are done with this pathfinding. + */ + int getNextPointIndex(); + + /** + * @return The next location in the path points the entity is trying to reach, or null if there is no next point + */ + @Nullable Location getNextPoint(); + + /** + * @return The closest point the path can get to the target location + */ + @Nullable Location getFinalPoint(); + } +} diff --git a/api/src/main/java/com/destroystokyo/paper/entity/RangedEntity.java b/api/src/main/java/com/destroystokyo/paper/entity/RangedEntity.java new file mode 100644 index 000000000..f2e3233a3 --- /dev/null +++ b/api/src/main/java/com/destroystokyo/paper/entity/RangedEntity.java @@ -0,0 +1,31 @@ +package com.destroystokyo.paper.entity; + +import org.bukkit.entity.LivingEntity; +import org.bukkit.entity.Mob; +import org.jetbrains.annotations.NotNull; + +public interface RangedEntity extends Mob { + /** + * Attack the specified entity using a ranged attack. + * + * @param target the entity to target + * @param charge How "charged" the attack is (how far back the bow was pulled for Bow attacks). + * This should be a value between 0 and 1, represented as targetDistance/maxDistance. + */ + void rangedAttack(@NotNull LivingEntity target, float charge); + + /** + * Sets that the Entity is "charging" up an attack, by raising its hands + * + * @param raiseHands Whether the entities hands are raised to charge attack + */ + void setChargingAttack(boolean raiseHands); + + /** + * Alias to {@link LivingEntity#isHandRaised()}, if the entity is charging an attack + * @return If entities hands are raised + */ + default boolean isChargingAttack() { + return isHandRaised(); + } +} diff --git a/api/src/main/java/com/destroystokyo/paper/entity/TargetEntityInfo.java b/api/src/main/java/com/destroystokyo/paper/entity/TargetEntityInfo.java new file mode 100644 index 000000000..f52644fab --- /dev/null +++ b/api/src/main/java/com/destroystokyo/paper/entity/TargetEntityInfo.java @@ -0,0 +1,38 @@ +package com.destroystokyo.paper.entity; + +import org.bukkit.entity.Entity; +import org.bukkit.util.Vector; +import org.jetbrains.annotations.NotNull; + +/** + * Represents information about a targeted entity + */ +public class TargetEntityInfo { + private final Entity entity; + private final Vector hitVec; + + public TargetEntityInfo(@NotNull Entity entity, @NotNull Vector hitVec) { + this.entity = entity; + this.hitVec = hitVec; + } + + /** + * Get the entity that is targeted + * + * @return Targeted entity + */ + @NotNull + public Entity getEntity() { + return entity; + } + + /** + * Get the position the entity is targeted at + * + * @return Targeted position + */ + @NotNull + public Vector getHitVector() { + return hitVec; + } +} diff --git a/api/src/main/java/com/destroystokyo/paper/event/block/AnvilDamagedEvent.java b/api/src/main/java/com/destroystokyo/paper/event/block/AnvilDamagedEvent.java new file mode 100644 index 000000000..a83c286c1 --- /dev/null +++ b/api/src/main/java/com/destroystokyo/paper/event/block/AnvilDamagedEvent.java @@ -0,0 +1,148 @@ +package com.destroystokyo.paper.event.block; + +import org.bukkit.Material; +import org.bukkit.block.data.BlockData; +import org.bukkit.event.Cancellable; +import org.bukkit.event.HandlerList; +import org.bukkit.event.inventory.InventoryEvent; +import org.bukkit.inventory.AnvilInventory; +import org.bukkit.inventory.InventoryView; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +/** + * Called when an anvil is damaged from being used + */ +public class AnvilDamagedEvent extends InventoryEvent implements Cancellable { + private static final HandlerList handlers = new HandlerList(); + private boolean cancel; + private DamageState damageState; + + public AnvilDamagedEvent(@NotNull InventoryView inventory, @NotNull BlockData blockData) { + super(inventory); + this.damageState = DamageState.getState(blockData); + } + + @NotNull + @Override + public AnvilInventory getInventory() { + return (AnvilInventory) super.getInventory(); + } + + /** + * Gets the new state of damage on the anvil + * + * @return Damage state + */ + @NotNull + public DamageState getDamageState() { + return damageState; + } + + /** + * Sets the new state of damage on the anvil + * + * @param damageState Damage state + */ + public void setDamageState(@NotNull DamageState damageState) { + this.damageState = damageState; + } + + /** + * Gets if anvil is breaking on this use + * + * @return True if breaking + */ + public boolean isBreaking() { + return damageState == DamageState.BROKEN; + } + + /** + * Sets if anvil is breaking on this use + * + * @param breaking True if breaking + */ + public void setBreaking(boolean breaking) { + if (breaking) { + damageState = DamageState.BROKEN; + } else if (damageState == DamageState.BROKEN) { + damageState = DamageState.DAMAGED; + } + } + + public boolean isCancelled() { + return cancel; + } + + public void setCancelled(boolean cancel) { + this.cancel = cancel; + } + + @NotNull + public HandlerList getHandlers() { + return handlers; + } + + @NotNull + public static HandlerList getHandlerList() { + return handlers; + } + + /** + * Represents the amount of damage on an anvil block + */ + public enum DamageState { + FULL(Material.ANVIL), + CHIPPED(Material.CHIPPED_ANVIL), + DAMAGED(Material.DAMAGED_ANVIL), + BROKEN(Material.AIR); + + private Material material; + + DamageState(@NotNull Material material) { + this.material = material; + } + + /** + * Get block material of this state + * + * @return Material + */ + @NotNull + public Material getMaterial() { + return material; + } + + /** + * Get damaged state by block data + * + * @param blockData Block data + * @return DamageState + * @throws IllegalArgumentException If non anvil block data is given + */ + @NotNull + public static DamageState getState(@Nullable BlockData blockData) { + return blockData == null ? BROKEN : getState(blockData.getMaterial()); + } + + /** + * Get damaged state by block material + * + * @param material Block material + * @return DamageState + * @throws IllegalArgumentException If non anvil material is given + */ + @NotNull + public static DamageState getState(@Nullable Material material) { + if (material == null) { + return BROKEN; + } + for (DamageState state : values()) { + if (state.material == material) { + return state; + } + } + throw new IllegalArgumentException("Material not an anvil"); + } + } +} diff --git a/api/src/main/java/com/destroystokyo/paper/event/block/BeaconEffectEvent.java b/api/src/main/java/com/destroystokyo/paper/event/block/BeaconEffectEvent.java new file mode 100644 index 000000000..978813b94 --- /dev/null +++ b/api/src/main/java/com/destroystokyo/paper/event/block/BeaconEffectEvent.java @@ -0,0 +1,86 @@ +package com.destroystokyo.paper.event.block; + +import org.bukkit.block.Block; +import org.bukkit.entity.Player; +import org.bukkit.event.Cancellable; +import org.bukkit.event.HandlerList; +import org.bukkit.event.block.BlockEvent; +import org.bukkit.potion.PotionEffect; +import org.jetbrains.annotations.NotNull; + +/** + * Called when a beacon effect is being applied to a player. + */ +public class BeaconEffectEvent extends BlockEvent implements Cancellable { + private static final HandlerList handlers = new HandlerList(); + private boolean cancelled; + private PotionEffect effect; + private Player player; + private boolean primary; + + public BeaconEffectEvent(@NotNull Block block, @NotNull PotionEffect effect, @NotNull Player player, boolean primary) { + super(block); + this.effect = effect; + this.player = player; + this.primary = primary; + } + + @Override + public boolean isCancelled() { + return cancelled; + } + + @Override + public void setCancelled(boolean cancelled) { + this.cancelled = cancelled; + } + + /** + * Gets the potion effect being applied. + * + * @return Potion effect + */ + @NotNull + public PotionEffect getEffect() { + return effect; + } + + /** + * Sets the potion effect that will be applied. + * + * @param effect Potion effect + */ + public void setEffect(@NotNull PotionEffect effect) { + this.effect = effect; + } + + /** + * Gets the player who the potion effect is being applied to. + * + * @return Affected player + */ + @NotNull + public Player getPlayer() { + return player; + } + + /** + * Gets whether the effect is a primary beacon effect. + * + * @return true if this event represents a primary effect + */ + public boolean isPrimary() { + return primary; + } + + @NotNull + @Override + public HandlerList getHandlers() { + return handlers; + } + + @NotNull + public static HandlerList getHandlerList() { + return handlers; + } +} diff --git a/api/src/main/java/com/destroystokyo/paper/event/block/BlockDestroyEvent.java b/api/src/main/java/com/destroystokyo/paper/event/block/BlockDestroyEvent.java new file mode 100644 index 000000000..3aee12f1c --- /dev/null +++ b/api/src/main/java/com/destroystokyo/paper/event/block/BlockDestroyEvent.java @@ -0,0 +1,92 @@ +package com.destroystokyo.paper.event.block; + +import org.bukkit.block.Block; +import org.bukkit.block.data.BlockData; +import org.bukkit.event.Cancellable; +import org.bukkit.event.HandlerList; +import org.bukkit.event.block.BlockEvent; +import org.jetbrains.annotations.NotNull; + +/** + * Fired anytime the server intends to 'destroy' a block through some triggering reason. + * This does not fire anytime a block is set to air, but only with more direct triggers such + * as physics updates, pistons, Entities changing blocks, commands set to "Destroy". + * + * This event is associated with the game playing a sound effect at the block in question, when + * something can be described as "intend to destroy what is there", + * + * Events such as leaves decaying, pistons retracting (where the block is moving), does NOT fire this event. + * + */ +public class BlockDestroyEvent extends BlockEvent implements Cancellable { + + private static final HandlerList handlers = new HandlerList(); + + @NotNull private final BlockData newState; + private final boolean willDrop; + private boolean playEffect; + + private boolean cancelled = false; + + public BlockDestroyEvent(@NotNull Block block, @NotNull BlockData newState, boolean willDrop) { + super(block); + this.newState = newState; + this.willDrop = willDrop; + } + + /** + * @return The new state of this block (Air, or a Fluid type) + */ + @NotNull + public BlockData getNewState() { + return newState; + } + + /** + * @return If the server is going to drop the block in question with this destroy event + */ + public boolean willDrop() { + return this.willDrop; + } + + /** + * @return If the server is going to play the sound effect for this destruction + */ + public boolean playEffect() { + return this.playEffect; + } + + /** + * @param playEffect If the server should play the sound effect for this destruction + */ + public void setPlayEffect(boolean playEffect) { + this.playEffect = playEffect; + } + + /** + * @return If the event is cancelled, meaning the block will not be destroyed + */ + @Override + public boolean isCancelled() { + return cancelled; + } + + /** + * If the event is cancelled, the block will remain in its previous state. + * @param cancel true if you wish to cancel this event + */ + @Override + public void setCancelled(boolean cancel) { + cancelled = cancel; + } + + @NotNull + public HandlerList getHandlers() { + return handlers; + } + + @NotNull + public static HandlerList getHandlerList() { + return handlers; + } +} diff --git a/api/src/main/java/com/destroystokyo/paper/event/block/TNTPrimeEvent.java b/api/src/main/java/com/destroystokyo/paper/event/block/TNTPrimeEvent.java new file mode 100644 index 000000000..73dabb82c --- /dev/null +++ b/api/src/main/java/com/destroystokyo/paper/event/block/TNTPrimeEvent.java @@ -0,0 +1,114 @@ +package com.destroystokyo.paper.event.block; + +import org.bukkit.block.Block; +import org.bukkit.entity.Entity; +import org.bukkit.event.Cancellable; +import org.bukkit.event.HandlerList; +import org.bukkit.event.block.BlockEvent; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +/** + * Called when TNT block is about to turn into {@link org.bukkit.entity.TNTPrimed} + *

+ * Cancelling it won't turn TNT into {@link org.bukkit.entity.TNTPrimed} and leaves + * the TNT block as-is + * + * @author Mark Vainomaa + */ +public class TNTPrimeEvent extends BlockEvent implements Cancellable { + private static final HandlerList handlers = new HandlerList(); + private boolean cancelled; + @NotNull private PrimeReason reason; + @Nullable private Entity primerEntity; + + public TNTPrimeEvent(@NotNull Block theBlock, @NotNull PrimeReason reason, @Nullable Entity primerEntity) { + super(theBlock); + this.reason = reason; + this.primerEntity = primerEntity; + } + + /** + * Gets the TNT prime reason + * + * @return Prime reason + */ + @NotNull + public PrimeReason getReason() { + return this.reason; + } + + /** + * Gets the TNT primer {@link Entity}. + * + * It's null if {@link #getReason()} is {@link PrimeReason#REDSTONE} or {@link PrimeReason#FIRE}. + * It's not null if {@link #getReason()} is {@link PrimeReason#ITEM} or {@link PrimeReason#PROJECTILE} + * It might be null if {@link #getReason()} is {@link PrimeReason#EXPLOSION} + * + * @return The {@link Entity} who primed the TNT + */ + @Nullable + public Entity getPrimerEntity() { + return this.primerEntity; + } + + /** + * Gets whether spawning {@link org.bukkit.entity.TNTPrimed} should be cancelled or not + * + * @return Whether spawning {@link org.bukkit.entity.TNTPrimed} should be cancelled or not + */ + @Override + public boolean isCancelled() { + return this.cancelled; + } + + /** + * Sets whether to cancel spawning {@link org.bukkit.entity.TNTPrimed} or not + * + * @param cancel whether spawning {@link org.bukkit.entity.TNTPrimed} should be cancelled or not + */ + @Override + public void setCancelled(boolean cancel) { + this.cancelled = cancel; + } + + @Nullable + @Override + public HandlerList getHandlers() { + return handlers; + } + + @Nullable + public static HandlerList getHandlerList() { + return handlers; + } + + public enum PrimeReason { + /** + * When TNT prime was caused by other explosion (chain reaction) + */ + EXPLOSION, + + /** + * When TNT prime was caused by fire + */ + FIRE, + + /** + * When {@link org.bukkit.entity.Player} used {@link org.bukkit.Material#FLINT_AND_STEEL} or + * {@link org.bukkit.Material#FIRE_CHARGE} on given TNT block + */ + ITEM, + + /** + * When TNT prime was caused by an {@link Entity} shooting TNT + * using a bow with {@link org.bukkit.enchantments.Enchantment#ARROW_FIRE} enchantment + */ + PROJECTILE, + + /** + * When redstone power triggered the TNT prime + */ + REDSTONE + } +} diff --git a/api/src/main/java/com/destroystokyo/paper/event/entity/CreeperIgniteEvent.java b/api/src/main/java/com/destroystokyo/paper/event/entity/CreeperIgniteEvent.java new file mode 100644 index 000000000..ff10251b6 --- /dev/null +++ b/api/src/main/java/com/destroystokyo/paper/event/entity/CreeperIgniteEvent.java @@ -0,0 +1,54 @@ +package com.destroystokyo.paper.event.entity; + +import org.bukkit.entity.Creeper; +import org.bukkit.event.Cancellable; +import org.bukkit.event.HandlerList; +import org.bukkit.event.entity.EntityEvent; +import org.jetbrains.annotations.NotNull; + +/** + * Called when a Creeper is ignite flag is changed (armed/disarmed to explode). + */ +public class CreeperIgniteEvent extends EntityEvent implements Cancellable { + private static final HandlerList handlers = new HandlerList(); + private boolean canceled; + private boolean ignited; + + public CreeperIgniteEvent(@NotNull Creeper creeper, boolean ignited) { + super(creeper); + this.ignited = ignited; + } + + @NotNull + @Override + public Creeper getEntity() { + return (Creeper) entity; + } + + public boolean isIgnited() { + return ignited; + } + + public void setIgnited(boolean ignited) { + this.ignited = ignited; + } + + public boolean isCancelled() { + return canceled; + } + + public void setCancelled(boolean cancel) { + canceled = cancel; + } + + @NotNull + @Override + public HandlerList getHandlers() { + return handlers; + } + + @NotNull + public static HandlerList getHandlerList() { + return handlers; + } +} diff --git a/api/src/main/java/com/destroystokyo/paper/event/entity/EnderDragonFireballHitEvent.java b/api/src/main/java/com/destroystokyo/paper/event/entity/EnderDragonFireballHitEvent.java new file mode 100644 index 000000000..118c7b677 --- /dev/null +++ b/api/src/main/java/com/destroystokyo/paper/event/entity/EnderDragonFireballHitEvent.java @@ -0,0 +1,79 @@ +package com.destroystokyo.paper.event.entity; + +import org.bukkit.entity.AreaEffectCloud; +import org.bukkit.entity.DragonFireball; +import org.bukkit.entity.LivingEntity; +import org.bukkit.event.Cancellable; +import org.bukkit.event.HandlerList; +import org.bukkit.event.entity.EntityEvent; + +import java.util.Collection; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +/** + * Fired when a DragonFireball collides with a block/entity and spawns an AreaEffectCloud + */ +public class EnderDragonFireballHitEvent extends EntityEvent implements Cancellable { + @Nullable private final Collection targets; + @NotNull private final AreaEffectCloud areaEffectCloud; + + public EnderDragonFireballHitEvent(@NotNull DragonFireball fireball, @Nullable Collection targets, @NotNull AreaEffectCloud areaEffectCloud) { + super(fireball); + this.targets = targets; + this.areaEffectCloud = areaEffectCloud; + } + + /** + * The fireball involved in this event + */ + @NotNull + @Override + public DragonFireball getEntity() { + return (DragonFireball) super.getEntity(); + } + + /** + * The living entities hit by fireball + * + * May be null if no entities were hit + * + * @return the targets + */ + @Nullable + public Collection getTargets() { + return targets; + } + + /** + * @return The area effect cloud spawned in this collision + */ + @NotNull + public AreaEffectCloud getAreaEffectCloud() { + return areaEffectCloud; + } + + private static final HandlerList handlers = new HandlerList(); + + @NotNull + public HandlerList getHandlers() { + return handlers; + } + + @NotNull + public static HandlerList getHandlerList() { + return handlers; + } + + private boolean cancelled = false; + + @Override + public boolean isCancelled() { + return cancelled; + } + + @Override + public void setCancelled(boolean cancel) { + cancelled = cancel; + } +} diff --git a/api/src/main/java/com/destroystokyo/paper/event/entity/EnderDragonFlameEvent.java b/api/src/main/java/com/destroystokyo/paper/event/entity/EnderDragonFlameEvent.java new file mode 100644 index 000000000..1915177f4 --- /dev/null +++ b/api/src/main/java/com/destroystokyo/paper/event/entity/EnderDragonFlameEvent.java @@ -0,0 +1,61 @@ +package com.destroystokyo.paper.event.entity; + +import org.bukkit.entity.AreaEffectCloud; +import org.bukkit.entity.EnderDragon; +import org.bukkit.event.Cancellable; +import org.bukkit.event.HandlerList; +import org.bukkit.event.entity.EntityEvent; +import org.jetbrains.annotations.NotNull; + +/** + * Fired when an EnderDragon spawns an AreaEffectCloud by shooting flames + */ +public class EnderDragonFlameEvent extends EntityEvent implements Cancellable { + @NotNull private final AreaEffectCloud areaEffectCloud; + + public EnderDragonFlameEvent(@NotNull EnderDragon enderDragon, @NotNull AreaEffectCloud areaEffectCloud) { + super(enderDragon); + this.areaEffectCloud = areaEffectCloud; + } + + /** + * The enderdragon involved in this event + */ + @NotNull + @Override + public EnderDragon getEntity() { + return (EnderDragon) super.getEntity(); + } + + /** + * @return The area effect cloud spawned in this collision + */ + @NotNull + public AreaEffectCloud getAreaEffectCloud() { + return areaEffectCloud; + } + + private static final HandlerList handlers = new HandlerList(); + + @NotNull + public HandlerList getHandlers() { + return handlers; + } + + @NotNull + public static HandlerList getHandlerList() { + return handlers; + } + + private boolean cancelled = false; + + @Override + public boolean isCancelled() { + return cancelled; + } + + @Override + public void setCancelled(boolean cancel) { + cancelled = cancel; + } +} diff --git a/api/src/main/java/com/destroystokyo/paper/event/entity/EnderDragonShootFireballEvent.java b/api/src/main/java/com/destroystokyo/paper/event/entity/EnderDragonShootFireballEvent.java new file mode 100644 index 000000000..8414bd805 --- /dev/null +++ b/api/src/main/java/com/destroystokyo/paper/event/entity/EnderDragonShootFireballEvent.java @@ -0,0 +1,61 @@ +package com.destroystokyo.paper.event.entity; + +import org.bukkit.entity.DragonFireball; +import org.bukkit.entity.EnderDragon; +import org.bukkit.event.Cancellable; +import org.bukkit.event.HandlerList; +import org.bukkit.event.entity.EntityEvent; +import org.jetbrains.annotations.NotNull; + +/** + * Fired when an EnderDragon shoots a fireball + */ +public class EnderDragonShootFireballEvent extends EntityEvent implements Cancellable { + @NotNull private final DragonFireball fireball; + + public EnderDragonShootFireballEvent(@NotNull EnderDragon entity, @NotNull DragonFireball fireball) { + super(entity); + this.fireball = fireball; + } + + /** + * The enderdragon shooting the fireball + */ + @NotNull + @Override + public EnderDragon getEntity() { + return (EnderDragon) super.getEntity(); + } + + /** + * @return The fireball being shot + */ + @NotNull + public DragonFireball getFireball() { + return fireball; + } + + private static final HandlerList handlers = new HandlerList(); + + @NotNull + public HandlerList getHandlers() { + return handlers; + } + + @NotNull + public static HandlerList getHandlerList() { + return handlers; + } + + private boolean cancelled = false; + + @Override + public boolean isCancelled() { + return cancelled; + } + + @Override + public void setCancelled(boolean cancel) { + cancelled = cancel; + } +} diff --git a/api/src/main/java/com/destroystokyo/paper/event/entity/EndermanAttackPlayerEvent.java b/api/src/main/java/com/destroystokyo/paper/event/entity/EndermanAttackPlayerEvent.java new file mode 100644 index 000000000..f530a3d93 --- /dev/null +++ b/api/src/main/java/com/destroystokyo/paper/event/entity/EndermanAttackPlayerEvent.java @@ -0,0 +1,101 @@ +/* + * Copyright (c) 2018 Daniel Ennis (Aikar) MIT License + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +package com.destroystokyo.paper.event.entity; + +import org.bukkit.entity.Enderman; +import org.bukkit.entity.Entity; +import org.bukkit.entity.Player; +import org.bukkit.event.Cancellable; +import org.bukkit.event.Event; +import org.bukkit.event.HandlerList; +import org.bukkit.event.entity.EntityEvent; +import org.jetbrains.annotations.NotNull; + +/** + * Fired when an Enderman determines if it should attack a player or not. + * Starts off cancelled if the player is wearing a pumpkin head or is not looking + * at the Enderman, according to Vanilla rules. + * + */ +public class EndermanAttackPlayerEvent extends EntityEvent implements Cancellable { + @NotNull private final Player player; + + public EndermanAttackPlayerEvent(@NotNull Enderman entity, @NotNull Player player) { + super(entity); + this.player = player; + } + + /** + * The enderman considering attacking + * + * @return The enderman considering attacking + */ + @NotNull + @Override + public Enderman getEntity() { + return (Enderman) super.getEntity(); + } + + /** + * The player the Enderman is considering attacking + * + * @return The player the Enderman is considering attacking + */ + @NotNull + public Player getPlayer() { + return player; + } + + private static final HandlerList handlers = new HandlerList(); + + @NotNull + public HandlerList getHandlers() { + return handlers; + } + + @NotNull + public static HandlerList getHandlerList() { + return handlers; + } + + private boolean cancelled = false; + + /** + * + * @return If cancelled, the enderman will not attack + */ + @Override + public boolean isCancelled() { + return cancelled; + } + + /** + * Cancels if the Enderman will attack this player + * @param cancel true if you wish to cancel this event + */ + @Override + public void setCancelled(boolean cancel) { + cancelled = cancel; + } +} diff --git a/api/src/main/java/com/destroystokyo/paper/event/entity/EndermanEscapeEvent.java b/api/src/main/java/com/destroystokyo/paper/event/entity/EndermanEscapeEvent.java new file mode 100644 index 000000000..806112a8b --- /dev/null +++ b/api/src/main/java/com/destroystokyo/paper/event/entity/EndermanEscapeEvent.java @@ -0,0 +1,87 @@ +package com.destroystokyo.paper.event.entity; + +import org.bukkit.entity.Enderman; +import org.bukkit.entity.Entity; +import org.bukkit.event.Cancellable; +import org.bukkit.event.Event; +import org.bukkit.event.HandlerList; +import org.bukkit.event.entity.EntityEvent; +import org.jetbrains.annotations.NotNull; + +public class EndermanEscapeEvent extends EntityEvent implements Cancellable { + @NotNull private final Reason reason; + + public EndermanEscapeEvent(@NotNull Enderman entity, @NotNull Reason reason) { + super(entity); + this.reason = reason; + } + + @NotNull + @Override + public Enderman getEntity() { + return (Enderman) super.getEntity(); + } + + /** + * @return The reason the enderman is trying to escape + */ + @NotNull + public Reason getReason() { + return reason; + } + + private static final HandlerList handlers = new HandlerList(); + + @NotNull + public HandlerList getHandlers() { + return handlers; + } + + @NotNull + public static HandlerList getHandlerList() { + return handlers; + } + + private boolean cancelled = false; + + @Override + public boolean isCancelled() { + return cancelled; + } + + /** + * Cancels the escape. + * + * If this escape normally would of resulted in damage avoidance such as indirect, + * the enderman will now take damage. + * + * @param cancel true if you wish to cancel this event + */ + @Override + public void setCancelled(boolean cancel) { + cancelled = cancel; + } + + public enum Reason { + /** + * The enderman has stopped attacking and ran away + */ + RUNAWAY, + /** + * The enderman has teleported away due to indirect damage (ranged) + */ + INDIRECT, + /** + * The enderman has teleported away due to a critical hit + */ + CRITICAL_HIT, + /** + * The enderman has teleported away due to the player staring at it during combat + */ + STARE, + /** + * Specific case for CRITICAL_HIT where the enderman is taking rain damage + */ + DROWN + } +} diff --git a/api/src/main/java/com/destroystokyo/paper/event/entity/EntityAddToWorldEvent.java b/api/src/main/java/com/destroystokyo/paper/event/entity/EntityAddToWorldEvent.java new file mode 100644 index 000000000..07660202e --- /dev/null +++ b/api/src/main/java/com/destroystokyo/paper/event/entity/EntityAddToWorldEvent.java @@ -0,0 +1,32 @@ +package com.destroystokyo.paper.event.entity; + +import org.bukkit.entity.Entity; +import org.bukkit.event.Event; +import org.bukkit.event.HandlerList; +import org.bukkit.event.entity.EntityEvent; +import org.jetbrains.annotations.NotNull; + +/** + * Fired any time an entity is being added to the world for any reason. + * + * Not to be confused with {@link org.bukkit.event.entity.CreatureSpawnEvent} + * This will fire anytime a chunk is reloaded too. + */ +public class EntityAddToWorldEvent extends EntityEvent { + + public EntityAddToWorldEvent(@NotNull Entity entity) { + super(entity); + } + + private static final HandlerList handlers = new HandlerList(); + + @NotNull + public HandlerList getHandlers() { + return handlers; + } + + @NotNull + public static HandlerList getHandlerList() { + return handlers; + } +} diff --git a/api/src/main/java/com/destroystokyo/paper/event/entity/EntityKnockbackByEntityEvent.java b/api/src/main/java/com/destroystokyo/paper/event/entity/EntityKnockbackByEntityEvent.java new file mode 100644 index 000000000..9efecabab --- /dev/null +++ b/api/src/main/java/com/destroystokyo/paper/event/entity/EntityKnockbackByEntityEvent.java @@ -0,0 +1,82 @@ +package com.destroystokyo.paper.event.entity; + +import org.bukkit.entity.Entity; +import org.bukkit.entity.LivingEntity; +import org.bukkit.event.Cancellable; +import org.bukkit.event.HandlerList; +import org.bukkit.event.entity.EntityEvent; +import org.bukkit.util.Vector; +import org.jetbrains.annotations.NotNull; + +/** + * Fired when an Entity is knocked back by the hit of another Entity. The acceleration + * vector can be modified. If this event is cancelled, the entity is not knocked back. + * + */ +public class EntityKnockbackByEntityEvent extends EntityEvent implements Cancellable { + private static final HandlerList handlers = new HandlerList(); + + @NotNull private final Entity hitBy; + private final float knockbackStrength; + @NotNull private final Vector acceleration; + private boolean cancelled = false; + + public EntityKnockbackByEntityEvent(@NotNull LivingEntity entity, @NotNull Entity hitBy, float knockbackStrength, @NotNull Vector acceleration) { + super(entity); + this.hitBy = hitBy; + this.knockbackStrength = knockbackStrength; + this.acceleration = acceleration; + } + + @NotNull + public HandlerList getHandlers() { + return handlers; + } + + @NotNull + public static HandlerList getHandlerList() { + return handlers; + } + + @Override + public boolean isCancelled() { + return cancelled; + } + + @Override + public void setCancelled(boolean cancel) { + cancelled = cancel; + } + + /** + * @return the entity which was knocked back + */ + @NotNull + @Override + public LivingEntity getEntity() { + return (LivingEntity) super.getEntity(); + } + + /** + * @return the original knockback strength. + */ + public float getKnockbackStrength() { + return knockbackStrength; + } + + /** + * @return the Entity which hit + */ + @NotNull + public Entity getHitBy() { + return hitBy; + } + + /** + * @return the acceleration that will be applied + */ + @NotNull + public Vector getAcceleration() { + return acceleration; + } +} diff --git a/api/src/main/java/com/destroystokyo/paper/event/entity/EntityPathfindEvent.java b/api/src/main/java/com/destroystokyo/paper/event/entity/EntityPathfindEvent.java new file mode 100644 index 000000000..63e46b2fb --- /dev/null +++ b/api/src/main/java/com/destroystokyo/paper/event/entity/EntityPathfindEvent.java @@ -0,0 +1,82 @@ +package com.destroystokyo.paper.event.entity; + +import org.bukkit.Location; +import org.bukkit.entity.Entity; +import org.bukkit.event.Cancellable; +import org.bukkit.event.HandlerList; +import org.bukkit.event.entity.EntityEvent; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +/** + * Fired when an Entity decides to start moving towards a location. + * + * This event does not fire for the entities actual movement. Only when it + * is choosing to start moving to a location. + */ +public class EntityPathfindEvent extends EntityEvent implements Cancellable { + @Nullable private final Entity targetEntity; + @NotNull private final Location loc; + + public EntityPathfindEvent(@NotNull Entity entity, @NotNull Location loc, @Nullable Entity targetEntity) { + super(entity); + this.targetEntity = targetEntity; + this.loc = loc; + } + + /** + * The Entity that is pathfinding. + * @return The Entity that is pathfinding. + */ + @NotNull + public Entity getEntity() { + return entity; + } + + /** + * If the Entity is trying to pathfind to an entity, this is the entity in relation. + * + * Otherwise this will return null. + * + * @return The entity target or null + */ + @Nullable + public Entity getTargetEntity() { + return targetEntity; + } + + /** + * The Location of where the entity is about to move to. + * + * Note that if the target happened to of been an entity + * @return Location of where the entity is trying to pathfind to. + */ + @NotNull + public Location getLoc() { + return loc; + } + + private static final HandlerList handlers = new HandlerList(); + + @NotNull + public HandlerList getHandlers() { + return handlers; + } + + @NotNull + public static HandlerList getHandlerList() { + return handlers; + } + + private boolean cancelled = false; + + @Override + public boolean isCancelled() { + return cancelled; + } + + @Override + public void setCancelled(boolean cancel) { + cancelled = cancel; + } +} diff --git a/api/src/main/java/com/destroystokyo/paper/event/entity/EntityRemoveFromWorldEvent.java b/api/src/main/java/com/destroystokyo/paper/event/entity/EntityRemoveFromWorldEvent.java new file mode 100644 index 000000000..e5dbbd660 --- /dev/null +++ b/api/src/main/java/com/destroystokyo/paper/event/entity/EntityRemoveFromWorldEvent.java @@ -0,0 +1,29 @@ +package com.destroystokyo.paper.event.entity; + +import org.bukkit.entity.Entity; +import org.bukkit.event.Event; +import org.bukkit.event.HandlerList; +import org.bukkit.event.entity.EntityEvent; +import org.jetbrains.annotations.NotNull; + +/** + * Fired any time an entity is being removed from a world for any reason + */ +public class EntityRemoveFromWorldEvent extends EntityEvent { + + public EntityRemoveFromWorldEvent(@NotNull Entity entity) { + super(entity); + } + + private static final HandlerList handlers = new HandlerList(); + + @NotNull + public HandlerList getHandlers() { + return handlers; + } + + @NotNull + public static HandlerList getHandlerList() { + return handlers; + } +} diff --git a/api/src/main/java/com/destroystokyo/paper/event/entity/EntityTeleportEndGatewayEvent.java b/api/src/main/java/com/destroystokyo/paper/event/entity/EntityTeleportEndGatewayEvent.java new file mode 100644 index 000000000..bfc69a43c --- /dev/null +++ b/api/src/main/java/com/destroystokyo/paper/event/entity/EntityTeleportEndGatewayEvent.java @@ -0,0 +1,31 @@ +package com.destroystokyo.paper.event.entity; + +import org.bukkit.Location; +import org.bukkit.block.EndGateway; +import org.bukkit.entity.Entity; +import org.bukkit.event.entity.EntityTeleportEvent; +import org.jetbrains.annotations.NotNull; + +/** + * Fired any time an entity attempts to teleport in an end gateway + */ +public class EntityTeleportEndGatewayEvent extends EntityTeleportEvent { + + @NotNull private final EndGateway gateway; + + public EntityTeleportEndGatewayEvent(@NotNull Entity what, @NotNull Location from, @NotNull Location to, @NotNull EndGateway gateway) { + super(what, from, to); + this.gateway = gateway; + } + + /** + * The gateway triggering the teleport + * + * @return EndGateway used + */ + @NotNull + public EndGateway getGateway() { + return gateway; + } + +} diff --git a/api/src/main/java/com/destroystokyo/paper/event/entity/EntityTransformedEvent.java b/api/src/main/java/com/destroystokyo/paper/event/entity/EntityTransformedEvent.java new file mode 100644 index 000000000..12194f1fc --- /dev/null +++ b/api/src/main/java/com/destroystokyo/paper/event/entity/EntityTransformedEvent.java @@ -0,0 +1,92 @@ +package com.destroystokyo.paper.event.entity; + + +import org.bukkit.entity.Entity; +import org.bukkit.event.Cancellable; +import org.bukkit.event.HandlerList; +import org.bukkit.event.entity.EntityEvent; +import org.bukkit.event.entity.EntityTransformEvent; + +/** + * Fired when an entity transforms into another entity + *

+ * If the event is cancelled, the entity will not transform + * @deprecated Bukkit has added {@link EntityTransformEvent}, you should start using that + */ +@Deprecated +public class EntityTransformedEvent extends EntityEvent implements Cancellable { + private static final HandlerList handlers = new HandlerList(); + private boolean cancelled; + private final Entity transformed; + private final TransformedReason reason; + + public EntityTransformedEvent(Entity entity, Entity transformed, TransformedReason reason) { + super(entity); + this.transformed = transformed; + this.reason = reason; + } + + /** + * The entity after it has transformed + * + * @return Transformed entity + * @deprecated see {@link EntityTransformEvent#getTransformedEntity()} + */ + @Deprecated + public Entity getTransformed() { + return transformed; + } + + /** + * @return The reason for the transformation + * @deprecated see {@link EntityTransformEvent#getTransformReason()} + */ + @Deprecated + public TransformedReason getReason() { + return reason; + } + + + @Override + public HandlerList getHandlers(){ + return handlers; + } + + public static HandlerList getHandlerList(){ + return handlers; + } + + @Override + public boolean isCancelled(){ + return cancelled; + } + + @Override + public void setCancelled(boolean cancel){ + cancelled = cancel; + } + + public enum TransformedReason { + /** + * When a zombie drowns + */ + DROWNED, + /** + * When a zombie villager is cured + */ + CURED, + /** + * When a villager turns to a zombie villager + */ + INFECTED, + /** + * When a mooshroom turns to a cow + */ + SHEARED, + /** + * When a pig turns to a zombiepigman + */ + LIGHTNING + + } +} diff --git a/api/src/main/java/com/destroystokyo/paper/event/entity/EntityZapEvent.java b/api/src/main/java/com/destroystokyo/paper/event/entity/EntityZapEvent.java new file mode 100644 index 000000000..3b725a489 --- /dev/null +++ b/api/src/main/java/com/destroystokyo/paper/event/entity/EntityZapEvent.java @@ -0,0 +1,65 @@ +package com.destroystokyo.paper.event.entity; + +import org.apache.commons.lang.Validate; +import org.bukkit.entity.Entity; +import org.bukkit.entity.LightningStrike; +import org.bukkit.event.Cancellable; +import org.bukkit.event.HandlerList; +import org.bukkit.event.entity.EntityEvent; +import org.bukkit.event.entity.EntityTransformEvent; + +import java.util.Collections; +import org.jetbrains.annotations.NotNull; + +/** + * Fired when lightning strikes an entity + */ +public class EntityZapEvent extends EntityTransformEvent implements Cancellable { + private static final HandlerList handlers = new HandlerList(); + private boolean cancelled; + @NotNull private final LightningStrike bolt; + + public EntityZapEvent(@NotNull final Entity entity, @NotNull final LightningStrike bolt, @NotNull final Entity replacementEntity) { + super(entity, Collections.singletonList(replacementEntity), TransformReason.LIGHTNING); + Validate.notNull(bolt); + Validate.notNull(replacementEntity); + this.bolt = bolt; + } + + public boolean isCancelled() { + return cancelled; + } + + public void setCancelled(boolean cancel) { + this.cancelled = cancel; + } + + /** + * Gets the lightning bolt that is striking the entity. + * @return The lightning bolt responsible for this event + */ + @NotNull + public LightningStrike getBolt() { + return bolt; + } + + /** + * Gets the entity that will replace the struck entity. + * @return The entity that will replace the struck entity + */ + @NotNull + public Entity getReplacementEntity() { + return getTransformedEntity(); + } + + @NotNull + @Override + public HandlerList getHandlers() { + return handlers; + } + + @NotNull + public static HandlerList getHandlerList() { + return handlers; + } +} diff --git a/api/src/main/java/com/destroystokyo/paper/event/entity/ExperienceOrbMergeEvent.java b/api/src/main/java/com/destroystokyo/paper/event/entity/ExperienceOrbMergeEvent.java new file mode 100644 index 000000000..0ce3e3977 --- /dev/null +++ b/api/src/main/java/com/destroystokyo/paper/event/entity/ExperienceOrbMergeEvent.java @@ -0,0 +1,87 @@ +/* + * Copyright (c) 2017 Daniel Ennis (Aikar) MIT License + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +package com.destroystokyo.paper.event.entity; + +import org.bukkit.entity.ExperienceOrb; +import org.bukkit.event.Cancellable; +import org.bukkit.event.HandlerList; +import org.bukkit.event.entity.EntityEvent; +import org.jetbrains.annotations.NotNull; + +/** + * Fired anytime the server is about to merge 2 experience orbs into one + */ +public class ExperienceOrbMergeEvent extends EntityEvent implements Cancellable { + @NotNull private final ExperienceOrb mergeTarget; + @NotNull private final ExperienceOrb mergeSource; + + public ExperienceOrbMergeEvent(@NotNull ExperienceOrb mergeTarget, @NotNull ExperienceOrb mergeSource) { + super(mergeTarget); + this.mergeTarget = mergeTarget; + this.mergeSource = mergeSource; + } + + /** + * @return The orb that will absorb the other experience orb + */ + @NotNull + public ExperienceOrb getMergeTarget() { + return mergeTarget; + } + + /** + * @return The orb that is subject to being removed and merged into the target orb + */ + @NotNull + public ExperienceOrb getMergeSource() { + return mergeSource; + } + + private static final HandlerList handlers = new HandlerList(); + + @NotNull + public HandlerList getHandlers() { + return handlers; + } + + @NotNull + public static HandlerList getHandlerList() { + return handlers; + } + + private boolean cancelled = false; + + @Override + public boolean isCancelled() { + return cancelled; + } + + /** + * @param cancel true if you wish to cancel this event, and prevent the orbs from merging + */ + @Override + public void setCancelled(boolean cancel) { + cancelled = cancel; + } +} diff --git a/api/src/main/java/com/destroystokyo/paper/event/entity/PhantomPreSpawnEvent.java b/api/src/main/java/com/destroystokyo/paper/event/entity/PhantomPreSpawnEvent.java new file mode 100644 index 000000000..9022f697a --- /dev/null +++ b/api/src/main/java/com/destroystokyo/paper/event/entity/PhantomPreSpawnEvent.java @@ -0,0 +1,31 @@ +package com.destroystokyo.paper.event.entity; + +import org.bukkit.Location; +import org.bukkit.entity.Entity; +import org.bukkit.entity.EntityType; +import org.bukkit.event.entity.CreatureSpawnEvent; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + + +/** + * Called when a phantom is spawned for an exhausted player + */ +public class PhantomPreSpawnEvent extends PreCreatureSpawnEvent { + @NotNull private final Entity entity; + + public PhantomPreSpawnEvent(@NotNull Location location, @NotNull Entity entity, @NotNull CreatureSpawnEvent.SpawnReason reason) { + super(location, EntityType.PHANTOM, reason); + this.entity = entity; + } + + /** + * Get the entity this phantom is spawning for + * + * @return Entity + */ + @Nullable + public Entity getSpawningEntity() { + return entity; + } +} diff --git a/api/src/main/java/com/destroystokyo/paper/event/entity/PlayerNaturallySpawnCreaturesEvent.java b/api/src/main/java/com/destroystokyo/paper/event/entity/PlayerNaturallySpawnCreaturesEvent.java new file mode 100644 index 000000000..112a0dbf5 --- /dev/null +++ b/api/src/main/java/com/destroystokyo/paper/event/entity/PlayerNaturallySpawnCreaturesEvent.java @@ -0,0 +1,64 @@ +package com.destroystokyo.paper.event.entity; + +import org.bukkit.entity.Player; +import org.bukkit.event.Cancellable; +import org.bukkit.event.Event; +import org.bukkit.event.HandlerList; +import org.bukkit.event.player.PlayerEvent; +import org.jetbrains.annotations.NotNull; + +/** + * Fired when the server is calculating what chunks to try to spawn monsters in every Monster Spawn Tick event + */ +public class PlayerNaturallySpawnCreaturesEvent extends PlayerEvent implements Cancellable { + private byte radius; + + public PlayerNaturallySpawnCreaturesEvent(@NotNull Player player, byte radius) { + super(player); + this.radius = radius; + } + + /** + * @return The radius of chunks around this player to be included in natural spawn selection + */ + public byte getSpawnRadius() { + return radius; + } + + /** + * @param radius The radius of chunks around this player to be included in natural spawn selection + */ + public void setSpawnRadius(byte radius) { + this.radius = radius; + } + + private static final HandlerList handlers = new HandlerList(); + + @NotNull + public HandlerList getHandlers() { + return handlers; + } + + @NotNull + public static HandlerList getHandlerList() { + return handlers; + } + + private boolean cancelled = false; + + /** + * @return If this players chunks will be excluded from natural spawns + */ + @Override + public boolean isCancelled() { + return cancelled; + } + + /** + * @param cancel true if you wish to cancel this event, and not include this players chunks for natural spawning + */ + @Override + public void setCancelled(boolean cancel) { + cancelled = cancel; + } +} diff --git a/api/src/main/java/com/destroystokyo/paper/event/entity/PreCreatureSpawnEvent.java b/api/src/main/java/com/destroystokyo/paper/event/entity/PreCreatureSpawnEvent.java new file mode 100644 index 000000000..d5edde9cd --- /dev/null +++ b/api/src/main/java/com/destroystokyo/paper/event/entity/PreCreatureSpawnEvent.java @@ -0,0 +1,104 @@ +package com.destroystokyo.paper.event.entity; + +import com.google.common.base.Preconditions; +import org.bukkit.Location; +import org.bukkit.entity.EntityType; +import org.bukkit.event.Cancellable; +import org.bukkit.event.Event; +import org.bukkit.event.HandlerList; +import org.bukkit.event.entity.CreatureSpawnEvent; +import org.jetbrains.annotations.NotNull; + +/** + * WARNING: This event only fires for a limited number of cases, and not for every case that CreatureSpawnEvent does. + * + * You should still listen to CreatureSpawnEvent as a backup, and only use this event as an "enhancement". + * The intent of this event is to improve server performance, so limited use cases. + * + * Currently: NATURAL and SPAWNER based reasons. Please submit a Pull Request for future additions. + * Also, Plugins that replace Entity Registrations with their own custom entities might not fire this event. + */ +public class PreCreatureSpawnEvent extends Event implements Cancellable { + @NotNull private final Location location; + @NotNull private final EntityType type; + @NotNull private final CreatureSpawnEvent.SpawnReason reason; + private boolean shouldAbortSpawn; + + public PreCreatureSpawnEvent(@NotNull Location location, @NotNull EntityType type, @NotNull CreatureSpawnEvent.SpawnReason reason) { + this.location = Preconditions.checkNotNull(location, "Location may not be null").clone(); + this.type = Preconditions.checkNotNull(type, "Type may not be null"); + this.reason = Preconditions.checkNotNull(reason, "Reason may not be null"); + } + + /** + * @return The location this creature is being spawned at + */ + @NotNull + public Location getSpawnLocation() { + return location; + } + + /** + * @return The type of creature being spawned + */ + @NotNull + public EntityType getType() { + return type; + } + + /** + * @return Reason this creature is spawning (ie, NATURAL vs SPAWNER) + */ + @NotNull + public CreatureSpawnEvent.SpawnReason getReason() { + return reason; + } + + /** + * @return If the spawn process should be aborted vs trying more attempts + */ + public boolean shouldAbortSpawn() { + return shouldAbortSpawn; + } + + /** + * Set this if you are more blanket blocking all types of these spawns, and wish to abort the spawn process from + * trying more attempts after this cancellation. + * + * @param shouldAbortSpawn Set if the spawn process should be aborted vs trying more attempts + */ + public void setShouldAbortSpawn(boolean shouldAbortSpawn) { + this.shouldAbortSpawn = shouldAbortSpawn; + } + + private static final HandlerList handlers = new HandlerList(); + + @NotNull + public HandlerList getHandlers() { + return handlers; + } + + @NotNull + public static HandlerList getHandlerList() { + return handlers; + } + + private boolean cancelled = false; + + /** + * @return If the spawn of this creature is cancelled or not + */ + @Override + public boolean isCancelled() { + return cancelled; + } + + /** + * Cancelling this event is more effecient than cancelling CreatureSpawnEvent + * @param cancel true if you wish to cancel this event, and abort the spawn of this creature + */ + @Override + public void setCancelled(boolean cancel) { + cancelled = cancel; + } +} diff --git a/api/src/main/java/com/destroystokyo/paper/event/entity/PreSpawnerSpawnEvent.java b/api/src/main/java/com/destroystokyo/paper/event/entity/PreSpawnerSpawnEvent.java new file mode 100644 index 000000000..48cff0635 --- /dev/null +++ b/api/src/main/java/com/destroystokyo/paper/event/entity/PreSpawnerSpawnEvent.java @@ -0,0 +1,29 @@ +package com.destroystokyo.paper.event.entity; + + +import com.google.common.base.Preconditions; +import org.bukkit.Location; +import org.bukkit.entity.EntityType; +import org.bukkit.event.entity.CreatureSpawnEvent; +import org.jetbrains.annotations.NotNull; + +/** + * Called before an entity is spawned into a world by a spawner. + * + * This only includes the spawner's location and not the full BlockState snapshot for performance reasons. + * If you really need it you have to get the spawner yourself. + */ + +public class PreSpawnerSpawnEvent extends PreCreatureSpawnEvent { + @NotNull private final Location spawnerLocation; + + public PreSpawnerSpawnEvent(@NotNull Location location, @NotNull EntityType type, @NotNull Location spawnerLocation) { + super(location, type, CreatureSpawnEvent.SpawnReason.SPAWNER); + this.spawnerLocation = Preconditions.checkNotNull(spawnerLocation, "Spawner location may not be null"); + } + + @NotNull + public Location getSpawnerLocation() { + return spawnerLocation; + } +} diff --git a/api/src/main/java/com/destroystokyo/paper/event/entity/ProjectileCollideEvent.java b/api/src/main/java/com/destroystokyo/paper/event/entity/ProjectileCollideEvent.java new file mode 100644 index 000000000..453663893 --- /dev/null +++ b/api/src/main/java/com/destroystokyo/paper/event/entity/ProjectileCollideEvent.java @@ -0,0 +1,67 @@ +package com.destroystokyo.paper.event.entity; + +import org.bukkit.entity.Entity; +import org.bukkit.entity.Projectile; +import org.bukkit.event.Cancellable; +import org.bukkit.event.HandlerList; +import org.bukkit.event.entity.EntityEvent; +import org.jetbrains.annotations.NotNull; + +/** + * Called when an projectile collides with an entity + *

+ * This event is called before {@link org.bukkit.event.entity.EntityDamageByEntityEvent}, and cancelling it will allow the projectile to continue flying + */ +public class ProjectileCollideEvent extends EntityEvent implements Cancellable { + @NotNull private final Entity collidedWith; + + /** + * Get the entity the projectile collided with + * + * @return the entity collided with + */ + @NotNull + public Entity getCollidedWith() { + return collidedWith; + } + + public ProjectileCollideEvent(@NotNull Projectile what, @NotNull Entity collidedWith) { + super(what); + this.collidedWith = collidedWith; + } + + /** + * Get the projectile that collided + * + * @return the projectile that collided + */ + @NotNull + public Projectile getEntity() { + return (Projectile) super.getEntity(); + } + + private static final HandlerList handlerList = new HandlerList(); + + @NotNull + public static HandlerList getHandlerList() { + return handlerList; + } + + @NotNull + @Override + public HandlerList getHandlers() { + return handlerList; + } + + private boolean cancelled = false; + + @Override + public boolean isCancelled() { + return cancelled; + } + + @Override + public void setCancelled(boolean cancel) { + this.cancelled = cancel; + } +} diff --git a/api/src/main/java/com/destroystokyo/paper/event/entity/SkeletonHorseTrapEvent.java b/api/src/main/java/com/destroystokyo/paper/event/entity/SkeletonHorseTrapEvent.java new file mode 100644 index 000000000..d79dbcd68 --- /dev/null +++ b/api/src/main/java/com/destroystokyo/paper/event/entity/SkeletonHorseTrapEvent.java @@ -0,0 +1,47 @@ +package com.destroystokyo.paper.event.entity; + +import org.bukkit.entity.SkeletonHorse; +import org.bukkit.event.Cancellable; +import org.bukkit.event.HandlerList; +import org.bukkit.event.entity.EntityEvent; +import org.jetbrains.annotations.NotNull; + +/** + * Event called when a player gets close to a skeleton horse and triggers the lightning trap + */ +public class SkeletonHorseTrapEvent extends EntityEvent implements Cancellable { + private static final HandlerList handlers = new HandlerList(); + private boolean cancelled; + + public SkeletonHorseTrapEvent(@NotNull SkeletonHorse horse) { + super(horse); + } + + @NotNull + @Override + public SkeletonHorse getEntity() { + return (SkeletonHorse) super.getEntity(); + } + + @Override + public boolean isCancelled() { + return cancelled; + } + + @Override + public void setCancelled(boolean cancel) { + this.cancelled = cancel; + } + + @NotNull + @Override + public HandlerList getHandlers() { + return handlers; + } + + @NotNull + public static HandlerList getHandlerList() { + return handlers; + } +} + diff --git a/api/src/main/java/com/destroystokyo/paper/event/entity/SlimeChangeDirectionEvent.java b/api/src/main/java/com/destroystokyo/paper/event/entity/SlimeChangeDirectionEvent.java new file mode 100644 index 000000000..2638bbd3e --- /dev/null +++ b/api/src/main/java/com/destroystokyo/paper/event/entity/SlimeChangeDirectionEvent.java @@ -0,0 +1,38 @@ +package com.destroystokyo.paper.event.entity; + +import org.bukkit.entity.Slime; +import org.bukkit.event.Cancellable; +import org.jetbrains.annotations.NotNull; + +/** + * Fired when a Slime decides to change it's facing direction. + *

+ * This event does not fire for the entity's actual movement. Only when it + * is choosing to change direction. + */ +public class SlimeChangeDirectionEvent extends SlimePathfindEvent implements Cancellable { + private float yaw; + + public SlimeChangeDirectionEvent(@NotNull Slime slime, float yaw) { + super(slime); + this.yaw = yaw; + } + + /** + * Get the new chosen yaw + * + * @return Chosen yaw + */ + public float getNewYaw() { + return yaw; + } + + /** + * Set the new chosen yaw + * + * @param yaw Chosen yaw + */ + public void setNewYaw(float yaw) { + this.yaw = yaw; + } +} diff --git a/api/src/main/java/com/destroystokyo/paper/event/entity/SlimePathfindEvent.java b/api/src/main/java/com/destroystokyo/paper/event/entity/SlimePathfindEvent.java new file mode 100644 index 000000000..14b67da10 --- /dev/null +++ b/api/src/main/java/com/destroystokyo/paper/event/entity/SlimePathfindEvent.java @@ -0,0 +1,53 @@ +package com.destroystokyo.paper.event.entity; + +import org.bukkit.entity.Slime; +import org.bukkit.event.Cancellable; +import org.bukkit.event.HandlerList; +import org.bukkit.event.entity.EntityEvent; +import org.jetbrains.annotations.NotNull; + +/** + * Fired when a Slime decides to start pathfinding. + *

+ * This event does not fire for the entity's actual movement. Only when it + * is choosing to start moving. + */ +public class SlimePathfindEvent extends EntityEvent implements Cancellable { + public SlimePathfindEvent(@NotNull Slime slime) { + super(slime); + } + + /** + * The Slime that is pathfinding. + * + * @return The Slime that is pathfinding. + */ + @NotNull + public Slime getEntity() { + return (Slime) entity; + } + + private static final HandlerList handlers = new HandlerList(); + + @NotNull + public HandlerList getHandlers() { + return handlers; + } + + @NotNull + public static HandlerList getHandlerList() { + return handlers; + } + + private boolean cancelled = false; + + @Override + public boolean isCancelled() { + return cancelled; + } + + @Override + public void setCancelled(boolean cancel) { + cancelled = cancel; + } +} diff --git a/api/src/main/java/com/destroystokyo/paper/event/entity/SlimeSwimEvent.java b/api/src/main/java/com/destroystokyo/paper/event/entity/SlimeSwimEvent.java new file mode 100644 index 000000000..c8dd49d11 --- /dev/null +++ b/api/src/main/java/com/destroystokyo/paper/event/entity/SlimeSwimEvent.java @@ -0,0 +1,17 @@ +package com.destroystokyo.paper.event.entity; + +import org.bukkit.entity.Slime; +import org.bukkit.event.Cancellable; +import org.jetbrains.annotations.NotNull; + +/** + * Fired when a Slime decides to start jumping while swimming in water/lava. + *

+ * This event does not fire for the entity's actual movement. Only when it + * is choosing to start jumping. + */ +public class SlimeSwimEvent extends SlimeWanderEvent implements Cancellable { + public SlimeSwimEvent(@NotNull Slime slime) { + super(slime); + } +} diff --git a/api/src/main/java/com/destroystokyo/paper/event/entity/SlimeTargetLivingEntityEvent.java b/api/src/main/java/com/destroystokyo/paper/event/entity/SlimeTargetLivingEntityEvent.java new file mode 100644 index 000000000..e9ba32799 --- /dev/null +++ b/api/src/main/java/com/destroystokyo/paper/event/entity/SlimeTargetLivingEntityEvent.java @@ -0,0 +1,31 @@ +package com.destroystokyo.paper.event.entity; + +import org.bukkit.entity.LivingEntity; +import org.bukkit.entity.Slime; +import org.bukkit.event.Cancellable; +import org.jetbrains.annotations.NotNull; + +/** + * Fired when a Slime decides to change direction to target a LivingEntity. + *

+ * This event does not fire for the entity's actual movement. Only when it + * is choosing to start moving. + */ +public class SlimeTargetLivingEntityEvent extends SlimePathfindEvent implements Cancellable { + @NotNull private final LivingEntity target; + + public SlimeTargetLivingEntityEvent(@NotNull Slime slime, @NotNull LivingEntity target) { + super(slime); + this.target = target; + } + + /** + * Get the targeted entity + * + * @return Targeted entity + */ + @NotNull + public LivingEntity getTarget() { + return target; + } +} diff --git a/api/src/main/java/com/destroystokyo/paper/event/entity/SlimeWanderEvent.java b/api/src/main/java/com/destroystokyo/paper/event/entity/SlimeWanderEvent.java new file mode 100644 index 000000000..4683a7237 --- /dev/null +++ b/api/src/main/java/com/destroystokyo/paper/event/entity/SlimeWanderEvent.java @@ -0,0 +1,17 @@ +package com.destroystokyo.paper.event.entity; + +import org.bukkit.entity.Slime; +import org.bukkit.event.Cancellable; +import org.jetbrains.annotations.NotNull; + +/** + * Fired when a Slime decides to start wandering. + *

+ * This event does not fire for the entity's actual movement. Only when it + * is choosing to start moving. + */ +public class SlimeWanderEvent extends SlimePathfindEvent implements Cancellable { + public SlimeWanderEvent(@NotNull Slime slime) { + super(slime); + } +} diff --git a/api/src/main/java/com/destroystokyo/paper/event/entity/TurtleGoHomeEvent.java b/api/src/main/java/com/destroystokyo/paper/event/entity/TurtleGoHomeEvent.java new file mode 100644 index 000000000..021356d15 --- /dev/null +++ b/api/src/main/java/com/destroystokyo/paper/event/entity/TurtleGoHomeEvent.java @@ -0,0 +1,49 @@ +package com.destroystokyo.paper.event.entity; + +import org.bukkit.entity.Turtle; +import org.bukkit.event.Cancellable; +import org.bukkit.event.HandlerList; +import org.bukkit.event.entity.EntityEvent; +import org.jetbrains.annotations.NotNull; + +/** + * Fired when a Turtle decides to go home + */ +public class TurtleGoHomeEvent extends EntityEvent implements Cancellable { + private static final HandlerList handlers = new HandlerList(); + private boolean cancelled = false; + + public TurtleGoHomeEvent(@NotNull Turtle turtle) { + super(turtle); + } + + /** + * The turtle going home + * + * @return The turtle + */ + @NotNull + public Turtle getEntity() { + return (Turtle) entity; + } + + @Override + public boolean isCancelled() { + return cancelled; + } + + @Override + public void setCancelled(boolean cancel) { + cancelled = cancel; + } + + @NotNull + public HandlerList getHandlers() { + return handlers; + } + + @NotNull + public static HandlerList getHandlerList() { + return handlers; + } +} diff --git a/api/src/main/java/com/destroystokyo/paper/event/entity/TurtleLayEggEvent.java b/api/src/main/java/com/destroystokyo/paper/event/entity/TurtleLayEggEvent.java new file mode 100644 index 000000000..a315c5185 --- /dev/null +++ b/api/src/main/java/com/destroystokyo/paper/event/entity/TurtleLayEggEvent.java @@ -0,0 +1,87 @@ +package com.destroystokyo.paper.event.entity; + +import org.bukkit.Location; +import org.bukkit.entity.Turtle; +import org.bukkit.event.Cancellable; +import org.bukkit.event.HandlerList; +import org.bukkit.event.entity.EntityEvent; +import org.jetbrains.annotations.NotNull; + +/** + * Fired when a Turtle lays eggs + */ +public class TurtleLayEggEvent extends EntityEvent implements Cancellable { + private static final HandlerList handlers = new HandlerList(); + private boolean cancelled = false; + @NotNull + private final Location location; + private int eggCount; + + public TurtleLayEggEvent(@NotNull Turtle turtle, @NotNull Location location, int eggCount) { + super(turtle); + this.location = location; + this.eggCount = eggCount; + } + + /** + * The turtle laying the eggs + * + * @return The turtle + */ + @NotNull + public Turtle getEntity() { + return (Turtle) entity; + } + + /** + * Get the location where the eggs are being laid + * + * @return Location of eggs + */ + @NotNull + public Location getLocation() { + return location; + } + + /** + * Get the number of eggs being laid + * + * @return Number of eggs + */ + public int getEggCount() { + return eggCount; + } + + /** + * Set the number of eggs being laid + * + * @param eggCount Number of eggs + */ + public void setEggCount(int eggCount) { + if (eggCount < 1) { + cancelled = true; + return; + } + eggCount = Math.min(eggCount, 4); + } + + @Override + public boolean isCancelled() { + return cancelled; + } + + @Override + public void setCancelled(boolean cancel) { + cancelled = cancel; + } + + @NotNull + public HandlerList getHandlers() { + return handlers; + } + + @NotNull + public static HandlerList getHandlerList() { + return handlers; + } +} diff --git a/api/src/main/java/com/destroystokyo/paper/event/entity/TurtleStartDiggingEvent.java b/api/src/main/java/com/destroystokyo/paper/event/entity/TurtleStartDiggingEvent.java new file mode 100644 index 000000000..abeb24fcc --- /dev/null +++ b/api/src/main/java/com/destroystokyo/paper/event/entity/TurtleStartDiggingEvent.java @@ -0,0 +1,62 @@ +package com.destroystokyo.paper.event.entity; + +import org.bukkit.Location; +import org.bukkit.entity.Turtle; +import org.bukkit.event.Cancellable; +import org.bukkit.event.HandlerList; +import org.bukkit.event.entity.EntityEvent; +import org.jetbrains.annotations.NotNull; + +/** + * Fired when a Turtle starts digging to lay eggs + */ +public class TurtleStartDiggingEvent extends EntityEvent implements Cancellable { + private static final HandlerList handlers = new HandlerList(); + private boolean cancelled = false; + @NotNull private final Location location; + + public TurtleStartDiggingEvent(@NotNull Turtle turtle, @NotNull Location location) { + super(turtle); + this.location = location; + } + + /** + * The turtle digging + * + * @return The turtle + */ + @NotNull + public Turtle getEntity() { + return (Turtle) entity; + } + + /** + * Get the location where the turtle is digging + * + * @return Location where digging + */ + @NotNull + public Location getLocation() { + return location; + } + + @Override + public boolean isCancelled() { + return cancelled; + } + + @Override + public void setCancelled(boolean cancel) { + cancelled = cancel; + } + + @NotNull + public HandlerList getHandlers() { + return handlers; + } + + @NotNull + public static HandlerList getHandlerList() { + return handlers; + } +} diff --git a/api/src/main/java/com/destroystokyo/paper/event/entity/WitchConsumePotionEvent.java b/api/src/main/java/com/destroystokyo/paper/event/entity/WitchConsumePotionEvent.java new file mode 100644 index 000000000..fbbace36d --- /dev/null +++ b/api/src/main/java/com/destroystokyo/paper/event/entity/WitchConsumePotionEvent.java @@ -0,0 +1,70 @@ +package com.destroystokyo.paper.event.entity; + +import org.bukkit.entity.Witch; +import org.bukkit.event.Cancellable; +import org.bukkit.event.HandlerList; +import org.bukkit.event.entity.EntityEvent; +import org.bukkit.inventory.ItemStack; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +/** + * Fired when a witch consumes the potion in their hand to buff themselves. + */ +public class WitchConsumePotionEvent extends EntityEvent implements Cancellable { + @Nullable private ItemStack potion; + + public WitchConsumePotionEvent(@NotNull Witch witch, @Nullable ItemStack potion) { + super(witch); + this.potion = potion; + } + + @NotNull + @Override + public Witch getEntity() { + return (Witch) super.getEntity(); + } + + /** + * @return the potion the witch will consume and have the effects applied. + */ + @Nullable + public ItemStack getPotion() { + return potion; + } + + /** + * Sets the potion to be consumed and applied to the witch. + * @param potion The potion + */ + public void setPotion(@Nullable ItemStack potion) { + this.potion = potion != null ? potion.clone() : null; + } + + private static final HandlerList handlers = new HandlerList(); + + @NotNull + public HandlerList getHandlers() { + return handlers; + } + + @NotNull + public static HandlerList getHandlerList() { + return handlers; + } + + private boolean cancelled = false; + + /** + * @return Event was cancelled or potion was null + */ + @Override + public boolean isCancelled() { + return cancelled || potion == null; + } + + @Override + public void setCancelled(boolean cancel) { + cancelled = cancel; + } +} diff --git a/api/src/main/java/com/destroystokyo/paper/event/entity/WitchReadyPotionEvent.java b/api/src/main/java/com/destroystokyo/paper/event/entity/WitchReadyPotionEvent.java new file mode 100644 index 000000000..5351b523d --- /dev/null +++ b/api/src/main/java/com/destroystokyo/paper/event/entity/WitchReadyPotionEvent.java @@ -0,0 +1,80 @@ +package com.destroystokyo.paper.event.entity; + +import org.bukkit.Material; +import org.bukkit.entity.Witch; +import org.bukkit.event.Cancellable; +import org.bukkit.event.HandlerList; +import org.bukkit.event.entity.EntityEvent; +import org.bukkit.inventory.ItemStack; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +public class WitchReadyPotionEvent extends EntityEvent implements Cancellable { + private ItemStack potion; + + public WitchReadyPotionEvent(@NotNull Witch witch, @Nullable ItemStack potion) { + super(witch); + this.potion = potion; + } + + /** + * Fires thee event, returning the desired potion, or air of cancelled + * @param witch the witch whom is readying to use a potion + * @param potion the potion to be used + * @return The ItemStack to be used + */ + @Nullable + public static ItemStack process(@NotNull Witch witch, @Nullable ItemStack potion) { + WitchReadyPotionEvent event = new WitchReadyPotionEvent(witch, potion); + if (!event.callEvent() || event.getPotion() == null) { + return new ItemStack(Material.AIR); + } + return event.getPotion(); + } + + @NotNull + @Override + public Witch getEntity() { + return (Witch) super.getEntity(); + } + + /** + * @return the potion the witch is readying to use + */ + @Nullable + public ItemStack getPotion() { + return potion; + } + + /** + * Sets the potion the which is going to hold and use + * @param potion The potion + */ + public void setPotion(@Nullable ItemStack potion) { + this.potion = potion != null ? potion.clone() : null; + } + + private static final HandlerList handlers = new HandlerList(); + + @NotNull + public HandlerList getHandlers() { + return handlers; + } + + @NotNull + public static HandlerList getHandlerList() { + return handlers; + } + + private boolean cancelled = false; + + @Override + public boolean isCancelled() { + return cancelled; + } + + @Override + public void setCancelled(boolean cancel) { + cancelled = cancel; + } +} diff --git a/api/src/main/java/com/destroystokyo/paper/event/entity/WitchThrowPotionEvent.java b/api/src/main/java/com/destroystokyo/paper/event/entity/WitchThrowPotionEvent.java new file mode 100644 index 000000000..688a596aa --- /dev/null +++ b/api/src/main/java/com/destroystokyo/paper/event/entity/WitchThrowPotionEvent.java @@ -0,0 +1,81 @@ +package com.destroystokyo.paper.event.entity; + +import org.bukkit.entity.LivingEntity; +import org.bukkit.entity.Witch; +import org.bukkit.event.Cancellable; +import org.bukkit.event.HandlerList; +import org.bukkit.event.entity.EntityEvent; +import org.bukkit.inventory.ItemStack; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +/** + * Fired when a witch throws a potion at a player + */ +public class WitchThrowPotionEvent extends EntityEvent implements Cancellable { + @NotNull private final LivingEntity target; + @Nullable private ItemStack potion; + + public WitchThrowPotionEvent(@NotNull Witch witch, @NotNull LivingEntity target, @Nullable ItemStack potion) { + super(witch); + this.target = target; + this.potion = potion; + } + + @NotNull + @Override + public Witch getEntity() { + return (Witch) super.getEntity(); + } + + /** + * @return The target of the potion + */ + @NotNull + public LivingEntity getTarget() { + return target; + } + + /** + * @return The potion the witch will throw at a player + */ + @Nullable + public ItemStack getPotion() { + return potion; + } + + /** + * Sets the potion to be thrown at a player + * @param potion The potion + */ + public void setPotion(@Nullable ItemStack potion) { + this.potion = potion != null ? potion.clone() : null; + } + + private static final HandlerList handlers = new HandlerList(); + + @NotNull + public HandlerList getHandlers() { + return handlers; + } + + @NotNull + public static HandlerList getHandlerList() { + return handlers; + } + + private boolean cancelled = false; + + /** + * @return Event was cancelled or potion was null + */ + @Override + public boolean isCancelled() { + return cancelled || potion == null; + } + + @Override + public void setCancelled(boolean cancel) { + cancelled = cancel; + } +} diff --git a/api/src/main/java/com/destroystokyo/paper/event/executor/MethodHandleEventExecutor.java b/api/src/main/java/com/destroystokyo/paper/event/executor/MethodHandleEventExecutor.java new file mode 100644 index 000000000..5b28e9b1d --- /dev/null +++ b/api/src/main/java/com/destroystokyo/paper/event/executor/MethodHandleEventExecutor.java @@ -0,0 +1,42 @@ +package com.destroystokyo.paper.event.executor; + +import java.lang.invoke.MethodHandle; +import java.lang.invoke.MethodHandles; +import java.lang.reflect.Method; + +import com.destroystokyo.paper.util.SneakyThrow; +import org.bukkit.event.Event; +import org.bukkit.event.EventException; +import org.bukkit.event.Listener; +import org.bukkit.plugin.EventExecutor; +import org.jetbrains.annotations.NotNull; + +public class MethodHandleEventExecutor implements EventExecutor { + private final Class eventClass; + private final MethodHandle handle; + + public MethodHandleEventExecutor(@NotNull Class eventClass, @NotNull MethodHandle handle) { + this.eventClass = eventClass; + this.handle = handle; + } + + public MethodHandleEventExecutor(@NotNull Class eventClass, @NotNull Method m) { + this.eventClass = eventClass; + try { + m.setAccessible(true); + this.handle = MethodHandles.lookup().unreflect(m); + } catch (IllegalAccessException e) { + throw new AssertionError("Unable to set accessible", e); + } + } + + @Override + public void execute(@NotNull Listener listener, @NotNull Event event) throws EventException { + if (!eventClass.isInstance(event)) return; + try { + handle.invoke(listener, event); + } catch (Throwable t) { + SneakyThrow.sneaky(t); + } + } +} diff --git a/api/src/main/java/com/destroystokyo/paper/event/executor/StaticMethodHandleEventExecutor.java b/api/src/main/java/com/destroystokyo/paper/event/executor/StaticMethodHandleEventExecutor.java new file mode 100644 index 000000000..c83672427 --- /dev/null +++ b/api/src/main/java/com/destroystokyo/paper/event/executor/StaticMethodHandleEventExecutor.java @@ -0,0 +1,43 @@ +package com.destroystokyo.paper.event.executor; + +import java.lang.invoke.MethodHandle; +import java.lang.invoke.MethodHandles; +import java.lang.reflect.Method; +import java.lang.reflect.Modifier; + +import com.destroystokyo.paper.util.SneakyThrow; +import com.google.common.base.Preconditions; + +import org.bukkit.Bukkit; +import org.bukkit.event.Event; +import org.bukkit.event.EventException; +import org.bukkit.event.Listener; +import org.bukkit.plugin.EventExecutor; +import org.jetbrains.annotations.NotNull; + +public class StaticMethodHandleEventExecutor implements EventExecutor { + private final Class eventClass; + private final MethodHandle handle; + + public StaticMethodHandleEventExecutor(@NotNull Class eventClass, @NotNull Method m) { + Preconditions.checkArgument(Modifier.isStatic(m.getModifiers()), "Not a static method: %s", m); + Preconditions.checkArgument(eventClass != null, "eventClass is null"); + this.eventClass = eventClass; + try { + m.setAccessible(true); + this.handle = MethodHandles.lookup().unreflect(m); + } catch (IllegalAccessException e) { + throw new AssertionError("Unable to set accessible", e); + } + } + + @Override + public void execute(@NotNull Listener listener, @NotNull Event event) throws EventException { + if (!eventClass.isInstance(event)) return; + try { + handle.invoke(event); + } catch (Throwable throwable) { + SneakyThrow.sneaky(throwable); + } + } +} diff --git a/api/src/main/java/com/destroystokyo/paper/event/executor/asm/ASMEventExecutorGenerator.java b/api/src/main/java/com/destroystokyo/paper/event/executor/asm/ASMEventExecutorGenerator.java new file mode 100644 index 000000000..b6e7d8ee8 --- /dev/null +++ b/api/src/main/java/com/destroystokyo/paper/event/executor/asm/ASMEventExecutorGenerator.java @@ -0,0 +1,47 @@ +package com.destroystokyo.paper.event.executor.asm; + +import java.lang.reflect.Method; +import java.util.concurrent.atomic.AtomicInteger; + +import org.bukkit.plugin.EventExecutor; +import org.jetbrains.annotations.NotNull; +import org.objectweb.asm.ClassWriter; +import org.objectweb.asm.Type; +import org.objectweb.asm.commons.GeneratorAdapter; + +import static org.objectweb.asm.Opcodes.*; + +public class ASMEventExecutorGenerator { + @NotNull + public static byte[] generateEventExecutor(@NotNull Method m, @NotNull String name) { + ClassWriter writer = new ClassWriter(ClassWriter.COMPUTE_FRAMES | ClassWriter.COMPUTE_MAXS); + writer.visit(V1_8, ACC_PUBLIC, name.replace('.', '/'), null, Type.getInternalName(Object.class), new String[] {Type.getInternalName(EventExecutor.class)}); + // Generate constructor + GeneratorAdapter methodGenerator = new GeneratorAdapter(writer.visitMethod(ACC_PUBLIC, "", "()V", null, null), ACC_PUBLIC, "", "()V"); + methodGenerator.loadThis(); + methodGenerator.visitMethodInsn(INVOKESPECIAL, Type.getInternalName(Object.class), "", "()V", false); // Invoke the super class (Object) constructor + methodGenerator.returnValue(); + methodGenerator.endMethod(); + // Generate the execute method + methodGenerator = new GeneratorAdapter(writer.visitMethod(ACC_PUBLIC, "execute", "(Lorg/bukkit/event/Listener;Lorg/bukkit/event/Event;)V", null, null), ACC_PUBLIC, "execute", "(Lorg/bukkit/event/Listener;Lorg/bukkit/event/Listener;)V");; + methodGenerator.loadArg(0); + methodGenerator.checkCast(Type.getType(m.getDeclaringClass())); + methodGenerator.loadArg(1); + methodGenerator.checkCast(Type.getType(m.getParameterTypes()[0])); + methodGenerator.visitMethodInsn(m.getDeclaringClass().isInterface() ? INVOKEINTERFACE : INVOKEVIRTUAL, Type.getInternalName(m.getDeclaringClass()), m.getName(), Type.getMethodDescriptor(m), m.getDeclaringClass().isInterface()); + if (m.getReturnType() != void.class) { + methodGenerator.pop(); + } + methodGenerator.returnValue(); + methodGenerator.endMethod(); + writer.visitEnd(); + return writer.toByteArray(); + } + + public static AtomicInteger NEXT_ID = new AtomicInteger(1); + @NotNull + public static String generateName() { + int id = NEXT_ID.getAndIncrement(); + return "com.destroystokyo.paper.event.executor.asm.generated.GeneratedEventExecutor" + id; + } +} diff --git a/api/src/main/java/com/destroystokyo/paper/event/executor/asm/ClassDefiner.java b/api/src/main/java/com/destroystokyo/paper/event/executor/asm/ClassDefiner.java new file mode 100644 index 000000000..beed9e6e0 --- /dev/null +++ b/api/src/main/java/com/destroystokyo/paper/event/executor/asm/ClassDefiner.java @@ -0,0 +1,35 @@ +package com.destroystokyo.paper.event.executor.asm; + +import com.destroystokyo.paper.utils.UnsafeUtils; +import org.jetbrains.annotations.NotNull; + +public interface ClassDefiner { + + /** + * Returns if the defined classes can bypass access checks + * + * @return if classes bypass access checks + */ + public default boolean isBypassAccessChecks() { + return false; + } + + /** + * Define a class + * + * @param parentLoader the parent classloader + * @param name the name of the class + * @param data the class data to load + * @return the defined class + * @throws ClassFormatError if the class data is invalid + * @throws NullPointerException if any of the arguments are null + */ + @NotNull + public Class defineClass(@NotNull ClassLoader parentLoader, @NotNull String name, @NotNull byte[] data); + + @NotNull + public static ClassDefiner getInstance() { + return SafeClassDefiner.INSTANCE; + } + +} diff --git a/api/src/main/java/com/destroystokyo/paper/event/executor/asm/SafeClassDefiner.java b/api/src/main/java/com/destroystokyo/paper/event/executor/asm/SafeClassDefiner.java new file mode 100644 index 000000000..ac99477e9 --- /dev/null +++ b/api/src/main/java/com/destroystokyo/paper/event/executor/asm/SafeClassDefiner.java @@ -0,0 +1,66 @@ +package com.destroystokyo.paper.event.executor.asm; + +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.ConcurrentMap; + +import com.google.common.base.Preconditions; + +import com.google.common.collect.MapMaker; +import org.jetbrains.annotations.NotNull; +import org.objectweb.asm.Type; + +public class SafeClassDefiner implements ClassDefiner { + /* default */ static final SafeClassDefiner INSTANCE = new SafeClassDefiner(); + + private SafeClassDefiner() {} + + private final ConcurrentMap loaders = new MapMaker().weakKeys().makeMap(); + + @NotNull + @Override + public Class defineClass(@NotNull ClassLoader parentLoader, @NotNull String name, @NotNull byte[] data) { + GeneratedClassLoader loader = loaders.computeIfAbsent(parentLoader, GeneratedClassLoader::new); + synchronized (loader.getClassLoadingLock(name)) { + Preconditions.checkState(!loader.hasClass(name), "%s already defined", name); + Class c = loader.define(name, data); + assert c.getName().equals(name); + return c; + } + } + + private static class GeneratedClassLoader extends ClassLoader { + static { + ClassLoader.registerAsParallelCapable(); + } + + protected GeneratedClassLoader(@NotNull ClassLoader parent) { + super(parent); + } + + private Class define(@NotNull String name, byte[] data) { + synchronized (getClassLoadingLock(name)) { + assert !hasClass(name); + Class c = defineClass(name, data, 0, data.length); + resolveClass(c); + return c; + } + } + + @Override + @NotNull + public Object getClassLoadingLock(@NotNull String name) { + return super.getClassLoadingLock(name); + } + + public boolean hasClass(@NotNull String name) { + synchronized (getClassLoadingLock(name)) { + try { + Class.forName(name); + return true; + } catch (ClassNotFoundException e) { + return false; + } + } + } + } +} diff --git a/api/src/main/java/com/destroystokyo/paper/event/player/IllegalPacketEvent.java b/api/src/main/java/com/destroystokyo/paper/event/player/IllegalPacketEvent.java new file mode 100644 index 000000000..37a17f0bb --- /dev/null +++ b/api/src/main/java/com/destroystokyo/paper/event/player/IllegalPacketEvent.java @@ -0,0 +1,70 @@ +package com.destroystokyo.paper.event.player; + +import org.bukkit.Bukkit; +import org.bukkit.entity.Player; +import org.bukkit.event.HandlerList; +import org.bukkit.event.player.PlayerEvent; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +public class IllegalPacketEvent extends PlayerEvent { + @Nullable private final String type; + @Nullable private final String ex; + @Nullable private String kickMessage; + private boolean shouldKick = true; + + public IllegalPacketEvent(@NotNull Player player, @Nullable String type, @Nullable String kickMessage, @NotNull Exception e) { + super(player); + this.type = type; + this.kickMessage = kickMessage; + this.ex = e.getMessage(); + } + + public boolean isShouldKick() { + return shouldKick; + } + + public void setShouldKick(boolean shouldKick) { + this.shouldKick = shouldKick; + } + + @Nullable + public String getKickMessage() { + return kickMessage; + } + + public void setKickMessage(@Nullable String kickMessage) { + this.kickMessage = kickMessage; + } + + @Nullable + public String getType() { + return type; + } + + @Nullable + public String getExceptionMessage() { + return ex; + } + + private static final HandlerList handlers = new HandlerList(); + + @NotNull + public HandlerList getHandlers() { + return handlers; + } + + @NotNull + public static HandlerList getHandlerList() { + return handlers; + } + + public static void process(@NotNull Player player, @Nullable String type, @Nullable String kickMessage, @NotNull Exception exception) { + IllegalPacketEvent event = new IllegalPacketEvent(player, type, kickMessage, exception); + event.callEvent(); + if (event.shouldKick) { + player.kickPlayer(kickMessage); + } + Bukkit.getLogger().severe(player.getName() + "/" + type + ": " + exception.getMessage()); + } +} diff --git a/api/src/main/java/com/destroystokyo/paper/event/player/PlayerAdvancementCriterionGrantEvent.java b/api/src/main/java/com/destroystokyo/paper/event/player/PlayerAdvancementCriterionGrantEvent.java new file mode 100644 index 000000000..bb8d7c959 --- /dev/null +++ b/api/src/main/java/com/destroystokyo/paper/event/player/PlayerAdvancementCriterionGrantEvent.java @@ -0,0 +1,63 @@ +package com.destroystokyo.paper.event.player; + +import org.bukkit.advancement.Advancement; +import org.bukkit.entity.Player; +import org.bukkit.event.Cancellable; +import org.bukkit.event.HandlerList; +import org.bukkit.event.player.PlayerEvent; +import org.jetbrains.annotations.NotNull; + +/** + * Called when a player is granted a criteria in an advancement. + */ +public class PlayerAdvancementCriterionGrantEvent extends PlayerEvent implements Cancellable { + private static final HandlerList handlers = new HandlerList(); + @NotNull private final Advancement advancement; + @NotNull private final String criterion; + private boolean cancel = false; + + public PlayerAdvancementCriterionGrantEvent(@NotNull Player who, @NotNull Advancement advancement, @NotNull String criterion) { + super(who); + this.advancement = advancement; + this.criterion = criterion; + } + + /** + * Get the advancement which has been affected. + * + * @return affected advancement + */ + @NotNull + public Advancement getAdvancement() { + return advancement; + } + + /** + * Get the criterion which has been granted. + * + * @return granted criterion + */ + @NotNull + public String getCriterion() { + return criterion; + } + + public boolean isCancelled() { + return cancel; + } + + public void setCancelled(boolean cancel) { + this.cancel = cancel; + } + + @NotNull + @Override + public HandlerList getHandlers() { + return handlers; + } + + @NotNull + public static HandlerList getHandlerList() { + return handlers; + } +} diff --git a/api/src/main/java/com/destroystokyo/paper/event/player/PlayerArmorChangeEvent.java b/api/src/main/java/com/destroystokyo/paper/event/player/PlayerArmorChangeEvent.java new file mode 100644 index 000000000..2827a1002 --- /dev/null +++ b/api/src/main/java/com/destroystokyo/paper/event/player/PlayerArmorChangeEvent.java @@ -0,0 +1,137 @@ +package com.destroystokyo.paper.event.player; + +import org.bukkit.Material; +import org.bukkit.entity.Player; +import org.bukkit.event.HandlerList; +import org.bukkit.event.player.PlayerEvent; +import org.bukkit.inventory.ItemStack; + +import java.util.Arrays; +import java.util.Collections; +import java.util.HashSet; +import java.util.Set; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +import static org.bukkit.Material.*; + +/** + * Called when the player themselves change their armor items + *

+ * Not currently called for environmental factors though it MAY BE IN THE FUTURE + */ +public class PlayerArmorChangeEvent extends PlayerEvent { + private static final HandlerList HANDLERS = new HandlerList(); + + @NotNull private final SlotType slotType; + @Nullable private final ItemStack oldItem; + @Nullable private final ItemStack newItem; + + public PlayerArmorChangeEvent(@NotNull Player player, @NotNull SlotType slotType, @Nullable ItemStack oldItem, @Nullable ItemStack newItem) { + super(player); + this.slotType = slotType; + this.oldItem = oldItem; + this.newItem = newItem; + } + + /** + * Gets the type of slot being altered. + * + * @return type of slot being altered + */ + @NotNull + public SlotType getSlotType() { + return this.slotType; + } + + /** + * Gets the existing item that's being replaced + * + * @return old item + */ + @Nullable + public ItemStack getOldItem() { + return this.oldItem; + } + + /** + * Gets the new item that's replacing the old + * + * @return new item + */ + @Nullable + public ItemStack getNewItem() { + return this.newItem; + } + + @Override + public String toString() { + return "ArmorChangeEvent{" + "player=" + player + ", slotType=" + slotType + ", oldItem=" + oldItem + ", newItem=" + newItem + '}'; + } + + @NotNull + @Override + public HandlerList getHandlers() { + return HANDLERS; + } + + @NotNull + public static HandlerList getHandlerList() { + return HANDLERS; + } + + public enum SlotType { + HEAD(DIAMOND_HELMET, GOLDEN_HELMET, IRON_HELMET, CHAINMAIL_HELMET, LEATHER_HELMET, PUMPKIN, JACK_O_LANTERN), + CHEST(DIAMOND_CHESTPLATE, GOLDEN_CHESTPLATE, IRON_CHESTPLATE, CHAINMAIL_CHESTPLATE, LEATHER_CHESTPLATE, ELYTRA), + LEGS(DIAMOND_LEGGINGS, GOLDEN_LEGGINGS, IRON_LEGGINGS, CHAINMAIL_LEGGINGS, LEATHER_LEGGINGS), + FEET(DIAMOND_BOOTS, GOLDEN_BOOTS, IRON_BOOTS, CHAINMAIL_BOOTS, LEATHER_BOOTS); + + private final Set mutableTypes = new HashSet<>(); + private Set immutableTypes; + + SlotType(Material... types) { + this.mutableTypes.addAll(Arrays.asList(types)); + } + + /** + * Gets an immutable set of all allowed material types that can be placed in an + * armor slot. + * + * @return immutable set of material types + */ + @NotNull + public Set getTypes() { + if (immutableTypes == null) { + immutableTypes = Collections.unmodifiableSet(mutableTypes); + } + + return immutableTypes; + } + + /** + * Gets the type of slot via the specified material + * + * @param material material to get slot by + * @return slot type the material will go in, or null if it won't + */ + @Nullable + public static SlotType getByMaterial(@NotNull Material material) { + for (SlotType slotType : values()) { + if (slotType.getTypes().contains(material)) { + return slotType; + } + } + return null; + } + + /** + * Gets whether or not this material can be equipped to a slot + * + * @param material material to check + * @return whether or not this material can be equipped + */ + public static boolean isEquipable(@NotNull Material material) { + return getByMaterial(material) != null; + } + } +} diff --git a/api/src/main/java/com/destroystokyo/paper/event/player/PlayerConnectionCloseEvent.java b/api/src/main/java/com/destroystokyo/paper/event/player/PlayerConnectionCloseEvent.java new file mode 100644 index 000000000..12c1c6fe9 --- /dev/null +++ b/api/src/main/java/com/destroystokyo/paper/event/player/PlayerConnectionCloseEvent.java @@ -0,0 +1,95 @@ +package com.destroystokyo.paper.event.player; + +import org.bukkit.event.Event; +import org.bukkit.event.HandlerList; + +import java.net.InetAddress; +import java.util.UUID; +import org.jetbrains.annotations.NotNull; + +/** + *

+ * This event is invoked when a player has disconnected. It is guaranteed that, + * if the server is in online-mode, that the provided uuid and username have been + * validated. + *

+ * + *

+ * The event is invoked for players who have not yet logged into the world, whereas + * {@link org.bukkit.event.player.PlayerQuitEvent} is only invoked on players who have logged into the world. + *

+ * + *

+ * The event is invoked for players who have already logged into the world, + * although whether or not the player exists in the world at the time of + * firing is undefined. (That is, whether the plugin can retrieve a Player object + * using the event parameters is undefined). However, it is guaranteed that this + * event is invoked AFTER {@link org.bukkit.event.player.PlayerQuitEvent}, if the player has already logged into the world. + *

+ * + *

+ * This event is guaranteed to never fire unless {@link org.bukkit.event.player.AsyncPlayerPreLoginEvent} has + * been fired beforehand, and this event may not be called in parallel with + * {@link org.bukkit.event.player.AsyncPlayerPreLoginEvent} for the same connection. + *

+ * + *

+ * Cancelling the {@link org.bukkit.event.player.AsyncPlayerPreLoginEvent} guarantees the corresponding + * {@code PlayerConnectionCloseEvent} is never called. + *

+ * + *

+ * The event may be invoked asynchronously or synchronously. Plugins should check + * {@link Event#isAsynchronous()} and handle accordingly. + *

+ */ +public class PlayerConnectionCloseEvent extends Event { + + private static final HandlerList HANDLERS = new HandlerList(); + + @NotNull private final UUID playerUniqueId; + @NotNull private final String playerName; + @NotNull private final InetAddress ipAddress; + + public PlayerConnectionCloseEvent(@NotNull final UUID playerUniqueId, @NotNull final String playerName, @NotNull final InetAddress ipAddress, final boolean async) { + super(async); + this.playerUniqueId = playerUniqueId; + this.playerName = playerName; + this.ipAddress = ipAddress; + } + + /** + * Returns the {@code UUID} of the player disconnecting. + */ + @NotNull + public UUID getPlayerUniqueId() { + return this.playerUniqueId; + } + + /** + * Returns the name of the player disconnecting. + */ + @NotNull + public String getPlayerName() { + return this.playerName; + } + + /** + * Returns the player's IP address. + */ + @NotNull + public InetAddress getIpAddress() { + return this.ipAddress; + } + + @NotNull + @Override + public HandlerList getHandlers() { + return HANDLERS; + } + + @NotNull + public static HandlerList getHandlerList() { + return HANDLERS; + } +} diff --git a/api/src/main/java/com/destroystokyo/paper/event/player/PlayerElytraBoostEvent.java b/api/src/main/java/com/destroystokyo/paper/event/player/PlayerElytraBoostEvent.java new file mode 100644 index 000000000..e9a76a25f --- /dev/null +++ b/api/src/main/java/com/destroystokyo/paper/event/player/PlayerElytraBoostEvent.java @@ -0,0 +1,85 @@ +package com.destroystokyo.paper.event.player; + +import org.bukkit.entity.Firework; +import org.bukkit.entity.Player; +import org.bukkit.event.Cancellable; +import org.bukkit.event.HandlerList; +import org.bukkit.event.player.PlayerEvent; +import org.bukkit.inventory.ItemStack; +import org.jetbrains.annotations.NotNull; + +/** + * Fired when a player boosts elytra flight with a firework + */ +public class PlayerElytraBoostEvent extends PlayerEvent implements Cancellable { + private static final HandlerList handlers = new HandlerList(); + private boolean cancelled = false; + @NotNull private final ItemStack itemStack; + @NotNull private Firework firework; + private boolean consume = true; + + public PlayerElytraBoostEvent(@NotNull Player player, @NotNull ItemStack itemStack, @NotNull Firework firework) { + super(player); + this.itemStack = itemStack; + this.firework = firework; + } + + /** + * Get the firework itemstack used + * + * @return ItemStack of firework + */ + @NotNull + public ItemStack getItemStack() { + return itemStack; + } + + /** + * Get the firework entity that was spawned + * + * @return Firework entity + */ + @NotNull + public Firework getFirework() { + return firework; + } + + /** + * Get whether to consume the firework or not + * + * @return True to consume + */ + public boolean shouldConsume() { + return consume; + } + + /** + * Set whether to consume the firework or not + * + * @param consume True to consume + */ + public void setShouldConsume(boolean consume) { + this.consume = consume; + } + + @NotNull + @Override + public HandlerList getHandlers() { + return handlers; + } + + @NotNull + public static HandlerList getHandlerList() { + return handlers; + } + + @Override + public boolean isCancelled() { + return cancelled; + } + + @Override + public void setCancelled(boolean cancel) { + cancelled = cancel; + } +} diff --git a/api/src/main/java/com/destroystokyo/paper/event/player/PlayerHandshakeEvent.java b/api/src/main/java/com/destroystokyo/paper/event/player/PlayerHandshakeEvent.java new file mode 100644 index 000000000..46d6f6ad6 --- /dev/null +++ b/api/src/main/java/com/destroystokyo/paper/event/player/PlayerHandshakeEvent.java @@ -0,0 +1,221 @@ +package com.destroystokyo.paper.event.player; + +import org.apache.commons.lang.Validate; +import org.bukkit.event.Cancellable; +import org.bukkit.event.Event; +import org.bukkit.event.HandlerList; + +import java.util.UUID; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +/** + * This event is fired during a player handshake. + * + *

If there are no listeners listening to this event, the logic default + * to your server platform will be ran.

+ * + *

WARNING: TAMPERING WITH THIS EVENT CAN BE DANGEROUS

+ */ +public class PlayerHandshakeEvent extends Event implements Cancellable { + + private static final HandlerList HANDLERS = new HandlerList(); + @NotNull private final String originalHandshake; + private boolean cancelled; + @Nullable private String serverHostname; + @Nullable private String socketAddressHostname; + @Nullable private UUID uniqueId; + @Nullable private String propertiesJson; + private boolean failed; + private String failMessage = "If you wish to use IP forwarding, please enable it in your BungeeCord config as well!"; + + /** + * Creates a new {@link PlayerHandshakeEvent}. + * + * @param originalHandshake the original handshake string + * @param cancelled if this event is enabled + */ + public PlayerHandshakeEvent(@NotNull String originalHandshake, boolean cancelled) { + this.originalHandshake = originalHandshake; + this.cancelled = cancelled; + } + + /** + * Determines if this event is cancelled. + * + *

When this event is cancelled, custom handshake logic will not + * be processed.

+ * + * @return {@code true} if this event is cancelled, {@code false} otherwise + */ + @Override + public boolean isCancelled() { + return this.cancelled; + } + + /** + * Sets if this event is cancelled. + * + *

When this event is cancelled, custom handshake logic will not + * be processed.

+ * + * @param cancelled {@code true} if this event is cancelled, {@code false} otherwise + */ + @Override + public void setCancelled(boolean cancelled) { + this.cancelled = cancelled; + } + + /** + * Gets the original handshake string. + * + * @return the original handshake string + */ + @NotNull + public String getOriginalHandshake() { + return this.originalHandshake; + } + + /** + * Gets the server hostname string. + * + *

This should not include the port.

+ * + * @return the server hostname string + */ + @Nullable + public String getServerHostname() { + return this.serverHostname; + } + + /** + * Sets the server hostname string. + * + *

This should not include the port.

+ * + * @param serverHostname the server hostname string + */ + public void setServerHostname(@NotNull String serverHostname) { + this.serverHostname = serverHostname; + } + + /** + * Gets the socket address hostname string. + * + *

This should not include the port.

+ * + * @return the socket address hostname string + */ + @Nullable + public String getSocketAddressHostname() { + return this.socketAddressHostname; + } + + /** + * Sets the socket address hostname string. + * + *

This should not include the port.

+ * + * @param socketAddressHostname the socket address hostname string + */ + public void setSocketAddressHostname(@NotNull String socketAddressHostname) { + this.socketAddressHostname = socketAddressHostname; + } + + /** + * Gets the unique id. + * + * @return the unique id + */ + @Nullable + public UUID getUniqueId() { + return this.uniqueId; + } + + /** + * Sets the unique id. + * + * @param uniqueId the unique id + */ + public void setUniqueId(@NotNull UUID uniqueId) { + this.uniqueId = uniqueId; + } + + /** + * Gets the profile properties. + * + *

This should be a valid JSON string.

+ * + * @return the profile properties, as JSON + */ + @Nullable + public String getPropertiesJson() { + return this.propertiesJson; + } + + /** + * Determines if authentication failed. + * + *

When {@code true}, the client connecting will be disconnected + * with the {@link #getFailMessage() fail message}.

+ * + * @return {@code true} if authentication failed, {@code false} otherwise + */ + public boolean isFailed() { + return this.failed; + } + + /** + * Sets if authentication failed and the client should be disconnected. + * + *

When {@code true}, the client connecting will be disconnected + * with the {@link #getFailMessage() fail message}.

+ * + * @param failed {@code true} if authentication failed, {@code false} otherwise + */ + public void setFailed(boolean failed) { + this.failed = failed; + } + + /** + * Sets the profile properties. + * + *

This should be a valid JSON string.

+ * + * @param propertiesJson the profile properties, as JSON + */ + public void setPropertiesJson(@NotNull String propertiesJson) { + this.propertiesJson = propertiesJson; + } + + /** + * Gets the message to display to the client when authentication fails. + * + * @return the message to display to the client + */ + @NotNull + public String getFailMessage() { + return this.failMessage; + } + + /** + * Sets the message to display to the client when authentication fails. + * + * @param failMessage the message to display to the client + */ + public void setFailMessage(@NotNull String failMessage) { + Validate.notEmpty(failMessage, "fail message cannot be null or empty"); + this.failMessage = failMessage; + } + + @NotNull + @Override + public HandlerList getHandlers() { + return HANDLERS; + } + + @NotNull + public static HandlerList getHandlerList() { + return HANDLERS; + } +} diff --git a/api/src/main/java/com/destroystokyo/paper/event/player/PlayerInitialSpawnEvent.java b/api/src/main/java/com/destroystokyo/paper/event/player/PlayerInitialSpawnEvent.java new file mode 100644 index 000000000..8e407eff1 --- /dev/null +++ b/api/src/main/java/com/destroystokyo/paper/event/player/PlayerInitialSpawnEvent.java @@ -0,0 +1,47 @@ +package com.destroystokyo.paper.event.player; + +import org.bukkit.Location; +import org.bukkit.entity.Player; +import org.bukkit.event.HandlerList; +import org.bukkit.event.player.PlayerEvent; +import org.jetbrains.annotations.NotNull; + +public class PlayerInitialSpawnEvent extends PlayerEvent { + private static final HandlerList handlers = new HandlerList(); + @NotNull private Location spawnLocation; + + public PlayerInitialSpawnEvent(@NotNull final Player player, @NotNull final Location spawnLocation) { + super(player); + this.spawnLocation = spawnLocation; + } + + /** + * Gets the current spawn location + * + * @return Location current spawn location + */ + @NotNull + public Location getSpawnLocation() { + return this.spawnLocation; + } + + /** + * Sets the new spawn location + * + * @param spawnLocation new location for the spawn + */ + public void setSpawnLocation(@NotNull Location spawnLocation) { + this.spawnLocation = spawnLocation; + } + + @NotNull + @Override + public HandlerList getHandlers() { + return handlers; + } + + @NotNull + public static HandlerList getHandlerList() { + return handlers; + } +} diff --git a/api/src/main/java/com/destroystokyo/paper/event/player/PlayerJumpEvent.java b/api/src/main/java/com/destroystokyo/paper/event/player/PlayerJumpEvent.java new file mode 100644 index 000000000..289a0d784 --- /dev/null +++ b/api/src/main/java/com/destroystokyo/paper/event/player/PlayerJumpEvent.java @@ -0,0 +1,106 @@ +package com.destroystokyo.paper.event.player; + +import com.google.common.base.Preconditions; +import org.bukkit.Location; +import org.bukkit.entity.Player; +import org.bukkit.event.Cancellable; +import org.bukkit.event.HandlerList; +import org.bukkit.event.player.PlayerEvent; +import org.jetbrains.annotations.NotNull; + +/** + * Called when the server detects the player is jumping. + *

+ * Added to avoid the overhead and special case logic that many plugins use + * when checking for jumps via PlayerMoveEvent, this event is fired whenever + * the server detects that the player is jumping. + */ +public class PlayerJumpEvent extends PlayerEvent implements Cancellable { + private static final HandlerList handlers = new HandlerList(); + private boolean cancel = false; + @NotNull private Location from; + @NotNull private Location to; + + public PlayerJumpEvent(@NotNull final Player player, @NotNull final Location from, @NotNull final Location to) { + super(player); + this.from = from; + this.to = to; + } + + /** + * Gets the cancellation state of this event. A cancelled event will not + * be executed in the server, but will still pass to other plugins + *

+ * If a jump event is cancelled, the player will be moved or + * teleported back to the Location as defined by getFrom(). This will not + * fire an event + * + * @return true if this event is cancelled + */ + public boolean isCancelled() { + return cancel; + } + + /** + * Sets the cancellation state of this event. A cancelled event will not + * be executed in the server, but will still pass to other plugins + *

+ * If a jump event is cancelled, the player will be moved or + * teleported back to the Location as defined by getFrom(). This will not + * fire an event + * + * @param cancel true if you wish to cancel this event + */ + public void setCancelled(boolean cancel) { + this.cancel = cancel; + } + + /** + * Gets the location this player jumped from + * + * @return Location the player jumped from + */ + @NotNull + public Location getFrom() { + return from; + } + + /** + * Sets the location to mark as where the player jumped from + * + * @param from New location to mark as the players previous location + */ + public void setFrom(@NotNull Location from) { + validateLocation(from); + this.from = from; + } + + /** + * Gets the location this player jumped to + * + * This information is based on what the client sends, it typically + * has little relation to the arc of the jump at any given point. + * + * @return Location the player jumped to + */ + @NotNull + public Location getTo() { + return to; + } + + private void validateLocation(Location loc) { + Preconditions.checkArgument(loc != null, "Cannot use null location!"); + Preconditions.checkArgument(loc.getWorld() != null, "Cannot use location with null world!"); + } + + @NotNull + @Override + public HandlerList getHandlers() { + return handlers; + } + + @NotNull + public static HandlerList getHandlerList() { + return handlers; + } +} diff --git a/api/src/main/java/com/destroystokyo/paper/event/player/PlayerLaunchProjectileEvent.java b/api/src/main/java/com/destroystokyo/paper/event/player/PlayerLaunchProjectileEvent.java new file mode 100644 index 000000000..9074b2ede --- /dev/null +++ b/api/src/main/java/com/destroystokyo/paper/event/player/PlayerLaunchProjectileEvent.java @@ -0,0 +1,83 @@ +package com.destroystokyo.paper.event.player; + +import org.bukkit.entity.Player; +import org.bukkit.entity.Projectile; +import org.bukkit.event.Cancellable; +import org.bukkit.event.HandlerList; +import org.bukkit.event.player.PlayerEvent; +import org.bukkit.inventory.ItemStack; +import org.jetbrains.annotations.NotNull; + +/** + * Called when a player shoots a projectile + */ +public class PlayerLaunchProjectileEvent extends PlayerEvent implements Cancellable { + private static final HandlerList handlers = new HandlerList(); + @NotNull private final Projectile projectile; + @NotNull private final ItemStack itemStack; + private boolean consumeItem = true; + private boolean cancelled; + + public PlayerLaunchProjectileEvent(@NotNull Player shooter, @NotNull ItemStack itemStack, @NotNull Projectile projectile) { + super(shooter); + this.itemStack = itemStack; + this.projectile = projectile; + } + + /** + * Gets the projectile which will be launched by this event + * + * @return the launched projectile + */ + @NotNull + public Projectile getProjectile() { + return projectile; + } + + /** + * Get the ItemStack used to fire the projectile + * + * @return The ItemStack used + */ + @NotNull + public ItemStack getItemStack() { + return itemStack; + } + + /** + * Get whether to consume the ItemStack or not + * + * @return True to consume + */ + public boolean shouldConsume() { + return consumeItem; + } + + /** + * Set whether to consume the ItemStack or not + * + * @param consumeItem True to consume + */ + public void setShouldConsume(boolean consumeItem) { + this.consumeItem = consumeItem; + } + + public boolean isCancelled() { + return cancelled; + } + + public void setCancelled(boolean cancel) { + cancelled = cancel; + } + + @NotNull + @Override + public HandlerList getHandlers() { + return handlers; + } + + @NotNull + public static HandlerList getHandlerList() { + return handlers; + } +} diff --git a/api/src/main/java/com/destroystokyo/paper/event/player/PlayerLocaleChangeEvent.java b/api/src/main/java/com/destroystokyo/paper/event/player/PlayerLocaleChangeEvent.java new file mode 100644 index 000000000..29dd763a9 --- /dev/null +++ b/api/src/main/java/com/destroystokyo/paper/event/player/PlayerLocaleChangeEvent.java @@ -0,0 +1,50 @@ +package com.destroystokyo.paper.event.player; + +import org.bukkit.entity.Player; +import org.bukkit.event.HandlerList; +import org.bukkit.event.player.PlayerEvent; + +/** + * Called when the locale of the player is changed. + * + * @deprecated Replaced by {@link org.bukkit.event.player.PlayerLocaleChangeEvent} upstream + */ +@Deprecated +public class PlayerLocaleChangeEvent extends PlayerEvent { + private static final HandlerList handlers = new HandlerList(); + private final String oldLocale; + private final String newLocale; + + public PlayerLocaleChangeEvent(final Player player, final String oldLocale, final String newLocale) { + super(player); + this.oldLocale = oldLocale; + this.newLocale = newLocale; + } + + /** + * Gets the locale the player switched from. + * + * @return player's old locale + */ + public String getOldLocale() { + return oldLocale; + } + + /** + * Gets the locale the player is changed to. + * + * @return player's new locale + */ + public String getNewLocale() { + return newLocale; + } + + @Override + public HandlerList getHandlers() { + return handlers; + } + + public static HandlerList getHandlerList() { + return handlers; + } +} diff --git a/api/src/main/java/com/destroystokyo/paper/event/player/PlayerPickupExperienceEvent.java b/api/src/main/java/com/destroystokyo/paper/event/player/PlayerPickupExperienceEvent.java new file mode 100644 index 000000000..f7beb22d5 --- /dev/null +++ b/api/src/main/java/com/destroystokyo/paper/event/player/PlayerPickupExperienceEvent.java @@ -0,0 +1,80 @@ +/* + * Copyright (c) 2017 Daniel Ennis (Aikar) MIT License + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +package com.destroystokyo.paper.event.player; + +import org.bukkit.entity.ExperienceOrb; +import org.bukkit.entity.Player; +import org.bukkit.event.Cancellable; +import org.bukkit.event.Event; +import org.bukkit.event.HandlerList; +import org.bukkit.event.player.PlayerEvent; +import org.jetbrains.annotations.NotNull; + +/** + * Fired when a player is attempting to pick up an experience orb + */ +public class PlayerPickupExperienceEvent extends PlayerEvent implements Cancellable { + @NotNull private final ExperienceOrb experienceOrb; + + public PlayerPickupExperienceEvent(@NotNull Player player, @NotNull ExperienceOrb experienceOrb) { + super(player); + this.experienceOrb = experienceOrb; + } + + /** + * @return Returns the Orb that the player is picking up + */ + @NotNull + public ExperienceOrb getExperienceOrb() { + return experienceOrb; + } + + private static final HandlerList handlers = new HandlerList(); + + @NotNull + public HandlerList getHandlers() { + return handlers; + } + + @NotNull + public static HandlerList getHandlerList() { + return handlers; + } + + private boolean cancelled = false; + + @Override + public boolean isCancelled() { + return cancelled; + } + + /** + * If true, Cancels picking up the experience orb, leaving it in the world + * @param cancel true if you wish to cancel this event + */ + @Override + public void setCancelled(boolean cancel) { + cancelled = cancel; + } +} diff --git a/api/src/main/java/com/destroystokyo/paper/event/player/PlayerPostRespawnEvent.java b/api/src/main/java/com/destroystokyo/paper/event/player/PlayerPostRespawnEvent.java new file mode 100644 index 000000000..31f34b548 --- /dev/null +++ b/api/src/main/java/com/destroystokyo/paper/event/player/PlayerPostRespawnEvent.java @@ -0,0 +1,52 @@ +package com.destroystokyo.paper.event.player; + +import org.bukkit.Location; +import org.bukkit.entity.Player; +import org.bukkit.event.HandlerList; +import org.bukkit.event.player.PlayerEvent; +import org.jetbrains.annotations.NotNull; + +/** + * Fired after a player has respawned + */ +public class PlayerPostRespawnEvent extends PlayerEvent { + private final static HandlerList handlers = new HandlerList(); + private final Location respawnedLocation; + private final boolean isBedSpawn; + + public PlayerPostRespawnEvent(@NotNull final Player respawnPlayer, @NotNull final Location respawnedLocation, final boolean isBedSpawn) { + super(respawnPlayer); + this.respawnedLocation = respawnedLocation; + this.isBedSpawn = isBedSpawn; + } + + /** + * Returns the location of the respawned player + * + * @return location of the respawned player + */ + @NotNull + public Location getRespawnedLocation() { + return respawnedLocation.clone(); + } + + /** + * Checks if the player respawned to their bed + * + * @return whether the player respawned to their bed + */ + public boolean isBedSpawn() { + return isBedSpawn; + } + + @Override + @NotNull + public HandlerList getHandlers() { + return handlers; + } + + @NotNull + public static HandlerList getHandlerList() { + return handlers; + } +} diff --git a/api/src/main/java/com/destroystokyo/paper/event/player/PlayerReadyArrowEvent.java b/api/src/main/java/com/destroystokyo/paper/event/player/PlayerReadyArrowEvent.java new file mode 100644 index 000000000..5d04a22fd --- /dev/null +++ b/api/src/main/java/com/destroystokyo/paper/event/player/PlayerReadyArrowEvent.java @@ -0,0 +1,93 @@ +/* + * Copyright (c) 2018 Daniel Ennis (Aikar) MIT License + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +package com.destroystokyo.paper.event.player; + +import org.bukkit.entity.Player; +import org.bukkit.event.Cancellable; +import org.bukkit.event.Event; +import org.bukkit.event.HandlerList; +import org.bukkit.event.player.PlayerEvent; +import org.bukkit.inventory.ItemStack; +import org.jetbrains.annotations.NotNull; + +/** + * Called when a player is firing a bow and the server is choosing an arrow to use. + */ +public class PlayerReadyArrowEvent extends PlayerEvent implements Cancellable { + @NotNull private final ItemStack bow; + @NotNull private final ItemStack arrow; + + public PlayerReadyArrowEvent(@NotNull Player player, @NotNull ItemStack bow, @NotNull ItemStack arrow) { + super(player); + this.bow = bow; + this.arrow = arrow; + } + + /** + * @return the player is using to fire the arrow + */ + @NotNull + public ItemStack getBow() { + return bow; + } + + /** + * @return the arrow that is attempting to be used + */ + @NotNull + public ItemStack getArrow() { + return arrow; + } + + private static final HandlerList handlers = new HandlerList(); + + @NotNull + public HandlerList getHandlers() { + return handlers; + } + + @NotNull + public static HandlerList getHandlerList() { + return handlers; + } + + private boolean cancelled = false; + + /** + * Whether or not use of this arrow is cancelled. On cancel, the server will try the next arrow available and fire another event. + */ + @Override + public boolean isCancelled() { + return cancelled; + } + + /** + * Cancel use of this arrow. On cancel, the server will try the next arrow available and fire another event. + * @param cancel true if you wish to cancel this event + */ + @Override + public void setCancelled(boolean cancel) { + cancelled = cancel; + } +} diff --git a/api/src/main/java/com/destroystokyo/paper/event/player/PlayerStartSpectatingEntityEvent.java b/api/src/main/java/com/destroystokyo/paper/event/player/PlayerStartSpectatingEntityEvent.java new file mode 100644 index 000000000..b8ec7ef2d --- /dev/null +++ b/api/src/main/java/com/destroystokyo/paper/event/player/PlayerStartSpectatingEntityEvent.java @@ -0,0 +1,67 @@ +package com.destroystokyo.paper.event.player; + +import org.bukkit.entity.Entity; +import org.bukkit.entity.Player; +import org.bukkit.event.Cancellable; +import org.bukkit.event.HandlerList; +import org.bukkit.event.player.PlayerEvent; +import org.jetbrains.annotations.NotNull; + +/** + * Triggered when a player starts spectating an entity in spectator mode. + */ +public class PlayerStartSpectatingEntityEvent extends PlayerEvent implements Cancellable { + + private static final HandlerList handlers = new HandlerList(); + private boolean cancelled; + @NotNull private final Entity currentSpectatorTarget; + @NotNull private final Entity newSpectatorTarget; + + public PlayerStartSpectatingEntityEvent(@NotNull Player player, @NotNull Entity currentSpectatorTarget, @NotNull Entity newSpectatorTarget) { + super(player); + this.currentSpectatorTarget = currentSpectatorTarget; + this.newSpectatorTarget = newSpectatorTarget; + } + + /** + * Gets the entity that the player is currently spectating or themselves if they weren't spectating anything + * + * @return The entity the player is currently spectating (before they start spectating the new target). + */ + @NotNull + public Entity getCurrentSpectatorTarget() { + return currentSpectatorTarget; + } + + /** + * Gets the new entity that the player will now be spectating + * + * @return The entity the player is now going to be spectating. + */ + @NotNull + public Entity getNewSpectatorTarget() { + return newSpectatorTarget; + } + + @Override + public boolean isCancelled() { + return cancelled; + } + + @Override + public void setCancelled(boolean cancel) { + this.cancelled = cancel; + } + + @NotNull + @Override + public HandlerList getHandlers() { + return handlers; + } + + @NotNull + public static HandlerList getHandlerList() { + return handlers; + } +} + diff --git a/api/src/main/java/com/destroystokyo/paper/event/player/PlayerStopSpectatingEntityEvent.java b/api/src/main/java/com/destroystokyo/paper/event/player/PlayerStopSpectatingEntityEvent.java new file mode 100644 index 000000000..693d119ab --- /dev/null +++ b/api/src/main/java/com/destroystokyo/paper/event/player/PlayerStopSpectatingEntityEvent.java @@ -0,0 +1,54 @@ +package com.destroystokyo.paper.event.player; + +import org.bukkit.entity.Entity; +import org.bukkit.entity.Player; +import org.bukkit.event.Cancellable; +import org.bukkit.event.HandlerList; +import org.bukkit.event.player.PlayerEvent; +import org.jetbrains.annotations.NotNull; + +/** + * Triggered when a player stops spectating an entity in spectator mode. + */ +public class PlayerStopSpectatingEntityEvent extends PlayerEvent implements Cancellable { + + private static final HandlerList handlers = new HandlerList(); + private boolean cancelled; + @NotNull private final Entity spectatorTarget; + + public PlayerStopSpectatingEntityEvent(@NotNull Player player, @NotNull Entity spectatorTarget) { + super(player); + this.spectatorTarget = spectatorTarget; + } + + /** + * Gets the entity that the player is spectating + * + * @return The entity the player is currently spectating (before they will stop). + */ + @NotNull + public Entity getSpectatorTarget() { + return spectatorTarget; + } + + @Override + public boolean isCancelled() { + return cancelled; + } + + @Override + public void setCancelled(boolean cancel) { + this.cancelled = cancel; + } + + @NotNull + @Override + public HandlerList getHandlers() { + return handlers; + } + + @NotNull + public static HandlerList getHandlerList() { + return handlers; + } +} diff --git a/api/src/main/java/com/destroystokyo/paper/event/player/PlayerTeleportEndGatewayEvent.java b/api/src/main/java/com/destroystokyo/paper/event/player/PlayerTeleportEndGatewayEvent.java new file mode 100644 index 000000000..b64ab6eec --- /dev/null +++ b/api/src/main/java/com/destroystokyo/paper/event/player/PlayerTeleportEndGatewayEvent.java @@ -0,0 +1,29 @@ +package com.destroystokyo.paper.event.player; + +import org.bukkit.Location; +import org.bukkit.block.EndGateway; +import org.bukkit.entity.Player; +import org.bukkit.event.player.PlayerTeleportEvent; +import org.jetbrains.annotations.NotNull; + +/** + * Fired when a teleport is triggered for an End Gateway + */ +public class PlayerTeleportEndGatewayEvent extends PlayerTeleportEvent { + @NotNull private final EndGateway gateway; + + public PlayerTeleportEndGatewayEvent(@NotNull Player player, @NotNull Location from, @NotNull Location to, @NotNull EndGateway gateway) { + super(player, from, to, PlayerTeleportEvent.TeleportCause.END_GATEWAY); + this.gateway = gateway; + } + + /** + * The gateway triggering the teleport + * + * @return EndGateway used + */ + @NotNull + public EndGateway getGateway() { + return gateway; + } +} diff --git a/api/src/main/java/com/destroystokyo/paper/event/player/PlayerUseUnknownEntityEvent.java b/api/src/main/java/com/destroystokyo/paper/event/player/PlayerUseUnknownEntityEvent.java new file mode 100644 index 000000000..09cfdf48e --- /dev/null +++ b/api/src/main/java/com/destroystokyo/paper/event/player/PlayerUseUnknownEntityEvent.java @@ -0,0 +1,46 @@ +package com.destroystokyo.paper.event.player; + +import org.bukkit.entity.Player; +import org.bukkit.event.HandlerList; +import org.bukkit.event.player.PlayerEvent; +import org.bukkit.inventory.EquipmentSlot; +import org.jetbrains.annotations.NotNull; + +public class PlayerUseUnknownEntityEvent extends PlayerEvent { + + private static final HandlerList handlers = new HandlerList(); + private final int entityId; + private final boolean attack; + @NotNull private final EquipmentSlot hand; + + public PlayerUseUnknownEntityEvent(@NotNull Player who, int entityId, boolean attack, @NotNull EquipmentSlot hand) { + super(who); + this.entityId = entityId; + this.attack = attack; + this.hand = hand; + } + + public int getEntityId() { + return this.entityId; + } + + public boolean isAttack() { + return this.attack; + } + + @NotNull + public EquipmentSlot getHand() { + return this.hand; + } + + @NotNull + @Override + public HandlerList getHandlers() { + return handlers; + } + + @NotNull + public static HandlerList getHandlerList() { + return handlers; + } +} diff --git a/api/src/main/java/com/destroystokyo/paper/event/profile/FillProfileEvent.java b/api/src/main/java/com/destroystokyo/paper/event/profile/FillProfileEvent.java new file mode 100644 index 000000000..71f36e9ca --- /dev/null +++ b/api/src/main/java/com/destroystokyo/paper/event/profile/FillProfileEvent.java @@ -0,0 +1,75 @@ +/* + * Copyright (c) 2018 Daniel Ennis (Aikar) MIT License + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +package com.destroystokyo.paper.event.profile; + +import com.destroystokyo.paper.profile.PlayerProfile; +import com.destroystokyo.paper.profile.ProfileProperty; +import org.bukkit.event.Event; +import org.bukkit.event.HandlerList; + +import java.util.Set; +import org.jetbrains.annotations.NotNull; + +/** + * Fired once a profiles additional properties (such as textures) has been filled + */ +public class FillProfileEvent extends Event { + @NotNull private final PlayerProfile profile; + + public FillProfileEvent(@NotNull PlayerProfile profile) { + super(!org.bukkit.Bukkit.isPrimaryThread()); + this.profile = profile; + } + + /** + * @return The Profile that had properties filled + */ + @NotNull + public PlayerProfile getPlayerProfile() { + return profile; + } + + /** + * Same as .getPlayerProfile().getProperties() + * + * @see PlayerProfile#getProperties() + * @return The new properties on the profile. + */ + @NotNull + public Set getProperties() { + return profile.getProperties(); + } + + private static final HandlerList handlers = new HandlerList(); + + @NotNull + public HandlerList getHandlers() { + return handlers; + } + + @NotNull + public static HandlerList getHandlerList() { + return handlers; + } +} diff --git a/api/src/main/java/com/destroystokyo/paper/event/profile/LookupProfileEvent.java b/api/src/main/java/com/destroystokyo/paper/event/profile/LookupProfileEvent.java new file mode 100644 index 000000000..8df37c07c --- /dev/null +++ b/api/src/main/java/com/destroystokyo/paper/event/profile/LookupProfileEvent.java @@ -0,0 +1,46 @@ +package com.destroystokyo.paper.event.profile; + +import com.destroystokyo.paper.profile.PlayerProfile; +import org.bukkit.Bukkit; +import org.bukkit.event.Event; +import org.bukkit.event.HandlerList; + +import org.jetbrains.annotations.NotNull; + +/** + * Allows a plugin to be notified anytime AFTER a Profile has been looked up from the Mojang API + * This is an opportunity to view the response and potentially cache things. + * + * No guarantees are made about thread execution context for this event. If you need to know, check + * event.isAsync() + */ +public class LookupProfileEvent extends Event { + + private static final HandlerList handlers = new HandlerList(); + + @NotNull private final PlayerProfile profile; + + public LookupProfileEvent(@NotNull PlayerProfile profile) { + super(!Bukkit.isPrimaryThread()); + this.profile = profile; + } + + /** + * @return The profile that was recently looked up. This profile can be mutated + */ + @NotNull + public PlayerProfile getPlayerProfile() { + return profile; + } + + @NotNull + @Override + public HandlerList getHandlers() { + return handlers; + } + + @NotNull + public static HandlerList getHandlerList() { + return handlers; + } +} diff --git a/api/src/main/java/com/destroystokyo/paper/event/profile/PreFillProfileEvent.java b/api/src/main/java/com/destroystokyo/paper/event/profile/PreFillProfileEvent.java new file mode 100644 index 000000000..021bc8631 --- /dev/null +++ b/api/src/main/java/com/destroystokyo/paper/event/profile/PreFillProfileEvent.java @@ -0,0 +1,77 @@ +/* + * Copyright (c) 2018 Daniel Ennis (Aikar) MIT License + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +package com.destroystokyo.paper.event.profile; + +import com.destroystokyo.paper.profile.PlayerProfile; +import com.destroystokyo.paper.profile.ProfileProperty; +import org.bukkit.event.Event; +import org.bukkit.event.HandlerList; + +import java.util.Collection; +import org.jetbrains.annotations.NotNull; + +/** + * Fired when the server is requesting to fill in properties of an incomplete profile, such as textures. + * + * Allows plugins to pre populate cached properties and avoid a call to the Mojang API + */ +public class PreFillProfileEvent extends Event { + @NotNull private final PlayerProfile profile; + + public PreFillProfileEvent(@NotNull PlayerProfile profile) { + super(!org.bukkit.Bukkit.isPrimaryThread()); + this.profile = profile; + } + + /** + * @return The profile that needs its properties filled + */ + @NotNull + public PlayerProfile getPlayerProfile() { + return profile; + } + + /** + * Sets the properties on the profile, avoiding the call to the Mojang API + * Same as .getPlayerProfile().setProperties(properties); + * + * @see PlayerProfile#setProperties(Collection) + * @param properties The properties to set/append + */ + public void setProperties(@NotNull Collection properties) { + profile.setProperties(properties); + } + + private static final HandlerList handlers = new HandlerList(); + + @NotNull + public HandlerList getHandlers() { + return handlers; + } + + @NotNull + public static HandlerList getHandlerList() { + return handlers; + } +} diff --git a/api/src/main/java/com/destroystokyo/paper/event/profile/PreLookupProfileEvent.java b/api/src/main/java/com/destroystokyo/paper/event/profile/PreLookupProfileEvent.java new file mode 100644 index 000000000..4dcf6242c --- /dev/null +++ b/api/src/main/java/com/destroystokyo/paper/event/profile/PreLookupProfileEvent.java @@ -0,0 +1,108 @@ +package com.destroystokyo.paper.event.profile; + +import com.destroystokyo.paper.profile.PlayerProfile; +import com.destroystokyo.paper.profile.ProfileProperty; +import com.google.common.collect.ArrayListMultimap; +import org.bukkit.Bukkit; +import org.bukkit.event.Event; +import org.bukkit.event.HandlerList; + +import java.util.HashSet; +import java.util.Set; +import java.util.UUID; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +/** + * Allows a plugin to intercept a Profile Lookup for a Profile by name + * + * At the point of event fire, the UUID and properties are unset. + * + * If a plugin sets the UUID, and optionally the properties, the API call to look up the profile may be skipped. + * + * No guarantees are made about thread execution context for this event. If you need to know, check + * event.isAsync() + */ +public class PreLookupProfileEvent extends Event { + + private static final HandlerList handlers = new HandlerList(); + @NotNull private final String name; + private UUID uuid; + @NotNull private Set properties = new HashSet<>(); + + public PreLookupProfileEvent(@NotNull String name) { + super(!Bukkit.isPrimaryThread()); + this.name = name; + } + + /** + * @return Name of the profile + */ + @NotNull + public String getName() { + return name; + } + + /** + * If this value is left null by the completion of the event call, then the server will + * trigger a call to the Mojang API to look up the UUID (Network Request), and subsequently, fire a + * {@link LookupProfileEvent} + * + * @return The UUID of the profile if it has already been provided by a plugin + */ + @Nullable + public UUID getUUID() { + return uuid; + } + + /** + * Sets the UUID for this player name. This will skip the initial API call to find the players UUID. + * + * However, if Profile Properties are needed by the server, you must also set them or else an API call might still be made. + * + * @param uuid the UUID to set for the profile or null to reset + */ + public void setUUID(@Nullable UUID uuid) { + this.uuid = uuid; + } + + /** + * @return The currently pending prepopulated properties. + * Any property in this Set will be automatically prefilled on this Profile + */ + @NotNull + public Set getProfileProperties() { + return this.properties; + } + + /** + * Clears any existing prepopulated properties and uses the supplied properties + * Any property in this Set will be automatically prefilled on this Profile + * @param properties The properties to add + */ + public void setProfileProperties(@NotNull Set properties) { + this.properties = new HashSet<>(); + this.properties.addAll(properties); + } + + /** + * Adds any properties currently missing to the prepopulated properties set, replacing any that already were set. + * Any property in this Set will be automatically prefilled on this Profile + * @param properties The properties to add + */ + public void addProfileProperties(@NotNull Set properties) { + this.properties.addAll(properties); + } + + @NotNull + @Override + public HandlerList getHandlers() { + return handlers; + } + + @NotNull + public static HandlerList getHandlerList() { + return handlers; + } + +} diff --git a/api/src/main/java/com/destroystokyo/paper/event/profile/ProfileWhitelistVerifyEvent.java b/api/src/main/java/com/destroystokyo/paper/event/profile/ProfileWhitelistVerifyEvent.java new file mode 100644 index 000000000..b10176289 --- /dev/null +++ b/api/src/main/java/com/destroystokyo/paper/event/profile/ProfileWhitelistVerifyEvent.java @@ -0,0 +1,117 @@ +/* + * Copyright (c) 2017 - Daniel Ennis (Aikar) - MIT License + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +package com.destroystokyo.paper.event.profile; + +import com.destroystokyo.paper.profile.PlayerProfile; +import org.bukkit.event.Event; +import org.bukkit.event.HandlerList; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +/** + * Fires when the server needs to verify if a player is whitelisted. + * + * Plugins may override/control the servers whitelist with this event, + * and dynamically change the kick message. + * + */ +public class ProfileWhitelistVerifyEvent extends Event { + private static final HandlerList handlers = new HandlerList(); + @NotNull private final PlayerProfile profile; + private final boolean whitelistEnabled; + private boolean whitelisted; + private final boolean isOp; + @Nullable private String kickMessage; + + public ProfileWhitelistVerifyEvent(@NotNull final PlayerProfile profile, boolean whitelistEnabled, boolean whitelisted, boolean isOp, @Nullable String kickMessage) { + this.profile = profile; + this.whitelistEnabled = whitelistEnabled; + this.whitelisted = whitelisted; + this.isOp = isOp; + this.kickMessage = kickMessage; + } + + /** + * @return the currently planned message to send to the user if they are not whitelisted + */ + @Nullable + public String getKickMessage() { + return kickMessage; + } + + /** + * @param kickMessage The message to send to the player on kick if not whitelisted. May set to null to use the server configured default + */ + public void setKickMessage(@Nullable String kickMessage) { + this.kickMessage = kickMessage; + } + + /** + * @return The profile of the player trying to connect + */ + @NotNull + public PlayerProfile getPlayerProfile() { + return profile; + } + + /** + * @return Whether the player is whitelisted to play on this server (whitelist may be off is why its true) + */ + public boolean isWhitelisted() { + return whitelisted; + } + + /** + * Changes the players whitelisted state. false will deny the login + * @param whitelisted The new whitelisted state + */ + public void setWhitelisted(boolean whitelisted) { + this.whitelisted = whitelisted; + } + + /** + * @return if the player obtained whitelist status by having op + */ + public boolean isOp() { + return isOp; + } + + /** + * @return if the server even has whitelist on + */ + public boolean isWhitelistEnabled() { + return whitelistEnabled; + } + + @NotNull + @Override + public HandlerList getHandlers() { + return handlers; + } + + @NotNull + public static HandlerList getHandlerList() { + return handlers; + } +} diff --git a/api/src/main/java/com/destroystokyo/paper/event/server/AsyncTabCompleteEvent.java b/api/src/main/java/com/destroystokyo/paper/event/server/AsyncTabCompleteEvent.java new file mode 100644 index 000000000..619ed3716 --- /dev/null +++ b/api/src/main/java/com/destroystokyo/paper/event/server/AsyncTabCompleteEvent.java @@ -0,0 +1,177 @@ +/* + * Copyright (c) 2017 Daniel Ennis (Aikar) MIT License + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +package com.destroystokyo.paper.event.server; + +import com.google.common.collect.ImmutableList; +import org.apache.commons.lang.Validate; +import org.bukkit.Location; +import org.bukkit.command.Command; +import org.bukkit.command.CommandSender; +import org.bukkit.event.Cancellable; +import org.bukkit.event.Event; +import org.bukkit.event.HandlerList; + +import java.util.ArrayList; +import java.util.List; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +/** + * Allows plugins to compute tab completion results asynchronously. If this event provides completions, then the standard synchronous process will not be fired to populate the results. However, the synchronous TabCompleteEvent will fire with the Async results. + * + * Only 1 process will be allowed to provide completions, the Async Event, or the standard process. + */ +public class AsyncTabCompleteEvent extends Event implements Cancellable { + @NotNull private final CommandSender sender; + @NotNull private final String buffer; + private final boolean isCommand; + @Nullable + private final Location loc; + @NotNull private List completions; + private boolean cancelled; + private boolean handled = false; + private boolean fireSyncHandler = true; + + public AsyncTabCompleteEvent(@NotNull CommandSender sender, @NotNull List completions, @NotNull String buffer, boolean isCommand, @Nullable Location loc) { + super(true); + this.sender = sender; + this.completions = completions; + this.buffer = buffer; + this.isCommand = isCommand; + this.loc = loc; + } + + /** + * Get the sender completing this command. + * + * @return the {@link CommandSender} instance + */ + @NotNull + public CommandSender getSender() { + return sender; + } + + /** + * The list of completions which will be offered to the sender, in order. + * This list is mutable and reflects what will be offered. + * + * If this collection is not empty after the event is fired, then + * the standard process of calling {@link Command#tabComplete(CommandSender, String, String[])} + * or current player names will not be called. + * + * @return a list of offered completions + */ + @NotNull + public List getCompletions() { + return completions; + } + + /** + * Set the completions offered, overriding any already set. + * If this collection is not empty after the event is fired, then + * the standard process of calling {@link Command#tabComplete(CommandSender, String, String[])} + * or current player names will not be called. + * + * The passed collection will be cloned to a new List. You must call {{@link #getCompletions()}} to mutate from here + * + * @param completions the new completions + */ + public void setCompletions(@NotNull List completions) { + Validate.notNull(completions); + this.completions = new ArrayList<>(completions); + } + + /** + * Return the entire buffer which formed the basis of this completion. + * + * @return command buffer, as entered + */ + @NotNull + public String getBuffer() { + return buffer; + } + + /** + * @return True if it is a command being tab completed, false if it is a chat message. + */ + public boolean isCommand() { + return isCommand; + } + + /** + * @return The position looked at by the sender, or null if none + */ + @Nullable + public Location getLocation() { + return loc; + } + + /** + * If true, the standard process of calling {@link Command#tabComplete(CommandSender, String, String[])} + * or current player names will not be called. + * + * @return Is completions considered handled. Always true if completions is not empty. + */ + public boolean isHandled() { + return !completions.isEmpty() || handled; + } + + /** + * Sets whether or not to consider the completion request handled. + * If true, the standard process of calling {@link Command#tabComplete(CommandSender, String, String[])} + * or current player names will not be called. + * + * @param handled if this completion should be marked as being handled + */ + public void setHandled(boolean handled) { + this.handled = handled; + } + + private static final HandlerList handlers = new HandlerList(); + + + @Override + public boolean isCancelled() { + return cancelled; + } + + /** + * Will provide no completions, and will not fire the synchronous process + * @param cancelled true if you wish to cancel this event + */ + @Override + public void setCancelled(boolean cancelled) { + this.cancelled = cancelled; + } + + @NotNull + public HandlerList getHandlers() { + return handlers; + } + + @NotNull + public static HandlerList getHandlerList() { + return handlers; + } +} diff --git a/api/src/main/java/com/destroystokyo/paper/event/server/GS4QueryEvent.java b/api/src/main/java/com/destroystokyo/paper/event/server/GS4QueryEvent.java new file mode 100644 index 000000000..2ead0466e --- /dev/null +++ b/api/src/main/java/com/destroystokyo/paper/event/server/GS4QueryEvent.java @@ -0,0 +1,411 @@ +package com.destroystokyo.paper.event.server; + +import com.google.common.base.Preconditions; +import com.google.common.collect.ImmutableList; +import org.bukkit.event.Event; +import org.bukkit.event.HandlerList; +import org.jetbrains.annotations.NotNull; + +import java.net.InetAddress; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collection; +import java.util.List; + +/** + * This event is fired if server is getting queried over GS4 Query protocol + * + * Adapted from Velocity's ProxyQueryEvent + * + * @author Mark Vainomaa + */ +public final class GS4QueryEvent extends Event { + private static final HandlerList handlers = new HandlerList(); + + private final QueryType queryType; + private final InetAddress querierAddress; + private QueryResponse response; + + public GS4QueryEvent(@NotNull QueryType queryType, @NotNull InetAddress querierAddress, @NotNull QueryResponse response) { + this.queryType = Preconditions.checkNotNull(queryType, "queryType"); + this.querierAddress = Preconditions.checkNotNull(querierAddress, "querierAddress"); + this.response = Preconditions.checkNotNull(response, "response"); + } + + /** + * Get query type + * @return query type + */ + @NotNull + public QueryType getQueryType() { + return queryType; + } + + /** + * Get querier address + * @return querier address + */ + @NotNull + public InetAddress getQuerierAddress() { + return querierAddress; + } + + /** + * Get query response + * @return query response + */ + @NotNull + public QueryResponse getResponse() { + return response; + } + + /** + * Set query response + * @param response query response + */ + public void setResponse(@NotNull QueryResponse response) { + this.response = Preconditions.checkNotNull(response, "response"); + } + + @Override + public String toString() { + return "GS4QueryEvent{" + + "queryType=" + queryType + + ", querierAddress=" + querierAddress + + ", response=" + response + + '}'; + } + + @NotNull + @Override + public HandlerList getHandlers() { + return handlers; + } + + @NotNull + public static HandlerList getHandlerList() { + return handlers; + } + + /** + * The type of query + */ + public enum QueryType { + /** + * Basic query asks only a subset of information, such as motd, game type (hardcoded to

MINECRAFT
), map, + * current players, max players, server port and server motd + */ + BASIC, + + /** + * Full query asks pretty much everything present on this event (only hardcoded values cannot be modified here). + */ + FULL + ; + } + + public final static class QueryResponse { + private final String motd; + private final String gameVersion; + private final String map; + private final int currentPlayers; + private final int maxPlayers; + private final String hostname; + private final int port; + private final Collection players; + private final String serverVersion; + private final Collection plugins; + + private QueryResponse(String motd, String gameVersion, String map, int currentPlayers, int maxPlayers, String hostname, int port, Collection players, String serverVersion, Collection plugins) { + this.motd = motd; + this.gameVersion = gameVersion; + this.map = map; + this.currentPlayers = currentPlayers; + this.maxPlayers = maxPlayers; + this.hostname = hostname; + this.port = port; + this.players = players; + this.serverVersion = serverVersion; + this.plugins = plugins; + } + + /** + * Get motd which will be used to reply to the query. By default it is {@link org.bukkit.Server#getMotd()}. + * @return motd + */ + @NotNull + public String getMotd() { + return motd; + } + + /** + * Get game version which will be used to reply to the query. By default supported Minecraft versions range is sent. + * @return game version + */ + @NotNull + public String getGameVersion() { + return gameVersion; + } + + /** + * Get map name which will be used to reply to the query. By default {@code world} is sent. + * @return map name + */ + @NotNull + public String getMap() { + return map; + } + + /** + * Get current online player count which will be used to reply to the query. + * @return online player count + */ + public int getCurrentPlayers() { + return currentPlayers; + } + + /** + * Get max player count which will be used to reply to the query. + * @return max player count + */ + public int getMaxPlayers() { + return maxPlayers; + } + + /** + * Get server (public facing) hostname + * @return server hostname + */ + @NotNull + public String getHostname() { + return hostname; + } + + /** + * Get server (public facing) port + * @return server port + */ + public int getPort() { + return port; + } + + /** + * Get collection of players which will be used to reply to the query. + * @return collection of players + */ + @NotNull + public Collection getPlayers() { + return players; + } + + /** + * Get server software (name and version) which will be used to reply to the query. + * @return server software + */ + @NotNull + public String getServerVersion() { + return serverVersion; + } + + /** + * Get list of plugins which will be used to reply to the query. + * @return collection of plugins + */ + @NotNull + public Collection getPlugins() { + return plugins; + } + + + /** + * Creates a new {@link Builder} instance from data represented by this response + * @return {@link QueryResponse} builder + */ + @NotNull + public Builder toBuilder() { + return QueryResponse.builder() + .motd(getMotd()) + .gameVersion(getGameVersion()) + .map(getMap()) + .currentPlayers(getCurrentPlayers()) + .maxPlayers(getMaxPlayers()) + .hostname(getHostname()) + .port(getPort()) + .players(getPlayers()) + .serverVersion(getServerVersion()) + .plugins(getPlugins()); + } + + /** + * Creates a new {@link Builder} instance + * @return {@link QueryResponse} builder + */ + @NotNull + public static Builder builder() { + return new Builder(); + } + + /** + * A builder for {@link QueryResponse} objects. + */ + public static final class Builder { + private String motd; + private String gameVersion; + private String map; + private String hostname; + private String serverVersion; + + private int currentPlayers; + private int maxPlayers; + private int port; + + private List players = new ArrayList<>(); + private List plugins = new ArrayList<>(); + + private Builder() {} + + @NotNull + public Builder motd(@NotNull String motd) { + this.motd = Preconditions.checkNotNull(motd, "motd"); + return this; + } + + @NotNull + public Builder gameVersion(@NotNull String gameVersion) { + this.gameVersion = Preconditions.checkNotNull(gameVersion, "gameVersion"); + return this; + } + + @NotNull + public Builder map(@NotNull String map) { + this.map = Preconditions.checkNotNull(map, "map"); + return this; + } + + @NotNull + public Builder currentPlayers(int currentPlayers) { + Preconditions.checkArgument(currentPlayers >= 0, "currentPlayers cannot be negative"); + this.currentPlayers = currentPlayers; + return this; + } + + @NotNull + public Builder maxPlayers(int maxPlayers) { + Preconditions.checkArgument(maxPlayers >= 0, "maxPlayers cannot be negative"); + this.maxPlayers = maxPlayers; + return this; + } + + @NotNull + public Builder hostname(@NotNull String hostname) { + this.hostname = Preconditions.checkNotNull(hostname, "hostname"); + return this; + } + + @NotNull + public Builder port(int port) { + Preconditions.checkArgument(port >= 1 && port <= 65535, "port must be between 1-65535"); + this.port = port; + return this; + } + + @NotNull + public Builder players(@NotNull Collection players) { + this.players.addAll(Preconditions.checkNotNull(players, "players")); + return this; + } + + @NotNull + public Builder players(@NotNull String... players) { + this.players.addAll(Arrays.asList(Preconditions.checkNotNull(players, "players"))); + return this; + } + + @NotNull + public Builder clearPlayers() { + this.players.clear(); + return this; + } + + @NotNull + public Builder serverVersion(@NotNull String serverVersion) { + this.serverVersion = Preconditions.checkNotNull(serverVersion, "serverVersion"); + return this; + } + + @NotNull + public Builder plugins(@NotNull Collection plugins) { + this.plugins.addAll(Preconditions.checkNotNull(plugins, "plugins")); + return this; + } + + @NotNull + public Builder plugins(@NotNull PluginInformation... plugins) { + this.plugins.addAll(Arrays.asList(Preconditions.checkNotNull(plugins, "plugins"))); + return this; + } + + @NotNull + public Builder clearPlugins() { + this.plugins.clear(); + return this; + } + + /** + * Builds new {@link QueryResponse} with supplied data + * @return response + */ + @NotNull + public QueryResponse build() { + return new QueryResponse( + Preconditions.checkNotNull(motd, "motd"), + Preconditions.checkNotNull(gameVersion, "gameVersion"), + Preconditions.checkNotNull(map, "map"), + currentPlayers, + maxPlayers, + Preconditions.checkNotNull(hostname, "hostname"), + port, + ImmutableList.copyOf(players), + Preconditions.checkNotNull(serverVersion, "serverVersion"), + ImmutableList.copyOf(plugins) + ); + } + } + + /** + * Plugin information + */ + public static class PluginInformation { + private String name; + private String version; + + public PluginInformation(@NotNull String name, @NotNull String version) { + this.name = Preconditions.checkNotNull(name, "name"); + this.version = Preconditions.checkNotNull(version, "version"); + } + + @NotNull + public String getName() { + return name; + } + + public void setName(@NotNull String name) { + this.name = name; + } + + public void setVersion(@NotNull String version) { + this.version = version; + } + + @NotNull + public String getVersion() { + return version; + } + + @NotNull + public static PluginInformation of(@NotNull String name, @NotNull String version) { + return new PluginInformation(name, version); + } + } + } +} diff --git a/api/src/main/java/com/destroystokyo/paper/event/server/PaperServerListPingEvent.java b/api/src/main/java/com/destroystokyo/paper/event/server/PaperServerListPingEvent.java new file mode 100644 index 000000000..0cc5dd573 --- /dev/null +++ b/api/src/main/java/com/destroystokyo/paper/event/server/PaperServerListPingEvent.java @@ -0,0 +1,323 @@ +package com.destroystokyo.paper.event.server; + +import static java.util.Objects.requireNonNull; + +import com.destroystokyo.paper.network.StatusClient; +import com.destroystokyo.paper.profile.PlayerProfile; +import org.bukkit.Bukkit; +import org.bukkit.entity.Player; +import org.bukkit.event.Cancellable; +import org.bukkit.event.server.ServerListPingEvent; +import org.bukkit.util.CachedServerIcon; + +import java.util.ArrayList; +import java.util.Iterator; +import java.util.List; +import java.util.NoSuchElementException; +import java.util.UUID; + +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +/** + * Extended version of {@link ServerListPingEvent} that allows full control + * of the response sent to the client. + */ +public class PaperServerListPingEvent extends ServerListPingEvent implements Cancellable { + + @NotNull private final StatusClient client; + + private int numPlayers; + private boolean hidePlayers; + @NotNull private final List playerSample = new ArrayList<>(); + + @NotNull private String version; + private int protocolVersion; + + @Nullable private CachedServerIcon favicon; + + private boolean cancelled; + + private boolean originalPlayerCount = true; + private Object[] players; + + public PaperServerListPingEvent(@NotNull StatusClient client, @NotNull String motd, int numPlayers, int maxPlayers, + @NotNull String version, int protocolVersion, @Nullable CachedServerIcon favicon) { + super(client.getAddress().getAddress(), motd, numPlayers, maxPlayers); + this.client = client; + this.numPlayers = numPlayers; + this.version = version; + this.protocolVersion = protocolVersion; + setServerIcon(favicon); + } + + /** + * Returns the {@link StatusClient} pinging the server. + * + * @return The client + */ + @NotNull + public StatusClient getClient() { + return this.client; + } + + /** + * {@inheritDoc} + * + *

Returns {@code -1} if players are hidden using + * {@link #shouldHidePlayers()}.

+ */ + @Override + public int getNumPlayers() { + if (this.hidePlayers) { + return -1; + } + + return this.numPlayers; + } + + /** + * Sets the number of players displayed in the server list. + * + *

Note that this won't have any effect if {@link #shouldHidePlayers()} + * is enabled.

+ * + * @param numPlayers The number of online players + */ + public void setNumPlayers(int numPlayers) { + if (this.numPlayers != numPlayers) { + this.numPlayers = numPlayers; + this.originalPlayerCount = false; + } + } + + /** + * {@inheritDoc} + * + *

Returns {@code -1} if players are hidden using + * {@link #shouldHidePlayers()}.

+ */ + @Override + public int getMaxPlayers() { + if (this.hidePlayers) { + return -1; + } + + return super.getMaxPlayers(); + } + + /** + * Returns whether all player related information is hidden in the server + * list. This will cause {@link #getNumPlayers()}, {@link #getMaxPlayers()} + * and {@link #getPlayerSample()} to be skipped in the response. + * + *

The Vanilla Minecraft client will display the player count as {@code ???} + * when this option is enabled.

+ * + * @return {@code true} if the player count is hidden + */ + public boolean shouldHidePlayers() { + return hidePlayers; + } + + /** + * Sets whether all player related information is hidden in the server + * list. This will cause {@link #getNumPlayers()}, {@link #getMaxPlayers()} + * and {@link #getPlayerSample()} to be skipped in the response. + * + *

The Vanilla Minecraft client will display the player count as {@code ???} + * when this option is enabled.

+ * + * @param hidePlayers {@code true} if the player count should be hidden + */ + public void setHidePlayers(boolean hidePlayers) { + this.hidePlayers = hidePlayers; + } + + /** + * Returns a mutable list of {@link PlayerProfile} that will be displayed + * as online players on the client. + * + *

The Vanilla Minecraft client will display them when hovering the + * player count with the mouse.

+ * + * @return The mutable player sample list + */ + @NotNull + public List getPlayerSample() { + return this.playerSample; + } + + /** + * Returns the version that will be sent as server version on the client. + * + * @return The server version + */ + @NotNull + public String getVersion() { + return version; + } + + /** + * Sets the version that will be sent as server version to the client. + * + * @param version The server version + */ + public void setVersion(@NotNull String version) { + this.version = requireNonNull(version, "version"); + } + + /** + * Returns the protocol version that will be sent as the protocol version + * of the server to the client. + * + * @return The protocol version of the server, or {@code -1} if the server + * has not finished initialization yet + */ + public int getProtocolVersion() { + return protocolVersion; + } + + /** + * Sets the protocol version that will be sent as the protocol version + * of the server to the client. + * + * @param protocolVersion The protocol version of the server + */ + public void setProtocolVersion(int protocolVersion) { + this.protocolVersion = protocolVersion; + } + + /** + * Gets the server icon sent to the client. + * + * @return The icon to send to the client, or {@code null} for none + */ + @Nullable + public CachedServerIcon getServerIcon() { + return this.favicon; + } + + /** + * Sets the server icon sent to the client. + * + * @param icon The icon to send to the client, or {@code null} for none + */ + @Override + public void setServerIcon(@Nullable CachedServerIcon icon) { + if (icon != null && icon.isEmpty()) { + // Represent empty icons as null + icon = null; + } + + this.favicon = icon; + } + + /** + * {@inheritDoc} + * + *

Cancelling this event will cause the connection to be closed immediately, + * without sending a response to the client.

+ */ + @Override + public boolean isCancelled() { + return this.cancelled; + } + + /** + * {@inheritDoc} + * + *

Cancelling this event will cause the connection to be closed immediately, + * without sending a response to the client.

+ */ + @Override + public void setCancelled(boolean cancel) { + this.cancelled = cancel; + } + + /** + * {@inheritDoc} + * + *

Note: For compatibility reasons, this method will return all + * online players, not just the ones referenced in {@link #getPlayerSample()}. + * Removing a player will:

+ * + *
    + *
  • Decrement the online player count (if and only if) the player + * count wasn't changed by another plugin before.
  • + *
  • Remove all entries from {@link #getPlayerSample()} that refer to + * the removed player (based on their {@link UUID}).
  • + *
+ */ + @NotNull + @Override + public Iterator iterator() { + if (this.players == null) { + this.players = getOnlinePlayers(); + } + + return new PlayerIterator(); + } + + @NotNull + protected Object[] getOnlinePlayers() { + return Bukkit.getOnlinePlayers().toArray(); + } + + @NotNull + protected Player getBukkitPlayer(@NotNull Object player) { + return (Player) player; + } + + private final class PlayerIterator implements Iterator { + + private int next; + private int current; + @Nullable private Player player; + + @Override + public boolean hasNext() { + for (; this.next < players.length; this.next++) { + if (players[this.next] != null) { + return true; + } + } + + return false; + } + + @NotNull + @Override + public Player next() { + if (!hasNext()) { + this.player = null; + throw new NoSuchElementException(); + } + + this.current = this.next++; + return this.player = getBukkitPlayer(players[this.current]); + } + + @Override + public void remove() { + if (this.player == null) { + throw new IllegalStateException(); + } + + UUID uniqueId = this.player.getUniqueId(); + this.player = null; + + // Remove player from iterator + players[this.current] = null; + + // Remove player from sample + getPlayerSample().removeIf(p -> uniqueId.equals(p.getId())); + + // Decrement player count + if (originalPlayerCount) { + numPlayers--; + } + } + } + +} diff --git a/api/src/main/java/com/destroystokyo/paper/event/server/ServerExceptionEvent.java b/api/src/main/java/com/destroystokyo/paper/event/server/ServerExceptionEvent.java new file mode 100644 index 000000000..d3b00f741 --- /dev/null +++ b/api/src/main/java/com/destroystokyo/paper/event/server/ServerExceptionEvent.java @@ -0,0 +1,41 @@ +package com.destroystokyo.paper.event.server; + +import com.google.common.base.Preconditions; +import org.apache.commons.lang.Validate; +import org.bukkit.event.Event; +import org.bukkit.event.HandlerList; +import com.destroystokyo.paper.exception.ServerException; +import org.jetbrains.annotations.NotNull; + +/** + * Called whenever an exception is thrown in a recoverable section of the server. + */ +public class ServerExceptionEvent extends Event { + private static final HandlerList handlers = new HandlerList(); + @NotNull private ServerException exception; + + public ServerExceptionEvent(@NotNull ServerException exception) { + this.exception = Preconditions.checkNotNull(exception, "exception"); + } + + /** + * Gets the wrapped exception that was thrown. + * + * @return Exception thrown + */ + @NotNull + public ServerException getException() { + return exception; + } + + @NotNull + @Override + public HandlerList getHandlers() { + return handlers; + } + + @NotNull + public static HandlerList getHandlerList() { + return handlers; + } +} diff --git a/api/src/main/java/com/destroystokyo/paper/event/server/ServerTickEndEvent.java b/api/src/main/java/com/destroystokyo/paper/event/server/ServerTickEndEvent.java new file mode 100644 index 000000000..9fd28e036 --- /dev/null +++ b/api/src/main/java/com/destroystokyo/paper/event/server/ServerTickEndEvent.java @@ -0,0 +1,59 @@ +package com.destroystokyo.paper.event.server; + +import org.bukkit.event.Event; +import org.bukkit.event.HandlerList; +import org.jetbrains.annotations.NotNull; + +/** + * Called when the server has finished ticking the main loop + */ +public class ServerTickEndEvent extends Event { + + private static final HandlerList handlers = new HandlerList(); + private final int tickNumber; + private final double tickDuration; + private final long timeEnd; + + public ServerTickEndEvent(int tickNumber, double tickDuration, long timeRemaining) { + this.tickNumber = tickNumber; + this.tickDuration = tickDuration; + this.timeEnd = System.nanoTime() + timeRemaining; + } + + /** + * @return What tick this was since start (first tick = 1) + */ + public int getTickNumber() { + return tickNumber; + } + + /** + * @return Time in milliseconds of how long this tick took + */ + public double getTickDuration() { + return tickDuration; + } + + /** + * Amount of nanoseconds remaining before the next tick should start. + * + * If this value is negative, then that means the server has exceeded the tick time limit and TPS has been lost. + * + * Method will continously return the updated time remaining value. (return value is not static) + * + * @return Amount of nanoseconds remaining before the next tick should start + */ + public long getTimeRemaining() { + return this.timeEnd - System.nanoTime(); + } + + @NotNull + public HandlerList getHandlers() { + return handlers; + } + + @NotNull + public static HandlerList getHandlerList() { + return handlers; + } +} diff --git a/api/src/main/java/com/destroystokyo/paper/event/server/ServerTickStartEvent.java b/api/src/main/java/com/destroystokyo/paper/event/server/ServerTickStartEvent.java new file mode 100644 index 000000000..eac85f1f4 --- /dev/null +++ b/api/src/main/java/com/destroystokyo/paper/event/server/ServerTickStartEvent.java @@ -0,0 +1,32 @@ +package com.destroystokyo.paper.event.server; + +import org.bukkit.event.Event; +import org.bukkit.event.HandlerList; +import org.jetbrains.annotations.NotNull; + +public class ServerTickStartEvent extends Event { + + private static final HandlerList handlers = new HandlerList(); + private final int tickNumber; + + public ServerTickStartEvent(int tickNumber) { + this.tickNumber = tickNumber; + } + + /** + * @return What tick this is going be since start (first tick = 1) + */ + public int getTickNumber() { + return tickNumber; + } + + @NotNull + public HandlerList getHandlers() { + return handlers; + } + + @NotNull + public static HandlerList getHandlerList() { + return handlers; + } +} diff --git a/api/src/main/java/com/destroystokyo/paper/event/server/WhitelistToggleEvent.java b/api/src/main/java/com/destroystokyo/paper/event/server/WhitelistToggleEvent.java new file mode 100644 index 000000000..fdd5eedb2 --- /dev/null +++ b/api/src/main/java/com/destroystokyo/paper/event/server/WhitelistToggleEvent.java @@ -0,0 +1,40 @@ +package com.destroystokyo.paper.event.server; + +import org.bukkit.event.Event; +import org.bukkit.event.HandlerList; +import org.jetbrains.annotations.NotNull; + +/** + * This event is fired when whitelist is toggled + * + * @author Mark Vainomaa + */ +public class WhitelistToggleEvent extends Event { + private static final HandlerList handlers = new HandlerList(); + + private boolean enabled; + + public WhitelistToggleEvent(boolean enabled) { + this.enabled = enabled; + } + + /** + * Gets whether whitelist is going to be enabled or not + * + * @return Whether whitelist is going to be enabled or not + */ + public boolean isEnabled() { + return enabled; + } + + @NotNull + @Override + public HandlerList getHandlers() { + return handlers; + } + + @NotNull + public static HandlerList getHandlerList() { + return handlers; + } +} diff --git a/api/src/main/java/com/destroystokyo/paper/exception/ServerCommandException.java b/api/src/main/java/com/destroystokyo/paper/exception/ServerCommandException.java new file mode 100644 index 000000000..6fb39af04 --- /dev/null +++ b/api/src/main/java/com/destroystokyo/paper/exception/ServerCommandException.java @@ -0,0 +1,64 @@ +package com.destroystokyo.paper.exception; + +import org.bukkit.command.Command; +import org.bukkit.command.CommandSender; + +import static com.google.common.base.Preconditions.checkNotNull; + +/** + * Thrown when a command throws an exception + */ +public class ServerCommandException extends ServerException { + + private final Command command; + private final CommandSender commandSender; + private final String[] arguments; + + public ServerCommandException(String message, Throwable cause, Command command, CommandSender commandSender, String[] arguments) { + super(message, cause); + this.commandSender = checkNotNull(commandSender, "commandSender"); + this.arguments = checkNotNull(arguments, "arguments"); + this.command = checkNotNull(command, "command"); + } + + public ServerCommandException(Throwable cause, Command command, CommandSender commandSender, String[] arguments) { + super(cause); + this.commandSender = checkNotNull(commandSender, "commandSender"); + this.arguments = checkNotNull(arguments, "arguments"); + this.command = checkNotNull(command, "command"); + } + + protected ServerCommandException(String message, Throwable cause, boolean enableSuppression, boolean writableStackTrace, Command command, CommandSender commandSender, String[] arguments) { + super(message, cause, enableSuppression, writableStackTrace); + this.commandSender = checkNotNull(commandSender, "commandSender"); + this.arguments = checkNotNull(arguments, "arguments"); + this.command = checkNotNull(command, "command"); + } + + /** + * Gets the command which threw the exception + * + * @return exception throwing command + */ + public Command getCommand() { + return command; + } + + /** + * Gets the command sender which executed the command request + * + * @return command sender of exception thrown command request + */ + public CommandSender getCommandSender() { + return commandSender; + } + + /** + * Gets the arguments which threw the exception for the command + * + * @return arguments of exception thrown command request + */ + public String[] getArguments() { + return arguments; + } +} diff --git a/api/src/main/java/com/destroystokyo/paper/exception/ServerEventException.java b/api/src/main/java/com/destroystokyo/paper/exception/ServerEventException.java new file mode 100644 index 000000000..410b24139 --- /dev/null +++ b/api/src/main/java/com/destroystokyo/paper/exception/ServerEventException.java @@ -0,0 +1,52 @@ +package com.destroystokyo.paper.exception; + +import org.bukkit.event.Event; +import org.bukkit.event.Listener; +import org.bukkit.plugin.Plugin; + +import static com.google.common.base.Preconditions.*; + +/** + * Exception thrown when a server event listener throws an exception + */ +public class ServerEventException extends ServerPluginException { + + private final Listener listener; + private final Event event; + + public ServerEventException(String message, Throwable cause, Plugin responsiblePlugin, Listener listener, Event event) { + super(message, cause, responsiblePlugin); + this.listener = checkNotNull(listener, "listener"); + this.event = checkNotNull(event, "event"); + } + + public ServerEventException(Throwable cause, Plugin responsiblePlugin, Listener listener, Event event) { + super(cause, responsiblePlugin); + this.listener = checkNotNull(listener, "listener"); + this.event = checkNotNull(event, "event"); + } + + protected ServerEventException(String message, Throwable cause, boolean enableSuppression, boolean writableStackTrace, Plugin responsiblePlugin, Listener listener, Event event) { + super(message, cause, enableSuppression, writableStackTrace, responsiblePlugin); + this.listener = checkNotNull(listener, "listener"); + this.event = checkNotNull(event, "event"); + } + + /** + * Gets the listener which threw the exception + * + * @return event listener + */ + public Listener getListener() { + return listener; + } + + /** + * Gets the event which caused the exception + * + * @return event + */ + public Event getEvent() { + return event; + } +} diff --git a/api/src/main/java/com/destroystokyo/paper/exception/ServerException.java b/api/src/main/java/com/destroystokyo/paper/exception/ServerException.java new file mode 100644 index 000000000..c06ea3942 --- /dev/null +++ b/api/src/main/java/com/destroystokyo/paper/exception/ServerException.java @@ -0,0 +1,23 @@ +package com.destroystokyo.paper.exception; + +/** + * Wrapper exception for all exceptions that are thrown by the server. + */ +public class ServerException extends Exception { + + public ServerException(String message) { + super(message); + } + + public ServerException(String message, Throwable cause) { + super(message, cause); + } + + public ServerException(Throwable cause) { + super(cause); + } + + protected ServerException(String message, Throwable cause, boolean enableSuppression, boolean writableStackTrace) { + super(message, cause, enableSuppression, writableStackTrace); + } +} diff --git a/api/src/main/java/com/destroystokyo/paper/exception/ServerInternalException.java b/api/src/main/java/com/destroystokyo/paper/exception/ServerInternalException.java new file mode 100644 index 000000000..e762ed0db --- /dev/null +++ b/api/src/main/java/com/destroystokyo/paper/exception/ServerInternalException.java @@ -0,0 +1,35 @@ +package com.destroystokyo.paper.exception; + +import org.bukkit.Bukkit; +import com.destroystokyo.paper.event.server.ServerExceptionEvent; + +/** + * Thrown when the internal server throws a recoverable exception. + */ +public class ServerInternalException extends ServerException { + + public ServerInternalException(String message) { + super(message); + } + + public ServerInternalException(String message, Throwable cause) { + super(message, cause); + } + + public ServerInternalException(Throwable cause) { + super(cause); + } + + protected ServerInternalException(String message, Throwable cause, boolean enableSuppression, boolean writableStackTrace) { + super(message, cause, enableSuppression, writableStackTrace); + } + + public static void reportInternalException(Throwable cause) { + try { + Bukkit.getPluginManager().callEvent(new ServerExceptionEvent(new ServerInternalException(cause))); + ; + } catch (Throwable t) { + t.printStackTrace(); // Don't want to rethrow! + } + } +} diff --git a/api/src/main/java/com/destroystokyo/paper/exception/ServerPluginEnableDisableException.java b/api/src/main/java/com/destroystokyo/paper/exception/ServerPluginEnableDisableException.java new file mode 100644 index 000000000..f016ba3b1 --- /dev/null +++ b/api/src/main/java/com/destroystokyo/paper/exception/ServerPluginEnableDisableException.java @@ -0,0 +1,20 @@ +package com.destroystokyo.paper.exception; + +import org.bukkit.plugin.Plugin; + +/** + * Thrown whenever there is an exception with any enabling or disabling of plugins. + */ +public class ServerPluginEnableDisableException extends ServerPluginException { + public ServerPluginEnableDisableException(String message, Throwable cause, Plugin responsiblePlugin) { + super(message, cause, responsiblePlugin); + } + + public ServerPluginEnableDisableException(Throwable cause, Plugin responsiblePlugin) { + super(cause, responsiblePlugin); + } + + protected ServerPluginEnableDisableException(String message, Throwable cause, boolean enableSuppression, boolean writableStackTrace, Plugin responsiblePlugin) { + super(message, cause, enableSuppression, writableStackTrace, responsiblePlugin); + } +} \ No newline at end of file diff --git a/api/src/main/java/com/destroystokyo/paper/exception/ServerPluginException.java b/api/src/main/java/com/destroystokyo/paper/exception/ServerPluginException.java new file mode 100644 index 000000000..6defac287 --- /dev/null +++ b/api/src/main/java/com/destroystokyo/paper/exception/ServerPluginException.java @@ -0,0 +1,38 @@ +package com.destroystokyo.paper.exception; + +import com.google.common.base.Preconditions; +import org.apache.commons.lang.Validate; +import org.bukkit.plugin.Plugin; + +import static com.google.common.base.Preconditions.*; + +/** + * Wrapper exception for all cases to which a plugin can be immediately blamed for + */ +public class ServerPluginException extends ServerException { + public ServerPluginException(String message, Throwable cause, Plugin responsiblePlugin) { + super(message, cause); + this.responsiblePlugin = checkNotNull(responsiblePlugin, "responsiblePlugin"); + } + + public ServerPluginException(Throwable cause, Plugin responsiblePlugin) { + super(cause); + this.responsiblePlugin = checkNotNull(responsiblePlugin, "responsiblePlugin"); + } + + protected ServerPluginException(String message, Throwable cause, boolean enableSuppression, boolean writableStackTrace, Plugin responsiblePlugin) { + super(message, cause, enableSuppression, writableStackTrace); + this.responsiblePlugin = checkNotNull(responsiblePlugin, "responsiblePlugin"); + } + + private final Plugin responsiblePlugin; + + /** + * Gets the plugin which is directly responsible for the exception being thrown + * + * @return plugin which is responsible for the exception throw + */ + public Plugin getResponsiblePlugin() { + return responsiblePlugin; + } +} diff --git a/api/src/main/java/com/destroystokyo/paper/exception/ServerPluginMessageException.java b/api/src/main/java/com/destroystokyo/paper/exception/ServerPluginMessageException.java new file mode 100644 index 000000000..89e132525 --- /dev/null +++ b/api/src/main/java/com/destroystokyo/paper/exception/ServerPluginMessageException.java @@ -0,0 +1,64 @@ +package com.destroystokyo.paper.exception; + +import org.bukkit.entity.Player; +import org.bukkit.plugin.Plugin; + +import static com.google.common.base.Preconditions.*; + +/** + * Thrown when an incoming plugin message channel throws an exception + */ +public class ServerPluginMessageException extends ServerPluginException { + + private final Player player; + private final String channel; + private final byte[] data; + + public ServerPluginMessageException(String message, Throwable cause, Plugin responsiblePlugin, Player player, String channel, byte[] data) { + super(message, cause, responsiblePlugin); + this.player = checkNotNull(player, "player"); + this.channel = checkNotNull(channel, "channel"); + this.data = checkNotNull(data, "data"); + } + + public ServerPluginMessageException(Throwable cause, Plugin responsiblePlugin, Player player, String channel, byte[] data) { + super(cause, responsiblePlugin); + this.player = checkNotNull(player, "player"); + this.channel = checkNotNull(channel, "channel"); + this.data = checkNotNull(data, "data"); + } + + protected ServerPluginMessageException(String message, Throwable cause, boolean enableSuppression, boolean writableStackTrace, Plugin responsiblePlugin, Player player, String channel, byte[] data) { + super(message, cause, enableSuppression, writableStackTrace, responsiblePlugin); + this.player = checkNotNull(player, "player"); + this.channel = checkNotNull(channel, "channel"); + this.data = checkNotNull(data, "data"); + } + + /** + * Gets the channel to which the error occurred from recieving data from + * + * @return exception channel + */ + public String getChannel() { + return channel; + } + + /** + * Gets the data to which the error occurred from + * + * @return exception data + */ + public byte[] getData() { + return data; + } + + /** + * Gets the player which the plugin message causing the exception originated from + * + * @return exception player + */ + public Player getPlayer() { + return player; + } +} diff --git a/api/src/main/java/com/destroystokyo/paper/exception/ServerSchedulerException.java b/api/src/main/java/com/destroystokyo/paper/exception/ServerSchedulerException.java new file mode 100644 index 000000000..2d0b2d4a9 --- /dev/null +++ b/api/src/main/java/com/destroystokyo/paper/exception/ServerSchedulerException.java @@ -0,0 +1,37 @@ +package com.destroystokyo.paper.exception; + +import org.bukkit.scheduler.BukkitTask; + +import static com.google.common.base.Preconditions.checkNotNull; + +/** + * Thrown when a plugin's scheduler fails with an exception + */ +public class ServerSchedulerException extends ServerPluginException { + + private final BukkitTask task; + + public ServerSchedulerException(String message, Throwable cause, BukkitTask task) { + super(message, cause, task.getOwner()); + this.task = checkNotNull(task, "task"); + } + + public ServerSchedulerException(Throwable cause, BukkitTask task) { + super(cause, task.getOwner()); + this.task = checkNotNull(task, "task"); + } + + protected ServerSchedulerException(String message, Throwable cause, boolean enableSuppression, boolean writableStackTrace, BukkitTask task) { + super(message, cause, enableSuppression, writableStackTrace, task.getOwner()); + this.task = checkNotNull(task, "task"); + } + + /** + * Gets the task which threw the exception + * + * @return exception throwing task + */ + public BukkitTask getTask() { + return task; + } +} diff --git a/api/src/main/java/com/destroystokyo/paper/exception/ServerTabCompleteException.java b/api/src/main/java/com/destroystokyo/paper/exception/ServerTabCompleteException.java new file mode 100644 index 000000000..5582999fe --- /dev/null +++ b/api/src/main/java/com/destroystokyo/paper/exception/ServerTabCompleteException.java @@ -0,0 +1,22 @@ +package com.destroystokyo.paper.exception; + +import org.bukkit.command.Command; +import org.bukkit.command.CommandSender; + +/** + * Called when a tab-complete request throws an exception + */ +public class ServerTabCompleteException extends ServerCommandException { + + public ServerTabCompleteException(String message, Throwable cause, Command command, CommandSender commandSender, String[] arguments) { + super(message, cause, command, commandSender, arguments); + } + + public ServerTabCompleteException(Throwable cause, Command command, CommandSender commandSender, String[] arguments) { + super(cause, command, commandSender, arguments); + } + + protected ServerTabCompleteException(String message, Throwable cause, boolean enableSuppression, boolean writableStackTrace, Command command, CommandSender commandSender, String[] arguments) { + super(message, cause, enableSuppression, writableStackTrace, command, commandSender, arguments); + } +} diff --git a/api/src/main/java/com/destroystokyo/paper/inventory/ItemStackRecipeChoice.java b/api/src/main/java/com/destroystokyo/paper/inventory/ItemStackRecipeChoice.java new file mode 100644 index 000000000..43e6576b1 --- /dev/null +++ b/api/src/main/java/com/destroystokyo/paper/inventory/ItemStackRecipeChoice.java @@ -0,0 +1,51 @@ +package com.destroystokyo.paper.inventory; + +import org.bukkit.inventory.ItemStack; +import org.bukkit.inventory.RecipeChoice; + +import java.util.ArrayList; +import java.util.List; + +/** + * Allows crafting Items that require full matching itemstacks to complete the recipe for custom items + * @deprecated Draft API + */ +@Deprecated +public class ItemStackRecipeChoice implements RecipeChoice { + + protected final List choices = new ArrayList<>(); + + public ItemStackRecipeChoice(ItemStack choices) { + this.choices.add(choices); + } + + public ItemStackRecipeChoice(List choices) { + this.choices.addAll(choices); + } + + @Override + public ItemStack getItemStack() { + return choices.isEmpty() ? null : choices.get(0); + } + + @Override + public RecipeChoice clone() { + try { + ItemStackRecipeChoice clone = (ItemStackRecipeChoice) super.clone(); + clone.choices.addAll(this.choices); + return clone; + } catch (CloneNotSupportedException ex) { + throw new AssertionError(ex); + } + } + + @Override + public boolean test(ItemStack itemStack) { + for (ItemStack stack : choices) { + if (stack.isSimilar(itemStack)) { + return true; + } + } + return false; + } +} diff --git a/api/src/main/java/com/destroystokyo/paper/inventory/meta/ArmorStandMeta.java b/api/src/main/java/com/destroystokyo/paper/inventory/meta/ArmorStandMeta.java new file mode 100644 index 000000000..7e4acfff1 --- /dev/null +++ b/api/src/main/java/com/destroystokyo/paper/inventory/meta/ArmorStandMeta.java @@ -0,0 +1,78 @@ +package com.destroystokyo.paper.inventory.meta; + +import org.bukkit.inventory.meta.ItemMeta; + +public interface ArmorStandMeta extends ItemMeta { + + /** + * Gets whether the ArmorStand should be invisible when spawned + * + * @return true if this should be invisible + */ + boolean isInvisible(); + + /** + * Gets whether this ArmorStand should have no base plate when spawned + * + * @return true if it will not have a base plate + */ + boolean hasNoBasePlate(); + + /** + * Gets whether this ArmorStand should show arms when spawned + * + * @return true if it will show arms + */ + boolean shouldShowArms(); + + /** + * Gets whether this ArmorStand will be small when spawned + * + * @return true if it will be small + */ + boolean isSmall(); + + /** + * Gets whether this ArmorStand will be a marker when spawned + * The exact details of this flag are an implementation detail + * + * @return true if it will be a marker + */ + boolean isMarker(); + + /** + * Sets that this ArmorStand should be invisible when spawned + * + * @param invisible true if set invisible + */ + void setInvisible(boolean invisible); + + /** + * Sets that this ArmorStand should have no base plate when spawned + * + * @param noBasePlate true if no base plate + */ + void setNoBasePlate(boolean noBasePlate); + + /** + * Sets that this ArmorStand should show arms when spawned + * + * @param showArms true if show arms + */ + void setShowArms(boolean showArms); + + /** + * Sets that this ArmorStand should be small when spawned + * + * @param small true if small + */ + void setSmall(boolean small); + + /** + * Sets that this ArmorStand should be a marker when spawned + * The exact details of this flag are an implementation detail + * + * @param marker true if a marker + */ + void setMarker(boolean marker); +} diff --git a/api/src/main/java/com/destroystokyo/paper/loottable/LootableBlockInventory.java b/api/src/main/java/com/destroystokyo/paper/loottable/LootableBlockInventory.java new file mode 100644 index 000000000..92d7b853a --- /dev/null +++ b/api/src/main/java/com/destroystokyo/paper/loottable/LootableBlockInventory.java @@ -0,0 +1,17 @@ +package com.destroystokyo.paper.loottable; + +import org.bukkit.block.Block; +import org.jetbrains.annotations.NotNull; + +/** + * Represents an Inventory that can generate loot, such as Chests inside of Fortresses and Mineshafts + */ +public interface LootableBlockInventory extends LootableInventory { + + /** + * Gets the block that is lootable + * @return The Block + */ + @NotNull + Block getBlock(); +} diff --git a/api/src/main/java/com/destroystokyo/paper/loottable/LootableEntityInventory.java b/api/src/main/java/com/destroystokyo/paper/loottable/LootableEntityInventory.java new file mode 100644 index 000000000..b387894fe --- /dev/null +++ b/api/src/main/java/com/destroystokyo/paper/loottable/LootableEntityInventory.java @@ -0,0 +1,17 @@ +package com.destroystokyo.paper.loottable; + +import org.bukkit.entity.Entity; +import org.jetbrains.annotations.NotNull; + +/** + * Represents an Inventory that can generate loot, such as Minecarts inside of Mineshafts + */ +public interface LootableEntityInventory extends LootableInventory { + + /** + * Gets the entity that is lootable + * @return The Entity + */ + @NotNull + Entity getEntity(); +} diff --git a/api/src/main/java/com/destroystokyo/paper/loottable/LootableInventory.java b/api/src/main/java/com/destroystokyo/paper/loottable/LootableInventory.java new file mode 100644 index 000000000..97815eeb2 --- /dev/null +++ b/api/src/main/java/com/destroystokyo/paper/loottable/LootableInventory.java @@ -0,0 +1,116 @@ +package com.destroystokyo.paper.loottable; + +import org.bukkit.entity.Player; +import org.bukkit.loot.Lootable; + +import java.util.UUID; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +/** + * Represents an Inventory that contains a Loot Table associated to it that will + * automatically fill on first open. + * + * A new feature and API is provided to support automatically refreshing the contents + * of the inventory based on that Loot Table after a configurable amount of time has passed. + * + * The behavior of how the Inventory is filled based on the loot table may vary based + * on Minecraft versions and the Loot Table feature. + */ +public interface LootableInventory extends Lootable { + + /** + * Server owners have to enable whether or not an object in a world should refill + * + * @return If the world this inventory is currently in has Replenishable Lootables enabled + */ + boolean isRefillEnabled(); + + /** + * Whether or not this object has ever been filled + * @return Has ever been filled + */ + boolean hasBeenFilled(); + + /** + * Has this player ever looted this block + * @param player The player to check + * @return Whether or not this player has looted this block + */ + default boolean hasPlayerLooted(@NotNull Player player) { + return hasPlayerLooted(player.getUniqueId()); + } + + /** + * Has this player ever looted this block + * @param player The player to check + * @return Whether or not this player has looted this block + */ + boolean hasPlayerLooted(@NotNull UUID player); + + /** + * Gets the timestamp, in milliseconds, of when the player last looted this object + * + * @param player The player to check + * @return Timestamp last looted, or null if player has not looted this object + */ + @Nullable + default Long getLastLooted(@NotNull Player player) { + return getLastLooted(player.getUniqueId()); + } + + /** + * Gets the timestamp, in milliseconds, of when the player last looted this object + * + * @param player The player to check + * @return Timestamp last looted, or null if player has not looted this object + */ + @Nullable + Long getLastLooted(@NotNull UUID player); + + /** + * Change the state of whether or not a player has looted this block + * @param player The player to change state for + * @param looted true to add player to looted list, false to remove + * @return The previous state of whether the player had looted this or not + */ + default boolean setHasPlayerLooted(@NotNull Player player, boolean looted) { + return setHasPlayerLooted(player.getUniqueId(), looted); + } + + /** + * Change the state of whether or not a player has looted this block + * @param player The player to change state for + * @param looted true to add player to looted list, false to remove + * @return The previous state of whether the player had looted this or not + */ + boolean setHasPlayerLooted(@NotNull UUID player, boolean looted); + + /** + * Returns Whether or not this object has been filled and now has a pending refill + * @return Has pending refill + */ + boolean hasPendingRefill(); + + /** + * Gets the timestamp in milliseconds that the Lootable object was last refilled + * + * @return -1 if it was never refilled, or timestamp in milliseconds + */ + long getLastFilled(); + + /** + * Gets the timestamp in milliseconds that the Lootable object will refill + * + * @return -1 if it is not scheduled for refill, or timestamp in milliseconds + */ + long getNextRefill(); + + /** + * Sets the timestamp in milliseconds of the next refill for this object + * + * @param refillAt timestamp in milliseconds. -1 to clear next refill + * @return The previous scheduled time to refill, or -1 if was not scheduled + */ + long setNextRefill(long refillAt); +} diff --git a/api/src/main/java/com/destroystokyo/paper/loottable/LootableInventoryReplenishEvent.java b/api/src/main/java/com/destroystokyo/paper/loottable/LootableInventoryReplenishEvent.java new file mode 100644 index 000000000..fd184f13f --- /dev/null +++ b/api/src/main/java/com/destroystokyo/paper/loottable/LootableInventoryReplenishEvent.java @@ -0,0 +1,45 @@ +package com.destroystokyo.paper.loottable; + +import org.bukkit.entity.Player; +import org.bukkit.event.Cancellable; +import org.bukkit.event.HandlerList; +import org.bukkit.event.player.PlayerEvent; +import org.jetbrains.annotations.NotNull; + +public class LootableInventoryReplenishEvent extends PlayerEvent implements Cancellable { + @NotNull private final LootableInventory inventory; + + public LootableInventoryReplenishEvent(@NotNull Player player, @NotNull LootableInventory inventory) { + super(player); + this.inventory = inventory; + } + + @NotNull + public LootableInventory getInventory() { + return inventory; + } + + private static final HandlerList handlers = new HandlerList(); + + @NotNull + public HandlerList getHandlers() { + return handlers; + } + + @NotNull + public static HandlerList getHandlerList() { + return handlers; + } + + private boolean cancelled = false; + + @Override + public boolean isCancelled() { + return cancelled; + } + + @Override + public void setCancelled(boolean cancel) { + cancelled = cancel; + } +} diff --git a/api/src/main/java/com/destroystokyo/paper/network/NetworkClient.java b/api/src/main/java/com/destroystokyo/paper/network/NetworkClient.java new file mode 100644 index 000000000..7b2af1bd7 --- /dev/null +++ b/api/src/main/java/com/destroystokyo/paper/network/NetworkClient.java @@ -0,0 +1,41 @@ +package com.destroystokyo.paper.network; + +import java.net.InetSocketAddress; + +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +/** + * Represents a client connected to the server. + */ +public interface NetworkClient { + + /** + * Returns the socket address of the client. + * + * @return The client's socket address + */ + @NotNull + InetSocketAddress getAddress(); + + /** + * Returns the protocol version of the client. + * + * @return The client's protocol version, or {@code -1} if unknown + * @see List of protocol + * version numbers + */ + int getProtocolVersion(); + + /** + * Returns the virtual host the client is connected to. + * + *

The virtual host refers to the hostname/port the client used to + * connect to the server.

+ * + * @return The client's virtual host, or {@code null} if unknown + */ + @Nullable + InetSocketAddress getVirtualHost(); + +} diff --git a/api/src/main/java/com/destroystokyo/paper/network/StatusClient.java b/api/src/main/java/com/destroystokyo/paper/network/StatusClient.java new file mode 100644 index 000000000..ffda9f6a8 --- /dev/null +++ b/api/src/main/java/com/destroystokyo/paper/network/StatusClient.java @@ -0,0 +1,25 @@ +package com.destroystokyo.paper.network; + +import com.destroystokyo.paper.event.server.PaperServerListPingEvent; + +/** + * Represents a client requesting the current status from the server (e.g. from + * the server list). + * + * @see PaperServerListPingEvent + */ +public interface StatusClient extends NetworkClient { + + /** + * Returns whether the client is using an older version that doesn't + * support all of the features in {@link PaperServerListPingEvent}. + * + *

For Vanilla, this returns {@code true} for all clients older than 1.7.

+ * + * @return {@code true} if the client is using legacy ping + */ + default boolean isLegacy() { + return false; + } + +} diff --git a/api/src/main/java/com/destroystokyo/paper/profile/PlayerProfile.java b/api/src/main/java/com/destroystokyo/paper/profile/PlayerProfile.java new file mode 100644 index 000000000..476151d2a --- /dev/null +++ b/api/src/main/java/com/destroystokyo/paper/profile/PlayerProfile.java @@ -0,0 +1,145 @@ +package com.destroystokyo.paper.profile; + +import java.util.Collection; +import java.util.Set; +import java.util.UUID; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +/** + * Represents a players profile for the game, such as UUID, Name, and textures. + */ +public interface PlayerProfile { + + /** + * @return The players name, if set + */ + @Nullable + String getName(); + + /** + * Sets this profiles Name + * + * @param name The new Name + * @return The previous Name + */ + @NotNull + String setName(@Nullable String name); + + /** + * @return The players unique identifier, if set + */ + @Nullable UUID getId(); + + /** + * Sets this profiles UUID + * + * @param uuid The new UUID + * @return The previous UUID + */ + @Nullable + UUID setId(@Nullable UUID uuid); + + /** + * @return A Mutable set of this players properties, such as textures. + * Values specified here are subject to implementation details. + */ + @NotNull Set getProperties(); + + /** + * Check if the Profile has the specified property + * @param property Property name to check + * @return If the property is set + */ + boolean hasProperty(@Nullable String property); + + /** + * Sets a property. If the property already exists, the previous one will be replaced + * @param property Property to set. + */ + void setProperty(@NotNull ProfileProperty property); + + /** + * Sets multiple properties. If any of the set properties already exist, it will be replaced + * @param properties The properties to set + */ + void setProperties(@NotNull Collection properties); + + /** + * Removes a specific property from this profile + * @param property The property to remove + * @return If a property was removed + */ + boolean removeProperty(@Nullable String property); + + /** + * Removes a specific property from this profile + * @param property The property to remove + * @return If a property was removed + */ + default boolean removeProperty(@NotNull ProfileProperty property) { + return removeProperty(property.getName()); + } + + /** + * Removes all properties in the collection + * @param properties The properties to remove + * @return If any property was removed + */ + default boolean removeProperties(@NotNull Collection properties) { + boolean removed = false; + for (ProfileProperty property : properties) { + if (removeProperty(property)) { + removed = true; + } + } + return removed; + } + + /** + * Clears all properties on this profile + */ + void clearProperties(); + + /** + * @return If the profile is now complete (has UUID and Name) + */ + boolean isComplete(); + + /** + * Like {@link #complete(boolean)} but will try only from cache, and not make network calls + * Does not account for textures. + * + * @return If the profile is now complete (has UUID and Name) + */ + boolean completeFromCache(); + + /** + * If this profile is not complete, then make the API call to complete it. + * This is a blocking operation and should be done asynchronously. + * + * This will also complete textures. If you do not want to load textures, use {{@link #complete(boolean)}} + * @return If the profile is now complete (has UUID and Name) (if you get rate limited, this operation may fail) + */ + default boolean complete() { + return complete(true); + } + + /** + * If this profile is not complete, then make the API call to complete it. + * This is a blocking operation and should be done asynchronously. + * + * Optionally will also fill textures. + * @param textures controls if we should fill the profile with texture properties + * @return If the profile is now complete (has UUID and Name) (if you get rate limited, this operation may fail) + */ + boolean complete(boolean textures); + + /** + * Whether or not this Profile has textures associated to it + * @return If has a textures property + */ + default boolean hasTextures() { + return hasProperty("textures"); + } +} diff --git a/api/src/main/java/com/destroystokyo/paper/profile/ProfileProperty.java b/api/src/main/java/com/destroystokyo/paper/profile/ProfileProperty.java new file mode 100644 index 000000000..7b3b6ef53 --- /dev/null +++ b/api/src/main/java/com/destroystokyo/paper/profile/ProfileProperty.java @@ -0,0 +1,72 @@ +package com.destroystokyo.paper.profile; + +import com.google.common.base.Preconditions; + +import java.util.Objects; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +/** + * Represents a property on a {@link PlayerProfile} + */ +public class ProfileProperty { + private final String name; + private final String value; + private final String signature; + + public ProfileProperty(@NotNull String name, @NotNull String value) { + this(name, value, null); + } + + public ProfileProperty(@NotNull String name, @NotNull String value, @Nullable String signature) { + this.name = Preconditions.checkNotNull(name, "ProfileProperty name can not be null"); + this.value = Preconditions.checkNotNull(value, "ProfileProperty value can not be null"); + this.signature = signature; + } + + /** + * @return The property name, ie "textures" + */ + @NotNull + public String getName() { + return name; + } + + /** + * @return The property value, likely to be base64 encoded + */ + @NotNull + public String getValue() { + return value; + } + + /** + * @return A signature from Mojang for signed properties + */ + @Nullable + public String getSignature() { + return signature; + } + + /** + * @return If this property has a signature or not + */ + public boolean isSigned() { + return this.signature != null; + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + ProfileProperty that = (ProfileProperty) o; + return Objects.equals(name, that.name) && + Objects.equals(value, that.value) && + Objects.equals(signature, that.signature); + } + + @Override + public int hashCode() { + return Objects.hash(name); + } +} diff --git a/api/src/main/java/com/destroystokyo/paper/util/SneakyThrow.java b/api/src/main/java/com/destroystokyo/paper/util/SneakyThrow.java new file mode 100644 index 000000000..9db0056ab --- /dev/null +++ b/api/src/main/java/com/destroystokyo/paper/util/SneakyThrow.java @@ -0,0 +1,16 @@ +package com.destroystokyo.paper.util; + +import org.jetbrains.annotations.NotNull; + +public class SneakyThrow { + + public static void sneaky(@NotNull Throwable exception) { + SneakyThrow.throwSneaky(exception); + } + + @SuppressWarnings("unchecked") + private static void throwSneaky(@NotNull Throwable exception) throws T { + throw (T) exception; + } + +} diff --git a/api/src/main/java/com/destroystokyo/paper/utils/CachedSizeConcurrentLinkedQueue.java b/api/src/main/java/com/destroystokyo/paper/utils/CachedSizeConcurrentLinkedQueue.java new file mode 100644 index 000000000..5bb677ce5 --- /dev/null +++ b/api/src/main/java/com/destroystokyo/paper/utils/CachedSizeConcurrentLinkedQueue.java @@ -0,0 +1,34 @@ +package com.destroystokyo.paper.utils; + +import java.util.concurrent.ConcurrentLinkedQueue; +import java.util.concurrent.atomic.LongAdder; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +public class CachedSizeConcurrentLinkedQueue extends ConcurrentLinkedQueue { + private final LongAdder cachedSize = new LongAdder(); + + @Override + public boolean add(@NotNull E e) { + boolean result = super.add(e); + if (result) { + cachedSize.increment(); + } + return result; + } + + @Nullable + @Override + public E poll() { + E result = super.poll(); + if (result != null) { + cachedSize.decrement(); + } + return result; + } + + @Override + public int size() { + return cachedSize.intValue(); + } +} diff --git a/api/src/main/java/com/destroystokyo/paper/utils/PaperPluginLogger.java b/api/src/main/java/com/destroystokyo/paper/utils/PaperPluginLogger.java new file mode 100644 index 000000000..76f2cb9cd --- /dev/null +++ b/api/src/main/java/com/destroystokyo/paper/utils/PaperPluginLogger.java @@ -0,0 +1,41 @@ +package com.destroystokyo.paper.utils; + +import org.bukkit.plugin.PluginDescriptionFile; + +import java.util.logging.Level; +import java.util.logging.LogManager; +import java.util.logging.Logger; +import org.jetbrains.annotations.NotNull; + +/** + * Prevents plugins (e.g. Essentials) from changing the parent of the plugin logger. + */ +public class PaperPluginLogger extends Logger { + + @NotNull + public static Logger getLogger(@NotNull PluginDescriptionFile description) { + Logger logger = new PaperPluginLogger(description); + if (!LogManager.getLogManager().addLogger(logger)) { + // Disable this if it's going to happen across reloads anyways... + //logger.log(Level.WARNING, "Could not insert plugin logger - one was already found: {}", LogManager.getLogManager().getLogger(this.getName())); + logger = LogManager.getLogManager().getLogger(description.getPrefix() != null ? description.getPrefix() : description.getName()); + } + + return logger; + } + + private PaperPluginLogger(@NotNull PluginDescriptionFile description) { + super(description.getPrefix() != null ? description.getPrefix() : description.getName(), null); + } + + @Override + public void setParent(@NotNull Logger parent) { + if (getParent() != null) { + warning("Ignoring attempt to change parent of plugin logger"); + } else { + this.log(Level.FINE, "Setting plugin logger parent to {0}", parent); + super.setParent(parent); + } + } + +} diff --git a/api/src/main/java/com/destroystokyo/paper/utils/UnsafeUtils.java b/api/src/main/java/com/destroystokyo/paper/utils/UnsafeUtils.java new file mode 100644 index 000000000..72e48e8ef --- /dev/null +++ b/api/src/main/java/com/destroystokyo/paper/utils/UnsafeUtils.java @@ -0,0 +1,35 @@ +package com.destroystokyo.paper.utils; + +import org.jetbrains.annotations.Nullable; +import sun.misc.Unsafe; + +import java.lang.reflect.Field; + +public class UnsafeUtils { + private UnsafeUtils() {} + + private static final Unsafe UNSAFE; + static { + Unsafe unsafe; + try { + Class c = Class.forName("sun.misc.Unsafe"); + Field f = c.getDeclaredField("theUnsafe"); + f.setAccessible(true); + unsafe = (Unsafe) f.get(null); + } catch (ClassNotFoundException | NoSuchFieldException | SecurityException e) { + unsafe = null; + } catch (IllegalAccessException e) { + throw new AssertionError(e); + } + UNSAFE = unsafe; + } + + public static boolean isUnsafeSupported() { + return UNSAFE != null; + } + + @Nullable + public static Unsafe getUnsafe() { + return UNSAFE; + } +} diff --git a/src/api/main/java/io/akarin/server/core/AkarinGlobalConfig.java b/api/src/main/java/io/akarin/server/core/AkarinGlobalConfig.java similarity index 100% rename from src/api/main/java/io/akarin/server/core/AkarinGlobalConfig.java rename to api/src/main/java/io/akarin/server/core/AkarinGlobalConfig.java diff --git a/api/src/main/java/org/bukkit/Achievement.java b/api/src/main/java/org/bukkit/Achievement.java new file mode 100644 index 000000000..edc388ffe --- /dev/null +++ b/api/src/main/java/org/bukkit/Achievement.java @@ -0,0 +1,75 @@ +package org.bukkit; + +import org.jetbrains.annotations.Nullable; + +/** + * Represents an achievement, which may be given to players. + * @deprecated future versions of Minecraft do not have achievements + */ +@Deprecated +public enum Achievement { + OPEN_INVENTORY, + MINE_WOOD (OPEN_INVENTORY), + BUILD_WORKBENCH (MINE_WOOD), + BUILD_PICKAXE (BUILD_WORKBENCH), + BUILD_FURNACE (BUILD_PICKAXE), + ACQUIRE_IRON (BUILD_FURNACE), + BUILD_HOE (BUILD_WORKBENCH), + MAKE_BREAD (BUILD_HOE), + BAKE_CAKE (BUILD_HOE), + BUILD_BETTER_PICKAXE (BUILD_PICKAXE), + COOK_FISH (BUILD_FURNACE), + ON_A_RAIL (ACQUIRE_IRON), + BUILD_SWORD (BUILD_WORKBENCH), + KILL_ENEMY (BUILD_SWORD), + KILL_COW (BUILD_SWORD), + FLY_PIG (KILL_COW), + SNIPE_SKELETON (KILL_ENEMY), + GET_DIAMONDS (ACQUIRE_IRON), + NETHER_PORTAL (GET_DIAMONDS), + GHAST_RETURN (NETHER_PORTAL), + GET_BLAZE_ROD (NETHER_PORTAL), + BREW_POTION (GET_BLAZE_ROD), + END_PORTAL (GET_BLAZE_ROD), + THE_END (END_PORTAL), + ENCHANTMENTS (GET_DIAMONDS), + OVERKILL (ENCHANTMENTS), + BOOKCASE (ENCHANTMENTS), + EXPLORE_ALL_BIOMES (END_PORTAL), + SPAWN_WITHER (THE_END), + KILL_WITHER (SPAWN_WITHER), + FULL_BEACON (KILL_WITHER), + BREED_COW (KILL_COW), + DIAMONDS_TO_YOU (GET_DIAMONDS), + OVERPOWERED (BUILD_BETTER_PICKAXE) + ; + + private final Achievement parent; + + private Achievement() { + parent = null; + } + + private Achievement(/*@Nullable*/ Achievement parent) { + this.parent = parent; + } + + /** + * Returns whether or not this achievement has a parent achievement. + * + * @return whether the achievement has a parent achievement + */ + public boolean hasParent() { + return parent != null; + } + + /** + * Returns the parent achievement of this achievement, or null if none. + * + * @return the parent achievement or null + */ + @Nullable + public Achievement getParent() { + return parent; + } +} diff --git a/api/src/main/java/org/bukkit/Art.java b/api/src/main/java/org/bukkit/Art.java new file mode 100644 index 000000000..cfbcaf72d --- /dev/null +++ b/api/src/main/java/org/bukkit/Art.java @@ -0,0 +1,115 @@ +package org.bukkit; + +import java.util.HashMap; + +import org.apache.commons.lang.Validate; + +import com.google.common.collect.Maps; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +/** + * Represents the art on a painting + */ +public enum Art { + KEBAB(0, 1, 1), + AZTEC(1, 1, 1), + ALBAN(2, 1, 1), + AZTEC2(3, 1, 1), + BOMB(4, 1, 1), + PLANT(5, 1, 1), + WASTELAND(6, 1, 1), + POOL(7, 2, 1), + COURBET(8, 2, 1), + SEA(9, 2, 1), + SUNSET(10, 2, 1), + CREEBET(11, 2, 1), + WANDERER(12, 1, 2), + GRAHAM(13, 1, 2), + MATCH(14, 2, 2), + BUST(15, 2, 2), + STAGE(16, 2, 2), + VOID(17, 2, 2), + SKULL_AND_ROSES(18, 2, 2), + WITHER(19, 2, 2), + FIGHTERS(20, 4, 2), + POINTER(21, 4, 4), + PIGSCENE(22, 4, 4), + BURNING_SKULL(23, 4, 4), + SKELETON(24, 4, 3), + DONKEY_KONG(25, 4, 3); + + private int id, width, height; + private static final HashMap BY_NAME = Maps.newHashMap(); + private static final HashMap BY_ID = Maps.newHashMap(); + + private Art(int id, int width, int height) { + this.id = id; + this.width = width; + this.height = height; + } + + /** + * Gets the width of the painting, in blocks + * + * @return The width of the painting, in blocks + */ + public int getBlockWidth() { + return width; + } + + /** + * Gets the height of the painting, in blocks + * + * @return The height of the painting, in blocks + */ + public int getBlockHeight() { + return height; + } + + /** + * Get the ID of this painting. + * + * @return The ID of this painting + * @deprecated Magic value + */ + @Deprecated + public int getId() { + return id; + } + + /** + * Get a painting by its numeric ID + * + * @param id The ID + * @return The painting + * @deprecated Magic value + */ + @Deprecated + @Nullable + public static Art getById(int id) { + return BY_ID.get(id); + } + + /** + * Get a painting by its unique name + *

+ * This ignores underscores and capitalization + * + * @param name The name + * @return The painting + */ + @Nullable + public static Art getByName(@NotNull String name) { + Validate.notNull(name, "Name cannot be null"); + + return BY_NAME.get(name.toLowerCase(java.util.Locale.ENGLISH)); + } + + static { + for (Art art : values()) { + BY_ID.put(art.id, art); + BY_NAME.put(art.toString().toLowerCase(java.util.Locale.ENGLISH), art); + } + } +} diff --git a/api/src/main/java/org/bukkit/Axis.java b/api/src/main/java/org/bukkit/Axis.java new file mode 100644 index 000000000..fd86d68d1 --- /dev/null +++ b/api/src/main/java/org/bukkit/Axis.java @@ -0,0 +1,21 @@ +package org.bukkit; + +/** + * Represents a mutually perpendicular axis in 3D Cartesian coordinates. In + * Minecraft the x, z axes lie in the horizontal plane, whilst the y axis points + * upwards. + */ +public enum Axis { + /** + * The x axis. + */ + X, + /** + * The y axis. + */ + Y, + /** + * The z axis. + */ + Z; +} diff --git a/api/src/main/java/org/bukkit/BanEntry.java b/api/src/main/java/org/bukkit/BanEntry.java new file mode 100644 index 000000000..30c8b0a0c --- /dev/null +++ b/api/src/main/java/org/bukkit/BanEntry.java @@ -0,0 +1,136 @@ +package org.bukkit; + +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +import java.util.Date; + +/** + * A single entry from a ban list. This may represent either a player ban or + * an IP ban. + *

+ * Ban entries include the following properties: + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + *
Property information
PropertyDescription
Target Name / IP AddressThe target name or IP address
Creation DateThe creation date of the ban
SourceThe source of the ban, such as a player, console, plugin, etc
Expiration DateThe expiration date of the ban
ReasonThe reason for the ban
+ *

+ * Unsaved information is not automatically written to the implementation's + * ban list, instead, the {@link #save()} method must be called to write the + * changes to the ban list. If this ban entry has expired (such as from an + * unban) and is no longer found in the list, the {@link #save()} call will + * re-add it to the list, therefore banning the victim specified. + *

+ * Likewise, changes to the associated {@link BanList} or other entries may or + * may not be reflected in this entry. + */ +public interface BanEntry { + + /** + * Gets the target involved. This may be in the form of an IP or a player + * name. + * + * @return the target name or IP address + */ + @NotNull + public String getTarget(); + + /** + * Gets the date this ban entry was created. + * + * @return the creation date + */ + @NotNull + public Date getCreated(); + + /** + * Sets the date this ban entry was created. + * + * @param created the new created date, cannot be null + * @see #save() saving changes + */ + public void setCreated(@NotNull Date created); + + /** + * Gets the source of this ban. + *

+ * Note: A source is considered any String, although this is generally a + * player name. + * + * @return the source of the ban + */ + @NotNull + public String getSource(); + + /** + * Sets the source of this ban. + *

+ * Note: A source is considered any String, although this is generally a + * player name. + * + * @param source the new source where null values become empty strings + * @see #save() saving changes + */ + public void setSource(@NotNull String source); + + /** + * Gets the date this ban expires on, or null for no defined end date. + * + * @return the expiration date + */ + @Nullable + public Date getExpiration(); + + /** + * Sets the date this ban expires on. Null values are considered + * "infinite" bans. + * + * @param expiration the new expiration date, or null to indicate an + * eternity + * @see #save() saving changes + */ + public void setExpiration(@Nullable Date expiration); + + /** + * Gets the reason for this ban. + * + * @return the ban reason, or null if not set + */ + @Nullable + public String getReason(); + + /** + * Sets the reason for this ban. Reasons must not be null. + * + * @param reason the new reason, null values assume the implementation + * default + * @see #save() saving changes + */ + public void setReason(@Nullable String reason); + + /** + * Saves the ban entry, overwriting any previous data in the ban list. + *

+ * Saving the ban entry of an unbanned player will cause the player to be + * banned once again. + */ + public void save(); +} diff --git a/api/src/main/java/org/bukkit/BanList.java b/api/src/main/java/org/bukkit/BanList.java new file mode 100644 index 000000000..f506b644a --- /dev/null +++ b/api/src/main/java/org/bukkit/BanList.java @@ -0,0 +1,78 @@ +package org.bukkit; + +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +import java.util.Date; +import java.util.Set; + +/** + * A ban list, containing bans of some {@link Type}. + */ +public interface BanList { + + /** + * Represents a ban-type that a {@link BanList} may track. + */ + public enum Type { + /** + * Banned player names + */ + NAME, + /** + * Banned player IP addresses + */ + IP, + ; + } + + /** + * Gets a {@link BanEntry} by target. + * + * @param target entry parameter to search for + * @return the corresponding entry, or null if none found + */ + @Nullable + public BanEntry getBanEntry(@NotNull String target); + + /** + * Adds a ban to the this list. If a previous ban exists, this will + * update the previous entry. + * + * @param target the target of the ban + * @param reason reason for the ban, null indicates implementation default + * @param expires date for the ban's expiration (unban), or null to imply + * forever + * @param source source of the ban, null indicates implementation default + * @return the entry for the newly created ban, or the entry for the + * (updated) previous ban + */ + @Nullable + public BanEntry addBan(@NotNull String target, @Nullable String reason, @Nullable Date expires, @Nullable String source); + + /** + * Gets a set containing every {@link BanEntry} in this list. + * + * @return an immutable set containing every entry tracked by this list + */ + @NotNull + public Set getBanEntries(); + + /** + * Gets if a {@link BanEntry} exists for the target, indicating an active + * ban status. + * + * @param target the target to find + * @return true if a {@link BanEntry} exists for the name, indicating an + * active ban status, false otherwise + */ + public boolean isBanned(@NotNull String target); + + /** + * Removes the specified target from this list, therefore indicating a + * "not banned" status. + * + * @param target the target to remove from this list + */ + public void pardon(@NotNull String target); +} diff --git a/api/src/main/java/org/bukkit/BlockChangeDelegate.java b/api/src/main/java/org/bukkit/BlockChangeDelegate.java new file mode 100644 index 000000000..41eff1a31 --- /dev/null +++ b/api/src/main/java/org/bukkit/BlockChangeDelegate.java @@ -0,0 +1,51 @@ +package org.bukkit; + +import org.bukkit.block.data.BlockData; +import org.jetbrains.annotations.NotNull; + +/** + * A delegate for handling block changes. This serves as a direct interface + * between generation algorithms in the server implementation and utilizing + * code. + */ +public interface BlockChangeDelegate { + + /** + * Set a block data at the specified coordinates. + * + * @param x X coordinate + * @param y Y coordinate + * @param z Z coordinate + * @param blockData Block data + * @return true if the block was set successfully + */ + public boolean setBlockData(int x, int y, int z, @NotNull BlockData blockData); + + /** + * Get the block data at the location. + * + * @param x X coordinate + * @param y Y coordinate + * @param z Z coordinate + * @return The block data + */ + @NotNull + public BlockData getBlockData(int x, int y, int z); + + /** + * Gets the height of the world. + * + * @return Height of the world + */ + public int getHeight(); + + /** + * Checks if the specified block is empty (air) or not. + * + * @param x X coordinate + * @param y Y coordinate + * @param z Z coordinate + * @return True if the block is considered empty. + */ + public boolean isEmpty(int x, int y, int z); +} diff --git a/api/src/main/java/org/bukkit/Bukkit.java b/api/src/main/java/org/bukkit/Bukkit.java new file mode 100644 index 000000000..5ed9726c8 --- /dev/null +++ b/api/src/main/java/org/bukkit/Bukkit.java @@ -0,0 +1,1641 @@ +package org.bukkit; + +import java.awt.image.BufferedImage; +import java.io.File; +import java.io.Serializable; +import java.util.Collection; +import java.util.Collections; +import java.util.Iterator; +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.UUID; +import java.util.function.Consumer; +import java.util.logging.Logger; + +import org.bukkit.Warning.WarningState; +import org.bukkit.block.data.BlockData; +import org.bukkit.boss.BarColor; +import org.bukkit.boss.BarFlag; +import org.bukkit.boss.BarStyle; +import org.bukkit.boss.BossBar; +import org.bukkit.boss.KeyedBossBar; +import org.bukkit.command.CommandException; +import org.bukkit.command.CommandSender; +import org.bukkit.command.ConsoleCommandSender; +import org.bukkit.command.PluginCommand; +import org.bukkit.entity.Entity; +import org.bukkit.entity.Player; +import org.bukkit.event.inventory.InventoryType; +import org.bukkit.event.server.ServerListPingEvent; +import org.bukkit.help.HelpMap; +import org.bukkit.inventory.Inventory; +import org.bukkit.inventory.InventoryHolder; +import org.bukkit.inventory.ItemStack; +import org.bukkit.inventory.Merchant; +import org.bukkit.inventory.Recipe; +import org.bukkit.loot.LootTable; +import org.bukkit.map.MapView; +import org.bukkit.permissions.Permissible; +import org.bukkit.plugin.PluginManager; +import org.bukkit.plugin.ServicesManager; +import org.bukkit.plugin.messaging.Messenger; +import org.bukkit.scheduler.BukkitScheduler; +import org.bukkit.scoreboard.ScoreboardManager; +import org.bukkit.util.CachedServerIcon; + +import com.google.common.collect.ImmutableList; +import org.bukkit.advancement.Advancement; +import org.bukkit.generator.ChunkGenerator; + +import org.bukkit.inventory.ItemFactory; +import org.bukkit.inventory.meta.ItemMeta; +import org.jetbrains.annotations.Contract; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +/** + * Represents the Bukkit core, for version and Server singleton handling + */ +public final class Bukkit { + private static Server server; + + /** + * Static class cannot be initialized. + */ + private Bukkit() {} + + /** + * Gets the current {@link Server} singleton + * + * @return Server instance being ran + */ + @NotNull + public static Server getServer() { + return server; + } + + /** + * Attempts to set the {@link Server} singleton. + *

+ * This cannot be done if the Server is already set. + * + * @param server Server instance + */ + public static void setServer(@NotNull Server server) { + if (Bukkit.server != null) { + throw new UnsupportedOperationException("Cannot redefine singleton Server"); + } + + Bukkit.server = server; + server.getLogger().info("This server is running " + getName() + " version " + getVersion() + " (Implementing API version " + getBukkitVersion() + ")"); + } + + /** + * Gets the name of this server implementation. + * + * @return name of this server implementation + */ + @NotNull + public static String getName() { + return server.getName(); + } + + /** + * Gets the version string of this server implementation. + * + * @return version of this server implementation + */ + @NotNull + public static String getVersion() { + return server.getVersion(); + } + + /** + * Gets the Bukkit version that this server is running. + * + * @return version of Bukkit + */ + @NotNull + public static String getBukkitVersion() { + return server.getBukkitVersion(); + } + + /** + * Gets a view of all currently logged in players. This {@linkplain + * Collections#unmodifiableCollection(Collection) view} is a reused + * object, making some operations like {@link Collection#size()} + * zero-allocation. + *

+ * The collection is a view backed by the internal representation, such + * that, changes to the internal state of the server will be reflected + * immediately. However, the reuse of the returned collection (identity) + * is not strictly guaranteed for future or all implementations. Casting + * the collection, or relying on interface implementations (like {@link + * Serializable} or {@link List}), is deprecated. + *

+ * Iteration behavior is undefined outside of self-contained main-thread + * uses. Normal and immediate iterator use without consequences that + * affect the collection are fully supported. The effects following + * (non-exhaustive) {@link Entity#teleport(Location) teleportation}, + * {@link Player#setHealth(double) death}, and {@link Player#kickPlayer( + * String) kicking} are undefined. Any use of this collection from + * asynchronous threads is unsafe. + *

+ * For safe consequential iteration or mimicking the old array behavior, + * using {@link Collection#toArray(Object[])} is recommended. For making + * snapshots, {@link ImmutableList#copyOf(Collection)} is recommended. + * + * @return a view of currently online players. + */ + @NotNull + public static Collection getOnlinePlayers() { + return server.getOnlinePlayers(); + } + + /** + * Get the maximum amount of players which can login to this server. + * + * @return the amount of players this server allows + */ + public static int getMaxPlayers() { + return server.getMaxPlayers(); + } + + /** + * Get the game port that the server runs on. + * + * @return the port number of this server + */ + public static int getPort() { + return server.getPort(); + } + + /** + * Get the view distance from this server. + * + * @return the view distance from this server. + */ + public static int getViewDistance() { + return server.getViewDistance(); + } + + /** + * Get the IP that this server is bound to, or empty string if not + * specified. + * + * @return the IP string that this server is bound to, otherwise empty + * string + */ + @NotNull + public static String getIp() { + return server.getIp(); + } + + /** + * Get the name of this server. + * + * @return the name of this server + * @deprecated not a standard server property + */ + @Deprecated + @NotNull + public static String getServerName() { + return server.getServerName(); + } + + /** + * Get an ID of this server. The ID is a simple generally alphanumeric ID + * that can be used for uniquely identifying this server. + * + * @return the ID of this server + * @deprecated not a standard server property + */ + @Deprecated + @NotNull + public static String getServerId() { + return server.getServerId(); + } + + /** + * Get world type (level-type setting) for default world. + * + * @return the value of level-type (e.g. DEFAULT, FLAT, DEFAULT_1_1) + */ + @NotNull + public static String getWorldType() { + return server.getWorldType(); + } + + /** + * Get generate-structures setting. + * + * @return true if structure generation is enabled, false otherwise + */ + public static boolean getGenerateStructures() { + return server.getGenerateStructures(); + } + + /** + * Gets whether this server allows the End or not. + * + * @return whether this server allows the End or not + */ + public static boolean getAllowEnd() { + return server.getAllowEnd(); + } + + /** + * Gets whether this server allows the Nether or not. + * + * @return whether this server allows the Nether or not + */ + public static boolean getAllowNether() { + return server.getAllowNether(); + } + + /** + * Gets whether this server has a whitelist or not. + * + * @return whether this server has a whitelist or not + */ + public static boolean hasWhitelist() { + return server.hasWhitelist(); + } + + /** + * Sets if the server is whitelisted. + * + * @param value true for whitelist on, false for off + */ + public static void setWhitelist(boolean value) { + server.setWhitelist(value); + } + + /** + * Gets a list of whitelisted players. + * + * @return a set containing all whitelisted players + */ + @NotNull + public static Set getWhitelistedPlayers() { + return server.getWhitelistedPlayers(); + } + + /** + * Reloads the whitelist from disk. + */ + public static void reloadWhitelist() { + server.reloadWhitelist(); + } + + /** + * Broadcast a message to all players. + *

+ * This is the same as calling {@link #broadcast(java.lang.String, + * java.lang.String)} to {@link Server#BROADCAST_CHANNEL_USERS} + * + * @param message the message + * @return the number of players + */ + public static int broadcastMessage(@NotNull String message) { + return server.broadcastMessage(message); + } + + // Paper start + /** + * Sends the component to all online players. + * + * @param component the component to send + */ + public static void broadcast(@NotNull net.md_5.bungee.api.chat.BaseComponent component) { + server.broadcast(component); + } + + /** + * Sends an array of components as a single message to all online players. + * + * @param components the components to send + */ + public static void broadcast(@NotNull net.md_5.bungee.api.chat.BaseComponent... components) { + server.broadcast(components); + } + // Paper end + + /** + * Gets the name of the update folder. The update folder is used to safely + * update plugins at the right moment on a plugin load. + *

+ * The update folder name is relative to the plugins folder. + * + * @return the name of the update folder + */ + @NotNull + public static String getUpdateFolder() { + return server.getUpdateFolder(); + } + + /** + * Gets the update folder. The update folder is used to safely update + * plugins at the right moment on a plugin load. + * + * @return the update folder + */ + @NotNull + public static File getUpdateFolderFile() { + return server.getUpdateFolderFile(); + } + + /** + * Gets the value of the connection throttle setting. + * + * @return the value of the connection throttle setting + */ + public static long getConnectionThrottle() { + return server.getConnectionThrottle(); + } + + /** + * Gets default ticks per animal spawns value. + *

+ * Example Usage: + *

    + *
  • A value of 1 will mean the server will attempt to spawn monsters + * every tick. + *
  • A value of 400 will mean the server will attempt to spawn monsters + * every 400th tick. + *
  • A value below 0 will be reset back to Minecraft's default. + *
+ *

+ * Note: If set to 0, animal spawning will be disabled. We + * recommend using spawn-animals to control this instead. + *

+ * Minecraft default: 400. + * + * @return the default ticks per animal spawns value + */ + public static int getTicksPerAnimalSpawns() { + return server.getTicksPerAnimalSpawns(); + } + + /** + * Gets the default ticks per monster spawns value. + *

+ * Example Usage: + *

    + *
  • A value of 1 will mean the server will attempt to spawn monsters + * every tick. + *
  • A value of 400 will mean the server will attempt to spawn monsters + * every 400th tick. + *
  • A value below 0 will be reset back to Minecraft's default. + *
+ *

+ * Note: If set to 0, monsters spawning will be disabled. We + * recommend using spawn-monsters to control this instead. + *

+ * Minecraft default: 1. + * + * @return the default ticks per monsters spawn value + */ + public static int getTicksPerMonsterSpawns() { + return server.getTicksPerMonsterSpawns(); + } + + /** + * Gets a player object by the given username. + *

+ * This method may not return objects for offline players. + * + * @param name the name to look up + * @return a player if one was found, null otherwise + */ + @Nullable + public static Player getPlayer(@NotNull String name) { + return server.getPlayer(name); + } + + /** + * Gets the player with the exact given name, case insensitive. + * + * @param name Exact name of the player to retrieve + * @return a player object if one was found, null otherwise + */ + @Nullable + public static Player getPlayerExact(@NotNull String name) { + return server.getPlayerExact(name); + } + + /** + * Attempts to match any players with the given name, and returns a list + * of all possibly matches. + *

+ * This list is not sorted in any particular order. If an exact match is + * found, the returned list will only contain a single result. + * + * @param name the (partial) name to match + * @return list of all possible players + */ + @NotNull + public static List matchPlayer(@NotNull String name) { + return server.matchPlayer(name); + } + + /** + * Gets the player with the given UUID. + * + * @param id UUID of the player to retrieve + * @return a player object if one was found, null otherwise + */ + @Nullable + public static Player getPlayer(@NotNull UUID id) { + return server.getPlayer(id); + } + + // Paper start + /** + * Gets the unique ID of the player currently known as the specified player name + * In Offline Mode, will return an Offline UUID + * + * @param playerName the player name to look up the unique ID for + * @return A UUID, or null if that player name is not registered with Minecraft and the server is in online mode + */ + @Nullable + public static UUID getPlayerUniqueId(@NotNull String playerName) { + return server.getPlayerUniqueId(playerName); + } + // Paper end + + /** + * Gets the plugin manager for interfacing with plugins. + * + * @return a plugin manager for this Server instance + */ + @NotNull + public static PluginManager getPluginManager() { + return server.getPluginManager(); + } + + /** + * Gets the scheduler for managing scheduled events. + * + * @return a scheduling service for this server + */ + @NotNull + public static BukkitScheduler getScheduler() { + return server.getScheduler(); + } + + /** + * Gets a services manager. + * + * @return s services manager + */ + @NotNull + public static ServicesManager getServicesManager() { + return server.getServicesManager(); + } + + /** + * Gets a list of all worlds on this server. + * + * @return a list of worlds + */ + @NotNull + public static List getWorlds() { + return server.getWorlds(); + } + + /** + * Creates or loads a world with the given name using the specified + * options. + *

+ * If the world is already loaded, it will just return the equivalent of + * getWorld(creator.name()). + * + * @param creator the options to use when creating the world + * @return newly created or loaded world + */ + @Nullable + public static World createWorld(@NotNull WorldCreator creator) { + return server.createWorld(creator); + } + + /** + * Unloads a world with the given name. + * + * @param name Name of the world to unload + * @param save whether to save the chunks before unloading + * @return true if successful, false otherwise + */ + public static boolean unloadWorld(@NotNull String name, boolean save) { + return server.unloadWorld(name, save); + } + + /** + * Unloads the given world. + * + * @param world the world to unload + * @param save whether to save the chunks before unloading + * @return true if successful, false otherwise + */ + public static boolean unloadWorld(@NotNull World world, boolean save) { + return server.unloadWorld(world, save); + } + + /** + * Gets the world with the given name. + * + * @param name the name of the world to retrieve + * @return a world with the given name, or null if none exists + */ + @Nullable + public static World getWorld(@NotNull String name) { + return server.getWorld(name); + } + + /** + * Gets the world from the given Unique ID. + * + * @param uid a unique-id of the world to retrieve + * @return a world with the given Unique ID, or null if none exists + */ + @Nullable + public static World getWorld(@NotNull UUID uid) { + return server.getWorld(uid); + } + + /** + * Gets the map from the given item ID. + * + * @param id the id of the map to get + * @return a map view if it exists, or null otherwise + * @deprecated Magic value + */ + @Deprecated + @Nullable + public static MapView getMap(int id) { + return server.getMap(id); + } + + /** + * Create a new map with an automatically assigned ID. + * + * @param world the world the map will belong to + * @return a newly created map view + */ + @NotNull + public static MapView createMap(@NotNull World world) { + return server.createMap(world); + } + + /** + * Create a new explorer map targeting the closest nearby structure of a + * given {@link StructureType}. + *
+ * This method uses implementation default values for radius and + * findUnexplored (usually 100, true). + * + * @param world the world the map will belong to + * @param location the origin location to find the nearest structure + * @param structureType the type of structure to find + * @return a newly created item stack + * + * @see World#locateNearestStructure(org.bukkit.Location, + * org.bukkit.StructureType, int, boolean) + */ + @NotNull + public static ItemStack createExplorerMap(@NotNull World world, @NotNull Location location, @NotNull StructureType structureType) { + return server.createExplorerMap(world, location, structureType); + } + + /** + * Create a new explorer map targeting the closest nearby structure of a + * given {@link StructureType}. + *
+ * This method uses implementation default values for radius and + * findUnexplored (usually 100, true). + * + * @param world the world the map will belong to + * @param location the origin location to find the nearest structure + * @param structureType the type of structure to find + * @param radius radius to search, see World#locateNearestStructure for more + * information + * @param findUnexplored whether to find unexplored structures + * @return the newly created item stack + * + * @see World#locateNearestStructure(org.bukkit.Location, + * org.bukkit.StructureType, int, boolean) + */ + @NotNull + public static ItemStack createExplorerMap(@NotNull World world, @NotNull Location location, @NotNull StructureType structureType, int radius, boolean findUnexplored) { + return server.createExplorerMap(world, location, structureType, radius, findUnexplored); + } + + /** + * Reloads the server, refreshing settings and plugin information. + */ + public static void reload() { + server.reload(); + } + + /** + * Reload only the Minecraft data for the server. This includes custom + * advancements and loot tables. + */ + public static void reloadData() { + server.reloadData(); + } + + /** + * Returns the primary logger associated with this server instance. + * + * @return Logger associated with this server + */ + @NotNull + public static Logger getLogger() { + return server.getLogger(); + } + + /** + * Gets a {@link PluginCommand} with the given name or alias. + * + * @param name the name of the command to retrieve + * @return a plugin command if found, null otherwise + */ + @Nullable + public static PluginCommand getPluginCommand(@NotNull String name) { + return server.getPluginCommand(name); + } + + /** + * Writes loaded players to disk. + */ + public static void savePlayers() { + server.savePlayers(); + } + + /** + * Dispatches a command on this server, and executes it if found. + * + * @param sender the apparent sender of the command + * @param commandLine the command + arguments. Example: test abc + * 123 + * @return returns false if no target is found + * @throws CommandException thrown when the executor for the given command + * fails with an unhandled exception + */ + public static boolean dispatchCommand(@NotNull CommandSender sender, @NotNull String commandLine) throws CommandException { + return server.dispatchCommand(sender, commandLine); + } + + /** + * Adds a recipe to the crafting manager. + * + * @param recipe the recipe to add + * @return true if the recipe was added, false if it wasn't for some + * reason + */ + @Contract("null -> false") + public static boolean addRecipe(@Nullable Recipe recipe) { + return server.addRecipe(recipe); + } + + /** + * Get a list of all recipes for a given item. The stack size is ignored + * in comparisons. If the durability is -1, it will match any data value. + * + * @param result the item to match against recipe results + * @return a list of recipes with the given result + */ + @NotNull + public static List getRecipesFor(@NotNull ItemStack result) { + return server.getRecipesFor(result); + } + + /** + * Get an iterator through the list of crafting recipes. + * + * @return an iterator + */ + @NotNull + public static Iterator recipeIterator() { + return server.recipeIterator(); + } + + /** + * Clears the list of crafting recipes. + */ + public static void clearRecipes() { + server.clearRecipes(); + } + + /** + * Resets the list of crafting recipes to the default. + */ + public static void resetRecipes() { + server.resetRecipes(); + } + + /** + * Gets a list of command aliases defined in the server properties. + * + * @return a map of aliases to command names + */ + @NotNull + public static Map getCommandAliases() { + return server.getCommandAliases(); + } + + /** + * Gets the radius, in blocks, around each worlds spawn point to protect. + * + * @return spawn radius, or 0 if none + */ + public static int getSpawnRadius() { + return server.getSpawnRadius(); + } + + /** + * Sets the radius, in blocks, around each worlds spawn point to protect. + * + * @param value new spawn radius, or 0 if none + */ + public static void setSpawnRadius(int value) { + server.setSpawnRadius(value); + } + + /** + * Gets whether the Server is in online mode or not. + * + * @return true if the server authenticates clients, false otherwise + */ + public static boolean getOnlineMode() { + return server.getOnlineMode(); + } + + /** + * Gets whether this server allows flying or not. + * + * @return true if the server allows flight, false otherwise + */ + public static boolean getAllowFlight() { + return server.getAllowFlight(); + } + + /** + * Gets whether the server is in hardcore mode or not. + * + * @return true if the server mode is hardcore, false otherwise + */ + public static boolean isHardcore() { + return server.isHardcore(); + } + + /** + * Shutdowns the server, stopping everything. + */ + public static void shutdown() { + server.shutdown(); + } + + /** + * Broadcasts the specified message to every user with the given + * permission name. + * + * @param message message to broadcast + * @param permission the required permission {@link Permissible + * permissibles} must have to receive the broadcast + * @return number of message recipients + */ + public static int broadcast(@NotNull String message, @NotNull String permission) { + return server.broadcast(message, permission); + } + + /** + * Gets the player by the given name, regardless if they are offline or + * online. + *

+ * This method may involve a blocking web request to get the UUID for the + * given name. + *

+ * This will return an object even if the player does not exist. To this + * method, all players will exist. + * + * @deprecated Persistent storage of users should be by UUID as names are no longer + * unique past a single session. + * @param name the name the player to retrieve + * @return an offline player + * @see #getOfflinePlayer(java.util.UUID) + */ + @Deprecated + @NotNull + public static OfflinePlayer getOfflinePlayer(@NotNull String name) { + return server.getOfflinePlayer(name); + } + + /** + * Gets the player by the given UUID, regardless if they are offline or + * online. + *

+ * This will return an object even if the player does not exist. To this + * method, all players will exist. + * + * @param id the UUID of the player to retrieve + * @return an offline player + */ + @NotNull + public static OfflinePlayer getOfflinePlayer(@NotNull UUID id) { + return server.getOfflinePlayer(id); + } + + /** + * Gets a set containing all current IPs that are banned. + * + * @return a set containing banned IP addresses + */ + @NotNull + public static Set getIPBans() { + return server.getIPBans(); + } + + /** + * Bans the specified address from the server. + * + * @param address the IP address to ban + */ + public static void banIP(@NotNull String address) { + server.banIP(address); + } + + /** + * Unbans the specified address from the server. + * + * @param address the IP address to unban + */ + public static void unbanIP(@NotNull String address) { + server.unbanIP(address); + } + + /** + * Gets a set containing all banned players. + * + * @return a set containing banned players + */ + @NotNull + public static Set getBannedPlayers() { + return server.getBannedPlayers(); + } + + /** + * Gets a ban list for the supplied type. + *

+ * Bans by name are no longer supported and this method will return + * null when trying to request them. The replacement is bans by UUID. + * + * @param type the type of list to fetch, cannot be null + * @return a ban list of the specified type + */ + @NotNull + public static BanList getBanList(@NotNull BanList.Type type) { + return server.getBanList(type); + } + + /** + * Gets a set containing all player operators. + * + * @return a set containing player operators + */ + @NotNull + public static Set getOperators() { + return server.getOperators(); + } + + /** + * Gets the default {@link GameMode} for new players. + * + * @return the default game mode + */ + @NotNull + public static GameMode getDefaultGameMode() { + return server.getDefaultGameMode(); + } + + /** + * Sets the default {@link GameMode} for new players. + * + * @param mode the new game mode + */ + public static void setDefaultGameMode(@NotNull GameMode mode) { + server.setDefaultGameMode(mode); + } + + /** + * Gets a {@link ConsoleCommandSender} that may be used as an input source + * for this server. + * + * @return a console command sender + */ + @NotNull + public static ConsoleCommandSender getConsoleSender() { + return server.getConsoleSender(); + } + + /** + * Gets the folder that contains all of the various {@link World}s. + * + * @return folder that contains all worlds + */ + @NotNull + public static File getWorldContainer() { + return server.getWorldContainer(); + } + + /** + * Gets every player that has ever played on this server. + * + * @return an array containing all previous players + */ + @NotNull + public static OfflinePlayer[] getOfflinePlayers() { + return server.getOfflinePlayers(); + } + + /** + * Gets the {@link Messenger} responsible for this server. + * + * @return messenger responsible for this server + */ + @NotNull + public static Messenger getMessenger() { + return server.getMessenger(); + } + + /** + * Gets the {@link HelpMap} providing help topics for this server. + * + * @return a help map for this server + */ + @NotNull + public static HelpMap getHelpMap() { + return server.getHelpMap(); + } + + /** + * Creates an empty inventory with the specified type and title. If the type + * is {@link InventoryType#CHEST}, the new inventory has a size of 27; + * otherwise the new inventory has the normal size for its type.
+ * It should be noted that some inventory types do not support titles and + * may not render with said titles on the Minecraft client. + *
+ * {@link InventoryType#WORKBENCH} will not process crafting recipes if + * created with this method. Use + * {@link Player#openWorkbench(Location, boolean)} instead. + *
+ * {@link InventoryType#ENCHANTING} will not process {@link ItemStack}s + * for possible enchanting results. Use + * {@link Player#openEnchanting(Location, boolean)} instead. + * + * @param owner the holder of the inventory, or null to indicate no holder + * @param type the type of inventory to create + * @return a new inventory + * @throws IllegalArgumentException if the {@link InventoryType} cannot be + * viewed. + * + * @see InventoryType#isCreatable() + */ + @NotNull + public static Inventory createInventory(@Nullable InventoryHolder owner, @NotNull InventoryType type) { + return server.createInventory(owner, type); + } + + /** + * Creates an empty inventory with the specified type and title. If the type + * is {@link InventoryType#CHEST}, the new inventory has a size of 27; + * otherwise the new inventory has the normal size for its type.
+ * It should be noted that some inventory types do not support titles and + * may not render with said titles on the Minecraft client. + *
+ * {@link InventoryType#WORKBENCH} will not process crafting recipes if + * created with this method. Use + * {@link Player#openWorkbench(Location, boolean)} instead. + *
+ * {@link InventoryType#ENCHANTING} will not process {@link ItemStack}s + * for possible enchanting results. Use + * {@link Player#openEnchanting(Location, boolean)} instead. + * + * @param owner The holder of the inventory; can be null if there's no holder. + * @param type The type of inventory to create. + * @param title The title of the inventory, to be displayed when it is viewed. + * @return The new inventory. + * @throws IllegalArgumentException if the {@link InventoryType} cannot be + * viewed. + * + * @see InventoryType#isCreatable() + */ + @NotNull + public static Inventory createInventory(@Nullable InventoryHolder owner, @NotNull InventoryType type, @NotNull String title) { + return server.createInventory(owner, type, title); + } + + /** + * Creates an empty inventory of type {@link InventoryType#CHEST} with the + * specified size. + * + * @param owner the holder of the inventory, or null to indicate no holder + * @param size a multiple of 9 as the size of inventory to create + * @return a new inventory + * @throws IllegalArgumentException if the size is not a multiple of 9 + */ + @NotNull + public static Inventory createInventory(@Nullable InventoryHolder owner, int size) throws IllegalArgumentException { + return server.createInventory(owner, size); + } + + /** + * Creates an empty inventory of type {@link InventoryType#CHEST} with the + * specified size and title. + * + * @param owner the holder of the inventory, or null to indicate no holder + * @param size a multiple of 9 as the size of inventory to create + * @param title the title of the inventory, displayed when inventory is + * viewed + * @return a new inventory + * @throws IllegalArgumentException if the size is not a multiple of 9 + */ + @NotNull + public static Inventory createInventory(@Nullable InventoryHolder owner, int size, @NotNull String title) throws IllegalArgumentException { + return server.createInventory(owner, size, title); + } + + /** + * Creates an empty merchant. + * + * @param title the title of the corresponding merchant inventory, displayed + * when the merchant inventory is viewed + * @return a new merchant + */ + @NotNull + public static Merchant createMerchant(@Nullable String title) { + return server.createMerchant(title); + } + + /** + * Gets user-specified limit for number of monsters that can spawn in a + * chunk. + * + * @return the monster spawn limit + */ + public static int getMonsterSpawnLimit() { + return server.getMonsterSpawnLimit(); + } + + /** + * Gets user-specified limit for number of animals that can spawn in a + * chunk. + * + * @return the animal spawn limit + */ + public static int getAnimalSpawnLimit() { + return server.getAnimalSpawnLimit(); + } + + /** + * Gets user-specified limit for number of water animals that can spawn in + * a chunk. + * + * @return the water animal spawn limit + */ + public static int getWaterAnimalSpawnLimit() { + return server.getWaterAnimalSpawnLimit(); + } + + /** + * Gets user-specified limit for number of ambient mobs that can spawn in + * a chunk. + * + * @return the ambient spawn limit + */ + public static int getAmbientSpawnLimit() { + return server.getAmbientSpawnLimit(); + } + + /** + * Checks the current thread against the expected primary thread for the + * server. + *

+ * Note: this method should not be used to indicate the current + * synchronized state of the runtime. A current thread matching the main + * thread indicates that it is synchronized, but a mismatch does not + * preclude the same assumption. + * + * @return true if the current thread matches the expected primary thread, + * false otherwise + */ + public static boolean isPrimaryThread() { + return server.isPrimaryThread(); + } + + /** + * Gets the message that is displayed on the server list. + * + * @return the servers MOTD + */ + @NotNull + public static String getMotd() { + return server.getMotd(); + } + + /** + * Gets the default message that is displayed when the server is stopped. + * + * @return the shutdown message + */ + @Nullable + public static String getShutdownMessage() { + return server.getShutdownMessage(); + } + + /** + * Gets the current warning state for the server. + * + * @return the configured warning state + */ + @NotNull + public static WarningState getWarningState() { + return server.getWarningState(); + } + + /** + * Gets the instance of the item factory (for {@link ItemMeta}). + * + * @return the item factory + * @see ItemFactory + */ + @NotNull + public static ItemFactory getItemFactory() { + return server.getItemFactory(); + } + + /** + * Gets the instance of the scoreboard manager. + *

+ * This will only exist after the first world has loaded. + * + * @return the scoreboard manager or null if no worlds are loaded. + */ + @NotNull // Paper + public static ScoreboardManager getScoreboardManager() { + return server.getScoreboardManager(); + } + + /** + * Gets an instance of the server's default server-icon. + * + * @return the default server-icon; null values may be used by the + * implementation to indicate no defined icon, but this behavior is + * not guaranteed + */ + @Nullable + public static CachedServerIcon getServerIcon() { + return server.getServerIcon(); + } + + /** + * Loads an image from a file, and returns a cached image for the specific + * server-icon. + *

+ * Size and type are implementation defined. An incompatible file is + * guaranteed to throw an implementation-defined {@link Exception}. + * + * @param file the file to load the from + * @throws IllegalArgumentException if image is null + * @throws Exception if the image does not meet current server server-icon + * specifications + * @return a cached server-icon that can be used for a {@link + * ServerListPingEvent#setServerIcon(CachedServerIcon)} + */ + @NotNull + public static CachedServerIcon loadServerIcon(@NotNull File file) throws IllegalArgumentException, Exception { + return server.loadServerIcon(file); + } + + /** + * Creates a cached server-icon for the specific image. + *

+ * Size and type are implementation defined. An incompatible file is + * guaranteed to throw an implementation-defined {@link Exception}. + * + * @param image the image to use + * @throws IllegalArgumentException if image is null + * @throws Exception if the image does not meet current server + * server-icon specifications + * @return a cached server-icon that can be used for a {@link + * ServerListPingEvent#setServerIcon(CachedServerIcon)} + */ + @NotNull + public static CachedServerIcon loadServerIcon(@NotNull BufferedImage image) throws IllegalArgumentException, Exception { + return server.loadServerIcon(image); + } + + /** + * Set the idle kick timeout. Any players idle for the specified amount of + * time will be automatically kicked. + *

+ * A value of 0 will disable the idle kick timeout. + * + * @param threshold the idle timeout in minutes + */ + public static void setIdleTimeout(int threshold) { + server.setIdleTimeout(threshold); + } + + /** + * Gets the idle kick timeout. + * + * @return the idle timeout in minutes + */ + public static int getIdleTimeout() { + return server.getIdleTimeout(); + } + + /** + * Create a ChunkData for use in a generator. + * + * See {@link ChunkGenerator#generateChunkData(org.bukkit.World, java.util.Random, int, int, org.bukkit.generator.ChunkGenerator.BiomeGrid)} + * + * @param world the world to create the ChunkData for + * @return a new ChunkData for the world + * + */ + @NotNull + public static ChunkGenerator.ChunkData createChunkData(@NotNull World world) { + return server.createChunkData(world); + } + + /** + * Creates a boss bar instance to display to players. The progress + * defaults to 1.0 + * + * @param title the title of the boss bar + * @param color the color of the boss bar + * @param style the style of the boss bar + * @param flags an optional list of flags to set on the boss bar + * @return the created boss bar + */ + @NotNull + public static BossBar createBossBar(@Nullable String title, @NotNull BarColor color, @NotNull BarStyle style, @NotNull BarFlag... flags) { + return server.createBossBar(title, color, style, flags); + } + + /** + * Creates a boss bar instance to display to players. The progress defaults + * to 1.0. + *
+ * This instance is added to the persistent storage of the server and will + * be editable by commands and restored after restart. + * + * @param key the key of the boss bar that is used to access the boss bar + * @param title the title of the boss bar + * @param color the color of the boss bar + * @param style the style of the boss bar + * @param flags an optional list of flags to set on the boss bar + * @return the created boss bar + */ + @NotNull + public static KeyedBossBar createBossBar(@NotNull NamespacedKey key, @Nullable String title, @NotNull BarColor color, @NotNull BarStyle style, @NotNull BarFlag... flags) { + return server.createBossBar(key, title, color, style, flags); + } + + /** + * Gets an unmodifiable iterator through all persistent bossbars. + *

    + *
  • not bound to a {@link org.bukkit.entity.Boss}
  • + *
  • + * not created using + * {@link #createBossBar(String, BarColor, BarStyle, BarFlag...)} + *
  • + *
+ * + * e.g. bossbars created using the bossbar command + * + * @return a bossbar iterator + */ + @NotNull + public static Iterator getBossBars() { + return server.getBossBars(); + } + + /** + * Gets the {@link KeyedBossBar} specified by this key. + *
    + *
  • not bound to a {@link org.bukkit.entity.Boss}
  • + *
  • + * not created using + * {@link #createBossBar(String, BarColor, BarStyle, BarFlag...)} + *
  • + *
+ * + * e.g. bossbars created using the bossbar command + * + * @param key unique bossbar key + * @return bossbar or null if not exists + */ + @Nullable + public static KeyedBossBar getBossBar(@NotNull NamespacedKey key) { + return server.getBossBar(key); + } + + /** + * Removes a {@link KeyedBossBar} specified by this key. + *
    + *
  • not bound to a {@link org.bukkit.entity.Boss}
  • + *
  • + * not created using + * {@link #createBossBar(String, BarColor, BarStyle, BarFlag...)} + *
  • + *
+ * + * e.g. bossbars created using the bossbar command + * + * @param key unique bossbar key + * @return true if removal succeeded or false + */ + public static boolean removeBossBar(@NotNull NamespacedKey key) { + return server.removeBossBar(key); + } + + /** + * Gets an entity on the server by its UUID + * + * @param uuid the UUID of the entity + * @return the entity with the given UUID, or null if it isn't found + */ + @Nullable + public static Entity getEntity(@NotNull UUID uuid) { + return server.getEntity(uuid); + } + + // Paper start + /** + * Gets the current server TPS + * @return current server TPS (1m, 5m, 15m in Paper-Server) + */ + @NotNull + public static double[] getTPS() { + return server.getTPS(); + } + // Paper end + + /** + * Get the advancement specified by this key. + * + * @param key unique advancement key + * @return advancement or null if not exists + */ + @Nullable + public static Advancement getAdvancement(@NotNull NamespacedKey key) { + return server.getAdvancement(key); + } + + /** + * Get an iterator through all advancements. Advancements cannot be removed + * from this iterator, + * + * @return an advancement iterator + */ + @NotNull + public static Iterator advancementIterator() { + return server.advancementIterator(); + } + + /** + * Creates a new {@link BlockData} instance for the specified Material, with + * all properties initialized to unspecified defaults. + * + * @param material the material + * @return new data instance + */ + @NotNull + public static BlockData createBlockData(@NotNull Material material) { + return server.createBlockData(material); + } + + /** + * Creates a new {@link BlockData} instance for the specified Material, with + * all properties initialized to unspecified defaults. + * + * @param material the material + * @param consumer consumer to run on new instance before returning + * @return new data instance + */ + @NotNull + public static BlockData createBlockData(@NotNull Material material, @Nullable Consumer consumer) { + return server.createBlockData(material, consumer); + } + + /** + * Creates a new {@link BlockData} instance with material and properties + * parsed from provided data. + * + * @param data data string + * @return new data instance + * @throws IllegalArgumentException if the specified data is not valid + */ + @NotNull + public static BlockData createBlockData(@NotNull String data) throws IllegalArgumentException { + return server.createBlockData(data); + } + + /** + * Creates a new {@link BlockData} instance for the specified Material, with + * all properties initialized to unspecified defaults, except for those + * provided in data. + * + * @param material the material + * @param data data string + * @return new data instance + * @throws IllegalArgumentException if the specified data is not valid + */ + @NotNull + @Contract("null, null -> fail") + public static BlockData createBlockData(@Nullable Material material, @Nullable String data) throws IllegalArgumentException { + return server.createBlockData(material, data); + } + + /** + * Gets a tag which has already been defined within the server. Plugins are + * suggested to use the concrete tags in {@link Tag} rather than this method + * which makes no guarantees about which tags are available, and may also be + * less performant due to lack of caching. + *
+ * Tags will be searched for in an implementation specific manner, but a + * path consisting of namespace/tags/registry/key is expected. + *
+ * Server implementations are allowed to handle only the registries + * indicated in {@link Tag}. + * + * @param type of the tag + * @param registry the tag registry to look at + * @param tag the name of the tag + * @param clazz the class of the tag entries + * @return the tag or null + */ + @UndefinedNullability // Paper + public static Tag getTag(@NotNull String registry, @NotNull NamespacedKey tag, @NotNull Class clazz) { + return server.getTag(registry, tag, clazz); + } + + /** + * Gets a all tags which have been defined within the server. + *
+ * Server implementations are allowed to handle only the registries + * indicated in {@link Tag}. + *
+ * No guarantees are made about the mutability of the returned iterator. + * + * @param type of the tag + * @param registry the tag registry to look at + * @param clazz the class of the tag entries + * @return all defined tags + */ + @NotNull + public static Iterable> getTags(@NotNull String registry, @NotNull Class clazz) { + return server.getTags(registry, clazz); + } + + /** + * Gets the specified {@link LootTable}. + * + * @param key the name of the LootTable + * @return the LootTable, or null if no LootTable is found with that name + */ + @Nullable + public static LootTable getLootTable(@NotNull NamespacedKey key) { + return server.getLootTable(key); + } + + /** + * Selects entities using the given Vanilla selector. + *
+ * No guarantees are made about the selector format, other than they match + * the Vanilla format for the active Minecraft version. + *
+ * Usually a selector will start with '@', unless selecting a Player in + * which case it may simply be the Player's name or UUID. + *
+ * Note that in Vanilla, elevated permissions are usually required to use + * '@' selectors, but this method should not check such permissions from the + * sender. + * + * @param sender the sender to execute as, must be provided + * @param selector the selection string + * @return a list of the selected entities. The list will not be null, but + * no further guarantees are made. + * @throws IllegalArgumentException if the selector is malformed in any way + * or a parameter is null + * @deprecated draft API + */ + @Deprecated + @NotNull + public static List selectEntities(@NotNull CommandSender sender, @NotNull String selector) throws IllegalArgumentException { + return server.selectEntities(sender, selector); + } + + /** + * @see UnsafeValues + * @return the unsafe values instance + */ + @Deprecated + @NotNull + public static UnsafeValues getUnsafe() { + return server.getUnsafe(); + } + + + // Paper start + /** + * Gets the active {@link org.bukkit.command.CommandMap} + * + * @return the active command map + */ + @NotNull + public static org.bukkit.command.CommandMap getCommandMap() { + return server.getCommandMap(); + } + + /** + * Reload the Permissions in permissions.yml + */ + public static void reloadPermissions() { + server.reloadPermissions(); + } + + /** + * Reload the Command Aliases in commands.yml + * + * @return Whether the reload was successful + */ + public static boolean reloadCommandAliases() { + return server.reloadCommandAliases(); + } + + /** + * Checks if player names should be suggested when a command returns {@code null} as + * their tab completion result. + * + * @return true if player names should be suggested + */ + public static boolean suggestPlayerNamesWhenNullTabCompletions() { + return server.suggestPlayerNamesWhenNullTabCompletions(); + } + + /** + * + * @return the default no permission message used on the server + */ + @NotNull + public static String getPermissionMessage() { + return server.getPermissionMessage(); + } + + /** + * Creates a PlayerProfile for the specified uuid, with name as null + * @param uuid UUID to create profile for + * @return A PlayerProfile object + */ + @NotNull + public static com.destroystokyo.paper.profile.PlayerProfile createProfile(@NotNull UUID uuid) { + return server.createProfile(uuid); + } + + /** + * Creates a PlayerProfile for the specified name, with UUID as null + * @param name Name to create profile for + * @return A PlayerProfile object + */ + @NotNull + public static com.destroystokyo.paper.profile.PlayerProfile createProfile(@NotNull String name) { + return server.createProfile(name); + } + + /** + * Creates a PlayerProfile for the specified name/uuid + * + * Both UUID and Name can not be null at same time. One must be supplied. + * + * @param uuid UUID to create profile for + * @param name Name to create profile for + * @return A PlayerProfile object + */ + @NotNull + public static com.destroystokyo.paper.profile.PlayerProfile createProfile(@Nullable UUID uuid, @Nullable String name) { + return server.createProfile(uuid, name); + } + // Paper end + + @NotNull + public static Server.Spigot spigot() + { + return server.spigot(); + } +} diff --git a/api/src/main/java/org/bukkit/ChatColor.java b/api/src/main/java/org/bukkit/ChatColor.java new file mode 100644 index 000000000..3ab8e9414 --- /dev/null +++ b/api/src/main/java/org/bukkit/ChatColor.java @@ -0,0 +1,404 @@ +package org.bukkit; + +import java.util.Map; +import java.util.regex.Pattern; + +import org.apache.commons.lang.Validate; + +import com.google.common.collect.Maps; +import org.jetbrains.annotations.Contract; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +/** + * All supported color values for chat + */ +public enum ChatColor{ + /** + * Represents black + */ + BLACK('0', 0x00) { + @NotNull + @Override + public net.md_5.bungee.api.ChatColor asBungee() { + return net.md_5.bungee.api.ChatColor.BLACK; + } + }, + /** + * Represents dark blue + */ + DARK_BLUE('1', 0x1){ + @NotNull + @Override + public net.md_5.bungee.api.ChatColor asBungee() { + return net.md_5.bungee.api.ChatColor.DARK_BLUE; + } + }, + /** + * Represents dark green + */ + DARK_GREEN('2', 0x2){ + @NotNull + @Override + public net.md_5.bungee.api.ChatColor asBungee() { + return net.md_5.bungee.api.ChatColor.DARK_GREEN; + } + }, + /** + * Represents dark blue (aqua) + */ + DARK_AQUA('3', 0x3){ + @NotNull + @Override + public net.md_5.bungee.api.ChatColor asBungee() { + return net.md_5.bungee.api.ChatColor.DARK_AQUA; + } + }, + /** + * Represents dark red + */ + DARK_RED('4', 0x4){ + @NotNull + @Override + public net.md_5.bungee.api.ChatColor asBungee() { + return net.md_5.bungee.api.ChatColor.DARK_RED; + } + }, + /** + * Represents dark purple + */ + DARK_PURPLE('5', 0x5){ + @NotNull + @Override + public net.md_5.bungee.api.ChatColor asBungee() { + return net.md_5.bungee.api.ChatColor.DARK_PURPLE; + } + }, + /** + * Represents gold + */ + GOLD('6', 0x6){ + @NotNull + @Override + public net.md_5.bungee.api.ChatColor asBungee() { + return net.md_5.bungee.api.ChatColor.GOLD; + } + }, + /** + * Represents gray + */ + GRAY('7', 0x7){ + @NotNull + @Override + public net.md_5.bungee.api.ChatColor asBungee() { + return net.md_5.bungee.api.ChatColor.GRAY; + } + }, + /** + * Represents dark gray + */ + DARK_GRAY('8', 0x8){ + @NotNull + @Override + public net.md_5.bungee.api.ChatColor asBungee() { + return net.md_5.bungee.api.ChatColor.DARK_GRAY; + } + }, + /** + * Represents blue + */ + BLUE('9', 0x9){ + @NotNull + @Override + public net.md_5.bungee.api.ChatColor asBungee() { + return net.md_5.bungee.api.ChatColor.BLUE; + } + }, + /** + * Represents green + */ + GREEN('a', 0xA){ + @NotNull + @Override + public net.md_5.bungee.api.ChatColor asBungee() { + return net.md_5.bungee.api.ChatColor.GREEN; + } + }, + /** + * Represents aqua + */ + AQUA('b', 0xB){ + @NotNull + @Override + public net.md_5.bungee.api.ChatColor asBungee() { + return net.md_5.bungee.api.ChatColor.AQUA; + } + }, + /** + * Represents red + */ + RED('c', 0xC){ + @NotNull + @Override + public net.md_5.bungee.api.ChatColor asBungee() { + return net.md_5.bungee.api.ChatColor.RED; + } + }, + /** + * Represents light purple + */ + LIGHT_PURPLE('d', 0xD){ + @NotNull + @Override + public net.md_5.bungee.api.ChatColor asBungee() { + return net.md_5.bungee.api.ChatColor.LIGHT_PURPLE; + } + }, + /** + * Represents yellow + */ + YELLOW('e', 0xE){ + @NotNull + @Override + public net.md_5.bungee.api.ChatColor asBungee() { + return net.md_5.bungee.api.ChatColor.YELLOW; + } + }, + /** + * Represents white + */ + WHITE('f', 0xF){ + @NotNull + @Override + public net.md_5.bungee.api.ChatColor asBungee() { + return net.md_5.bungee.api.ChatColor.WHITE; + } + }, + /** + * Represents magical characters that change around randomly + */ + MAGIC('k', 0x10, true){ + @NotNull + @Override + public net.md_5.bungee.api.ChatColor asBungee() { + return net.md_5.bungee.api.ChatColor.MAGIC; + } + }, + /** + * Makes the text bold. + */ + BOLD('l', 0x11, true){ + @NotNull + @Override + public net.md_5.bungee.api.ChatColor asBungee() { + return net.md_5.bungee.api.ChatColor.BOLD; + } + }, + /** + * Makes a line appear through the text. + */ + STRIKETHROUGH('m', 0x12, true){ + @NotNull + @Override + public net.md_5.bungee.api.ChatColor asBungee() { + return net.md_5.bungee.api.ChatColor.STRIKETHROUGH; + } + }, + /** + * Makes the text appear underlined. + */ + UNDERLINE('n', 0x13, true){ + @NotNull + @Override + public net.md_5.bungee.api.ChatColor asBungee() { + return net.md_5.bungee.api.ChatColor.UNDERLINE; + } + }, + /** + * Makes the text italic. + */ + ITALIC('o', 0x14, true){ + @NotNull + @Override + public net.md_5.bungee.api.ChatColor asBungee() { + return net.md_5.bungee.api.ChatColor.ITALIC; + } + }, + /** + * Resets all previous chat colors or formats. + */ + RESET('r', 0x15){ + @NotNull + @Override + public net.md_5.bungee.api.ChatColor asBungee() { + return net.md_5.bungee.api.ChatColor.RESET; + } + }; + + /** + * The special character which prefixes all chat colour codes. Use this if + * you need to dynamically convert colour codes from your custom format. + */ + public static final char COLOR_CHAR = '\u00A7'; + private static final Pattern STRIP_COLOR_PATTERN = Pattern.compile("(?i)" + String.valueOf(COLOR_CHAR) + "[0-9A-FK-OR]"); + + private final int intCode; + private final char code; + private final boolean isFormat; + private final String toString; + private final static Map BY_ID = Maps.newHashMap(); + private final static Map BY_CHAR = Maps.newHashMap(); + + private ChatColor(char code, int intCode) { + this(code, intCode, false); + } + + private ChatColor(char code, int intCode, boolean isFormat) { + this.code = code; + this.intCode = intCode; + this.isFormat = isFormat; + this.toString = new String(new char[] {COLOR_CHAR, code}); + } + + @NotNull + public net.md_5.bungee.api.ChatColor asBungee() { + return net.md_5.bungee.api.ChatColor.RESET; + }; + + /** + * Gets the char value associated with this color + * + * @return A char value of this color code + */ + public char getChar() { + return code; + } + + @NotNull + @Override + public String toString() { + return toString; + } + + /** + * Checks if this code is a format code as opposed to a color code. + * + * @return whether this ChatColor is a format code + */ + public boolean isFormat() { + return isFormat; + } + + /** + * Checks if this code is a color code as opposed to a format code. + * + * @return whether this ChatColor is a color code + */ + public boolean isColor() { + return !isFormat && this != RESET; + } + + /** + * Gets the color represented by the specified color code + * + * @param code Code to check + * @return Associative {@link org.bukkit.ChatColor} with the given code, + * or null if it doesn't exist + */ + @Nullable + public static ChatColor getByChar(char code) { + return BY_CHAR.get(code); + } + + /** + * Gets the color represented by the specified color code + * + * @param code Code to check + * @return Associative {@link org.bukkit.ChatColor} with the given code, + * or null if it doesn't exist + */ + @Nullable + public static ChatColor getByChar(@NotNull String code) { + Validate.notNull(code, "Code cannot be null"); + Validate.isTrue(code.length() > 0, "Code must have at least one char"); + + return BY_CHAR.get(code.charAt(0)); + } + + /** + * Strips the given message of all color codes + * + * @param input String to strip of color + * @return A copy of the input string, without any coloring + */ + @Contract("!null -> !null; null -> null") + @Nullable + public static String stripColor(@Nullable final String input) { + if (input == null) { + return null; + } + + return STRIP_COLOR_PATTERN.matcher(input).replaceAll(""); + } + + /** + * Translates a string using an alternate color code character into a + * string that uses the internal ChatColor.COLOR_CODE color code + * character. The alternate color code character will only be replaced if + * it is immediately followed by 0-9, A-F, a-f, K-O, k-o, R or r. + * + * @param altColorChar The alternate color code character to replace. Ex: {@literal &} + * @param textToTranslate Text containing the alternate color code character. + * @return Text containing the ChatColor.COLOR_CODE color code character. + */ + @NotNull + public static String translateAlternateColorCodes(char altColorChar, @NotNull String textToTranslate) { + char[] b = textToTranslate.toCharArray(); + for (int i = 0; i < b.length - 1; i++) { + if (b[i] == altColorChar && "0123456789AaBbCcDdEeFfKkLlMmNnOoRr".indexOf(b[i+1]) > -1) { + b[i] = ChatColor.COLOR_CHAR; + b[i+1] = Character.toLowerCase(b[i+1]); + } + } + return new String(b); + } + + /** + * Gets the ChatColors used at the end of the given input string. + * + * @param input Input string to retrieve the colors from. + * @return Any remaining ChatColors to pass onto the next line. + */ + @NotNull + public static String getLastColors(@NotNull String input) { + String result = ""; + int length = input.length(); + + // Search backwards from the end as it is faster + for (int index = length - 1; index > -1; index--) { + char section = input.charAt(index); + if (section == COLOR_CHAR && index < length - 1) { + char c = input.charAt(index + 1); + ChatColor color = getByChar(c); + + if (color != null) { + result = color.toString() + result; + + // Once we find a color or reset we can stop searching + if (color.isColor() || color.equals(RESET)) { + break; + } + } + } + } + + return result; + } + + static { + for (ChatColor color : values()) { + BY_ID.put(color.intCode, color); + BY_CHAR.put(color.code, color); + } + } +} diff --git a/api/src/main/java/org/bukkit/Chunk.java b/api/src/main/java/org/bukkit/Chunk.java new file mode 100644 index 000000000..22ff63e52 --- /dev/null +++ b/api/src/main/java/org/bukkit/Chunk.java @@ -0,0 +1,201 @@ +package org.bukkit; + +import org.bukkit.block.Block; +import org.bukkit.block.BlockState; +import org.bukkit.entity.Entity; +import org.jetbrains.annotations.NotNull; + +/** + * Represents a chunk of blocks + */ +public interface Chunk { + + /** + * Gets the X-coordinate of this chunk + * + * @return X-coordinate + */ + int getX(); + + /** + * Gets the Z-coordinate of this chunk + * + * @return Z-coordinate + */ + int getZ(); + + // Paper start + /** + * @return The Chunks X and Z coordinates packed into a long + */ + default long getChunkKey() { + return getChunkKey(getX(), getZ()); + } + + /** + * @param loc Location to get chunk key + * @return Location's chunk coordinates packed into a long + */ + static long getChunkKey(@NotNull Location loc) { + return getChunkKey((int) Math.floor(loc.getX()) << 4, (int) Math.floor(loc.getZ()) << 4); + } + + /** + * @param x X Coordinate + * @param z Z Coordinate + * @return Chunk coordinates packed into a long + */ + static long getChunkKey(int x, int z) { + return (long) x & 0xffffffffL | ((long) z & 0xffffffffL) << 32; + } + // Paper end + + /** + * Gets the world containing this chunk + * + * @return Parent World + */ + @NotNull + World getWorld(); + + /** + * Gets a block from this chunk + * + * @param x 0-15 + * @param y 0-255 + * @param z 0-15 + * @return the Block + */ + @NotNull + Block getBlock(int x, int y, int z); + + /** + * Capture thread-safe read-only snapshot of chunk data + * + * @return ChunkSnapshot + */ + @NotNull + ChunkSnapshot getChunkSnapshot(); + + /** + * Capture thread-safe read-only snapshot of chunk data + * + * @param includeMaxblocky - if true, snapshot includes per-coordinate + * maximum Y values + * @param includeBiome - if true, snapshot includes per-coordinate biome + * type + * @param includeBiomeTempRain - if true, snapshot includes per-coordinate + * raw biome temperature and rainfall + * @return ChunkSnapshot + */ + @NotNull + ChunkSnapshot getChunkSnapshot(boolean includeMaxblocky, boolean includeBiome, boolean includeBiomeTempRain); + + /** + * Get a list of all entities in the chunk. + * + * @return The entities. + */ + @NotNull + Entity[] getEntities(); + + // Paper start + /** + * Get a list of all tile entities in the chunk. + * + * @return The tile entities. + */ + @NotNull + default BlockState[] getTileEntities() { + return getTileEntities(true); + } + + /** + * Get a list of all tile entities in the chunk. + * + * @param useSnapshot Take snapshots or direct references + * @return The tile entities. + */ + @NotNull + BlockState[] getTileEntities(boolean useSnapshot); + // Paper end + + /** + * Checks if the chunk is loaded. + * + * @return True if it is loaded. + */ + boolean isLoaded(); + + /** + * Loads the chunk. + * + * @param generate Whether or not to generate a chunk if it doesn't + * already exist + * @return true if the chunk has loaded successfully, otherwise false + */ + boolean load(boolean generate); + + /** + * Loads the chunk. + * + * @return true if the chunk has loaded successfully, otherwise false + */ + boolean load(); + + /** + * Unloads and optionally saves the Chunk + * + * @param save Controls whether the chunk is saved + * @param safe Controls whether to unload the chunk when players are + * nearby + * @return true if the chunk has unloaded successfully, otherwise false + * @deprecated it is never safe to remove a chunk in use + */ + @Deprecated + boolean unload(boolean save, boolean safe); + + /** + * Unloads and optionally saves the Chunk + * + * @param save Controls whether the chunk is saved + * @return true if the chunk has unloaded successfully, otherwise false + */ + boolean unload(boolean save); + + /** + * Unloads and optionally saves the Chunk + * + * @return true if the chunk has unloaded successfully, otherwise false + */ + boolean unload(); + + /** + * Checks if this chunk can spawn slimes without being a swamp biome. + * + * @return true if slimes are able to spawn in this chunk + */ + boolean isSlimeChunk(); + + /** + * Gets whether the chunk at the specified chunk coordinates is force + * loaded. + *

+ * A force loaded chunk will not be unloaded due to lack of player activity. + * + * @return force load status + * @see World#isChunkForceLoaded(int, int) + */ + boolean isForceLoaded(); + + /** + * Sets whether the chunk at the specified chunk coordinates is force + * loaded. + *

+ * A force loaded chunk will not be unloaded due to lack of player activity. + * + * @param forced + * @see World#setChunkForceLoaded(int, int, boolean) + */ + void setForceLoaded(boolean forced); +} diff --git a/api/src/main/java/org/bukkit/ChunkSnapshot.java b/api/src/main/java/org/bukkit/ChunkSnapshot.java new file mode 100644 index 000000000..153d72b0e --- /dev/null +++ b/api/src/main/java/org/bukkit/ChunkSnapshot.java @@ -0,0 +1,134 @@ +package org.bukkit; + +import org.bukkit.block.Biome; +import org.bukkit.block.data.BlockData; +import org.jetbrains.annotations.NotNull; + +/** + * Represents a static, thread-safe snapshot of chunk of blocks. + *

+ * Purpose is to allow clean, efficient copy of a chunk data to be made, and + * then handed off for processing in another thread (e.g. map rendering) + */ +public interface ChunkSnapshot { + + /** + * Gets the X-coordinate of this chunk + * + * @return X-coordinate + */ + int getX(); + + /** + * Gets the Z-coordinate of this chunk + * + * @return Z-coordinate + */ + int getZ(); + + /** + * Gets name of the world containing this chunk + * + * @return Parent World Name + */ + @NotNull + String getWorldName(); + + /** + * Get block type for block at corresponding coordinate in the chunk + * + * @param x 0-15 + * @param y 0-255 + * @param z 0-15 + * @return block material type + */ + @NotNull + Material getBlockType(int x, int y, int z); + + /** + * Get block data for block at corresponding coordinate in the chunk + * + * @param x 0-15 + * @param y 0-255 + * @param z 0-15 + * @return block material type + */ + @NotNull + BlockData getBlockData(int x, int y, int z); + + /** + * Get block data for block at corresponding coordinate in the chunk + * + * @param x 0-15 + * @param y 0-255 + * @param z 0-15 + * @return 0-15 + * @deprecated Magic value + */ + @Deprecated + int getData(int x, int y, int z); + + /** + * Get sky light level for block at corresponding coordinate in the chunk + * + * @param x 0-15 + * @param y 0-255 + * @param z 0-15 + * @return 0-15 + */ + int getBlockSkyLight(int x, int y, int z); + + /** + * Get light level emitted by block at corresponding coordinate in the + * chunk + * + * @param x 0-15 + * @param y 0-255 + * @param z 0-15 + * @return 0-15 + */ + int getBlockEmittedLight(int x, int y, int z); + + /** + * Gets the highest non-air coordinate at the given coordinates + * + * @param x X-coordinate of the blocks (0-15) + * @param z Z-coordinate of the blocks (0-15) + * @return Y-coordinate of the highest non-air block + */ + int getHighestBlockYAt(int x, int z); + + /** + * Get biome at given coordinates + * + * @param x X-coordinate (0-15) + * @param z Z-coordinate (0-15) + * @return Biome at given coordinate + */ + @NotNull + Biome getBiome(int x, int z); + + /** + * Get raw biome temperature (0.0-1.0) at given coordinate + * + * @param x X-coordinate (0-15) + * @param z Z-coordinate (0-15) + * @return temperature at given coordinate + */ + double getRawBiomeTemperature(int x, int z); + + /** + * Get world full time when chunk snapshot was captured + * + * @return time in ticks + */ + long getCaptureFullTime(); + + /** + * Test if section is empty + * + * @param sy - section Y coordinate (block Y / 16, 0-255) + * @return true if empty, false if not + */ + boolean isSectionEmpty(int sy); +} diff --git a/api/src/main/java/org/bukkit/CoalType.java b/api/src/main/java/org/bukkit/CoalType.java new file mode 100644 index 000000000..9e59a7ba1 --- /dev/null +++ b/api/src/main/java/org/bukkit/CoalType.java @@ -0,0 +1,52 @@ +package org.bukkit; + +import java.util.Map; + +import com.google.common.collect.Maps; +import org.jetbrains.annotations.Nullable; + +/** + * Represents the two types of coal + */ +public enum CoalType { + COAL(0x0), + CHARCOAL(0x1); + + private final byte data; + private final static Map BY_DATA = Maps.newHashMap(); + + private CoalType(final int data) { + this.data = (byte) data; + } + + /** + * Gets the associated data value representing this type of coal + * + * @return A byte containing the data value of this coal type + * @deprecated Magic value + */ + @Deprecated + public byte getData() { + return data; + } + + /** + * Gets the type of coal with the given data value + * + * @param data Data value to fetch + * @return The {@link CoalType} representing the given value, or null if + * it doesn't exist + * @deprecated Magic value + */ + @Deprecated + @Nullable + public static CoalType getByData(final byte data) { + return BY_DATA.get(data); + } + + static { + for (CoalType type : values()) { + BY_DATA.put(type.data, type); + } + } +} diff --git a/api/src/main/java/org/bukkit/Color.java b/api/src/main/java/org/bukkit/Color.java new file mode 100644 index 000000000..fbad45905 --- /dev/null +++ b/api/src/main/java/org/bukkit/Color.java @@ -0,0 +1,356 @@ +package org.bukkit; + +import java.util.Map; + +import org.apache.commons.lang.Validate; +import org.bukkit.configuration.serialization.ConfigurationSerializable; +import org.bukkit.configuration.serialization.SerializableAs; + +import com.google.common.collect.ImmutableMap; +import org.jetbrains.annotations.NotNull; + +/** + * A container for a color palette. This class is immutable; the set methods + * return a new color. The color names listed as fields are HTML4 standards, + * but subject to change. + */ +@SerializableAs("Color") +public final class Color implements ConfigurationSerializable { + private static final int BIT_MASK = 0xff; + + /** + * White, or (0xFF,0xFF,0xFF) in (R,G,B) + */ + public static final Color WHITE = fromRGB(0xFFFFFF); + + /** + * Silver, or (0xC0,0xC0,0xC0) in (R,G,B) + */ + public static final Color SILVER = fromRGB(0xC0C0C0); + + /** + * Gray, or (0x80,0x80,0x80) in (R,G,B) + */ + public static final Color GRAY = fromRGB(0x808080); + + /** + * Black, or (0x00,0x00,0x00) in (R,G,B) + */ + public static final Color BLACK = fromRGB(0x000000); + + /** + * Red, or (0xFF,0x00,0x00) in (R,G,B) + */ + public static final Color RED = fromRGB(0xFF0000); + + /** + * Maroon, or (0x80,0x00,0x00) in (R,G,B) + */ + public static final Color MAROON = fromRGB(0x800000); + + /** + * Yellow, or (0xFF,0xFF,0x00) in (R,G,B) + */ + public static final Color YELLOW = fromRGB(0xFFFF00); + + /** + * Olive, or (0x80,0x80,0x00) in (R,G,B) + */ + public static final Color OLIVE = fromRGB(0x808000); + + /** + * Lime, or (0x00,0xFF,0x00) in (R,G,B) + */ + public static final Color LIME = fromRGB(0x00FF00); + + /** + * Green, or (0x00,0x80,0x00) in (R,G,B) + */ + public static final Color GREEN = fromRGB(0x008000); + + /** + * Aqua, or (0x00,0xFF,0xFF) in (R,G,B) + */ + public static final Color AQUA = fromRGB(0x00FFFF); + + /** + * Teal, or (0x00,0x80,0x80) in (R,G,B) + */ + public static final Color TEAL = fromRGB(0x008080); + + /** + * Blue, or (0x00,0x00,0xFF) in (R,G,B) + */ + public static final Color BLUE = fromRGB(0x0000FF); + + /** + * Navy, or (0x00,0x00,0x80) in (R,G,B) + */ + public static final Color NAVY = fromRGB(0x000080); + + /** + * Fuchsia, or (0xFF,0x00,0xFF) in (R,G,B) + */ + public static final Color FUCHSIA = fromRGB(0xFF00FF); + + /** + * Purple, or (0x80,0x00,0x80) in (R,G,B) + */ + public static final Color PURPLE = fromRGB(0x800080); + + /** + * Orange, or (0xFF,0xA5,0x00) in (R,G,B) + */ + public static final Color ORANGE = fromRGB(0xFFA500); + + private final byte red; + private final byte green; + private final byte blue; + + /** + * Creates a new Color object from a red, green, and blue + * + * @param red integer from 0-255 + * @param green integer from 0-255 + * @param blue integer from 0-255 + * @return a new Color object for the red, green, blue + * @throws IllegalArgumentException if any value is strictly {@literal >255 or <0} + */ + @NotNull + public static Color fromRGB(int red, int green, int blue) throws IllegalArgumentException { + return new Color(red, green, blue); + } + + /** + * Creates a new Color object from a blue, green, and red + * + * @param blue integer from 0-255 + * @param green integer from 0-255 + * @param red integer from 0-255 + * @return a new Color object for the red, green, blue + * @throws IllegalArgumentException if any value is strictly {@literal >255 or <0} + */ + @NotNull + public static Color fromBGR(int blue, int green, int red) throws IllegalArgumentException { + return new Color(red, green, blue); + } + + /** + * Creates a new color object from an integer that contains the red, + * green, and blue bytes in the lowest order 24 bits. + * + * @param rgb the integer storing the red, green, and blue values + * @return a new color object for specified values + * @throws IllegalArgumentException if any data is in the highest order 8 + * bits + */ + @NotNull + public static Color fromRGB(int rgb) throws IllegalArgumentException { + Validate.isTrue((rgb >> 24) == 0, "Extrenuous data in: ", rgb); + return fromRGB(rgb >> 16 & BIT_MASK, rgb >> 8 & BIT_MASK, rgb >> 0 & BIT_MASK); + } + + /** + * Creates a new color object from an integer that contains the blue, + * green, and red bytes in the lowest order 24 bits. + * + * @param bgr the integer storing the blue, green, and red values + * @return a new color object for specified values + * @throws IllegalArgumentException if any data is in the highest order 8 + * bits + */ + @NotNull + public static Color fromBGR(int bgr) throws IllegalArgumentException { + Validate.isTrue((bgr >> 24) == 0, "Extrenuous data in: ", bgr); + return fromBGR(bgr >> 16 & BIT_MASK, bgr >> 8 & BIT_MASK, bgr >> 0 & BIT_MASK); + } + + private Color(int red, int green, int blue) { + Validate.isTrue(red >= 0 && red <= BIT_MASK, "Red is not between 0-255: ", red); + Validate.isTrue(green >= 0 && green <= BIT_MASK, "Green is not between 0-255: ", green); + Validate.isTrue(blue >= 0 && blue <= BIT_MASK, "Blue is not between 0-255: ", blue); + + this.red = (byte) red; + this.green = (byte) green; + this.blue = (byte) blue; + } + + /** + * Gets the red component + * + * @return red component, from 0 to 255 + */ + public int getRed() { + return BIT_MASK & red; + } + + /** + * Creates a new Color object with specified component + * + * @param red the red component, from 0 to 255 + * @return a new color object with the red component + */ + @NotNull + public Color setRed(int red) { + return fromRGB(red, getGreen(), getBlue()); + } + + /** + * Gets the green component + * + * @return green component, from 0 to 255 + */ + public int getGreen() { + return BIT_MASK & green; + } + + /** + * Creates a new Color object with specified component + * + * @param green the red component, from 0 to 255 + * @return a new color object with the red component + */ + @NotNull + public Color setGreen(int green) { + return fromRGB(getRed(), green, getBlue()); + } + + /** + * Gets the blue component + * + * @return blue component, from 0 to 255 + */ + public int getBlue() { + return BIT_MASK & blue; + } + + /** + * Creates a new Color object with specified component + * + * @param blue the red component, from 0 to 255 + * @return a new color object with the red component + */ + @NotNull + public Color setBlue(int blue) { + return fromRGB(getRed(), getGreen(), blue); + } + + /** + * + * @return An integer representation of this color, as 0xRRGGBB + */ + public int asRGB() { + return getRed() << 16 | getGreen() << 8 | getBlue() << 0; + } + + /** + * + * @return An integer representation of this color, as 0xBBGGRR + */ + public int asBGR() { + return getBlue() << 16 | getGreen() << 8 | getRed() << 0; + } + + /** + * Creates a new color with its RGB components changed as if it was dyed + * with the colors passed in, replicating vanilla workbench dyeing + * + * @param colors The DyeColors to dye with + * @return A new color with the changed rgb components + */ + // TODO: Javadoc what this method does, not what it mimics. API != Implementation + @NotNull + public Color mixDyes(@NotNull DyeColor... colors) { + Validate.noNullElements(colors, "Colors cannot be null"); + + Color[] toPass = new Color[colors.length]; + for (int i = 0; i < colors.length; i++) { + toPass[i] = colors[i].getColor(); + } + + return mixColors(toPass); + } + + /** + * Creates a new color with its RGB components changed as if it was dyed + * with the colors passed in, replicating vanilla workbench dyeing + * + * @param colors The colors to dye with + * @return A new color with the changed rgb components + */ + // TODO: Javadoc what this method does, not what it mimics. API != Implementation + @NotNull + public Color mixColors(@NotNull Color... colors) { + Validate.noNullElements(colors, "Colors cannot be null"); + + int totalRed = this.getRed(); + int totalGreen = this.getGreen(); + int totalBlue = this.getBlue(); + int totalMax = Math.max(Math.max(totalRed, totalGreen), totalBlue); + for (Color color : colors) { + totalRed += color.getRed(); + totalGreen += color.getGreen(); + totalBlue += color.getBlue(); + totalMax += Math.max(Math.max(color.getRed(), color.getGreen()), color.getBlue()); + } + + float averageRed = totalRed / (colors.length + 1); + float averageGreen = totalGreen / (colors.length + 1); + float averageBlue = totalBlue / (colors.length + 1); + float averageMax = totalMax / (colors.length + 1); + + float maximumOfAverages = Math.max(Math.max(averageRed, averageGreen), averageBlue); + float gainFactor = averageMax / maximumOfAverages; + + return Color.fromRGB((int) (averageRed * gainFactor), (int) (averageGreen * gainFactor), (int) (averageBlue * gainFactor)); + } + + @Override + public boolean equals(Object o) { + if (!(o instanceof Color)) { + return false; + } + final Color that = (Color) o; + return this.blue == that.blue && this.green == that.green && this.red == that.red; + } + + @Override + public int hashCode() { + return asRGB() ^ Color.class.hashCode(); + } + + @NotNull + public Map serialize() { + return ImmutableMap.of( + "RED", getRed(), + "BLUE", getBlue(), + "GREEN", getGreen() + ); + } + + @SuppressWarnings("javadoc") + @NotNull + public static Color deserialize(@NotNull Map map) { + return fromRGB( + asInt("RED", map), + asInt("GREEN", map), + asInt("BLUE", map) + ); + } + + private static int asInt(@NotNull String string, @NotNull Map map) { + Object value = map.get(string); + if (value == null) { + throw new IllegalArgumentException(string + " not in map " + map); + } + if (!(value instanceof Number)) { + throw new IllegalArgumentException(string + '(' + value + ") is not a number"); + } + return ((Number) value).intValue(); + } + + @Override + public String toString() { + return "Color:[rgb0x" + Integer.toHexString(getRed()).toUpperCase() + Integer.toHexString(getGreen()).toUpperCase() + Integer.toHexString(getBlue()).toUpperCase() + "]"; + } +} diff --git a/api/src/main/java/org/bukkit/CropState.java b/api/src/main/java/org/bukkit/CropState.java new file mode 100644 index 000000000..9a3990dff --- /dev/null +++ b/api/src/main/java/org/bukkit/CropState.java @@ -0,0 +1,83 @@ +package org.bukkit; + +import java.util.Map; + +import com.google.common.collect.Maps; +import org.jetbrains.annotations.Nullable; + +/** + * Represents the different growth states of crops + */ +public enum CropState { + + /** + * State when first seeded + */ + SEEDED(0x0), + /** + * First growth stage + */ + GERMINATED(0x1), + /** + * Second growth stage + */ + VERY_SMALL(0x2), + /** + * Third growth stage + */ + SMALL(0x3), + /** + * Fourth growth stage + */ + MEDIUM(0x4), + /** + * Fifth growth stage + */ + TALL(0x5), + /** + * Almost ripe stage + */ + VERY_TALL(0x6), + /** + * Ripe stage + */ + RIPE(0x7); + + private final byte data; + private final static Map BY_DATA = Maps.newHashMap(); + + private CropState(final int data) { + this.data = (byte) data; + } + + /** + * Gets the associated data value representing this growth state + * + * @return A byte containing the data value of this growth state + * @deprecated Magic value + */ + @Deprecated + public byte getData() { + return data; + } + + /** + * Gets the CropState with the given data value + * + * @param data Data value to fetch + * @return The {@link CropState} representing the given value, or null if + * it doesn't exist + * @deprecated Magic value + */ + @Deprecated + @Nullable + public static CropState getByData(final byte data) { + return BY_DATA.get(data); + } + + static { + for (CropState cropState : values()) { + BY_DATA.put(cropState.getData(), cropState); + } + } +} diff --git a/api/src/main/java/org/bukkit/Difficulty.java b/api/src/main/java/org/bukkit/Difficulty.java new file mode 100644 index 000000000..19367a700 --- /dev/null +++ b/api/src/main/java/org/bukkit/Difficulty.java @@ -0,0 +1,74 @@ +package org.bukkit; + +import java.util.Map; + +import com.google.common.collect.Maps; +import org.jetbrains.annotations.Nullable; + +/** + * Represents the various difficulty levels that are available. + */ +public enum Difficulty { + /** + * Players regain health over time, hostile mobs don't spawn, the hunger + * bar does not deplete. + */ + PEACEFUL(0), + + /** + * Hostile mobs spawn, enemies deal less damage than on normal difficulty, + * the hunger bar does deplete and starving deals up to 5 hearts of + * damage. (Default value) + */ + EASY(1), + + /** + * Hostile mobs spawn, enemies deal normal amounts of damage, the hunger + * bar does deplete and starving deals up to 9.5 hearts of damage. + */ + NORMAL(2), + + /** + * Hostile mobs spawn, enemies deal greater damage than on normal + * difficulty, the hunger bar does deplete and starving can kill players. + */ + HARD(3); + + private final int value; + private final static Map BY_ID = Maps.newHashMap(); + + private Difficulty(final int value) { + this.value = value; + } + + /** + * Gets the difficulty value associated with this Difficulty. + * + * @return An integer value of this difficulty + * @deprecated Magic value + */ + @Deprecated + public int getValue() { + return value; + } + + /** + * Gets the Difficulty represented by the specified value + * + * @param value Value to check + * @return Associative {@link Difficulty} with the given value, or null if + * it doesn't exist + * @deprecated Magic value + */ + @Deprecated + @Nullable + public static Difficulty getByValue(final int value) { + return BY_ID.get(value); + } + + static { + for (Difficulty diff : values()) { + BY_ID.put(diff.value, diff); + } + } +} diff --git a/api/src/main/java/org/bukkit/DyeColor.java b/api/src/main/java/org/bukkit/DyeColor.java new file mode 100644 index 000000000..ac75be657 --- /dev/null +++ b/api/src/main/java/org/bukkit/DyeColor.java @@ -0,0 +1,230 @@ +package org.bukkit; + +import java.util.Map; + +import com.google.common.collect.ImmutableMap; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +/** + * All supported color values for dyes and cloth + */ +public enum DyeColor { + + /** + * Represents white dye. + */ + WHITE(0x0, 0xF, Color.fromRGB(0xF9FFFE), Color.fromRGB(0xF0F0F0)), + /** + * Represents orange dye. + */ + ORANGE(0x1, 0xE, Color.fromRGB(0xF9801D), Color.fromRGB(0xEB8844)), + /** + * Represents magenta dye. + */ + MAGENTA(0x2, 0xD, Color.fromRGB(0xC74EBD), Color.fromRGB(0xC354CD)), + /** + * Represents light blue dye. + */ + LIGHT_BLUE(0x3, 0xC, Color.fromRGB(0x3AB3DA), Color.fromRGB(0x6689D3)), + /** + * Represents yellow dye. + */ + YELLOW(0x4, 0xB, Color.fromRGB(0xFED83D), Color.fromRGB(0xDECF2A)), + /** + * Represents lime dye. + */ + LIME(0x5, 0xA, Color.fromRGB(0x80C71F), Color.fromRGB(0x41CD34)), + /** + * Represents pink dye. + */ + PINK(0x6, 0x9, Color.fromRGB(0xF38BAA), Color.fromRGB(0xD88198)), + /** + * Represents gray dye. + */ + GRAY(0x7, 0x8, Color.fromRGB(0x474F52), Color.fromRGB(0x434343)), + /** + * Represents light gray dye. + */ + LIGHT_GRAY(0x8, 0x7, Color.fromRGB(0x9D9D97), Color.fromRGB(0xABABAB)), + /** + * Represents cyan dye. + */ + CYAN(0x9, 0x6, Color.fromRGB(0x169C9C), Color.fromRGB(0x287697)), + /** + * Represents purple dye. + */ + PURPLE(0xA, 0x5, Color.fromRGB(0x8932B8), Color.fromRGB(0x7B2FBE)), + /** + * Represents blue dye. + */ + BLUE(0xB, 0x4, Color.fromRGB(0x3C44AA), Color.fromRGB(0x253192)), + /** + * Represents brown dye. + */ + BROWN(0xC, 0x3, Color.fromRGB(0x835432), Color.fromRGB(0x51301A)), + /** + * Represents green dye. + */ + GREEN(0xD, 0x2, Color.fromRGB(0x5E7C16), Color.fromRGB(0x3B511A)), + /** + * Represents red dye. + */ + RED(0xE, 0x1, Color.fromRGB(0xB02E26), Color.fromRGB(0xB3312C)), + /** + * Represents black dye. + */ + BLACK(0xF, 0x0, Color.fromRGB(0x1D1D21), Color.fromRGB(0x1E1B1B)); + + private final byte woolData; + private final byte dyeData; + private final Color color; + private final Color firework; + private final static DyeColor[] BY_WOOL_DATA; + private final static DyeColor[] BY_DYE_DATA; + private final static Map BY_COLOR; + private final static Map BY_FIREWORK; + + private DyeColor(final int woolData, final int dyeData, /*@NotNull*/ Color color, /*@NotNull*/ Color firework) { + this.woolData = (byte) woolData; + this.dyeData = (byte) dyeData; + this.color = color; + this.firework = firework; + } + + /** + * Gets the associated wool data value representing this color. + * + * @return A byte containing the wool data value of this color + * @see #getDyeData() + * @deprecated Magic value + */ + @Deprecated + public byte getWoolData() { + return woolData; + } + + /** + * Gets the associated dye data value representing this color. + * + * @return A byte containing the dye data value of this color + * @see #getWoolData() + * @deprecated Magic value + */ + @Deprecated + public byte getDyeData() { + return dyeData; + } + + /** + * Gets the color that this dye represents. + * + * @return The {@link Color} that this dye represents + */ + @NotNull + public Color getColor() { + return color; + } + + /** + * Gets the firework color that this dye represents. + * + * @return The {@link Color} that this dye represents + */ + @NotNull + public Color getFireworkColor() { + return firework; + } + + /** + * Gets the DyeColor with the given wool data value. + * + * @param data Wool data value to fetch + * @return The {@link DyeColor} representing the given value, or null if + * it doesn't exist + * @see #getByDyeData(byte) + * @deprecated Magic value + */ + @Deprecated + @Nullable + public static DyeColor getByWoolData(final byte data) { + int i = 0xff & data; + if (i >= BY_WOOL_DATA.length) { + return null; + } + return BY_WOOL_DATA[i]; + } + + /** + * Gets the DyeColor with the given dye data value. + * + * @param data Dye data value to fetch + * @return The {@link DyeColor} representing the given value, or null if + * it doesn't exist + * @see #getByWoolData(byte) + * @deprecated Magic value + */ + @Deprecated + @Nullable + public static DyeColor getByDyeData(final byte data) { + int i = 0xff & data; + if (i >= BY_DYE_DATA.length) { + return null; + } + return BY_DYE_DATA[i]; + } + + /** + * Gets the DyeColor with the given color value. + * + * @param color Color value to get the dye by + * @return The {@link DyeColor} representing the given value, or null if + * it doesn't exist + */ + @Nullable + public static DyeColor getByColor(@NotNull final Color color) { + return BY_COLOR.get(color); + } + + /** + * Gets the DyeColor with the given firework color value. + * + * @param color Color value to get dye by + * @return The {@link DyeColor} representing the given value, or null if + * it doesn't exist + */ + @Nullable + public static DyeColor getByFireworkColor(@NotNull final Color color) { + return BY_FIREWORK.get(color); + } + + /** + * Gets the DyeColor for the given name, possibly doing legacy transformations. + * + * @param name dye name + * @return dye color + * @deprecated legacy use only + */ + @Deprecated + @NotNull + public static DyeColor legacyValueOf(@Nullable String name) { + return "SILVER".equals(name) ? DyeColor.LIGHT_GRAY : DyeColor.valueOf(name); + } + + static { + BY_WOOL_DATA = values(); + BY_DYE_DATA = values(); + ImmutableMap.Builder byColor = ImmutableMap.builder(); + ImmutableMap.Builder byFirework = ImmutableMap.builder(); + + for (DyeColor color : values()) { + BY_WOOL_DATA[color.woolData & 0xff] = color; + BY_DYE_DATA[color.dyeData & 0xff] = color; + byColor.put(color.getColor(), color); + byFirework.put(color.getFireworkColor(), color); + } + + BY_COLOR = byColor.build(); + BY_FIREWORK = byFirework.build(); + } +} diff --git a/api/src/main/java/org/bukkit/Effect.java b/api/src/main/java/org/bukkit/Effect.java new file mode 100644 index 000000000..e476de658 --- /dev/null +++ b/api/src/main/java/org/bukkit/Effect.java @@ -0,0 +1,269 @@ +package org.bukkit; + +import java.util.Map; + +import com.google.common.collect.Maps; + +import org.bukkit.block.BlockFace; +import org.bukkit.potion.Potion; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +/** + * A list of effects that the server is able to send to players. + */ +public enum Effect { + /** + * An alternate click sound. + */ + CLICK2(1000, Type.SOUND), + /** + * A click sound. + */ + CLICK1(1001, Type.SOUND), + /** + * Sound of a bow firing. + */ + BOW_FIRE(1002, Type.SOUND), + /** + * Sound of a door opening. + */ + DOOR_TOGGLE(1006, Type.SOUND), + /** + * Sound of a door opening. + */ + IRON_DOOR_TOGGLE(1005, Type.SOUND), + /** + * Sound of a trapdoor opening. + */ + TRAPDOOR_TOGGLE(1007, Type.SOUND), + /** + * Sound of a door opening. + */ + IRON_TRAPDOOR_TOGGLE(1037, Type.SOUND), + /** + * Sound of a door opening. + */ + FENCE_GATE_TOGGLE(1008, Type.SOUND), + /** + * Sound of a door closing. + */ + DOOR_CLOSE(1012, Type.SOUND), + /** + * Sound of a door closing. + */ + IRON_DOOR_CLOSE(1011, Type.SOUND), + /** + * Sound of a trapdoor closing. + */ + TRAPDOOR_CLOSE(1013, Type.SOUND), + /** + * Sound of a door closing. + */ + IRON_TRAPDOOR_CLOSE(1036, Type.SOUND), + /** + * Sound of a door closing. + */ + FENCE_GATE_CLOSE(1014, Type.SOUND), + /** + * Sound of fire being extinguished. + */ + EXTINGUISH(1009, Type.SOUND), + /** + * A song from a record. Needs the record item ID as additional info + */ + RECORD_PLAY(1010, Type.SOUND, Material.class), + /** + * Sound of ghast shrieking. + */ + GHAST_SHRIEK(1015, Type.SOUND), + /** + * Sound of ghast firing. + */ + GHAST_SHOOT(1016, Type.SOUND), + /** + * Sound of blaze firing. + */ + BLAZE_SHOOT(1018, Type.SOUND), + /** + * Sound of zombies chewing on wooden doors. + */ + ZOMBIE_CHEW_WOODEN_DOOR(1019, Type.SOUND), + /** + * Sound of zombies chewing on iron doors. + */ + ZOMBIE_CHEW_IRON_DOOR(1020, Type.SOUND), + /** + * Sound of zombies destroying a door. + */ + ZOMBIE_DESTROY_DOOR(1021, Type.SOUND), + /** + * A visual smoke effect. Needs direction as additional info. + */ + SMOKE(2000, Type.VISUAL, BlockFace.class), + /** + * Sound of a block breaking. Needs block ID as additional info. + */ + STEP_SOUND(2001, Type.SOUND, Material.class), + /** + * Visual effect of a splash potion breaking. Needs potion data value as + * additional info. + */ + POTION_BREAK(2002, Type.VISUAL, Potion.class), + /** + * An ender eye signal; a visual effect. + */ + ENDER_SIGNAL(2003, Type.VISUAL), + /** + * The flames seen on a mobspawner; a visual effect. + */ + MOBSPAWNER_FLAMES(2004, Type.VISUAL), + /** + * The sound played by brewing stands when brewing + */ + BREWING_STAND_BREW(1035, Type.SOUND), + /** + * The sound played when a chorus flower grows + */ + CHORUS_FLOWER_GROW(1033, Type.SOUND), + /** + * The sound played when a chorus flower dies + */ + CHORUS_FLOWER_DEATH(1034, Type.SOUND), + /** + * The sound played when traveling through a portal + */ + PORTAL_TRAVEL(1032, Type.SOUND), + /** + * The sound played when launching an endereye + */ + ENDEREYE_LAUNCH(1003, Type.SOUND), + /** + * The sound played when launching a firework + */ + FIREWORK_SHOOT(1004, Type.SOUND), + /** + * Particles displayed when a villager grows a plant, data + * is the number of particles + */ + VILLAGER_PLANT_GROW(2005, Type.VISUAL, Integer.class), + /** + * The sound/particles used by the enderdragon's breath + * attack. + */ + DRAGON_BREATH(2006, Type.VISUAL), + /** + * The sound played when an anvil breaks + */ + ANVIL_BREAK(1029, Type.SOUND), + /** + * The sound played when an anvil is used + */ + ANVIL_USE(1030, Type.SOUND), + /** + * The sound played when an anvil lands after + * falling + */ + ANVIL_LAND(1031, Type.SOUND), + /** + * Sound of an enderdragon firing + */ + ENDERDRAGON_SHOOT(1017, Type.SOUND), + /** + * The sound played when a wither breaks a block + */ + WITHER_BREAK_BLOCK(1022, Type.SOUND), + /** + * Sound of a wither shooting + */ + WITHER_SHOOT(1024, Type.SOUND), + /** + * The sound played when a zombie infects a target + */ + ZOMBIE_INFECT(1026, Type.SOUND), + /** + * The sound played when a villager is converted by + * a zombie + */ + ZOMBIE_CONVERTED_VILLAGER(1027, Type.SOUND), + /** + * Sound played by a bat taking off + */ + BAT_TAKEOFF(1025, Type.SOUND), + /** + * The sound/particles caused by a end gateway spawning + */ + END_GATEWAY_SPAWN(3000, Type.VISUAL), + /** + * The sound of an enderdragon growling + */ + ENDERDRAGON_GROWL(3001, Type.SOUND), + ; + + private final int id; + private final Type type; + private final Class data; + private static final Map BY_ID = Maps.newHashMap(); + + Effect(int id, /*@NotNull*/ Type type) { + this(id, type, null); + } + + Effect(int id, /*@NotNull*/ Type type, /*@Nullable*/ Class data) { + this.id = id; + this.type = type; + this.data = data; + } + + /** + * Gets the ID for this effect. + * + * @return ID of this effect + * @deprecated Magic value + */ + @Deprecated + public int getId() { + return this.id; + } + + /** + * @return The type of the effect. + */ + @NotNull + public Type getType() { + return this.type; + } + + /** + * @return The class which represents data for this effect, or null if + * none + */ + @Nullable + public Class getData() { + return this.data; + } + + /** + * Gets the Effect associated with the given ID. + * + * @param id ID of the Effect to return + * @return Effect with the given ID + * @deprecated Magic value + */ + @Deprecated + @Nullable + public static Effect getById(int id) { + return BY_ID.get(id); + } + + static { + for (Effect effect : values()) { + BY_ID.put(effect.id, effect); + } + } + + /** + * Represents the type of an effect. + */ + public enum Type {SOUND, VISUAL} +} diff --git a/api/src/main/java/org/bukkit/EntityEffect.java b/api/src/main/java/org/bukkit/EntityEffect.java new file mode 100644 index 000000000..b7576f681 --- /dev/null +++ b/api/src/main/java/org/bukkit/EntityEffect.java @@ -0,0 +1,203 @@ +package org.bukkit; + +import java.util.Map; + +import com.google.common.collect.Maps; +import org.bukkit.entity.Ageable; +import org.bukkit.entity.ArmorStand; +import org.bukkit.entity.Entity; +import org.bukkit.entity.Firework; +import org.bukkit.entity.Guardian; +import org.bukkit.entity.IronGolem; +import org.bukkit.entity.LivingEntity; +import org.bukkit.entity.Rabbit; +import org.bukkit.entity.Squid; +import org.bukkit.entity.Tameable; +import org.bukkit.entity.TippedArrow; +import org.bukkit.entity.Villager; +import org.bukkit.entity.Witch; +import org.bukkit.entity.Wolf; +import org.bukkit.entity.ZombieVillager; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +/** + * A list of all Effects that can happen to entities. + */ +public enum EntityEffect { + + /** + * Colored particles from a tipped arrow. + */ + ARROW_PARTICLES(0, TippedArrow.class), + /** + * Rabbit jumping. + */ + RABBIT_JUMP(1, Rabbit.class), + /** + * When mobs get hurt. + */ + HURT(2, LivingEntity.class), + /** + * When a mob dies. + *

+ * This will cause client-glitches! + * + * @deprecated although this effect may trigger other events on non-living + * entities, it's only supported usage is on living ones. + */ + @Deprecated + DEATH(3, Entity.class), + // PAIL - SPIGOT-3641 duplicate + // GOLEM_ATTACK(4, IronGolem.class), + // 5 - unused + /** + * The smoke when taming a wolf fails. + */ + WOLF_SMOKE(6, Tameable.class), + /** + * The hearts when taming a wolf succeeds. + */ + WOLF_HEARTS(7, Wolf.class), + /** + * When a wolf shakes (after being wet). + */ + WOLF_SHAKE(8, Wolf.class), + // 9 - unused + /** + * When an entity eats a LONG_GRASS block. + * + * @deprecated although this effect may trigger other events on non-living + * entities, it's only supported usage is on living ones. + */ + @Deprecated + SHEEP_EAT(10, Entity.class), + /** + * When an Iron Golem gives a rose. + */ + IRON_GOLEM_ROSE(11, IronGolem.class), + /** + * Hearts from a villager. + */ + VILLAGER_HEART(12, Villager.class), + /** + * When a villager is angry. + */ + VILLAGER_ANGRY(13, Villager.class), + /** + * Happy particles from a villager. + */ + VILLAGER_HAPPY(14, Villager.class), + /** + * Magic particles from a witch. + */ + WITCH_MAGIC(15, Witch.class), + /** + * When a zombie transforms into a villager by shaking violently. + */ + ZOMBIE_TRANSFORM(16, ZombieVillager.class), + /** + * When a firework explodes. + */ + FIREWORK_EXPLODE(17, Firework.class), + /** + * Hearts from a breeding entity. + */ + LOVE_HEARTS(18, Ageable.class), + /** + * Resets squid rotation. + */ + SQUID_ROTATE(19, Squid.class), + /** + * Silverfish entering block, spawner spawning. + */ + ENTITY_POOF(20, LivingEntity.class), + /** + * Guardian sets laser target. + */ + GUARDIAN_TARGET(21, Guardian.class), + // 22-28 player internal flags + /** + * Shield blocks attack. + */ + SHIELD_BLOCK(29, LivingEntity.class), + /** + * Shield breaks. + */ + SHIELD_BREAK(30, LivingEntity.class), + // 31 - unused + /** + * Armor stand is hit. + */ + ARMOR_STAND_HIT(32, ArmorStand.class), + /** + * Entity hurt by thorns attack. + */ + THORNS_HURT(33, LivingEntity.class), + /** + * Iron golem puts away rose. + */ + IRON_GOLEM_SHEATH(34, IronGolem.class), + /** + * Totem prevents entity death. + */ + TOTEM_RESURRECT(35, LivingEntity.class), + /** + * Entity hurt due to drowning damage. + */ + HURT_DROWN(36, LivingEntity.class), + /** + * Entity hurt due to explosion damage. + */ + HURT_EXPLOSION(37, LivingEntity.class); + + private final byte data; + private final Class applicable; + private final static Map BY_DATA = Maps.newHashMap(); + + EntityEffect(final int data, /*@NotNull*/ Class clazz) { + this.data = (byte) data; + this.applicable = clazz; + } + + /** + * Gets the data value of this EntityEffect + * + * @return The data value + * @deprecated Magic value + */ + @Deprecated + public byte getData() { + return data; + } + + /** + * Gets entity superclass which this affect is applicable to. + * + * @return applicable class + */ + @NotNull + public Class getApplicable() { + return applicable; + } + + /** + * Gets the EntityEffect with the given data value + * + * @param data Data value to fetch + * @return The {@link EntityEffect} representing the given value, or null + * if it doesn't exist + * @deprecated Magic value + */ + @Deprecated + @Nullable + public static EntityEffect getByData(final byte data) { + return BY_DATA.get(data); + } + + static { + for (EntityEffect entityEffect : values()) { + BY_DATA.put(entityEffect.data, entityEffect); + } + } +} diff --git a/api/src/main/java/org/bukkit/FireworkEffect.java b/api/src/main/java/org/bukkit/FireworkEffect.java new file mode 100644 index 000000000..524dc9989 --- /dev/null +++ b/api/src/main/java/org/bukkit/FireworkEffect.java @@ -0,0 +1,440 @@ +package org.bukkit; + +import java.util.List; +import java.util.Map; + +import org.apache.commons.lang.Validate; +import org.bukkit.configuration.serialization.ConfigurationSerializable; +import org.bukkit.configuration.serialization.SerializableAs; + +import com.google.common.collect.ImmutableList; +import com.google.common.collect.ImmutableMap; +import org.jetbrains.annotations.NotNull; + +/** + * Represents a single firework effect. + */ +@SerializableAs("Firework") +public final class FireworkEffect implements ConfigurationSerializable { + + /** + * The type or shape of the effect. + */ + public enum Type { + /** + * A small ball effect. + */ + BALL, + /** + * A large ball effect. + */ + BALL_LARGE, + /** + * A star-shaped effect. + */ + STAR, + /** + * A burst effect. + */ + BURST, + /** + * A creeper-face effect. + */ + CREEPER, + ; + } + + /** + * Construct a firework effect. + * + * @return A utility object for building a firework effect + */ + @NotNull + public static Builder builder() { + return new Builder(); + } + + /** + * This is a builder for FireworkEffects. + * + * @see FireworkEffect#builder() + */ + public static final class Builder { + boolean flicker = false; + boolean trail = false; + final ImmutableList.Builder colors = ImmutableList.builder(); + ImmutableList.Builder fadeColors = null; + Type type = Type.BALL; + + Builder() {} + + /** + * Specify the type of the firework effect. + * + * @param type The effect type + * @return This object, for chaining + * @throws IllegalArgumentException If type is null + */ + @NotNull + public Builder with(@NotNull Type type) throws IllegalArgumentException { + Validate.notNull(type, "Cannot have null type"); + this.type = type; + return this; + } + + /** + * Add a flicker to the firework effect. + * + * @return This object, for chaining + */ + @NotNull + public Builder withFlicker() { + flicker = true; + return this; + } + + /** + * Set whether the firework effect should flicker. + * + * @param flicker true if it should flicker, false if not + * @return This object, for chaining + */ + @NotNull + public Builder flicker(boolean flicker) { + this.flicker = flicker; + return this; + } + + /** + * Add a trail to the firework effect. + * + * @return This object, for chaining + */ + @NotNull + public Builder withTrail() { + trail = true; + return this; + } + + /** + * Set whether the firework effect should have a trail. + * + * @param trail true if it should have a trail, false for no trail + * @return This object, for chaining + */ + @NotNull + public Builder trail(boolean trail) { + this.trail = trail; + return this; + } + + /** + * Add a primary color to the firework effect. + * + * @param color The color to add + * @return This object, for chaining + * @throws IllegalArgumentException If color is null + */ + @NotNull + public Builder withColor(@NotNull Color color) throws IllegalArgumentException { + Validate.notNull(color, "Cannot have null color"); + + colors.add(color); + + return this; + } + + /** + * Add several primary colors to the firework effect. + * + * @param colors The colors to add + * @return This object, for chaining + * @throws IllegalArgumentException If colors is null + * @throws IllegalArgumentException If any color is null (may be + * thrown after changes have occurred) + */ + @NotNull + public Builder withColor(@NotNull Color... colors) throws IllegalArgumentException { + Validate.notNull(colors, "Cannot have null colors"); + if (colors.length == 0) { + return this; + } + + ImmutableList.Builder list = this.colors; + for (Color color : colors) { + Validate.notNull(color, "Color cannot be null"); + list.add(color); + } + + return this; + } + + /** + * Add several primary colors to the firework effect. + * + * @param colors An iterable object whose iterator yields the desired + * colors + * @return This object, for chaining + * @throws IllegalArgumentException If colors is null + * @throws IllegalArgumentException If any color is null (may be + * thrown after changes have occurred) + */ + @NotNull + public Builder withColor(@NotNull Iterable colors) throws IllegalArgumentException { + Validate.notNull(colors, "Cannot have null colors"); + + ImmutableList.Builder list = this.colors; + for (Object color : colors) { + if (!(color instanceof Color)) { + throw new IllegalArgumentException(color + " is not a Color in " + colors); + } + list.add((Color) color); + } + + return this; + } + + /** + * Add a fade color to the firework effect. + * + * @param color The color to add + * @return This object, for chaining + * @throws IllegalArgumentException If colors is null + * @throws IllegalArgumentException If any color is null (may be + * thrown after changes have occurred) + */ + @NotNull + public Builder withFade(@NotNull Color color) throws IllegalArgumentException { + Validate.notNull(color, "Cannot have null color"); + + if (fadeColors == null) { + fadeColors = ImmutableList.builder(); + } + + fadeColors.add(color); + + return this; + } + + /** + * Add several fade colors to the firework effect. + * + * @param colors The colors to add + * @return This object, for chaining + * @throws IllegalArgumentException If colors is null + * @throws IllegalArgumentException If any color is null (may be + * thrown after changes have occurred) + */ + @NotNull + public Builder withFade(@NotNull Color... colors) throws IllegalArgumentException { + Validate.notNull(colors, "Cannot have null colors"); + if (colors.length == 0) { + return this; + } + + ImmutableList.Builder list = this.fadeColors; + if (list == null) { + list = this.fadeColors = ImmutableList.builder(); + } + + for (Color color : colors) { + Validate.notNull(color, "Color cannot be null"); + list.add(color); + } + + return this; + } + + /** + * Add several fade colors to the firework effect. + * + * @param colors An iterable object whose iterator yields the desired + * colors + * @return This object, for chaining + * @throws IllegalArgumentException If colors is null + * @throws IllegalArgumentException If any color is null (may be + * thrown after changes have occurred) + */ + @NotNull + public Builder withFade(@NotNull Iterable colors) throws IllegalArgumentException { + Validate.notNull(colors, "Cannot have null colors"); + + ImmutableList.Builder list = this.fadeColors; + if (list == null) { + list = this.fadeColors = ImmutableList.builder(); + } + + for (Object color : colors) { + if (!(color instanceof Color)) { + throw new IllegalArgumentException(color + " is not a Color in " + colors); + } + list.add((Color) color); + } + + return this; + } + + /** + * Create a {@link FireworkEffect} from the current contents of this + * builder. + *

+ * To successfully build, you must have specified at least one color. + * + * @return The representative firework effect + */ + @NotNull + public FireworkEffect build() { + return new FireworkEffect( + flicker, + trail, + colors.build(), + fadeColors == null ? ImmutableList.of() : fadeColors.build(), + type + ); + } + } + + private static final String FLICKER = "flicker"; + private static final String TRAIL = "trail"; + private static final String COLORS = "colors"; + private static final String FADE_COLORS = "fade-colors"; + private static final String TYPE = "type"; + + private final boolean flicker; + private final boolean trail; + private final ImmutableList colors; + private final ImmutableList fadeColors; + private final Type type; + private String string = null; + + FireworkEffect(boolean flicker, boolean trail, @NotNull ImmutableList colors, @NotNull ImmutableList fadeColors, @NotNull Type type) { + if (colors.isEmpty()) { + throw new IllegalStateException("Cannot make FireworkEffect without any color"); + } + this.flicker = flicker; + this.trail = trail; + this.colors = colors; + this.fadeColors = fadeColors; + this.type = type; + } + + /** + * Get whether the firework effect flickers. + * + * @return true if it flickers, false if not + */ + public boolean hasFlicker() { + return flicker; + } + + /** + * Get whether the firework effect has a trail. + * + * @return true if it has a trail, false if not + */ + public boolean hasTrail() { + return trail; + } + + /** + * Get the primary colors of the firework effect. + * + * @return An immutable list of the primary colors + */ + @NotNull + public List getColors() { + return colors; + } + + /** + * Get the fade colors of the firework effect. + * + * @return An immutable list of the fade colors + */ + @NotNull + public List getFadeColors() { + return fadeColors; + } + + /** + * Get the type of the firework effect. + * + * @return The effect type + */ + @NotNull + public Type getType() { + return type; + } + + /** + * @see ConfigurationSerializable + * @param map the map to deserialize + * @return the resulting serializable + */ + @NotNull + public static ConfigurationSerializable deserialize(@NotNull Map map) { + Type type = Type.valueOf((String) map.get(TYPE)); + + return builder() + .flicker((Boolean) map.get(FLICKER)) + .trail((Boolean) map.get(TRAIL)) + .withColor((Iterable) map.get(COLORS)) + .withFade((Iterable) map.get(FADE_COLORS)) + .with(type) + .build(); + } + + @NotNull + @Override + public Map serialize() { + return ImmutableMap.of( + FLICKER, flicker, + TRAIL, trail, + COLORS, colors, + FADE_COLORS, fadeColors, + TYPE, type.name() + ); + } + + @Override + public String toString() { + final String string = this.string; + if (string == null) { + return this.string = "FireworkEffect:" + serialize(); + } + return string; + } + + @Override + public int hashCode() { + /** + * TRUE and FALSE as per boolean.hashCode() + */ + final int PRIME = 31, TRUE = 1231, FALSE = 1237; + int hash = 1; + hash = hash * PRIME + (flicker ? TRUE : FALSE); + hash = hash * PRIME + (trail ? TRUE : FALSE); + hash = hash * PRIME + type.hashCode(); + hash = hash * PRIME + colors.hashCode(); + hash = hash * PRIME + fadeColors.hashCode(); + return hash; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } + + if (!(obj instanceof FireworkEffect)) { + return false; + } + + FireworkEffect that = (FireworkEffect) obj; + return this.flicker == that.flicker + && this.trail == that.trail + && this.type == that.type + && this.colors.equals(that.colors) + && this.fadeColors.equals(that.fadeColors); + } +} diff --git a/api/src/main/java/org/bukkit/FluidCollisionMode.java b/api/src/main/java/org/bukkit/FluidCollisionMode.java new file mode 100644 index 000000000..ae2895894 --- /dev/null +++ b/api/src/main/java/org/bukkit/FluidCollisionMode.java @@ -0,0 +1,20 @@ +package org.bukkit; + +/** + * Determines the collision behavior when fluids get hit during ray tracing. + */ +public enum FluidCollisionMode { + + /** + * Ignore fluids. + */ + NEVER, + /** + * Only collide with source fluid blocks. + */ + SOURCE_ONLY, + /** + * Collide with all fluids. + */ + ALWAYS; +} diff --git a/api/src/main/java/org/bukkit/GameMode.java b/api/src/main/java/org/bukkit/GameMode.java new file mode 100644 index 000000000..4cfc7762e --- /dev/null +++ b/api/src/main/java/org/bukkit/GameMode.java @@ -0,0 +1,75 @@ +package org.bukkit; + +import java.util.Map; + +import org.bukkit.entity.HumanEntity; + +import com.google.common.collect.Maps; +import org.jetbrains.annotations.Nullable; + +/** + * Represents the various type of game modes that {@link HumanEntity}s may + * have + */ +public enum GameMode { + /** + * Creative mode may fly, build instantly, become invulnerable and create + * free items. + */ + CREATIVE(1), + + /** + * Survival mode is the "normal" gameplay type, with no special features. + */ + SURVIVAL(0), + + /** + * Adventure mode cannot break blocks without the correct tools. + */ + ADVENTURE(2), + + /** + * Spectator mode cannot interact with the world in anyway and is + * invisible to normal players. This grants the player the + * ability to no-clip through the world. + */ + SPECTATOR(3); + + private final int value; + private final static Map BY_ID = Maps.newHashMap(); + + private GameMode(final int value) { + this.value = value; + } + + /** + * Gets the mode value associated with this GameMode + * + * @return An integer value of this gamemode + * @deprecated Magic value + */ + @Deprecated + public int getValue() { + return value; + } + + /** + * Gets the GameMode represented by the specified value + * + * @param value Value to check + * @return Associative {@link GameMode} with the given value, or null if + * it doesn't exist + * @deprecated Magic value + */ + @Deprecated + @Nullable + public static GameMode getByValue(final int value) { + return BY_ID.get(value); + } + + static { + for (GameMode mode : values()) { + BY_ID.put(mode.getValue(), mode); + } + } +} diff --git a/api/src/main/java/org/bukkit/GameRule.java b/api/src/main/java/org/bukkit/GameRule.java new file mode 100644 index 000000000..da49c26c5 --- /dev/null +++ b/api/src/main/java/org/bukkit/GameRule.java @@ -0,0 +1,224 @@ +package org.bukkit; + +import com.google.common.base.Preconditions; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +import java.util.HashMap; +import java.util.Map; + +/** + * GameRules dictate certain behavior within Minecraft itself + *
+ * For more information please visit the + * Minecraft + * Wiki + */ +public final class GameRule { + + private static Map> gameRules = new HashMap<>(); + // Boolean rules + /** + * Toggles the announcing of advancements. + */ + public static final GameRule ANNOUNCE_ADVANCEMENTS = new GameRule<>("announceAdvancements", Boolean.class); + + /** + * Whether command blocks should notify admins when they perform commands. + */ + public static final GameRule COMMAND_BLOCK_OUTPUT = new GameRule<>("commandBlockOutput", Boolean.class); + + /** + * Whether the server should skip checking player speed when the player is + * wearing elytra. + */ + public static final GameRule DISABLE_ELYTRA_MOVEMENT_CHECK = new GameRule<>("disableElytraMovementCheck", Boolean.class); + + /** + * Whether time progresses from the current moment. + */ + public static final GameRule DO_DAYLIGHT_CYCLE = new GameRule<>("doDaylightCycle", Boolean.class); + + /** + * Whether entities that are not mobs should have drops. + */ + public static final GameRule DO_ENTITY_DROPS = new GameRule<>("doEntityDrops", Boolean.class); + + /** + * Whether fire should spread and naturally extinguish. + */ + public static final GameRule DO_FIRE_TICK = new GameRule<>("doFireTick", Boolean.class); + + /** + * Whether players should only be able to craft recipes they've unlocked + * first. + */ + public static final GameRule DO_LIMITED_CRAFTING = new GameRule<>("doLimitedCrafting", Boolean.class); + + /** + * Whether mobs should drop items. + */ + public static final GameRule DO_MOB_LOOT = new GameRule<>("doMobLoot", Boolean.class); + + /** + * Whether mobs should naturally spawn. + */ + public static final GameRule DO_MOB_SPAWNING = new GameRule<>("doMobSpawning", Boolean.class); + + /** + * Whether blocks should have drops. + */ + public static final GameRule DO_TILE_DROPS = new GameRule<>("doTileDrops", Boolean.class); + + /** + * Whether the weather will change from the current moment. + */ + public static final GameRule DO_WEATHER_CYCLE = new GameRule<>("doWeatherCycle", Boolean.class); + + /** + * Whether the player should keep items in their inventory after death. + */ + public static final GameRule KEEP_INVENTORY = new GameRule<>("keepInventory", Boolean.class); + + /** + * Whether to log admin commands to server log. + */ + public static final GameRule LOG_ADMIN_COMMANDS = new GameRule<>("logAdminCommands", Boolean.class); + + /** + * Whether mobs can pick up items or change blocks. + */ + public static final GameRule MOB_GRIEFING = new GameRule<>("mobGriefing", Boolean.class); + + /** + * Whether players can regenerate health naturally through their hunger bar. + */ + public static final GameRule NATURAL_REGENERATION = new GameRule<>("naturalRegeneration", Boolean.class); + + /** + * Whether the debug screen shows all or reduced information. + */ + public static final GameRule REDUCED_DEBUG_INFO = new GameRule<>("reducedDebugInfo", Boolean.class); + + /** + * Whether the feedback from commands executed by a player should show up in + * chat. Also affects the default behavior of whether command blocks store + * their output text. + */ + public static final GameRule SEND_COMMAND_FEEDBACK = new GameRule<>("sendCommandFeedback", Boolean.class); + + /** + * Whether a message appears in chat when a player dies. + */ + public static final GameRule SHOW_DEATH_MESSAGES = new GameRule<>("showDeathMessages", Boolean.class); + + /** + * Whether players in spectator mode can generate chunks. + */ + public static final GameRule SPECTATORS_GENERATE_CHUNKS = new GameRule<>("spectatorsGenerateChunks", Boolean.class); + + // Numerical rules + /** + * How often a random block tick occurs (such as plant growth, leaf decay, + * etc.) per chunk section per game tick. 0 will disable random ticks, + * higher numbers will increase random ticks. + */ + public static final GameRule RANDOM_TICK_SPEED = new GameRule<>("randomTickSpeed", Integer.class); + + /** + * The number of blocks outward from the world spawn coordinates that a + * player will spawn in when first joining a server or when dying without a + * spawnpoint. + */ + public static final GameRule SPAWN_RADIUS = new GameRule<>("spawnRadius", Integer.class); + + /** + * The maximum number of other pushable entities a mob or player can push, + * before taking suffocation damage. + *
+ * Setting to 0 disables this rule. + */ + public static final GameRule MAX_ENTITY_CRAMMING = new GameRule<>("maxEntityCramming", Integer.class); + + /** + * Determines the number at which the chain of command blocks act as a + * "chain." + *
+ * This is the maximum amount of command blocks that can be activated in a + * single tick from a single chain. + */ + public static final GameRule MAX_COMMAND_CHAIN_LENGTH = new GameRule<>("maxCommandChainLength", Integer.class); + + // All GameRules instantiated above this for organizational purposes + private final String name; + private final Class type; + + private GameRule(@NotNull String name, @NotNull Class clazz) { + Preconditions.checkNotNull(name, "GameRule name cannot be null"); + Preconditions.checkNotNull(clazz, "GameRule type cannot be null"); + Preconditions.checkArgument(clazz == Boolean.class || clazz == Integer.class, "Must be of type Boolean or Integer. Found %s ", clazz.getName()); + this.name = name; + this.type = clazz; + gameRules.put(name, this); + } + + /** + * Get the name of this GameRule. + * + * @return the name of this GameRule + */ + @NotNull + public String getName() { + return name; + } + + /** + * Get the type of this rule. + * + * @return the rule type; Integer or Boolean + */ + @NotNull + public Class getType() { + return type; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } + if (!(obj instanceof GameRule)) { + return false; + } + GameRule other = (GameRule) obj; + return this.getName().equals(other.getName()) && this.getType() == other.getType(); + } + + @Override + public String toString() { + return "GameRule{" + "key=" + name + ", type=" + type + '}'; + } + + /** + * Get a {@link GameRule} by its name. + * + * @param rule the name of the GameRule + * @return the {@link GameRule} or null if no GameRule matches the given + * name + */ + @Nullable + public static GameRule getByName(@NotNull String rule) { + Preconditions.checkNotNull(rule, "Rule cannot be null"); + return gameRules.get(rule); + } + + /** + * Get an immutable collection of {@link GameRule}s. + * + * @return an immutable collection containing all registered GameRules. + */ + @NotNull + public static GameRule[] values() { + return gameRules.values().toArray(new GameRule[gameRules.size()]); + } +} diff --git a/api/src/main/java/org/bukkit/GrassSpecies.java b/api/src/main/java/org/bukkit/GrassSpecies.java new file mode 100644 index 000000000..5943eddd3 --- /dev/null +++ b/api/src/main/java/org/bukkit/GrassSpecies.java @@ -0,0 +1,63 @@ +package org.bukkit; + +import java.util.Map; + +import com.google.common.collect.Maps; +import org.jetbrains.annotations.Nullable; + +/** + * Represents the different types of grass. + */ +public enum GrassSpecies { + + /** + * Represents the dead looking grass. + */ + DEAD(0x0), + /** + * Represents the normal grass species. + */ + NORMAL(0x1), + /** + * Represents the fern-looking grass species. + */ + FERN_LIKE(0x2); + + private final byte data; + private final static Map BY_DATA = Maps.newHashMap(); + + private GrassSpecies(final int data) { + this.data = (byte) data; + } + + /** + * Gets the associated data value representing this species + * + * @return A byte containing the data value of this grass species + * @deprecated Magic value + */ + @Deprecated + public byte getData() { + return data; + } + + /** + * Gets the GrassSpecies with the given data value + * + * @param data Data value to fetch + * @return The {@link GrassSpecies} representing the given value, or null + * if it doesn't exist + * @deprecated Magic value + */ + @Deprecated + @Nullable + public static GrassSpecies getByData(final byte data) { + return BY_DATA.get(data); + } + + static { + for (GrassSpecies grassSpecies : values()) { + BY_DATA.put(grassSpecies.getData(), grassSpecies); + } + } +} diff --git a/api/src/main/java/org/bukkit/Instrument.java b/api/src/main/java/org/bukkit/Instrument.java new file mode 100644 index 000000000..f21497fff --- /dev/null +++ b/api/src/main/java/org/bukkit/Instrument.java @@ -0,0 +1,90 @@ +package org.bukkit; + +import java.util.Map; + +import com.google.common.collect.Maps; +import org.jetbrains.annotations.Nullable; + +public enum Instrument { + + /** + * Piano is the standard instrument for a note block. + */ + PIANO(0x0), + /** + * Bass drum is normally played when a note block is on top of a + * stone-like block. + */ + BASS_DRUM(0x1), + /** + * Snare drum is normally played when a note block is on top of a sandy + * block. + */ + SNARE_DRUM(0x2), + /** + * Sticks are normally played when a note block is on top of a glass + * block. + */ + STICKS(0x3), + /** + * Bass guitar is normally played when a note block is on top of a wooden + * block. + */ + BASS_GUITAR(0x4), + /** + * Flute is normally played when a note block is on top of a clay block. + */ + FLUTE(0x5), + /** + * Bell is normally played when a note block is on top of a gold block. + */ + BELL(0x6), + /** + * Guitar is normally played when a note block is on top of a woolen block. + */ + GUITAR(0x7), + /** + * Chime is normally played when a note block is on top of a packed ice + * block. + */ + CHIME(0x8), + /** + * Xylophone is normally played when a note block is on top of a bone block. + */ + XYLOPHONE(0x9); + + private final byte type; + private final static Map BY_DATA = Maps.newHashMap(); + + private Instrument(final int type) { + this.type = (byte) type; + } + + /** + * @return The type ID of this instrument. + * @deprecated Magic value + */ + @Deprecated + public byte getType() { + return this.type; + } + + /** + * Get an instrument by its type ID. + * + * @param type The type ID + * @return The instrument + * @deprecated Magic value + */ + @Deprecated + @Nullable + public static Instrument getByType(final byte type) { + return BY_DATA.get(type); + } + + static { + for (Instrument instrument : Instrument.values()) { + BY_DATA.put(instrument.getType(), instrument); + } + } +} diff --git a/api/src/main/java/org/bukkit/Keyed.java b/api/src/main/java/org/bukkit/Keyed.java new file mode 100644 index 000000000..32c92621c --- /dev/null +++ b/api/src/main/java/org/bukkit/Keyed.java @@ -0,0 +1,17 @@ +package org.bukkit; + +import org.jetbrains.annotations.NotNull; + +/** + * Represents an object which has a {@link NamespacedKey} attached to it. + */ +public interface Keyed { + + /** + * Return the namespaced identifier for this object. + * + * @return this object's key + */ + @NotNull + NamespacedKey getKey(); +} diff --git a/api/src/main/java/org/bukkit/Location.java b/api/src/main/java/org/bukkit/Location.java new file mode 100644 index 000000000..8352b77c5 --- /dev/null +++ b/api/src/main/java/org/bukkit/Location.java @@ -0,0 +1,1096 @@ +package org.bukkit; + +import com.google.common.base.Preconditions; // Paper +import java.util.HashMap; +import java.util.Map; + +import org.bukkit.block.Block; +import org.bukkit.configuration.serialization.ConfigurationSerializable; +import org.bukkit.entity.Entity; // Paper +import org.bukkit.util.NumberConversions; +import org.bukkit.util.Vector; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +// Paper start +import java.util.Collection; +import java.util.function.Predicate; +import org.bukkit.entity.Entity; +import org.bukkit.entity.LivingEntity; +import org.bukkit.entity.Player; +// Paper end + +/** + * Represents a 3-dimensional position in a world. + *
+ * No constraints are placed on any angular values other than that they be + * specified in degrees. This means that negative angles or angles of greater + * magnitude than 360 are valid, but may be normalized to any other equivalent + * representation by the implementation. + */ +public class Location implements Cloneable, ConfigurationSerializable { + private World world; + private double x; + private double y; + private double z; + private float pitch; + private float yaw; + + /** + * Constructs a new Location with the given coordinates + * + * @param world The world in which this location resides + * @param x The x-coordinate of this new location + * @param y The y-coordinate of this new location + * @param z The z-coordinate of this new location + */ + public Location(@UndefinedNullability final World world, final double x, final double y, final double z) { // Paper + this(world, x, y, z, 0, 0); + } + + /** + * Constructs a new Location with the given coordinates and direction + * + * @param world The world in which this location resides + * @param x The x-coordinate of this new location + * @param y The y-coordinate of this new location + * @param z The z-coordinate of this new location + * @param yaw The absolute rotation on the x-plane, in degrees + * @param pitch The absolute rotation on the y-plane, in degrees + */ + public Location(@UndefinedNullability final World world, final double x, final double y, final double z, final float yaw, final float pitch) { // Paper + this.world = world; + this.x = x; + this.y = y; + this.z = z; + this.pitch = pitch; + this.yaw = yaw; + } + + /** + * Sets the world that this location resides in + * + * @param world New world that this location resides in + */ + public void setWorld(@Nullable World world) { + this.world = world; + } + + /** + * Gets the world that this location resides in + * + * @return World that contains this location + */ + @UndefinedNullability + public World getWorld() { + return world; + } + + /** + * Gets the chunk at the represented location + * + * @return Chunk at the represented location + */ + @NotNull + public Chunk getChunk() { + return world.getChunkAt(this); + } + + /** + * Gets the block at the represented location + * + * @return Block at the represented location + */ + @NotNull + public Block getBlock() { + return world.getBlockAt(this); + } + + /** + * Sets the x-coordinate of this location + * + * @param x X-coordinate + */ + public void setX(double x) { + this.x = x; + } + + /** + * Gets the x-coordinate of this location + * + * @return x-coordinate + */ + public double getX() { + return x; + } + + /** + * Gets the floored value of the X component, indicating the block that + * this location is contained with. + * + * @return block X + */ + public int getBlockX() { + return locToBlock(x); + } + + /** + * Sets the y-coordinate of this location + * + * @param y y-coordinate + */ + public void setY(double y) { + this.y = y; + } + + /** + * Gets the y-coordinate of this location + * + * @return y-coordinate + */ + public double getY() { + return y; + } + + /** + * Gets the floored value of the Y component, indicating the block that + * this location is contained with. + * + * @return block y + */ + public int getBlockY() { + return locToBlock(y); + } + + /** + * Sets the z-coordinate of this location + * + * @param z z-coordinate + */ + public void setZ(double z) { + this.z = z; + } + + /** + * Gets the z-coordinate of this location + * + * @return z-coordinate + */ + public double getZ() { + return z; + } + + /** + * Gets the floored value of the Z component, indicating the block that + * this location is contained with. + * + * @return block z + */ + public int getBlockZ() { + return locToBlock(z); + } + + /** + * Sets the yaw of this location, measured in degrees. + *

    + *
  • A yaw of 0 or 360 represents the positive z direction. + *
  • A yaw of 180 represents the negative z direction. + *
  • A yaw of 90 represents the negative x direction. + *
  • A yaw of 270 represents the positive x direction. + *
+ * Increasing yaw values are the equivalent of turning to your + * right-facing, increasing the scale of the next respective axis, and + * decreasing the scale of the previous axis. + * + * @param yaw new rotation's yaw + */ + public void setYaw(float yaw) { + this.yaw = yaw; + } + + /** + * Gets the yaw of this location, measured in degrees. + *
    + *
  • A yaw of 0 or 360 represents the positive z direction. + *
  • A yaw of 180 represents the negative z direction. + *
  • A yaw of 90 represents the negative x direction. + *
  • A yaw of 270 represents the positive x direction. + *
+ * Increasing yaw values are the equivalent of turning to your + * right-facing, increasing the scale of the next respective axis, and + * decreasing the scale of the previous axis. + * + * @return the rotation's yaw + */ + public float getYaw() { + return yaw; + } + + /** + * Sets the pitch of this location, measured in degrees. + *
    + *
  • A pitch of 0 represents level forward facing. + *
  • A pitch of 90 represents downward facing, or negative y + * direction. + *
  • A pitch of -90 represents upward facing, or positive y direction. + *
+ * Increasing pitch values the equivalent of looking down. + * + * @param pitch new incline's pitch + */ + public void setPitch(float pitch) { + this.pitch = pitch; + } + + /** + * Gets the pitch of this location, measured in degrees. + *
    + *
  • A pitch of 0 represents level forward facing. + *
  • A pitch of 90 represents downward facing, or negative y + * direction. + *
  • A pitch of -90 represents upward facing, or positive y direction. + *
+ * Increasing pitch values the equivalent of looking down. + * + * @return the incline's pitch + */ + public float getPitch() { + return pitch; + } + + /** + * Gets a unit-vector pointing in the direction that this Location is + * facing. + * + * @return a vector pointing the direction of this location's {@link + * #getPitch() pitch} and {@link #getYaw() yaw} + */ + @NotNull + public Vector getDirection() { + Vector vector = new Vector(); + + double rotX = this.getYaw(); + double rotY = this.getPitch(); + + vector.setY(-Math.sin(Math.toRadians(rotY))); + + double xz = Math.cos(Math.toRadians(rotY)); + + vector.setX(-xz * Math.sin(Math.toRadians(rotX))); + vector.setZ(xz * Math.cos(Math.toRadians(rotX))); + + return vector; + } + + /** + * Sets the {@link #getYaw() yaw} and {@link #getPitch() pitch} to point + * in the direction of the vector. + * + * @param vector the direction vector + * @return the same location + */ + @NotNull + public Location setDirection(@NotNull Vector vector) { + /* + * Sin = Opp / Hyp + * Cos = Adj / Hyp + * Tan = Opp / Adj + * + * x = -Opp + * z = Adj + */ + final double _2PI = 2 * Math.PI; + final double x = vector.getX(); + final double z = vector.getZ(); + + if (x == 0 && z == 0) { + pitch = vector.getY() > 0 ? -90 : 90; + return this; + } + + double theta = Math.atan2(-x, z); + yaw = (float) Math.toDegrees((theta + _2PI) % _2PI); + + double x2 = NumberConversions.square(x); + double z2 = NumberConversions.square(z); + double xz = Math.sqrt(x2 + z2); + pitch = (float) Math.toDegrees(Math.atan(-vector.getY() / xz)); + + return this; + } + + /** + * Adds the location by another. + * + * @see Vector + * @param vec The other location + * @return the same location + * @throws IllegalArgumentException for differing worlds + */ + @NotNull + public Location add(@NotNull Location vec) { + if (vec == null || vec.getWorld() != getWorld()) { + throw new IllegalArgumentException("Cannot add Locations of differing worlds"); + } + + x += vec.x; + y += vec.y; + z += vec.z; + return this; + } + + /** + * Adds the location by a vector. + * + * @see Vector + * @param vec Vector to use + * @return the same location + */ + @NotNull + public Location add(@NotNull Vector vec) { + this.x += vec.getX(); + this.y += vec.getY(); + this.z += vec.getZ(); + return this; + } + + /** + * Adds the location by another. Not world-aware. + * + * @see Vector + * @param x X coordinate + * @param y Y coordinate + * @param z Z coordinate + * @return the same location + */ + @NotNull + public Location add(double x, double y, double z) { + this.x += x; + this.y += y; + this.z += z; + return this; + } + + /** + * Subtracts the location by another. + * + * @see Vector + * @param vec The other location + * @return the same location + * @throws IllegalArgumentException for differing worlds + */ + @NotNull + public Location subtract(@NotNull Location vec) { + if (vec == null || vec.getWorld() != getWorld()) { + throw new IllegalArgumentException("Cannot add Locations of differing worlds"); + } + + x -= vec.x; + y -= vec.y; + z -= vec.z; + return this; + } + + /** + * Subtracts the location by a vector. + * + * @see Vector + * @param vec The vector to use + * @return the same location + */ + @NotNull + public Location subtract(@NotNull Vector vec) { + this.x -= vec.getX(); + this.y -= vec.getY(); + this.z -= vec.getZ(); + return this; + } + + /** + * Subtracts the location by another. Not world-aware and + * orientation independent. + * + * @see Vector + * @param x X coordinate + * @param y Y coordinate + * @param z Z coordinate + * @return the same location + */ + @NotNull + public Location subtract(double x, double y, double z) { + this.x -= x; + this.y -= y; + this.z -= z; + return this; + } + + /** + * Gets the magnitude of the location, defined as sqrt(x^2+y^2+z^2). The + * value of this method is not cached and uses a costly square-root + * function, so do not repeatedly call this method to get the location's + * magnitude. NaN will be returned if the inner result of the sqrt() + * function overflows, which will be caused if the length is too long. Not + * world-aware and orientation independent. + * + * @see Vector + * @return the magnitude + */ + public double length() { + return Math.sqrt(NumberConversions.square(x) + NumberConversions.square(y) + NumberConversions.square(z)); + } + + /** + * Gets the magnitude of the location squared. Not world-aware and + * orientation independent. + * + * @see Vector + * @return the magnitude + */ + public double lengthSquared() { + return NumberConversions.square(x) + NumberConversions.square(y) + NumberConversions.square(z); + } + + /** + * Get the distance between this location and another. The value of this + * method is not cached and uses a costly square-root function, so do not + * repeatedly call this method to get the location's magnitude. NaN will + * be returned if the inner result of the sqrt() function overflows, which + * will be caused if the distance is too long. + * + * @see Vector + * @param o The other location + * @return the distance + * @throws IllegalArgumentException for differing worlds + */ + public double distance(@NotNull Location o) { + return Math.sqrt(distanceSquared(o)); + } + + /** + * Get the squared distance between this location and another. + * + * @see Vector + * @param o The other location + * @return the distance + * @throws IllegalArgumentException for differing worlds + */ + public double distanceSquared(@NotNull Location o) { + if (o == null) { + throw new IllegalArgumentException("Cannot measure distance to a null location"); + } else if (o.getWorld() == null || getWorld() == null) { + throw new IllegalArgumentException("Cannot measure distance to a null world"); + } else if (o.getWorld() != getWorld()) { + throw new IllegalArgumentException("Cannot measure distance between " + getWorld().getName() + " and " + o.getWorld().getName()); + } + + return NumberConversions.square(x - o.x) + NumberConversions.square(y - o.y) + NumberConversions.square(z - o.z); + } + + /** + * Performs scalar multiplication, multiplying all components with a + * scalar. Not world-aware. + * + * @param m The factor + * @see Vector + * @return the same location + */ + @NotNull + public Location multiply(double m) { + x *= m; + y *= m; + z *= m; + return this; + } + + /** + * Zero this location's components. Not world-aware. + * + * @see Vector + * @return the same location + */ + @NotNull + public Location zero() { + x = 0; + y = 0; + z = 0; + return this; + } + + public boolean isChunkLoaded() { return world.isChunkLoaded(locToBlock(x) >> 4, locToBlock(z) >> 4); } // Paper + + // Paper start + /** + * Checks if a {@link Chunk} has been generated at this location. + * + * @return true if a chunk has been generated at this location + */ + public boolean isGenerated() { + Preconditions.checkNotNull(world, "Location has no world!"); + return world.isChunkGenerated(locToBlock(x) >> 4, locToBlock(z) >> 4); + } + + /** + * Sets the position of this Location and returns itself + * + * This mutates this object, clone first. + * @param x X coordinate + * @param y Y coordinate + * @param z Z coordinate + * @return self (not cloned) + */ + @NotNull + public Location set(double x, double y, double z) { + this.x = x; + this.y = y; + this.z = z; + return this; + } + + /** + * Takes the x/y/z from base and adds the specified x/y/z to it and returns self + * + * This mutates this object, clone first. + * @param base The base coordinate to modify + * @param x X coordinate to add to base + * @param y Y coordinate to add to base + * @param z Z coordinate to add to base + * @return self (not cloned) + */ + @NotNull + public Location add(@NotNull Location base, double x, double y, double z) { + return this.set(base.x + x, base.y + y, base.z + z); + } + + /** + * Takes the x/y/z from base and subtracts the specified x/y/z to it and returns self + * + * This mutates this object, clone first. + * @param base The base coordinate to modify + * @param x X coordinate to subtract from base + * @param y Y coordinate to subtract from base + * @param z Z coordinate to subtract from base + * @return self (not cloned) + */ + @NotNull + public Location subtract(@NotNull Location base, double x, double y, double z) { + return this.set(base.x - x, base.y - y, base.z - z); + } + + /** + * @return A new location where X/Y/Z are on the Block location (integer value of X/Y/Z) + */ + @NotNull + public Location toBlockLocation() { + Location blockLoc = clone(); + blockLoc.setX(getBlockX()); + blockLoc.setY(getBlockY()); + blockLoc.setZ(getBlockZ()); + return blockLoc; + } + + // Paper Start + /** + * @return The block key for this location's block location. + * @see Block#getBlockKey(int, int, int) + */ + public long toBlockKey() { + return Block.getBlockKey(getBlockX(), getBlockY(), getBlockZ()); + } + // Paper End + + /** + * @return A new location where X/Y/Z are the center of the block + */ + @NotNull + public Location toCenterLocation() { + Location centerLoc = clone(); + centerLoc.setX(getBlockX() + 0.5); + centerLoc.setY(getBlockY() + 0.5); + centerLoc.setZ(getBlockZ() + 0.5); + return centerLoc; + } + + /** + * Creates explosion at this location with given power + * + * Will break blocks and ignite blocks on fire. + * + * @param power The power of explosion, where 4F is TNT + * @return false if explosion was canceled, otherwise true + */ + public boolean createExplosion(float power) { + return world.createExplosion(this, power); + } + + /** + * Creates explosion at this location with given power and optionally + * setting blocks on fire. + * + * Will break blocks. + * + * @param power The power of explosion, where 4F is TNT + * @param setFire Whether or not to set blocks on fire + * @return false if explosion was canceled, otherwise true + */ + public boolean createExplosion(float power, boolean setFire) { + return world.createExplosion(this, power, setFire); + } + + /** + * Creates explosion at this location with given power and optionally + * setting blocks on fire. + * + * @param power The power of explosion, where 4F is TNT + * @param setFire Whether or not to set blocks on fire + * @param breakBlocks Whether or not to have blocks be destroyed + * @return false if explosion was canceled, otherwise true + */ + public boolean createExplosion(float power, boolean setFire, boolean breakBlocks) { + return world.createExplosion(this, power, setFire, breakBlocks); + } + + /** + * Creates explosion at this location with given power, with the specified entity as the source. + * + * Will break blocks and ignite blocks on fire. + * + * @param source The source entity of the explosion + * @param power The power of explosion, where 4F is TNT + * @return false if explosion was canceled, otherwise true + */ + public boolean createExplosion(@Nullable Entity source, float power) { + return world.createExplosion(source, this, power, true, true); + } + + /** + * Creates explosion at this location with given power and optionally + * setting blocks on fire, with the specified entity as the source. + * + * Will break blocks. + * + * @param source The source entity of the explosion + * @param power The power of explosion, where 4F is TNT + * @param setFire Whether or not to set blocks on fire + * @return false if explosion was canceled, otherwise true + */ + public boolean createExplosion(@Nullable Entity source, float power, boolean setFire) { + return world.createExplosion(source, this, power, setFire, true); + } + + /** + * Creates explosion at this location with given power and optionally + * setting blocks on fire, with the specified entity as the source. + * + * @param source The source entity of the explosion + * @param power The power of explosion, where 4F is TNT + * @param setFire Whether or not to set blocks on fire + * @param breakBlocks Whether or not to have blocks be destroyed + * @return false if explosion was canceled, otherwise true + */ + public boolean createExplosion(@NotNull Entity source, float power, boolean setFire, boolean breakBlocks) { + return world.createExplosion(source, source.getLocation(), power, setFire, breakBlocks); + } + + /** + * Returns a list of entities within a bounding box centered around a Location. + * + * Some implementations may impose artificial restrictions on the size of the search bounding box. + * + * @param x 1/2 the size of the box along x axis + * @param y 1/2 the size of the box along y axis + * @param z 1/2 the size of the box along z axis + * @return the collection of entities near location. This will always be a non-null collection. + */ + @NotNull + public Collection getNearbyEntities(double x, double y, double z) { + if (world == null) { + throw new IllegalArgumentException("Location has no world"); + } + return world.getNearbyEntities(this, x, y, z); + } + + /** + * Gets nearby players within the specified radius (bounding box) + * @param radius X Radius + * @return the collection of entities near location. This will always be a non-null collection. + */ + @NotNull + public Collection getNearbyLivingEntities(double radius) { + return getNearbyEntitiesByType(org.bukkit.entity.LivingEntity.class, radius, radius, radius); + } + + /** + * Gets nearby players within the specified radius (bounding box) + * @param xzRadius X/Z Radius + * @param yRadius Y Radius + * @return the collection of living entities near location. This will always be a non-null collection. + */ + @NotNull + public Collection getNearbyLivingEntities(double xzRadius, double yRadius) { + return getNearbyEntitiesByType(org.bukkit.entity.LivingEntity.class, xzRadius, yRadius, xzRadius); + } + + /** + * Gets nearby players within the specified radius (bounding box) + * @param xRadius X Radius + * @param yRadius Y Radius + * @param zRadius Z radius + * @return the collection of living entities near location. This will always be a non-null collection. + */ + @NotNull + public Collection getNearbyLivingEntities(double xRadius, double yRadius, double zRadius) { + return getNearbyEntitiesByType(org.bukkit.entity.LivingEntity.class, xRadius, yRadius, zRadius); + } + + /** + * Gets nearby players within the specified radius (bounding box) + * @param radius Radius + * @param predicate a predicate used to filter results + * @return the collection of living entities near location. This will always be a non-null collection. + */ + @NotNull + public Collection getNearbyLivingEntities(double radius, @Nullable Predicate predicate) { + return getNearbyEntitiesByType(org.bukkit.entity.LivingEntity.class, radius, radius, radius, predicate); + } + + /** + * Gets nearby players within the specified radius (bounding box) + * @param xzRadius X/Z Radius + * @param yRadius Y Radius + * @param predicate a predicate used to filter results + * @return the collection of living entities near location. This will always be a non-null collection. + */ + @NotNull + public Collection getNearbyLivingEntities(double xzRadius, double yRadius, @Nullable Predicate predicate) { + return getNearbyEntitiesByType(org.bukkit.entity.LivingEntity.class, xzRadius, yRadius, xzRadius, predicate); + } + + /** + * Gets nearby players within the specified radius (bounding box) + * @param xRadius X Radius + * @param yRadius Y Radius + * @param zRadius Z radius + * @param predicate a predicate used to filter results + * @return the collection of living entities near location. This will always be a non-null collection. + */ + @NotNull + public Collection getNearbyLivingEntities(double xRadius, double yRadius, double zRadius, @Nullable Predicate predicate) { + return getNearbyEntitiesByType(org.bukkit.entity.LivingEntity.class, xRadius, yRadius, zRadius, predicate); + } + + /** + * Gets nearby players within the specified radius (bounding box) + * @param radius X/Y/Z Radius + * @return the collection of players near location. This will always be a non-null collection. + */ + @NotNull + public Collection getNearbyPlayers(double radius) { + return getNearbyEntitiesByType(org.bukkit.entity.Player.class, radius, radius, radius); + } + + /** + * Gets nearby players within the specified radius (bounding box) + * @param xzRadius X/Z Radius + * @param yRadius Y Radius + * @return the collection of players near location. This will always be a non-null collection. + */ + @NotNull + public Collection getNearbyPlayers(double xzRadius, double yRadius) { + return getNearbyEntitiesByType(org.bukkit.entity.Player.class, xzRadius, yRadius, xzRadius); + } + + /** + * Gets nearby players within the specified radius (bounding box) + * @param xRadius X Radius + * @param yRadius Y Radius + * @param zRadius Z Radius + * @return the collection of players near location. This will always be a non-null collection. + */ + @NotNull + public Collection getNearbyPlayers(double xRadius, double yRadius, double zRadius) { + return getNearbyEntitiesByType(org.bukkit.entity.Player.class, xRadius, yRadius, zRadius); + } + + /** + * Gets nearby players within the specified radius (bounding box) + * @param radius X/Y/Z Radius + * @param predicate a predicate used to filter results + * @return the collection of players near location. This will always be a non-null collection. + */ + @NotNull + public Collection getNearbyPlayers(double radius, @Nullable Predicate predicate) { + return getNearbyEntitiesByType(org.bukkit.entity.Player.class, radius, radius, radius, predicate); + } + + /** + * Gets nearby players within the specified radius (bounding box) + * @param xzRadius X/Z Radius + * @param yRadius Y Radius + * @param predicate a predicate used to filter results + * @return the collection of players near location. This will always be a non-null collection. + */ + @NotNull + public Collection getNearbyPlayers(double xzRadius, double yRadius, @Nullable Predicate predicate) { + return getNearbyEntitiesByType(org.bukkit.entity.Player.class, xzRadius, yRadius, xzRadius, predicate); + } + + /** + * Gets nearby players within the specified radius (bounding box) + * @param xRadius X Radius + * @param yRadius Y Radius + * @param zRadius Z Radius + * @param predicate a predicate used to filter results + * @return the collection of players near location. This will always be a non-null collection. + */ + @NotNull + public Collection getNearbyPlayers(double xRadius, double yRadius, double zRadius, @Nullable Predicate predicate) { + return getNearbyEntitiesByType(org.bukkit.entity.Player.class, xRadius, yRadius, zRadius, predicate); + } + + /** + * Gets all nearby entities of the specified type, within the specified radius (bounding box) + * @param clazz Type to filter by + * @param radius X/Y/Z radius to search within + * @param the entity type + * @return the collection of entities of type clazz near location. This will always be a non-null collection. + */ + @NotNull + public Collection getNearbyEntitiesByType(@Nullable Class clazz, double radius) { + return getNearbyEntitiesByType(clazz, radius, radius, radius, null); + } + + /** + * Gets all nearby entities of the specified type, within the specified radius, with x and x radius matching (bounding box) + * @param clazz Type to filter by + * @param xzRadius X/Z radius to search within + * @param yRadius Y radius to search within + * @param the entity type + * @return the collection of entities near location. This will always be a non-null collection. + */ + @NotNull + public Collection getNearbyEntitiesByType(@Nullable Class clazz, double xzRadius, double yRadius) { + return getNearbyEntitiesByType(clazz, xzRadius, yRadius, xzRadius, null); + } + + /** + * Gets all nearby entities of the specified type, within the specified radius (bounding box) + * @param clazz Type to filter by + * @param xRadius X Radius + * @param yRadius Y Radius + * @param zRadius Z Radius + * @param the entity type + * @return the collection of entities near location. This will always be a non-null collection. + */ + @NotNull + public Collection getNearbyEntitiesByType(@Nullable Class clazz, double xRadius, double yRadius, double zRadius) { + return getNearbyEntitiesByType(clazz, xRadius, yRadius, zRadius, null); + } + + /** + * Gets all nearby entities of the specified type, within the specified radius (bounding box) + * @param clazz Type to filter by + * @param radius X/Y/Z radius to search within + * @param predicate a predicate used to filter results + * @param the entity type + * @return the collection of entities near location. This will always be a non-null collection. + */ + @NotNull + public Collection getNearbyEntitiesByType(@Nullable Class clazz, double radius, @Nullable Predicate predicate) { + return getNearbyEntitiesByType(clazz, radius, radius, radius, predicate); + } + + /** + * Gets all nearby entities of the specified type, within the specified radius, with x and x radius matching (bounding box) + * @param clazz Type to filter by + * @param xzRadius X/Z radius to search within + * @param yRadius Y radius to search within + * @param predicate a predicate used to filter results + * @param the entity type + * @return the collection of entities near location. This will always be a non-null collection. + */ + @NotNull + public Collection getNearbyEntitiesByType(@Nullable Class clazz, double xzRadius, double yRadius, @Nullable Predicate predicate) { + return getNearbyEntitiesByType(clazz, xzRadius, yRadius, xzRadius, predicate); + } + + /** + * Gets all nearby entities of the specified type, within the specified radius (bounding box) + * @param clazz Type to filter by + * @param xRadius X Radius + * @param yRadius Y Radius + * @param zRadius Z Radius + * @param predicate a predicate used to filter results + * @param the entity type + * @return the collection of entities near location. This will always be a non-null collection. + */ + @NotNull + public Collection getNearbyEntitiesByType(@Nullable Class clazz, double xRadius, double yRadius, double zRadius, @Nullable Predicate predicate) { + if (world == null) { + throw new IllegalArgumentException("Location has no world"); + } + return world.getNearbyEntitiesByType(clazz, this, xRadius, yRadius, zRadius, predicate); + } + // Paper end + @Override + public boolean equals(Object obj) { + if (obj == null) { + return false; + } + if (getClass() != obj.getClass()) { + return false; + } + final Location other = (Location) obj; + + if (this.world != other.world && (this.world == null || !this.world.equals(other.world))) { + return false; + } + if (Double.doubleToLongBits(this.x) != Double.doubleToLongBits(other.x)) { + return false; + } + if (Double.doubleToLongBits(this.y) != Double.doubleToLongBits(other.y)) { + return false; + } + if (Double.doubleToLongBits(this.z) != Double.doubleToLongBits(other.z)) { + return false; + } + if (Float.floatToIntBits(this.pitch) != Float.floatToIntBits(other.pitch)) { + return false; + } + if (Float.floatToIntBits(this.yaw) != Float.floatToIntBits(other.yaw)) { + return false; + } + return true; + } + + @Override + public int hashCode() { + int hash = 3; + + hash = 19 * hash + (this.world != null ? this.world.hashCode() : 0); + hash = 19 * hash + (int) (Double.doubleToLongBits(this.x) ^ (Double.doubleToLongBits(this.x) >>> 32)); + hash = 19 * hash + (int) (Double.doubleToLongBits(this.y) ^ (Double.doubleToLongBits(this.y) >>> 32)); + hash = 19 * hash + (int) (Double.doubleToLongBits(this.z) ^ (Double.doubleToLongBits(this.z) >>> 32)); + hash = 19 * hash + Float.floatToIntBits(this.pitch); + hash = 19 * hash + Float.floatToIntBits(this.yaw); + return hash; + } + + @Override + public String toString() { + return "Location{" + "world=" + world + ",x=" + x + ",y=" + y + ",z=" + z + ",pitch=" + pitch + ",yaw=" + yaw + '}'; + } + + /** + * Constructs a new {@link Vector} based on this Location + * + * @return New Vector containing the coordinates represented by this + * Location + */ + @NotNull + public Vector toVector() { + return new Vector(x, y, z); + } + + @Override + @NotNull + public Location clone() { + try { + return (Location) super.clone(); + } catch (CloneNotSupportedException e) { + throw new Error(e); + } + } + + /** + * Check if each component of this Location is finite. + * + * @throws IllegalArgumentException if any component is not finite + */ + public void checkFinite() throws IllegalArgumentException { + NumberConversions.checkFinite(x, "x not finite"); + NumberConversions.checkFinite(y, "y not finite"); + NumberConversions.checkFinite(z, "z not finite"); + NumberConversions.checkFinite(pitch, "pitch not finite"); + NumberConversions.checkFinite(yaw, "yaw not finite"); + } + + /** + * Safely converts a double (location coordinate) to an int (block + * coordinate) + * + * @param loc Precise coordinate + * @return Block coordinate + */ + public static int locToBlock(double loc) { + return NumberConversions.floor(loc); + } + + @Utility + @NotNull + public Map serialize() { + Map data = new HashMap(); + data.put("world", this.world.getName()); + + data.put("x", this.x); + data.put("y", this.y); + data.put("z", this.z); + + data.put("yaw", this.yaw); + data.put("pitch", this.pitch); + + return data; + } + + /** + * Required method for deserialization + * + * @param args map to deserialize + * @return deserialized location + * @throws IllegalArgumentException if the world don't exists + * @see ConfigurationSerializable + */ + @NotNull + public static Location deserialize(@NotNull Map args) { + World world = Bukkit.getWorld((String) args.get("world")); + if (world == null) { + throw new IllegalArgumentException("unknown world"); + } + + return new Location(world, NumberConversions.toDouble(args.get("x")), NumberConversions.toDouble(args.get("y")), NumberConversions.toDouble(args.get("z")), NumberConversions.toFloat(args.get("yaw")), NumberConversions.toFloat(args.get("pitch"))); + } + + /** + * Normalizes the given yaw angle to a value between +/-180 + * degrees. + * + * @param yaw the yaw in degrees + * @return the normalized yaw in degrees + * @see Location#getYaw() + */ + public static float normalizeYaw(float yaw) { + yaw %= 360.0f; + if (yaw >= 180.0f) { + yaw -= 360.0f; + } else if (yaw < -180.0f) { + yaw += 360.0f; + } + return yaw; + } + + /** + * Normalizes the given pitch angle to a value between +/-90 + * degrees. + * + * @param pitch the pitch in degrees + * @return the normalized pitch in degrees + * @see Location#getPitch() + */ + public static float normalizePitch(float pitch) { + if (pitch > 90.0f) { + pitch = 90.0f; + } else if (pitch < -90.0f) { + pitch = -90.0f; + } + return pitch; + } +} diff --git a/api/src/main/java/org/bukkit/Material.java b/api/src/main/java/org/bukkit/Material.java new file mode 100644 index 000000000..c628a9349 --- /dev/null +++ b/api/src/main/java/org/bukkit/Material.java @@ -0,0 +1,7218 @@ +package org.bukkit; + +import com.google.common.collect.Maps; +import java.lang.reflect.Constructor; +import java.util.Locale; +import java.util.Map; +import java.util.function.Consumer; +import org.apache.commons.lang.Validate; +import org.bukkit.block.data.Ageable; +import org.bukkit.block.data.AnaloguePowerable; +import org.bukkit.block.data.Bisected; +import org.bukkit.block.data.BlockData; +import org.bukkit.block.data.Directional; +import org.bukkit.block.data.Levelled; +import org.bukkit.block.data.Lightable; +import org.bukkit.block.data.MultipleFacing; +import org.bukkit.block.data.Orientable; +import org.bukkit.block.data.Powerable; +import org.bukkit.block.data.Rotatable; +import org.bukkit.block.data.Snowable; +import org.bukkit.block.data.Rail; +import org.bukkit.block.data.Waterlogged; +import org.bukkit.block.data.type.Bed; +import org.bukkit.block.data.type.BrewingStand; +import org.bukkit.block.data.type.BubbleColumn; +import org.bukkit.block.data.type.Cake; +import org.bukkit.block.data.type.Chest; +import org.bukkit.block.data.type.Cocoa; +import org.bukkit.block.data.type.CommandBlock; +import org.bukkit.block.data.type.Comparator; +import org.bukkit.block.data.type.CoralWallFan; +import org.bukkit.block.data.type.DaylightDetector; +import org.bukkit.block.data.type.Dispenser; +import org.bukkit.block.data.type.Door; +import org.bukkit.block.data.type.EndPortalFrame; +import org.bukkit.block.data.type.EnderChest; +import org.bukkit.block.data.type.Farmland; +import org.bukkit.block.data.type.Fence; +import org.bukkit.block.data.type.Fire; +import org.bukkit.block.data.type.Furnace; +import org.bukkit.block.data.type.Gate; +import org.bukkit.block.data.type.GlassPane; +import org.bukkit.block.data.type.Hopper; +import org.bukkit.block.data.type.Jukebox; +import org.bukkit.block.data.type.Ladder; +import org.bukkit.block.data.type.Leaves; +import org.bukkit.block.data.type.NoteBlock; +import org.bukkit.block.data.type.Observer; +import org.bukkit.block.data.type.Piston; +import org.bukkit.block.data.type.PistonHead; +import org.bukkit.block.data.type.RedstoneRail; +import org.bukkit.block.data.type.RedstoneWallTorch; +import org.bukkit.block.data.type.RedstoneWire; +import org.bukkit.block.data.type.Repeater; +import org.bukkit.block.data.type.Sapling; +import org.bukkit.block.data.type.SeaPickle; +import org.bukkit.block.data.type.Sign; +import org.bukkit.block.data.type.Slab; +import org.bukkit.block.data.type.Snow; +import org.bukkit.block.data.type.Stairs; +import org.bukkit.block.data.type.StructureBlock; +import org.bukkit.block.data.type.Switch; +import org.bukkit.block.data.type.TNT; +import org.bukkit.block.data.type.TechnicalPiston; +import org.bukkit.block.data.type.TrapDoor; +import org.bukkit.block.data.type.Tripwire; +import org.bukkit.block.data.type.TripwireHook; +import org.bukkit.block.data.type.TurtleEgg; +import org.bukkit.block.data.type.WallSign; +import org.bukkit.material.MaterialData; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +/** + * An enum of all material IDs accepted by the official server and client + */ +@SuppressWarnings({"DeprecatedIsStillUsed", "deprecation"}) // Paper +public enum Material implements Keyed { + // + ACACIA_BOAT(27326, 1), + /** + * BlockData: {@link Switch} + */ + ACACIA_BUTTON(13993, Switch.class), + /** + * BlockData: {@link Door} + */ + ACACIA_DOOR(23797, Door.class), + /** + * BlockData: {@link Fence} + */ + ACACIA_FENCE(4569, Fence.class), + /** + * BlockData: {@link Gate} + */ + ACACIA_FENCE_GATE(14145, Gate.class), + /** + * BlockData: {@link Leaves} + */ + ACACIA_LEAVES(16606, Leaves.class), + /** + * BlockData: {@link Orientable} + */ + ACACIA_LOG(8385, Orientable.class), + ACACIA_PLANKS(31312), + /** + * BlockData: {@link Powerable} + */ + ACACIA_PRESSURE_PLATE(17586, Powerable.class), + /** + * BlockData: {@link Sapling} + */ + ACACIA_SAPLING(20806, Sapling.class), + /** + * BlockData: {@link Slab} + */ + ACACIA_SLAB(23730, Slab.class), + /** + * BlockData: {@link Stairs} + */ + ACACIA_STAIRS(17453, Stairs.class), + /** + * BlockData: {@link TrapDoor} + */ + ACACIA_TRAPDOOR(18343, TrapDoor.class), + /** + * BlockData: {@link Orientable} + */ + ACACIA_WOOD(9541, Orientable.class), + /** + * BlockData: {@link RedstoneRail} + */ + ACTIVATOR_RAIL(5834, RedstoneRail.class), + AIR(9648, 0), + ALLIUM(6871), + ANDESITE(25975), + /** + * BlockData: {@link Directional} + */ + ANVIL(18718, Directional.class), + APPLE(7720), + ARMOR_STAND(12852, 16), + ARROW(31091), + /** + * BlockData: {@link Directional} + */ + ATTACHED_MELON_STEM(30882, Directional.class), + /** + * BlockData: {@link Directional} + */ + ATTACHED_PUMPKIN_STEM(12724, Directional.class), + AZURE_BLUET(17608), + BAKED_POTATO(14624), + BARRIER(26453), + BAT_SPAWN_EGG(14607), + BEACON(6608), + BEDROCK(23130), + BEEF(4803), + BEETROOT(23305), + /** + * BlockData: {@link Ageable} + */ + BEETROOTS(22075, Ageable.class), + BEETROOT_SEEDS(21282), + BEETROOT_SOUP(16036, 1), + BIRCH_BOAT(28104, 1), + /** + * BlockData: {@link Switch} + */ + BIRCH_BUTTON(26934, Switch.class), + /** + * BlockData: {@link Door} + */ + BIRCH_DOOR(14759, Door.class), + /** + * BlockData: {@link Fence} + */ + BIRCH_FENCE(17347, Fence.class), + /** + * BlockData: {@link Gate} + */ + BIRCH_FENCE_GATE(6322, Gate.class), + /** + * BlockData: {@link Leaves} + */ + BIRCH_LEAVES(12601, Leaves.class), + /** + * BlockData: {@link Orientable} + */ + BIRCH_LOG(26727, Orientable.class), + BIRCH_PLANKS(29322), + /** + * BlockData: {@link Powerable} + */ + BIRCH_PRESSURE_PLATE(9664, Powerable.class), + /** + * BlockData: {@link Sapling} + */ + BIRCH_SAPLING(31533, Sapling.class), + /** + * BlockData: {@link Slab} + */ + BIRCH_SLAB(13807, Slab.class), + /** + * BlockData: {@link Stairs} + */ + BIRCH_STAIRS(7657, Stairs.class), + /** + * BlockData: {@link TrapDoor} + */ + BIRCH_TRAPDOOR(32585, TrapDoor.class), + /** + * BlockData: {@link Orientable} + */ + BIRCH_WOOD(20913, Orientable.class), + /** + * BlockData: {@link Rotatable} + */ + BLACK_BANNER(9365, 16, Rotatable.class), + /** + * BlockData: {@link Bed} + */ + BLACK_BED(20490, 1, Bed.class), + BLACK_CARPET(6056), + BLACK_CONCRETE(13338), + BLACK_CONCRETE_POWDER(16150), + /** + * BlockData: {@link Directional} + */ + BLACK_GLAZED_TERRACOTTA(29678, Directional.class), + /** + * BlockData: {@link Directional} + */ + BLACK_SHULKER_BOX(24076, 1, Directional.class), + BLACK_STAINED_GLASS(13941), + /** + * BlockData: {@link GlassPane} + */ + BLACK_STAINED_GLASS_PANE(13201, GlassPane.class), + BLACK_TERRACOTTA(26691), + /** + * BlockData: {@link Directional} + */ + BLACK_WALL_BANNER(4919, Directional.class), + BLACK_WOOL(16693), + BLAZE_POWDER(18941), + BLAZE_ROD(8289), + BLAZE_SPAWN_EGG(4759), + /** + * BlockData: {@link Rotatable} + */ + BLUE_BANNER(18481, 16, Rotatable.class), + /** + * BlockData: {@link Bed} + */ + BLUE_BED(12714, 1, Bed.class), + BLUE_CARPET(13292), + BLUE_CONCRETE(18756), + BLUE_CONCRETE_POWDER(17773), + /** + * BlockData: {@link Directional} + */ + BLUE_GLAZED_TERRACOTTA(23823, Directional.class), + BLUE_ICE(22449), + BLUE_ORCHID(13432), + /** + * BlockData: {@link Directional} + */ + BLUE_SHULKER_BOX(11476, 1, Directional.class), + BLUE_STAINED_GLASS(7107), + /** + * BlockData: {@link GlassPane} + */ + BLUE_STAINED_GLASS_PANE(28484, GlassPane.class), + BLUE_TERRACOTTA(5236), + /** + * BlockData: {@link Directional} + */ + BLUE_WALL_BANNER(17757, Directional.class), + BLUE_WOOL(15738), + BONE(5686), + /** + * BlockData: {@link Orientable} + */ + BONE_BLOCK(17312, Orientable.class), + BONE_MEAL(32458), + BOOK(23097), + BOOKSHELF(10069), + BOW(8745, 1, 384), + BOWL(32661), + /** + * BlockData: {@link Waterlogged} + */ + BRAIN_CORAL(31316, Waterlogged.class), + BRAIN_CORAL_BLOCK(30618), + /** + * BlockData: {@link Waterlogged} + */ + BRAIN_CORAL_FAN(13849, Waterlogged.class), + /** + * BlockData: {@link CoralWallFan} + */ + BRAIN_CORAL_WALL_FAN(22685, CoralWallFan.class), + BREAD(32049), + /** + * BlockData: {@link BrewingStand} + */ + BREWING_STAND(14539, BrewingStand.class), + BRICK(6820), + BRICKS(14165), + /** + * BlockData: {@link Slab} + */ + BRICK_SLAB(26333, Slab.class), + /** + * BlockData: {@link Stairs} + */ + BRICK_STAIRS(21534, Stairs.class), + /** + * BlockData: {@link Rotatable} + */ + BROWN_BANNER(11481, 16, Rotatable.class), + /** + * BlockData: {@link Bed} + */ + BROWN_BED(25624, 1, Bed.class), + BROWN_CARPET(23352), + BROWN_CONCRETE(19006), + BROWN_CONCRETE_POWDER(21485), + /** + * BlockData: {@link Directional} + */ + BROWN_GLAZED_TERRACOTTA(5655, Directional.class), + BROWN_MUSHROOM(9665), + /** + * BlockData: {@link MultipleFacing} + */ + BROWN_MUSHROOM_BLOCK(6291, MultipleFacing.class), + /** + * BlockData: {@link Directional} + */ + BROWN_SHULKER_BOX(24230, 1, Directional.class), + BROWN_STAINED_GLASS(20945), + /** + * BlockData: {@link GlassPane} + */ + BROWN_STAINED_GLASS_PANE(17557, GlassPane.class), + BROWN_TERRACOTTA(23664), + /** + * BlockData: {@link Directional} + */ + BROWN_WALL_BANNER(14731, Directional.class), + BROWN_WOOL(32638), + /** + * BlockData: {@link BubbleColumn} + */ + BUBBLE_COLUMN(13758, BubbleColumn.class), + /** + * BlockData: {@link Waterlogged} + */ + BUBBLE_CORAL(12464, Waterlogged.class), + BUBBLE_CORAL_BLOCK(15437), + /** + * BlockData: {@link Waterlogged} + */ + BUBBLE_CORAL_FAN(10795, Waterlogged.class), + /** + * BlockData: {@link CoralWallFan} + */ + BUBBLE_CORAL_WALL_FAN(20382, CoralWallFan.class), + BUCKET(15215, 16), + /** + * BlockData: {@link Ageable} + */ + CACTUS(12191, Ageable.class), + CACTUS_GREEN(17296), + /** + * BlockData: {@link Cake} + */ + CAKE(27048, 1, Cake.class), + CARROT(22824), + /** + * BlockData: {@link Ageable} + */ + CARROTS(17258, Ageable.class), + CARROT_ON_A_STICK(27809, 1, 25), + /** + * BlockData: {@link Directional} + */ + CARVED_PUMPKIN(25833, Directional.class), + /** + * BlockData: {@link Levelled} + */ + CAULDRON(26531, Levelled.class), + CAVE_AIR(17422), + CAVE_SPIDER_SPAWN_EGG(23341), + CHAINMAIL_BOOTS(17953, 1, 195), + CHAINMAIL_CHESTPLATE(23602, 1, 240), + CHAINMAIL_HELMET(26114, 1, 165), + CHAINMAIL_LEGGINGS(19087, 1, 225), + /** + * BlockData: {@link CommandBlock} + */ + CHAIN_COMMAND_BLOCK(26798, CommandBlock.class), + CHARCOAL(5390), + /** + * BlockData: {@link Chest} + */ + CHEST(22969, Chest.class), + CHEST_MINECART(4497, 1), + CHICKEN(17281), + CHICKEN_SPAWN_EGG(5462), + /** + * BlockData: {@link Directional} + */ + CHIPPED_ANVIL(10623, Directional.class), + CHISELED_QUARTZ_BLOCK(30964), + CHISELED_RED_SANDSTONE(15529), + CHISELED_SANDSTONE(31763), + CHISELED_STONE_BRICKS(9087), + /** + * BlockData: {@link Ageable} + */ + CHORUS_FLOWER(28542, Ageable.class), + CHORUS_FRUIT(7652), + /** + * BlockData: {@link MultipleFacing} + */ + CHORUS_PLANT(28243, MultipleFacing.class), + CLAY(27880), + CLAY_BALL(24603), + CLOCK(14980), + COAL(29067), + COAL_BLOCK(27968), + COAL_ORE(30965), + COARSE_DIRT(15411), + COBBLESTONE(32147), + /** + * BlockData: {@link Slab} + */ + COBBLESTONE_SLAB(6340, Slab.class), + /** + * BlockData: {@link Stairs} + */ + COBBLESTONE_STAIRS(24715, Stairs.class), + /** + * BlockData: {@link Fence} + */ + COBBLESTONE_WALL(12616, Fence.class), + COBWEB(9469), + /** + * BlockData: {@link Cocoa} + */ + COCOA(29709, Cocoa.class), + COCOA_BEANS(27381), + COD(24691), + COD_BUCKET(28601, 1), + COD_SPAWN_EGG(27248), + /** + * BlockData: {@link CommandBlock} + */ + COMMAND_BLOCK(4355, CommandBlock.class), + COMMAND_BLOCK_MINECART(7992, 1), + /** + * BlockData: {@link Comparator} + */ + COMPARATOR(18911, Comparator.class), + COMPASS(24139), + /** + * BlockData: {@link Waterlogged} + */ + CONDUIT(5148, Waterlogged.class), + COOKED_BEEF(21595), + COOKED_CHICKEN(20780), + COOKED_COD(9681), + COOKED_MUTTON(31447), + COOKED_PORKCHOP(27231), + COOKED_RABBIT(4454), + COOKED_SALMON(5615), + COOKIE(27431), + COW_SPAWN_EGG(14761), + CRACKED_STONE_BRICKS(27869), + CRAFTING_TABLE(20706), + /** + * BlockData: {@link Rotatable} + */ + CREEPER_HEAD(29146, Rotatable.class), + CREEPER_SPAWN_EGG(9653), + /** + * BlockData: {@link Directional} + */ + CREEPER_WALL_HEAD(30123, Directional.class), + CUT_RED_SANDSTONE(26842), + CUT_SANDSTONE(6118), + /** + * BlockData: {@link Rotatable} + */ + CYAN_BANNER(9839, 16, Rotatable.class), + /** + * BlockData: {@link Bed} + */ + CYAN_BED(16746, 1, Bed.class), + CYAN_CARPET(31495), + CYAN_CONCRETE(26522), + CYAN_CONCRETE_POWDER(15734), + CYAN_DYE(8043), + /** + * BlockData: {@link Directional} + */ + CYAN_GLAZED_TERRACOTTA(9550, Directional.class), + /** + * BlockData: {@link Directional} + */ + CYAN_SHULKER_BOX(28123, 1, Directional.class), + CYAN_STAINED_GLASS(30604), + /** + * BlockData: {@link GlassPane} + */ + CYAN_STAINED_GLASS_PANE(11784, GlassPane.class), + CYAN_TERRACOTTA(25940), + /** + * BlockData: {@link Directional} + */ + CYAN_WALL_BANNER(10889, Directional.class), + CYAN_WOOL(12221), + /** + * BlockData: {@link Directional} + */ + DAMAGED_ANVIL(10274, Directional.class), + DANDELION(30558), + DANDELION_YELLOW(21789), + DARK_OAK_BOAT(28618, 1), + /** + * BlockData: {@link Switch} + */ + DARK_OAK_BUTTON(6214, Switch.class), + /** + * BlockData: {@link Door} + */ + DARK_OAK_DOOR(10669, Door.class), + /** + * BlockData: {@link Fence} + */ + DARK_OAK_FENCE(21767, Fence.class), + /** + * BlockData: {@link Gate} + */ + DARK_OAK_FENCE_GATE(10679, Gate.class), + /** + * BlockData: {@link Leaves} + */ + DARK_OAK_LEAVES(22254, Leaves.class), + /** + * BlockData: {@link Orientable} + */ + DARK_OAK_LOG(14831, Orientable.class), + DARK_OAK_PLANKS(20869), + /** + * BlockData: {@link Powerable} + */ + DARK_OAK_PRESSURE_PLATE(31375, Powerable.class), + /** + * BlockData: {@link Sapling} + */ + DARK_OAK_SAPLING(14933, Sapling.class), + /** + * BlockData: {@link Slab} + */ + DARK_OAK_SLAB(28852, Slab.class), + /** + * BlockData: {@link Stairs} + */ + DARK_OAK_STAIRS(22921, Stairs.class), + /** + * BlockData: {@link TrapDoor} + */ + DARK_OAK_TRAPDOOR(10355, TrapDoor.class), + /** + * BlockData: {@link Orientable} + */ + DARK_OAK_WOOD(16995, Orientable.class), + DARK_PRISMARINE(19940), + /** + * BlockData: {@link Slab} + */ + DARK_PRISMARINE_SLAB(7577, Slab.class), + /** + * BlockData: {@link Stairs} + */ + DARK_PRISMARINE_STAIRS(26511, Stairs.class), + /** + * BlockData: {@link DaylightDetector} + */ + DAYLIGHT_DETECTOR(8864, DaylightDetector.class), + /** + * BlockData: {@link Waterlogged} + */ + DEAD_BRAIN_CORAL(9116, Waterlogged.class), + DEAD_BRAIN_CORAL_BLOCK(12979), + /** + * BlockData: {@link Waterlogged} + */ + DEAD_BRAIN_CORAL_FAN(26150, Waterlogged.class), + /** + * BlockData: {@link CoralWallFan} + */ + DEAD_BRAIN_CORAL_WALL_FAN(23718, CoralWallFan.class), + /** + * BlockData: {@link Waterlogged} + */ + DEAD_BUBBLE_CORAL(30583, Waterlogged.class), + DEAD_BUBBLE_CORAL_BLOCK(28220), + /** + * BlockData: {@link Waterlogged} + */ + DEAD_BUBBLE_CORAL_FAN(17322, Waterlogged.class), + /** + * BlockData: {@link CoralWallFan} + */ + DEAD_BUBBLE_CORAL_WALL_FAN(18453, CoralWallFan.class), + DEAD_BUSH(22888), + /** + * BlockData: {@link Waterlogged} + */ + DEAD_FIRE_CORAL(8365, Waterlogged.class), + DEAD_FIRE_CORAL_BLOCK(5307), + /** + * BlockData: {@link Waterlogged} + */ + DEAD_FIRE_CORAL_FAN(27073, Waterlogged.class), + /** + * BlockData: {@link CoralWallFan} + */ + DEAD_FIRE_CORAL_WALL_FAN(23375, CoralWallFan.class), + /** + * BlockData: {@link Waterlogged} + */ + DEAD_HORN_CORAL(5755, Waterlogged.class), + DEAD_HORN_CORAL_BLOCK(15103), + /** + * BlockData: {@link Waterlogged} + */ + DEAD_HORN_CORAL_FAN(11387, Waterlogged.class), + /** + * BlockData: {@link CoralWallFan} + */ + DEAD_HORN_CORAL_WALL_FAN(27550, CoralWallFan.class), + /** + * BlockData: {@link Waterlogged} + */ + DEAD_TUBE_CORAL(18028, Waterlogged.class), + DEAD_TUBE_CORAL_BLOCK(28350), + /** + * BlockData: {@link Waterlogged} + */ + DEAD_TUBE_CORAL_FAN(17628, Waterlogged.class), + /** + * BlockData: {@link CoralWallFan} + */ + DEAD_TUBE_CORAL_WALL_FAN(5128, CoralWallFan.class), + DEBUG_STICK(24562, 1), + /** + * BlockData: {@link RedstoneRail} + */ + DETECTOR_RAIL(13475, RedstoneRail.class), + DIAMOND(20865), + DIAMOND_AXE(27277, 1, 1561), + DIAMOND_BLOCK(5944), + DIAMOND_BOOTS(16522, 1, 429), + DIAMOND_CHESTPLATE(32099, 1, 528), + DIAMOND_HELMET(10755, 1, 363), + DIAMOND_HOE(24050, 1, 1561), + DIAMOND_HORSE_ARMOR(10321, 1), + DIAMOND_LEGGINGS(11202, 1, 495), + DIAMOND_ORE(9292), + DIAMOND_PICKAXE(24291, 1, 1561), + DIAMOND_SHOVEL(25415, 1, 1561), + DIAMOND_SWORD(27707, 1, 1561), + DIORITE(24688), + DIRT(10580), + /** + * BlockData: {@link Dispenser} + */ + DISPENSER(20871, Dispenser.class), + DOLPHIN_SPAWN_EGG(20787), + DONKEY_SPAWN_EGG(14513), + DRAGON_BREATH(20154), + DRAGON_EGG(29946), + /** + * BlockData: {@link Rotatable} + */ + DRAGON_HEAD(20084, Rotatable.class), + /** + * BlockData: {@link Directional} + */ + DRAGON_WALL_HEAD(19818, Directional.class), + DRIED_KELP(21042), + DRIED_KELP_BLOCK(12966), + /** + * BlockData: {@link Dispenser} + */ + DROPPER(31273, Dispenser.class), + DROWNED_SPAWN_EGG(19368), + EGG(21603, 16), + ELDER_GUARDIAN_SPAWN_EGG(11418), + ELYTRA(23829, 1, 432), + EMERALD(5654), + EMERALD_BLOCK(9914), + EMERALD_ORE(16630), + ENCHANTED_BOOK(11741, 1), + ENCHANTED_GOLDEN_APPLE(8280), + ENCHANTING_TABLE(16255), + ENDERMAN_SPAWN_EGG(29488), + ENDERMITE_SPAWN_EGG(16617), + /** + * BlockData: {@link EnderChest} + */ + ENDER_CHEST(32349, EnderChest.class), + ENDER_EYE(24860), + ENDER_PEARL(5259, 16), + END_CRYSTAL(19090), + END_GATEWAY(26605), + END_PORTAL(16782), + /** + * BlockData: {@link EndPortalFrame} + */ + END_PORTAL_FRAME(15480, EndPortalFrame.class), + /** + * BlockData: {@link Directional} + */ + END_ROD(24832, Directional.class), + END_STONE(29686), + END_STONE_BRICKS(20314), + EVOKER_SPAWN_EGG(21271), + EXPERIENCE_BOTTLE(12858), + /** + * BlockData: {@link Farmland} + */ + FARMLAND(31166, Farmland.class), + FEATHER(30548), + FERMENTED_SPIDER_EYE(19386), + FERN(15794), + FILLED_MAP(23504), + /** + * BlockData: {@link Fire} + */ + FIRE(16396, Fire.class), + FIREWORK_ROCKET(23841), + FIREWORK_STAR(12190), + FIRE_CHARGE(4842), + /** + * BlockData: {@link Waterlogged} + */ + FIRE_CORAL(29151, Waterlogged.class), + FIRE_CORAL_BLOCK(12119), + /** + * BlockData: {@link Waterlogged} + */ + FIRE_CORAL_FAN(11112, Waterlogged.class), + /** + * BlockData: {@link CoralWallFan} + */ + FIRE_CORAL_WALL_FAN(20100, CoralWallFan.class), + FISHING_ROD(4167, 1, 64), + FLINT(23596), + FLINT_AND_STEEL(28620, 1, 64), + FLOWER_POT(30567), + /** + * BlockData: {@link Ageable} + */ + FROSTED_ICE(21814, Ageable.class), + /** + * BlockData: {@link Furnace} + */ + FURNACE(8133, Furnace.class), + FURNACE_MINECART(14196, 1), + GHAST_SPAWN_EGG(9970), + GHAST_TEAR(18222), + GLASS(6195), + GLASS_BOTTLE(6116), + /** + * BlockData: {@link GlassPane} + */ + GLASS_PANE(5709, GlassPane.class), + GLISTERING_MELON_SLICE(20158), + GLOWSTONE(32713), + GLOWSTONE_DUST(6665), + GOLDEN_APPLE(27732), + GOLDEN_AXE(4878, 1, 32), + GOLDEN_BOOTS(7859, 1, 91), + GOLDEN_CARROT(5300), + GOLDEN_CHESTPLATE(4507, 1, 112), + GOLDEN_HELMET(7945, 1, 77), + GOLDEN_HOE(19337, 1, 32), + GOLDEN_HORSE_ARMOR(7996, 1), + GOLDEN_LEGGINGS(21002, 1, 105), + GOLDEN_PICKAXE(10901, 1, 32), + GOLDEN_SHOVEL(15597, 1, 32), + GOLDEN_SWORD(10505, 1, 32), + GOLD_BLOCK(27392), + GOLD_INGOT(28927), + GOLD_NUGGET(28814), + GOLD_ORE(32625), + GRANITE(21091), + GRASS(6155), + /** + * BlockData: {@link Snowable} + */ + GRASS_BLOCK(28346, Snowable.class), + GRASS_PATH(8604), + GRAVEL(7804), + /** + * BlockData: {@link Rotatable} + */ + GRAY_BANNER(12053, 16, Rotatable.class), + /** + * BlockData: {@link Bed} + */ + GRAY_BED(15745, 1, Bed.class), + GRAY_CARPET(26991), + GRAY_CONCRETE(13959), + GRAY_CONCRETE_POWDER(13031), + GRAY_DYE(9184), + /** + * BlockData: {@link Directional} + */ + GRAY_GLAZED_TERRACOTTA(6256, Directional.class), + /** + * BlockData: {@link Directional} + */ + GRAY_SHULKER_BOX(12754, 1, Directional.class), + GRAY_STAINED_GLASS(29979), + /** + * BlockData: {@link GlassPane} + */ + GRAY_STAINED_GLASS_PANE(25272, GlassPane.class), + GRAY_TERRACOTTA(18004), + /** + * BlockData: {@link Directional} + */ + GRAY_WALL_BANNER(24275, Directional.class), + GRAY_WOOL(27209), + /** + * BlockData: {@link Rotatable} + */ + GREEN_BANNER(10698, 16, Rotatable.class), + /** + * BlockData: {@link Bed} + */ + GREEN_BED(13797, 1, Bed.class), + GREEN_CARPET(7780), + GREEN_CONCRETE(17949), + GREEN_CONCRETE_POWDER(6904), + /** + * BlockData: {@link Directional} + */ + GREEN_GLAZED_TERRACOTTA(6958, Directional.class), + /** + * BlockData: {@link Directional} + */ + GREEN_SHULKER_BOX(9377, 1, Directional.class), + GREEN_STAINED_GLASS(22503), + /** + * BlockData: {@link GlassPane} + */ + GREEN_STAINED_GLASS_PANE(4767, GlassPane.class), + GREEN_TERRACOTTA(4105), + /** + * BlockData: {@link Directional} + */ + GREEN_WALL_BANNER(15046, Directional.class), + GREEN_WOOL(25085), + GUARDIAN_SPAWN_EGG(20113), + GUNPOWDER(29974), + /** + * BlockData: {@link Orientable} + */ + HAY_BLOCK(17461, Orientable.class), + HEART_OF_THE_SEA(11807), + /** + * BlockData: {@link AnaloguePowerable} + */ + HEAVY_WEIGHTED_PRESSURE_PLATE(16970, AnaloguePowerable.class), + /** + * BlockData: {@link Hopper} + */ + HOPPER(31974, Hopper.class), + HOPPER_MINECART(19024, 1), + /** + * BlockData: {@link Waterlogged} + */ + HORN_CORAL(19511, Waterlogged.class), + HORN_CORAL_BLOCK(19958), + /** + * BlockData: {@link Waterlogged} + */ + HORN_CORAL_FAN(13610, Waterlogged.class), + /** + * BlockData: {@link CoralWallFan} + */ + HORN_CORAL_WALL_FAN(28883, CoralWallFan.class), + HORSE_SPAWN_EGG(25981), + HUSK_SPAWN_EGG(20178), + ICE(30428), + INFESTED_CHISELED_STONE_BRICKS(4728), + INFESTED_COBBLESTONE(28798), + INFESTED_CRACKED_STONE_BRICKS(7476), + INFESTED_MOSSY_STONE_BRICKS(9850), + INFESTED_STONE(18440), + INFESTED_STONE_BRICKS(19749), + INK_SAC(7184), + IRON_AXE(15894, 1, 250), + /** + * BlockData: {@link Fence} + */ + IRON_BARS(9378, Fence.class), + IRON_BLOCK(24754), + IRON_BOOTS(8531, 1, 195), + IRON_CHESTPLATE(28112, 1, 240), + /** + * BlockData: {@link Door} + */ + IRON_DOOR(4788, Door.class), + IRON_HELMET(12025, 1, 165), + IRON_HOE(11339, 1, 250), + IRON_HORSE_ARMOR(30108, 1), + IRON_INGOT(24895), + IRON_LEGGINGS(18951, 1, 225), + IRON_NUGGET(13715), + IRON_ORE(19834), + IRON_PICKAXE(8842, 1, 250), + IRON_SHOVEL(30045, 1, 250), + IRON_SWORD(10904, 1, 250), + /** + * BlockData: {@link TrapDoor} + */ + IRON_TRAPDOOR(17095, TrapDoor.class), + ITEM_FRAME(27318), + /** + * BlockData: {@link Directional} + */ + JACK_O_LANTERN(31612, Directional.class), + /** + * BlockData: {@link Jukebox} + */ + JUKEBOX(19264, Jukebox.class), + JUNGLE_BOAT(4495, 1), + /** + * BlockData: {@link Switch} + */ + JUNGLE_BUTTON(25317, Switch.class), + /** + * BlockData: {@link Door} + */ + JUNGLE_DOOR(28163, Door.class), + /** + * BlockData: {@link Fence} + */ + JUNGLE_FENCE(14358, Fence.class), + /** + * BlockData: {@link Gate} + */ + JUNGLE_FENCE_GATE(21360, Gate.class), + /** + * BlockData: {@link Leaves} + */ + JUNGLE_LEAVES(5133, Leaves.class), + /** + * BlockData: {@link Orientable} + */ + JUNGLE_LOG(20721, Orientable.class), + JUNGLE_PLANKS(26445), + /** + * BlockData: {@link Powerable} + */ + JUNGLE_PRESSURE_PLATE(11376, Powerable.class), + /** + * BlockData: {@link Sapling} + */ + JUNGLE_SAPLING(17951, Sapling.class), + /** + * BlockData: {@link Slab} + */ + JUNGLE_SLAB(19117, Slab.class), + /** + * BlockData: {@link Stairs} + */ + JUNGLE_STAIRS(20636, Stairs.class), + /** + * BlockData: {@link TrapDoor} + */ + JUNGLE_TRAPDOOR(8626, TrapDoor.class), + /** + * BlockData: {@link Orientable} + */ + JUNGLE_WOOD(10341, Orientable.class), + /** + * BlockData: {@link Ageable} + */ + KELP(21916, Ageable.class), + KELP_PLANT(29697), + KNOWLEDGE_BOOK(12646, 1), + /** + * BlockData: {@link Ladder} + */ + LADDER(23599, Ladder.class), + LAPIS_BLOCK(14485), + LAPIS_LAZULI(11075), + LAPIS_ORE(22934), + /** + * BlockData: {@link Bisected} + */ + LARGE_FERN(30177, Bisected.class), + /** + * BlockData: {@link Levelled} + */ + LAVA(8415, Levelled.class), + LAVA_BUCKET(9228, 1), + LEAD(29539), + LEATHER(16414), + LEATHER_BOOTS(15282, 1, 65), + LEATHER_CHESTPLATE(29275, 1, 80), + LEATHER_HELMET(11624, 1, 55), + LEATHER_LEGGINGS(28210, 1, 75), + /** + * BlockData: {@link Switch} + */ + LEVER(15319, Switch.class), + /** + * BlockData: {@link Rotatable} + */ + LIGHT_BLUE_BANNER(18060, 16, Rotatable.class), + /** + * BlockData: {@link Bed} + */ + LIGHT_BLUE_BED(20957, 1, Bed.class), + LIGHT_BLUE_CARPET(21194), + LIGHT_BLUE_CONCRETE(29481), + LIGHT_BLUE_CONCRETE_POWDER(31206), + LIGHT_BLUE_DYE(28738), + /** + * BlockData: {@link Directional} + */ + LIGHT_BLUE_GLAZED_TERRACOTTA(4336, Directional.class), + /** + * BlockData: {@link Directional} + */ + LIGHT_BLUE_SHULKER_BOX(18226, 1, Directional.class), + LIGHT_BLUE_STAINED_GLASS(17162), + /** + * BlockData: {@link GlassPane} + */ + LIGHT_BLUE_STAINED_GLASS_PANE(18721, GlassPane.class), + LIGHT_BLUE_TERRACOTTA(31779), + /** + * BlockData: {@link Directional} + */ + LIGHT_BLUE_WALL_BANNER(12011, Directional.class), + LIGHT_BLUE_WOOL(21073), + /** + * BlockData: {@link Rotatable} + */ + LIGHT_GRAY_BANNER(11417, 16, Rotatable.class), + /** + * BlockData: {@link Bed} + */ + LIGHT_GRAY_BED(5090, 1, Bed.class), + LIGHT_GRAY_CARPET(11317), + LIGHT_GRAY_CONCRETE(14453), + LIGHT_GRAY_CONCRETE_POWDER(21589), + LIGHT_GRAY_DYE(27643), + /** + * BlockData: {@link Directional} + */ + LIGHT_GRAY_GLAZED_TERRACOTTA(10707, Directional.class), + /** + * BlockData: {@link Directional} + */ + LIGHT_GRAY_SHULKER_BOX(21345, 1, Directional.class), + LIGHT_GRAY_STAINED_GLASS(5843), + /** + * BlockData: {@link GlassPane} + */ + LIGHT_GRAY_STAINED_GLASS_PANE(19008, GlassPane.class), + LIGHT_GRAY_TERRACOTTA(26388), + /** + * BlockData: {@link Directional} + */ + LIGHT_GRAY_WALL_BANNER(31088, Directional.class), + LIGHT_GRAY_WOOL(22936), + /** + * BlockData: {@link AnaloguePowerable} + */ + LIGHT_WEIGHTED_PRESSURE_PLATE(14875, AnaloguePowerable.class), + /** + * BlockData: {@link Bisected} + */ + LILAC(22837, Bisected.class), + LILY_PAD(19271), + /** + * BlockData: {@link Rotatable} + */ + LIME_BANNER(18887, 16, Rotatable.class), + /** + * BlockData: {@link Bed} + */ + LIME_BED(27860, 1, Bed.class), + LIME_CARPET(15443), + LIME_CONCRETE(5863), + LIME_CONCRETE_POWDER(28859), + LIME_DYE(6147), + /** + * BlockData: {@link Directional} + */ + LIME_GLAZED_TERRACOTTA(13861, Directional.class), + /** + * BlockData: {@link Directional} + */ + LIME_SHULKER_BOX(28360, 1, Directional.class), + LIME_STAINED_GLASS(24266), + /** + * BlockData: {@link GlassPane} + */ + LIME_STAINED_GLASS_PANE(10610, GlassPane.class), + LIME_TERRACOTTA(24013), + /** + * BlockData: {@link Directional} + */ + LIME_WALL_BANNER(21422, Directional.class), + LIME_WOOL(10443), + LINGERING_POTION(25857, 1), + LLAMA_SPAWN_EGG(23640), + /** + * BlockData: {@link Rotatable} + */ + MAGENTA_BANNER(15591, 16, Rotatable.class), + /** + * BlockData: {@link Bed} + */ + MAGENTA_BED(20061, 1, Bed.class), + MAGENTA_CARPET(6180), + MAGENTA_CONCRETE(20591), + MAGENTA_CONCRETE_POWDER(8272), + MAGENTA_DYE(11788), + /** + * BlockData: {@link Directional} + */ + MAGENTA_GLAZED_TERRACOTTA(8067, Directional.class), + /** + * BlockData: {@link Directional} + */ + MAGENTA_SHULKER_BOX(21566, 1, Directional.class), + MAGENTA_STAINED_GLASS(26814), + /** + * BlockData: {@link GlassPane} + */ + MAGENTA_STAINED_GLASS_PANE(14082, GlassPane.class), + MAGENTA_TERRACOTTA(25900), + /** + * BlockData: {@link Directional} + */ + MAGENTA_WALL_BANNER(23291, Directional.class), + MAGENTA_WOOL(11853), + MAGMA_BLOCK(25927), + MAGMA_CREAM(25097), + MAGMA_CUBE_SPAWN_EGG(26638), + MAP(21655), + MELON(25172), + MELON_SEEDS(18340), + MELON_SLICE(5347), + /** + * BlockData: {@link Ageable} + */ + MELON_STEM(8247, Ageable.class), + MILK_BUCKET(9680, 1), + MINECART(14352, 1), + MOOSHROOM_SPAWN_EGG(22125), + /** + * BlockData: {@link MultipleFacing} + */ + MOSSY_COBBLESTONE(21900, MultipleFacing.class), + /** + * BlockData: {@link Fence} + */ + MOSSY_COBBLESTONE_WALL(11536, Fence.class), + MOSSY_STONE_BRICKS(16415), + /** + * BlockData: {@link TechnicalPiston} + */ + MOVING_PISTON(13831, TechnicalPiston.class), + MULE_SPAWN_EGG(11229), + /** + * BlockData: {@link MultipleFacing} + */ + MUSHROOM_STEM(16543, MultipleFacing.class), + MUSHROOM_STEW(16336, 1), + MUSIC_DISC_11(27426, 1), + MUSIC_DISC_13(16359, 1), + MUSIC_DISC_BLOCKS(26667, 1), + MUSIC_DISC_CAT(16246, 1), + MUSIC_DISC_CHIRP(19436, 1), + MUSIC_DISC_FAR(13823, 1), + MUSIC_DISC_MALL(11517, 1), + MUSIC_DISC_MELLOHI(26117, 1), + MUSIC_DISC_STAL(14989, 1), + MUSIC_DISC_STRAD(16785, 1), + MUSIC_DISC_WAIT(26499, 1), + MUSIC_DISC_WARD(24026, 1), + MUTTON(4792), + /** + * BlockData: {@link Snowable} + */ + MYCELIUM(9913, Snowable.class), + NAME_TAG(30731), + NAUTILUS_SHELL(19989), + NETHERRACK(23425), + NETHER_BRICK(19996), + NETHER_BRICKS(27802), + /** + * BlockData: {@link Fence} + */ + NETHER_BRICK_FENCE(5286, Fence.class), + /** + * BlockData: {@link Slab} + */ + NETHER_BRICK_SLAB(26586, Slab.class), + /** + * BlockData: {@link Stairs} + */ + NETHER_BRICK_STAIRS(12085, Stairs.class), + /** + * BlockData: {@link Orientable} + */ + NETHER_PORTAL(19469, Orientable.class), + NETHER_QUARTZ_ORE(4807), + NETHER_STAR(12469), + /** + * BlockData: {@link Ageable} + */ + NETHER_WART(29227, Ageable.class), + NETHER_WART_BLOCK(15486), + /** + * BlockData: {@link NoteBlock} + */ + NOTE_BLOCK(20979, NoteBlock.class), + OAK_BOAT(17570, 1), + /** + * BlockData: {@link Switch} + */ + OAK_BUTTON(13510, Switch.class), + /** + * BlockData: {@link Door} + */ + OAK_DOOR(20341, Door.class), + /** + * BlockData: {@link Fence} + */ + OAK_FENCE(6442, Fence.class), + /** + * BlockData: {@link Gate} + */ + OAK_FENCE_GATE(16689, Gate.class), + /** + * BlockData: {@link Leaves} + */ + OAK_LEAVES(4385, Leaves.class), + /** + * BlockData: {@link Orientable} + */ + OAK_LOG(26723, Orientable.class), + OAK_PLANKS(14905), + /** + * BlockData: {@link Powerable} + */ + OAK_PRESSURE_PLATE(20108, Powerable.class), + /** + * BlockData: {@link Sapling} + */ + OAK_SAPLING(9636, Sapling.class), + /** + * BlockData: {@link Slab} + */ + OAK_SLAB(12002, Slab.class), + /** + * BlockData: {@link Stairs} + */ + OAK_STAIRS(5449, Stairs.class), + /** + * BlockData: {@link TrapDoor} + */ + OAK_TRAPDOOR(16927, TrapDoor.class), + /** + * BlockData: {@link Orientable} + */ + OAK_WOOD(7378, Orientable.class), + /** + * BlockData: {@link Observer} + */ + OBSERVER(10726, Observer.class), + OBSIDIAN(32723), + OCELOT_SPAWN_EGG(30080), + /** + * BlockData: {@link Rotatable} + */ + ORANGE_BANNER(4839, 16, Rotatable.class), + /** + * BlockData: {@link Bed} + */ + ORANGE_BED(11194, 1, Bed.class), + ORANGE_CARPET(24752), + ORANGE_CONCRETE(19914), + ORANGE_CONCRETE_POWDER(30159), + ORANGE_DYE(13866), + /** + * BlockData: {@link Directional} + */ + ORANGE_GLAZED_TERRACOTTA(27451, Directional.class), + /** + * BlockData: {@link Directional} + */ + ORANGE_SHULKER_BOX(21673, 1, Directional.class), + ORANGE_STAINED_GLASS(25142), + /** + * BlockData: {@link GlassPane} + */ + ORANGE_STAINED_GLASS_PANE(21089, GlassPane.class), + ORANGE_TERRACOTTA(18684), + ORANGE_TULIP(26038), + /** + * BlockData: {@link Directional} + */ + ORANGE_WALL_BANNER(9936, Directional.class), + ORANGE_WOOL(23957), + OXEYE_DAISY(11709), + PACKED_ICE(28993), + PAINTING(23945), + PAPER(9923), + PARROT_SPAWN_EGG(23614), + /** + * BlockData: {@link Bisected} + */ + PEONY(21155, Bisected.class), + /** + * BlockData: {@link Slab} + */ + PETRIFIED_OAK_SLAB(18658, Slab.class), + PHANTOM_MEMBRANE(18398), + PHANTOM_SPAWN_EGG(24648), + PIG_SPAWN_EGG(22584), + /** + * BlockData: {@link Rotatable} + */ + PINK_BANNER(19439, 16, Rotatable.class), + /** + * BlockData: {@link Bed} + */ + PINK_BED(13795, 1, Bed.class), + PINK_CARPET(30186), + PINK_CONCRETE(5227), + PINK_CONCRETE_POWDER(6421), + PINK_DYE(31151), + /** + * BlockData: {@link Directional} + */ + PINK_GLAZED_TERRACOTTA(10260, Directional.class), + /** + * BlockData: {@link Directional} + */ + PINK_SHULKER_BOX(24968, 1, Directional.class), + PINK_STAINED_GLASS(16164), + /** + * BlockData: {@link GlassPane} + */ + PINK_STAINED_GLASS_PANE(24637, GlassPane.class), + PINK_TERRACOTTA(23727), + PINK_TULIP(27319), + /** + * BlockData: {@link Directional} + */ + PINK_WALL_BANNER(9421, Directional.class), + PINK_WOOL(7611), + /** + * BlockData: {@link Piston} + */ + PISTON(21130, Piston.class), + /** + * BlockData: {@link PistonHead} + */ + PISTON_HEAD(30226, PistonHead.class), + /** + * BlockData: {@link Rotatable} + */ + PLAYER_HEAD(21174, Rotatable.class), + /** + * BlockData: {@link Directional} + */ + PLAYER_WALL_HEAD(13164, Directional.class), + /** + * BlockData: {@link Snowable} + */ + PODZOL(24068, Snowable.class), + POISONOUS_POTATO(32640), + POLAR_BEAR_SPAWN_EGG(17015), + POLISHED_ANDESITE(8335), + POLISHED_DIORITE(31615), + POLISHED_GRANITE(5477), + POPPED_CHORUS_FRUIT(27844), + POPPY(12851), + PORKCHOP(30896), + POTATO(21088), + /** + * BlockData: {@link Ageable} + */ + POTATOES(10879, Ageable.class), + POTION(24020, 1), + POTTED_ACACIA_SAPLING(14096), + POTTED_ALLIUM(13184), + POTTED_AZURE_BLUET(8754), + POTTED_BIRCH_SAPLING(32484), + POTTED_BLUE_ORCHID(6599), + POTTED_BROWN_MUSHROOM(14481), + POTTED_CACTUS(8777), + POTTED_DANDELION(9727), + POTTED_DARK_OAK_SAPLING(6486), + POTTED_DEAD_BUSH(13020), + POTTED_FERN(23315), + POTTED_JUNGLE_SAPLING(7525), + POTTED_OAK_SAPLING(11905), + POTTED_ORANGE_TULIP(28807), + POTTED_OXEYE_DAISY(19707), + POTTED_PINK_TULIP(10089), + POTTED_POPPY(7457), + POTTED_RED_MUSHROOM(22881), + POTTED_RED_TULIP(28594), + POTTED_SPRUCE_SAPLING(29498), + POTTED_WHITE_TULIP(24330), + /** + * BlockData: {@link RedstoneRail} + */ + POWERED_RAIL(11064, RedstoneRail.class), + PRISMARINE(7539), + PRISMARINE_BRICKS(29118), + /** + * BlockData: {@link Slab} + */ + PRISMARINE_BRICK_SLAB(26672, Slab.class), + /** + * BlockData: {@link Stairs} + */ + PRISMARINE_BRICK_STAIRS(15445, Stairs.class), + PRISMARINE_CRYSTALS(31546), + PRISMARINE_SHARD(10993), + /** + * BlockData: {@link Slab} + */ + PRISMARINE_SLAB(31323, Slab.class), + /** + * BlockData: {@link Stairs} + */ + PRISMARINE_STAIRS(19217, Stairs.class), + PUFFERFISH(8115), + PUFFERFISH_BUCKET(8861, 1), + PUFFERFISH_SPAWN_EGG(24573), + PUMPKIN(19170), + PUMPKIN_PIE(28725), + PUMPKIN_SEEDS(28985), + /** + * BlockData: {@link Ageable} + */ + PUMPKIN_STEM(19021, Ageable.class), + /** + * BlockData: {@link Rotatable} + */ + PURPLE_BANNER(29027, 16, Rotatable.class), + /** + * BlockData: {@link Bed} + */ + PURPLE_BED(29755, 1, Bed.class), + PURPLE_CARPET(5574), + PURPLE_CONCRETE(20623), + PURPLE_CONCRETE_POWDER(26808), + PURPLE_DYE(6347), + /** + * BlockData: {@link Directional} + */ + PURPLE_GLAZED_TERRACOTTA(4818, Directional.class), + /** + * BlockData: {@link Directional} + */ + PURPLE_SHULKER_BOX(10373, 1, Directional.class), + PURPLE_STAINED_GLASS(21845), + /** + * BlockData: {@link GlassPane} + */ + PURPLE_STAINED_GLASS_PANE(10948, GlassPane.class), + PURPLE_TERRACOTTA(10387), + /** + * BlockData: {@link Directional} + */ + PURPLE_WALL_BANNER(14298, Directional.class), + PURPLE_WOOL(11922), + PURPUR_BLOCK(7538), + /** + * BlockData: {@link Orientable} + */ + PURPUR_PILLAR(26718, Orientable.class), + /** + * BlockData: {@link Slab} + */ + PURPUR_SLAB(11487, Slab.class), + /** + * BlockData: {@link Stairs} + */ + PURPUR_STAIRS(8921, Stairs.class), + QUARTZ(23608), + QUARTZ_BLOCK(11987), + /** + * BlockData: {@link Orientable} + */ + QUARTZ_PILLAR(16452, Orientable.class), + /** + * BlockData: {@link Slab} + */ + QUARTZ_SLAB(4423, Slab.class), + /** + * BlockData: {@link Stairs} + */ + QUARTZ_STAIRS(24079, Stairs.class), + RABBIT(23068), + RABBIT_FOOT(13864), + RABBIT_HIDE(12467), + RABBIT_SPAWN_EGG(26496), + RABBIT_STEW(10611, 1), + /** + * BlockData: {@link Rail} + */ + RAIL(13285, Rail.class), + REDSTONE(11233), + REDSTONE_BLOCK(19496), + /** + * BlockData: {@link Lightable} + */ + REDSTONE_LAMP(8217, Lightable.class), + /** + * BlockData: {@link Lightable} + */ + REDSTONE_ORE(10887, Lightable.class), + /** + * BlockData: {@link Lightable} + */ + REDSTONE_TORCH(22547, Lightable.class), + /** + * BlockData: {@link RedstoneWallTorch} + */ + REDSTONE_WALL_TORCH(7595, RedstoneWallTorch.class), + /** + * BlockData: {@link RedstoneWire} + */ + REDSTONE_WIRE(25984, RedstoneWire.class), + /** + * BlockData: {@link Rotatable} + */ + RED_BANNER(26961, 16, Rotatable.class), + /** + * BlockData: {@link Bed} + */ + RED_BED(30910, 1, Bed.class), + RED_CARPET(5424), + RED_CONCRETE(8032), + RED_CONCRETE_POWDER(13286), + /** + * BlockData: {@link Directional} + */ + RED_GLAZED_TERRACOTTA(24989, Directional.class), + RED_MUSHROOM(19728), + /** + * BlockData: {@link MultipleFacing} + */ + RED_MUSHROOM_BLOCK(20766, MultipleFacing.class), + RED_NETHER_BRICKS(18056), + RED_SAND(16279), + RED_SANDSTONE(9092), + /** + * BlockData: {@link Slab} + */ + RED_SANDSTONE_SLAB(17550, Slab.class), + /** + * BlockData: {@link Stairs} + */ + RED_SANDSTONE_STAIRS(25466, Stairs.class), + /** + * BlockData: {@link Directional} + */ + RED_SHULKER_BOX(32448, 1, Directional.class), + RED_STAINED_GLASS(9717), + /** + * BlockData: {@link GlassPane} + */ + RED_STAINED_GLASS_PANE(8630, GlassPane.class), + RED_TERRACOTTA(5086), + RED_TULIP(16781), + /** + * BlockData: {@link Directional} + */ + RED_WALL_BANNER(4378, Directional.class), + RED_WOOL(11621), + /** + * BlockData: {@link Repeater} + */ + REPEATER(28823, Repeater.class), + /** + * BlockData: {@link CommandBlock} + */ + REPEATING_COMMAND_BLOCK(12405, CommandBlock.class), + /** + * BlockData: {@link Bisected} + */ + ROSE_BUSH(6080, Bisected.class), + ROSE_RED(15694), + ROTTEN_FLESH(21591), + SADDLE(30206, 1), + SALMON(18516), + SALMON_BUCKET(31427, 1), + SALMON_SPAWN_EGG(18739), + SAND(11542), + SANDSTONE(13141), + /** + * BlockData: {@link Slab} + */ + SANDSTONE_SLAB(29830, Slab.class), + /** + * BlockData: {@link Stairs} + */ + SANDSTONE_STAIRS(18474, Stairs.class), + SCUTE(11914), + SEAGRASS(23942), + SEA_LANTERN(16984), + /** + * BlockData: {@link SeaPickle} + */ + SEA_PICKLE(19562, SeaPickle.class), + SHEARS(27971, 1, 238), + SHEEP_SPAWN_EGG(24488), + SHIELD(29943, 1, 336), + /** + * BlockData: {@link Directional} + */ + SHULKER_BOX(7776, 1, Directional.class), + SHULKER_SHELL(27848), + SHULKER_SPAWN_EGG(31848), + /** + * BlockData: {@link Sign} + */ + SIGN(16918, 16, Sign.class), + SILVERFISH_SPAWN_EGG(14537), + SKELETON_HORSE_SPAWN_EGG(21356), + /** + * BlockData: {@link Rotatable} + */ + SKELETON_SKULL(13270, Rotatable.class), + SKELETON_SPAWN_EGG(15261), + /** + * BlockData: {@link Directional} + */ + SKELETON_WALL_SKULL(31650, Directional.class), + SLIME_BALL(5242), + SLIME_BLOCK(31892), + SLIME_SPAWN_EGG(6550), + SMOOTH_QUARTZ(14415), + SMOOTH_RED_SANDSTONE(25180), + SMOOTH_SANDSTONE(30039), + SMOOTH_STONE(21910), + /** + * BlockData: {@link Snow} + */ + SNOW(14146, Snow.class), + SNOWBALL(19487, 16), + SNOW_BLOCK(19913), + SOUL_SAND(16841), + SPAWNER(7018), + SPECTRAL_ARROW(4568), + SPIDER_EYE(9318), + SPIDER_SPAWN_EGG(14984), + SPLASH_POTION(30248, 1), + SPONGE(15860), + SPRUCE_BOAT(9606, 1), + /** + * BlockData: {@link Switch} + */ + SPRUCE_BUTTON(23281, Switch.class), + /** + * BlockData: {@link Door} + */ + SPRUCE_DOOR(10642, Door.class), + /** + * BlockData: {@link Fence} + */ + SPRUCE_FENCE(25416, Fence.class), + /** + * BlockData: {@link Gate} + */ + SPRUCE_FENCE_GATE(26423, Gate.class), + /** + * BlockData: {@link Leaves} + */ + SPRUCE_LEAVES(20039, Leaves.class), + /** + * BlockData: {@link Orientable} + */ + SPRUCE_LOG(9726, Orientable.class), + SPRUCE_PLANKS(14593), + /** + * BlockData: {@link Powerable} + */ + SPRUCE_PRESSURE_PLATE(15932, Powerable.class), + /** + * BlockData: {@link Sapling} + */ + SPRUCE_SAPLING(19874, Sapling.class), + /** + * BlockData: {@link Slab} + */ + SPRUCE_SLAB(4348, Slab.class), + /** + * BlockData: {@link Stairs} + */ + SPRUCE_STAIRS(11192, Stairs.class), + /** + * BlockData: {@link TrapDoor} + */ + SPRUCE_TRAPDOOR(10289, TrapDoor.class), + /** + * BlockData: {@link Orientable} + */ + SPRUCE_WOOD(32328, Orientable.class), + SQUID_SPAWN_EGG(10682), + STICK(9773), + /** + * BlockData: {@link Piston} + */ + STICKY_PISTON(18127, Piston.class), + STONE(22948), + STONE_AXE(6338, 1, 131), + STONE_BRICKS(6962), + /** + * BlockData: {@link Slab} + */ + STONE_BRICK_SLAB(19676, Slab.class), + /** + * BlockData: {@link Stairs} + */ + STONE_BRICK_STAIRS(27032, Stairs.class), + /** + * BlockData: {@link Switch} + */ + STONE_BUTTON(12279, Switch.class), + STONE_HOE(22855, 1, 131), + STONE_PICKAXE(14611, 1, 131), + /** + * BlockData: {@link Powerable} + */ + STONE_PRESSURE_PLATE(22591, Powerable.class), + STONE_SHOVEL(9520, 1, 131), + /** + * BlockData: {@link Slab} + */ + STONE_SLAB(19838, Slab.class), + STONE_SWORD(25084, 1, 131), + STRAY_SPAWN_EGG(30153), + STRING(12806), + /** + * BlockData: {@link Orientable} + */ + STRIPPED_ACACIA_LOG(18167, Orientable.class), + /** + * BlockData: {@link Orientable} + */ + STRIPPED_ACACIA_WOOD(27193, Orientable.class), + /** + * BlockData: {@link Orientable} + */ + STRIPPED_BIRCH_LOG(8838, Orientable.class), + /** + * BlockData: {@link Orientable} + */ + STRIPPED_BIRCH_WOOD(22350, Orientable.class), + /** + * BlockData: {@link Orientable} + */ + STRIPPED_DARK_OAK_LOG(6492, Orientable.class), + /** + * BlockData: {@link Orientable} + */ + STRIPPED_DARK_OAK_WOOD(16000, Orientable.class), + /** + * BlockData: {@link Orientable} + */ + STRIPPED_JUNGLE_LOG(15476, Orientable.class), + /** + * BlockData: {@link Orientable} + */ + STRIPPED_JUNGLE_WOOD(30315, Orientable.class), + /** + * BlockData: {@link Orientable} + */ + STRIPPED_OAK_LOG(20523, Orientable.class), + /** + * BlockData: {@link Orientable} + */ + STRIPPED_OAK_WOOD(31455, Orientable.class), + /** + * BlockData: {@link Orientable} + */ + STRIPPED_SPRUCE_LOG(6140, Orientable.class), + /** + * BlockData: {@link Orientable} + */ + STRIPPED_SPRUCE_WOOD(6467, Orientable.class), + /** + * BlockData: {@link StructureBlock} + */ + STRUCTURE_BLOCK(26831, StructureBlock.class), + STRUCTURE_VOID(30806), + SUGAR(30638), + /** + * BlockData: {@link Ageable} + */ + SUGAR_CANE(7726, Ageable.class), + /** + * BlockData: {@link Bisected} + */ + SUNFLOWER(7408, Bisected.class), + /** + * BlockData: {@link Bisected} + */ + TALL_GRASS(21559, Bisected.class), + /** + * BlockData: {@link Bisected} + */ + TALL_SEAGRASS(27189, Bisected.class), + TERRACOTTA(16544), + TIPPED_ARROW(25164), + /** + * BlockData: {@link TNT} + */ + TNT(7896, TNT.class), + TNT_MINECART(4277, 1), + TORCH(6063), + TOTEM_OF_UNDYING(10139, 1), + /** + * BlockData: {@link Chest} + */ + TRAPPED_CHEST(18970, Chest.class), + TRIDENT(7534, 1, 250), + /** + * BlockData: {@link Tripwire} + */ + TRIPWIRE(8810, Tripwire.class), + /** + * BlockData: {@link TripwireHook} + */ + TRIPWIRE_HOOK(8130, TripwireHook.class), + TROPICAL_FISH(24879), + TROPICAL_FISH_BUCKET(29995, 1), + TROPICAL_FISH_SPAWN_EGG(19713), + /** + * BlockData: {@link Waterlogged} + */ + TUBE_CORAL(23048, Waterlogged.class), + TUBE_CORAL_BLOCK(23723), + /** + * BlockData: {@link Waterlogged} + */ + TUBE_CORAL_FAN(19929, Waterlogged.class), + /** + * BlockData: {@link CoralWallFan} + */ + TUBE_CORAL_WALL_FAN(25282, CoralWallFan.class), + /** + * BlockData: {@link TurtleEgg} + */ + TURTLE_EGG(32101, TurtleEgg.class), + TURTLE_HELMET(30120, 1, 275), + TURTLE_SPAWN_EGG(17324), + VEX_SPAWN_EGG(27751), + VILLAGER_SPAWN_EGG(30348), + VINDICATOR_SPAWN_EGG(25324), + /** + * BlockData: {@link MultipleFacing} + */ + VINE(14564, MultipleFacing.class), + VOID_AIR(13668), + /** + * BlockData: {@link WallSign} + */ + WALL_SIGN(10644, WallSign.class), + /** + * BlockData: {@link Directional} + */ + WALL_TORCH(25890, Directional.class), + /** + * BlockData: {@link Levelled} + */ + WATER(24998, Levelled.class), + WATER_BUCKET(8802, 1), + WET_SPONGE(9043), + /** + * BlockData: {@link Ageable} + */ + WHEAT(27709, Ageable.class), + WHEAT_SEEDS(28742), + /** + * BlockData: {@link Rotatable} + */ + WHITE_BANNER(17562, 16, Rotatable.class), + /** + * BlockData: {@link Bed} + */ + WHITE_BED(8185, 1, Bed.class), + WHITE_CARPET(15117), + WHITE_CONCRETE(6281), + WHITE_CONCRETE_POWDER(10363), + /** + * BlockData: {@link Directional} + */ + WHITE_GLAZED_TERRACOTTA(11326, Directional.class), + /** + * BlockData: {@link Directional} + */ + WHITE_SHULKER_BOX(31750, 1, Directional.class), + WHITE_STAINED_GLASS(31190), + /** + * BlockData: {@link GlassPane} + */ + WHITE_STAINED_GLASS_PANE(10557, GlassPane.class), + WHITE_TERRACOTTA(20975), + WHITE_TULIP(9742), + /** + * BlockData: {@link Directional} + */ + WHITE_WALL_BANNER(15967, Directional.class), + WHITE_WOOL(8624), + WITCH_SPAWN_EGG(11837), + /** + * BlockData: {@link Rotatable} + */ + WITHER_SKELETON_SKULL(31487, Rotatable.class), + WITHER_SKELETON_SPAWN_EGG(10073), + /** + * BlockData: {@link Directional} + */ + WITHER_SKELETON_WALL_SKULL(9326, Directional.class), + WOLF_SPAWN_EGG(21692), + WOODEN_AXE(6292, 1, 59), + WOODEN_HOE(16043, 1, 59), + WOODEN_PICKAXE(12792, 1, 59), + WOODEN_SHOVEL(28432, 1, 59), + WOODEN_SWORD(7175, 1, 59), + WRITABLE_BOOK(13393, 1), + WRITTEN_BOOK(24164, 16), + /** + * BlockData: {@link Rotatable} + */ + YELLOW_BANNER(30382, 16, Rotatable.class), + /** + * BlockData: {@link Bed} + */ + YELLOW_BED(30410, 1, Bed.class), + YELLOW_CARPET(18149), + YELLOW_CONCRETE(15722), + YELLOW_CONCRETE_POWDER(10655), + /** + * BlockData: {@link Directional} + */ + YELLOW_GLAZED_TERRACOTTA(10914, Directional.class), + /** + * BlockData: {@link Directional} + */ + YELLOW_SHULKER_BOX(28700, 1, Directional.class), + YELLOW_STAINED_GLASS(12182), + /** + * BlockData: {@link GlassPane} + */ + YELLOW_STAINED_GLASS_PANE(20298, GlassPane.class), + YELLOW_TERRACOTTA(32129), + /** + * BlockData: {@link Directional} + */ + YELLOW_WALL_BANNER(32004, Directional.class), + YELLOW_WOOL(29507), + /** + * BlockData: {@link Rotatable} + */ + ZOMBIE_HEAD(9304, Rotatable.class), + ZOMBIE_HORSE_SPAWN_EGG(4275), + ZOMBIE_PIGMAN_SPAWN_EGG(11531), + ZOMBIE_SPAWN_EGG(5814), + ZOMBIE_VILLAGER_SPAWN_EGG(10311), + /** + * BlockData: {@link Directional} + */ + ZOMBIE_WALL_HEAD(16296, Directional.class), + // ----- Legacy Separator ----- + @Deprecated + LEGACY_AIR(0, 0), + @Deprecated + LEGACY_STONE(1), + @Deprecated + LEGACY_GRASS(2), + @Deprecated + LEGACY_DIRT(3), + @Deprecated + LEGACY_COBBLESTONE(4), + @Deprecated + LEGACY_WOOD(5, org.bukkit.material.Wood.class), + @Deprecated + LEGACY_SAPLING(6, org.bukkit.material.Sapling.class), + @Deprecated + LEGACY_BEDROCK(7), + @Deprecated + LEGACY_WATER(8, org.bukkit.material.MaterialData.class), + @Deprecated + LEGACY_STATIONARY_WATER(9, org.bukkit.material.MaterialData.class), + @Deprecated + LEGACY_LAVA(10, org.bukkit.material.MaterialData.class), + @Deprecated + LEGACY_STATIONARY_LAVA(11, org.bukkit.material.MaterialData.class), + @Deprecated + LEGACY_SAND(12), + @Deprecated + LEGACY_GRAVEL(13), + @Deprecated + LEGACY_GOLD_ORE(14), + @Deprecated + LEGACY_IRON_ORE(15), + @Deprecated + LEGACY_COAL_ORE(16), + @Deprecated + LEGACY_LOG(17, org.bukkit.material.Tree.class), + @Deprecated + LEGACY_LEAVES(18, org.bukkit.material.Leaves.class), + @Deprecated + LEGACY_SPONGE(19), + @Deprecated + LEGACY_GLASS(20), + @Deprecated + LEGACY_LAPIS_ORE(21), + @Deprecated + LEGACY_LAPIS_BLOCK(22), + @Deprecated + LEGACY_DISPENSER(23, org.bukkit.material.Dispenser.class), + @Deprecated + LEGACY_SANDSTONE(24, org.bukkit.material.Sandstone.class), + @Deprecated + LEGACY_NOTE_BLOCK(25), + @Deprecated + LEGACY_BED_BLOCK(26, org.bukkit.material.Bed.class), + @Deprecated + LEGACY_POWERED_RAIL(27, org.bukkit.material.PoweredRail.class), + @Deprecated + LEGACY_DETECTOR_RAIL(28, org.bukkit.material.DetectorRail.class), + @Deprecated + LEGACY_PISTON_STICKY_BASE(29, org.bukkit.material.PistonBaseMaterial.class), + @Deprecated + LEGACY_WEB(30), + @Deprecated + LEGACY_LONG_GRASS(31, org.bukkit.material.LongGrass.class), + @Deprecated + LEGACY_DEAD_BUSH(32), + @Deprecated + LEGACY_PISTON_BASE(33, org.bukkit.material.PistonBaseMaterial.class), + @Deprecated + LEGACY_PISTON_EXTENSION(34, org.bukkit.material.PistonExtensionMaterial.class), + @Deprecated + LEGACY_WOOL(35, org.bukkit.material.Wool.class), + @Deprecated + LEGACY_PISTON_MOVING_PIECE(36), + @Deprecated + LEGACY_YELLOW_FLOWER(37), + @Deprecated + LEGACY_RED_ROSE(38), + @Deprecated + LEGACY_BROWN_MUSHROOM(39), + @Deprecated + LEGACY_RED_MUSHROOM(40), + @Deprecated + LEGACY_GOLD_BLOCK(41), + @Deprecated + LEGACY_IRON_BLOCK(42), + @Deprecated + LEGACY_DOUBLE_STEP(43, org.bukkit.material.Step.class), + @Deprecated + LEGACY_STEP(44, org.bukkit.material.Step.class), + @Deprecated + LEGACY_BRICK(45), + @Deprecated + LEGACY_TNT(46), + @Deprecated + LEGACY_BOOKSHELF(47), + @Deprecated + LEGACY_MOSSY_COBBLESTONE(48), + @Deprecated + LEGACY_OBSIDIAN(49), + @Deprecated + LEGACY_TORCH(50, org.bukkit.material.Torch.class), + @Deprecated + LEGACY_FIRE(51), + @Deprecated + LEGACY_MOB_SPAWNER(52), + @Deprecated + LEGACY_WOOD_STAIRS(53, org.bukkit.material.Stairs.class), + @Deprecated + LEGACY_CHEST(54, org.bukkit.material.Chest.class), + @Deprecated + LEGACY_REDSTONE_WIRE(55, org.bukkit.material.RedstoneWire.class), + @Deprecated + LEGACY_DIAMOND_ORE(56), + @Deprecated + LEGACY_DIAMOND_BLOCK(57), + @Deprecated + LEGACY_WORKBENCH(58), + @Deprecated + LEGACY_CROPS(59, org.bukkit.material.Crops.class), + @Deprecated + LEGACY_SOIL(60, org.bukkit.material.MaterialData.class), + @Deprecated + LEGACY_FURNACE(61, org.bukkit.material.Furnace.class), + @Deprecated + LEGACY_BURNING_FURNACE(62, org.bukkit.material.Furnace.class), + @Deprecated + LEGACY_SIGN_POST(63, 64, org.bukkit.material.Sign.class), + @Deprecated + LEGACY_WOODEN_DOOR(64, org.bukkit.material.Door.class), + @Deprecated + LEGACY_LADDER(65, org.bukkit.material.Ladder.class), + @Deprecated + LEGACY_RAILS(66, org.bukkit.material.Rails.class), + @Deprecated + LEGACY_COBBLESTONE_STAIRS(67, org.bukkit.material.Stairs.class), + @Deprecated + LEGACY_WALL_SIGN(68, 64, org.bukkit.material.Sign.class), + @Deprecated + LEGACY_LEVER(69, org.bukkit.material.Lever.class), + @Deprecated + LEGACY_STONE_PLATE(70, org.bukkit.material.PressurePlate.class), + @Deprecated + LEGACY_IRON_DOOR_BLOCK(71, org.bukkit.material.Door.class), + @Deprecated + LEGACY_WOOD_PLATE(72, org.bukkit.material.PressurePlate.class), + @Deprecated + LEGACY_REDSTONE_ORE(73), + @Deprecated + LEGACY_GLOWING_REDSTONE_ORE(74), + @Deprecated + LEGACY_REDSTONE_TORCH_OFF(75, org.bukkit.material.RedstoneTorch.class), + @Deprecated + LEGACY_REDSTONE_TORCH_ON(76, org.bukkit.material.RedstoneTorch.class), + @Deprecated + LEGACY_STONE_BUTTON(77, org.bukkit.material.Button.class), + @Deprecated + LEGACY_SNOW(78), + @Deprecated + LEGACY_ICE(79), + @Deprecated + LEGACY_SNOW_BLOCK(80), + @Deprecated + LEGACY_CACTUS(81, org.bukkit.material.MaterialData.class), + @Deprecated + LEGACY_CLAY(82), + @Deprecated + LEGACY_SUGAR_CANE_BLOCK(83, org.bukkit.material.MaterialData.class), + @Deprecated + LEGACY_JUKEBOX(84), + @Deprecated + LEGACY_FENCE(85), + @Deprecated + LEGACY_PUMPKIN(86, org.bukkit.material.Pumpkin.class), + @Deprecated + LEGACY_NETHERRACK(87), + @Deprecated + LEGACY_SOUL_SAND(88), + @Deprecated + LEGACY_GLOWSTONE(89), + @Deprecated + LEGACY_PORTAL(90), + @Deprecated + LEGACY_JACK_O_LANTERN(91, org.bukkit.material.Pumpkin.class), + @Deprecated + LEGACY_CAKE_BLOCK(92, 64, org.bukkit.material.Cake.class), + @Deprecated + LEGACY_DIODE_BLOCK_OFF(93, org.bukkit.material.Diode.class), + @Deprecated + LEGACY_DIODE_BLOCK_ON(94, org.bukkit.material.Diode.class), + @Deprecated + LEGACY_STAINED_GLASS(95), + @Deprecated + LEGACY_TRAP_DOOR(96, org.bukkit.material.TrapDoor.class), + @Deprecated + LEGACY_MONSTER_EGGS(97, org.bukkit.material.MonsterEggs.class), + @Deprecated + LEGACY_SMOOTH_BRICK(98, org.bukkit.material.SmoothBrick.class), + @Deprecated + LEGACY_HUGE_MUSHROOM_1(99, org.bukkit.material.Mushroom.class), + @Deprecated + LEGACY_HUGE_MUSHROOM_2(100, org.bukkit.material.Mushroom.class), + @Deprecated + LEGACY_IRON_FENCE(101), + @Deprecated + LEGACY_THIN_GLASS(102), + @Deprecated + LEGACY_MELON_BLOCK(103), + @Deprecated + LEGACY_PUMPKIN_STEM(104, org.bukkit.material.MaterialData.class), + @Deprecated + LEGACY_MELON_STEM(105, org.bukkit.material.MaterialData.class), + @Deprecated + LEGACY_VINE(106, org.bukkit.material.Vine.class), + @Deprecated + LEGACY_FENCE_GATE(107, org.bukkit.material.Gate.class), + @Deprecated + LEGACY_BRICK_STAIRS(108, org.bukkit.material.Stairs.class), + @Deprecated + LEGACY_SMOOTH_STAIRS(109, org.bukkit.material.Stairs.class), + @Deprecated + LEGACY_MYCEL(110), + @Deprecated + LEGACY_WATER_LILY(111), + @Deprecated + LEGACY_NETHER_BRICK(112), + @Deprecated + LEGACY_NETHER_FENCE(113), + @Deprecated + LEGACY_NETHER_BRICK_STAIRS(114, org.bukkit.material.Stairs.class), + @Deprecated + LEGACY_NETHER_WARTS(115, org.bukkit.material.NetherWarts.class), + @Deprecated + LEGACY_ENCHANTMENT_TABLE(116), + @Deprecated + LEGACY_BREWING_STAND(117, org.bukkit.material.MaterialData.class), + @Deprecated + LEGACY_CAULDRON(118, org.bukkit.material.Cauldron.class), + @Deprecated + LEGACY_ENDER_PORTAL(119), + @Deprecated + LEGACY_ENDER_PORTAL_FRAME(120), + @Deprecated + LEGACY_ENDER_STONE(121), + @Deprecated + LEGACY_DRAGON_EGG(122), + @Deprecated + LEGACY_REDSTONE_LAMP_OFF(123), + @Deprecated + LEGACY_REDSTONE_LAMP_ON(124), + @Deprecated + LEGACY_WOOD_DOUBLE_STEP(125, org.bukkit.material.Wood.class), + @Deprecated + LEGACY_WOOD_STEP(126, org.bukkit.material.WoodenStep.class), + @Deprecated + LEGACY_COCOA(127, org.bukkit.material.CocoaPlant.class), + @Deprecated + LEGACY_SANDSTONE_STAIRS(128, org.bukkit.material.Stairs.class), + @Deprecated + LEGACY_EMERALD_ORE(129), + @Deprecated + LEGACY_ENDER_CHEST(130, org.bukkit.material.EnderChest.class), + @Deprecated + LEGACY_TRIPWIRE_HOOK(131, org.bukkit.material.TripwireHook.class), + @Deprecated + LEGACY_TRIPWIRE(132, org.bukkit.material.Tripwire.class), + @Deprecated + LEGACY_EMERALD_BLOCK(133), + @Deprecated + LEGACY_SPRUCE_WOOD_STAIRS(134, org.bukkit.material.Stairs.class), + @Deprecated + LEGACY_BIRCH_WOOD_STAIRS(135, org.bukkit.material.Stairs.class), + @Deprecated + LEGACY_JUNGLE_WOOD_STAIRS(136, org.bukkit.material.Stairs.class), + @Deprecated + LEGACY_COMMAND(137, org.bukkit.material.Command.class), + @Deprecated + LEGACY_BEACON(138), + @Deprecated + LEGACY_COBBLE_WALL(139), + @Deprecated + LEGACY_FLOWER_POT(140, org.bukkit.material.FlowerPot.class), + @Deprecated + LEGACY_CARROT(141, org.bukkit.material.Crops.class), + @Deprecated + LEGACY_POTATO(142, org.bukkit.material.Crops.class), + @Deprecated + LEGACY_WOOD_BUTTON(143, org.bukkit.material.Button.class), + @Deprecated + LEGACY_SKULL(144, org.bukkit.material.Skull.class), + @Deprecated + LEGACY_ANVIL(145), + @Deprecated + LEGACY_TRAPPED_CHEST(146, org.bukkit.material.Chest.class), + @Deprecated + LEGACY_GOLD_PLATE(147), + @Deprecated + LEGACY_IRON_PLATE(148), + @Deprecated + LEGACY_REDSTONE_COMPARATOR_OFF(149, org.bukkit.material.Comparator.class), + @Deprecated + LEGACY_REDSTONE_COMPARATOR_ON(150, org.bukkit.material.Comparator.class), + @Deprecated + LEGACY_DAYLIGHT_DETECTOR(151), + @Deprecated + LEGACY_REDSTONE_BLOCK(152), + @Deprecated + LEGACY_QUARTZ_ORE(153), + @Deprecated + LEGACY_HOPPER(154, org.bukkit.material.Hopper.class), + @Deprecated + LEGACY_QUARTZ_BLOCK(155), + @Deprecated + LEGACY_QUARTZ_STAIRS(156, org.bukkit.material.Stairs.class), + @Deprecated + LEGACY_ACTIVATOR_RAIL(157, org.bukkit.material.PoweredRail.class), + @Deprecated + LEGACY_DROPPER(158, org.bukkit.material.Dispenser.class), + @Deprecated + LEGACY_STAINED_CLAY(159), + @Deprecated + LEGACY_STAINED_GLASS_PANE(160), + @Deprecated + LEGACY_LEAVES_2(161, org.bukkit.material.Leaves.class), + @Deprecated + LEGACY_LOG_2(162, org.bukkit.material.Tree.class), + @Deprecated + LEGACY_ACACIA_STAIRS(163, org.bukkit.material.Stairs.class), + @Deprecated + LEGACY_DARK_OAK_STAIRS(164, org.bukkit.material.Stairs.class), + @Deprecated + LEGACY_SLIME_BLOCK(165), + @Deprecated + LEGACY_BARRIER(166), + @Deprecated + LEGACY_IRON_TRAPDOOR(167, org.bukkit.material.TrapDoor.class), + @Deprecated + LEGACY_PRISMARINE(168), + @Deprecated + LEGACY_SEA_LANTERN(169), + @Deprecated + LEGACY_HAY_BLOCK(170), + @Deprecated + LEGACY_CARPET(171), + @Deprecated + LEGACY_HARD_CLAY(172), + @Deprecated + LEGACY_COAL_BLOCK(173), + @Deprecated + LEGACY_PACKED_ICE(174), + @Deprecated + LEGACY_DOUBLE_PLANT(175), + @Deprecated + LEGACY_STANDING_BANNER(176, org.bukkit.material.Banner.class), + @Deprecated + LEGACY_WALL_BANNER(177, org.bukkit.material.Banner.class), + @Deprecated + LEGACY_DAYLIGHT_DETECTOR_INVERTED(178), + @Deprecated + LEGACY_RED_SANDSTONE(179), + @Deprecated + LEGACY_RED_SANDSTONE_STAIRS(180, org.bukkit.material.Stairs.class), + @Deprecated + LEGACY_DOUBLE_STONE_SLAB2(181), + @Deprecated + LEGACY_STONE_SLAB2(182), + @Deprecated + LEGACY_SPRUCE_FENCE_GATE(183, org.bukkit.material.Gate.class), + @Deprecated + LEGACY_BIRCH_FENCE_GATE(184, org.bukkit.material.Gate.class), + @Deprecated + LEGACY_JUNGLE_FENCE_GATE(185, org.bukkit.material.Gate.class), + @Deprecated + LEGACY_DARK_OAK_FENCE_GATE(186, org.bukkit.material.Gate.class), + @Deprecated + LEGACY_ACACIA_FENCE_GATE(187, org.bukkit.material.Gate.class), + @Deprecated + LEGACY_SPRUCE_FENCE(188), + @Deprecated + LEGACY_BIRCH_FENCE(189), + @Deprecated + LEGACY_JUNGLE_FENCE(190), + @Deprecated + LEGACY_DARK_OAK_FENCE(191), + @Deprecated + LEGACY_ACACIA_FENCE(192), + @Deprecated + LEGACY_SPRUCE_DOOR(193, org.bukkit.material.Door.class), + @Deprecated + LEGACY_BIRCH_DOOR(194, org.bukkit.material.Door.class), + @Deprecated + LEGACY_JUNGLE_DOOR(195, org.bukkit.material.Door.class), + @Deprecated + LEGACY_ACACIA_DOOR(196, org.bukkit.material.Door.class), + @Deprecated + LEGACY_DARK_OAK_DOOR(197, org.bukkit.material.Door.class), + @Deprecated + LEGACY_END_ROD(198), + @Deprecated + LEGACY_CHORUS_PLANT(199), + @Deprecated + LEGACY_CHORUS_FLOWER(200), + @Deprecated + LEGACY_PURPUR_BLOCK(201), + @Deprecated + LEGACY_PURPUR_PILLAR(202), + @Deprecated + LEGACY_PURPUR_STAIRS(203, org.bukkit.material.Stairs.class), + @Deprecated + LEGACY_PURPUR_DOUBLE_SLAB(204), + @Deprecated + LEGACY_PURPUR_SLAB(205), + @Deprecated + LEGACY_END_BRICKS(206), + @Deprecated + LEGACY_BEETROOT_BLOCK(207, org.bukkit.material.Crops.class), + @Deprecated + LEGACY_GRASS_PATH(208), + @Deprecated + LEGACY_END_GATEWAY(209), + @Deprecated + LEGACY_COMMAND_REPEATING(210, org.bukkit.material.Command.class), + @Deprecated + LEGACY_COMMAND_CHAIN(211, org.bukkit.material.Command.class), + @Deprecated + LEGACY_FROSTED_ICE(212), + @Deprecated + LEGACY_MAGMA(213), + @Deprecated + LEGACY_NETHER_WART_BLOCK(214), + @Deprecated + LEGACY_RED_NETHER_BRICK(215), + @Deprecated + LEGACY_BONE_BLOCK(216), + @Deprecated + LEGACY_STRUCTURE_VOID(217), + @Deprecated + LEGACY_OBSERVER(218, org.bukkit.material.Observer.class), + @Deprecated + LEGACY_WHITE_SHULKER_BOX(219, 1), + @Deprecated + LEGACY_ORANGE_SHULKER_BOX(220, 1), + @Deprecated + LEGACY_MAGENTA_SHULKER_BOX(221, 1), + @Deprecated + LEGACY_LIGHT_BLUE_SHULKER_BOX(222, 1), + @Deprecated + LEGACY_YELLOW_SHULKER_BOX(223, 1), + @Deprecated + LEGACY_LIME_SHULKER_BOX(224, 1), + @Deprecated + LEGACY_PINK_SHULKER_BOX(225, 1), + @Deprecated + LEGACY_GRAY_SHULKER_BOX(226, 1), + @Deprecated + LEGACY_SILVER_SHULKER_BOX(227, 1), + @Deprecated + LEGACY_CYAN_SHULKER_BOX(228, 1), + @Deprecated + LEGACY_PURPLE_SHULKER_BOX(229, 1), + @Deprecated + LEGACY_BLUE_SHULKER_BOX(230, 1), + @Deprecated + LEGACY_BROWN_SHULKER_BOX(231, 1), + @Deprecated + LEGACY_GREEN_SHULKER_BOX(232, 1), + @Deprecated + LEGACY_RED_SHULKER_BOX(233, 1), + @Deprecated + LEGACY_BLACK_SHULKER_BOX(234, 1), + @Deprecated + LEGACY_WHITE_GLAZED_TERRACOTTA(235), + @Deprecated + LEGACY_ORANGE_GLAZED_TERRACOTTA(236), + @Deprecated + LEGACY_MAGENTA_GLAZED_TERRACOTTA(237), + @Deprecated + LEGACY_LIGHT_BLUE_GLAZED_TERRACOTTA(238), + @Deprecated + LEGACY_YELLOW_GLAZED_TERRACOTTA(239), + @Deprecated + LEGACY_LIME_GLAZED_TERRACOTTA(240), + @Deprecated + LEGACY_PINK_GLAZED_TERRACOTTA(241), + @Deprecated + LEGACY_GRAY_GLAZED_TERRACOTTA(242), + @Deprecated + LEGACY_SILVER_GLAZED_TERRACOTTA(243), + @Deprecated + LEGACY_CYAN_GLAZED_TERRACOTTA(244), + @Deprecated + LEGACY_PURPLE_GLAZED_TERRACOTTA(245), + @Deprecated + LEGACY_BLUE_GLAZED_TERRACOTTA(246), + @Deprecated + LEGACY_BROWN_GLAZED_TERRACOTTA(247), + @Deprecated + LEGACY_GREEN_GLAZED_TERRACOTTA(248), + @Deprecated + LEGACY_RED_GLAZED_TERRACOTTA(249), + @Deprecated + LEGACY_BLACK_GLAZED_TERRACOTTA(250), + @Deprecated + LEGACY_CONCRETE(251), + @Deprecated + LEGACY_CONCRETE_POWDER(252), + @Deprecated + LEGACY_STRUCTURE_BLOCK(255), + // ----- Item Separator ----- + @Deprecated + LEGACY_IRON_SPADE(256, 1, 250), + @Deprecated + LEGACY_IRON_PICKAXE(257, 1, 250), + @Deprecated + LEGACY_IRON_AXE(258, 1, 250), + @Deprecated + LEGACY_FLINT_AND_STEEL(259, 1, 64), + @Deprecated + LEGACY_APPLE(260), + @Deprecated + LEGACY_BOW(261, 1, 384), + @Deprecated + LEGACY_ARROW(262), + @Deprecated + LEGACY_COAL(263, org.bukkit.material.Coal.class), + @Deprecated + LEGACY_DIAMOND(264), + @Deprecated + LEGACY_IRON_INGOT(265), + @Deprecated + LEGACY_GOLD_INGOT(266), + @Deprecated + LEGACY_IRON_SWORD(267, 1, 250), + @Deprecated + LEGACY_WOOD_SWORD(268, 1, 59), + @Deprecated + LEGACY_WOOD_SPADE(269, 1, 59), + @Deprecated + LEGACY_WOOD_PICKAXE(270, 1, 59), + @Deprecated + LEGACY_WOOD_AXE(271, 1, 59), + @Deprecated + LEGACY_STONE_SWORD(272, 1, 131), + @Deprecated + LEGACY_STONE_SPADE(273, 1, 131), + @Deprecated + LEGACY_STONE_PICKAXE(274, 1, 131), + @Deprecated + LEGACY_STONE_AXE(275, 1, 131), + @Deprecated + LEGACY_DIAMOND_SWORD(276, 1, 1561), + @Deprecated + LEGACY_DIAMOND_SPADE(277, 1, 1561), + @Deprecated + LEGACY_DIAMOND_PICKAXE(278, 1, 1561), + @Deprecated + LEGACY_DIAMOND_AXE(279, 1, 1561), + @Deprecated + LEGACY_STICK(280), + @Deprecated + LEGACY_BOWL(281), + @Deprecated + LEGACY_MUSHROOM_SOUP(282, 1), + @Deprecated + LEGACY_GOLD_SWORD(283, 1, 32), + @Deprecated + LEGACY_GOLD_SPADE(284, 1, 32), + @Deprecated + LEGACY_GOLD_PICKAXE(285, 1, 32), + @Deprecated + LEGACY_GOLD_AXE(286, 1, 32), + @Deprecated + LEGACY_STRING(287), + @Deprecated + LEGACY_FEATHER(288), + @Deprecated + LEGACY_SULPHUR(289), + @Deprecated + LEGACY_WOOD_HOE(290, 1, 59), + @Deprecated + LEGACY_STONE_HOE(291, 1, 131), + @Deprecated + LEGACY_IRON_HOE(292, 1, 250), + @Deprecated + LEGACY_DIAMOND_HOE(293, 1, 1561), + @Deprecated + LEGACY_GOLD_HOE(294, 1, 32), + @Deprecated + LEGACY_SEEDS(295), + @Deprecated + LEGACY_WHEAT(296), + @Deprecated + LEGACY_BREAD(297), + @Deprecated + LEGACY_LEATHER_HELMET(298, 1, 55), + @Deprecated + LEGACY_LEATHER_CHESTPLATE(299, 1, 80), + @Deprecated + LEGACY_LEATHER_LEGGINGS(300, 1, 75), + @Deprecated + LEGACY_LEATHER_BOOTS(301, 1, 65), + @Deprecated + LEGACY_CHAINMAIL_HELMET(302, 1, 165), + @Deprecated + LEGACY_CHAINMAIL_CHESTPLATE(303, 1, 240), + @Deprecated + LEGACY_CHAINMAIL_LEGGINGS(304, 1, 225), + @Deprecated + LEGACY_CHAINMAIL_BOOTS(305, 1, 195), + @Deprecated + LEGACY_IRON_HELMET(306, 1, 165), + @Deprecated + LEGACY_IRON_CHESTPLATE(307, 1, 240), + @Deprecated + LEGACY_IRON_LEGGINGS(308, 1, 225), + @Deprecated + LEGACY_IRON_BOOTS(309, 1, 195), + @Deprecated + LEGACY_DIAMOND_HELMET(310, 1, 363), + @Deprecated + LEGACY_DIAMOND_CHESTPLATE(311, 1, 528), + @Deprecated + LEGACY_DIAMOND_LEGGINGS(312, 1, 495), + @Deprecated + LEGACY_DIAMOND_BOOTS(313, 1, 429), + @Deprecated + LEGACY_GOLD_HELMET(314, 1, 77), + @Deprecated + LEGACY_GOLD_CHESTPLATE(315, 1, 112), + @Deprecated + LEGACY_GOLD_LEGGINGS(316, 1, 105), + @Deprecated + LEGACY_GOLD_BOOTS(317, 1, 91), + @Deprecated + LEGACY_FLINT(318), + @Deprecated + LEGACY_PORK(319), + @Deprecated + LEGACY_GRILLED_PORK(320), + @Deprecated + LEGACY_PAINTING(321), + @Deprecated + LEGACY_GOLDEN_APPLE(322), + @Deprecated + LEGACY_SIGN(323, 16), + @Deprecated + LEGACY_WOOD_DOOR(324, 64), + @Deprecated + LEGACY_BUCKET(325, 16), + @Deprecated + LEGACY_WATER_BUCKET(326, 1), + @Deprecated + LEGACY_LAVA_BUCKET(327, 1), + @Deprecated + LEGACY_MINECART(328, 1), + @Deprecated + LEGACY_SADDLE(329, 1), + @Deprecated + LEGACY_IRON_DOOR(330, 64), + @Deprecated + LEGACY_REDSTONE(331), + @Deprecated + LEGACY_SNOW_BALL(332, 16), + @Deprecated + LEGACY_BOAT(333, 1), + @Deprecated + LEGACY_LEATHER(334), + @Deprecated + LEGACY_MILK_BUCKET(335, 1), + @Deprecated + LEGACY_CLAY_BRICK(336), + @Deprecated + LEGACY_CLAY_BALL(337), + @Deprecated + LEGACY_SUGAR_CANE(338), + @Deprecated + LEGACY_PAPER(339), + @Deprecated + LEGACY_BOOK(340), + @Deprecated + LEGACY_SLIME_BALL(341), + @Deprecated + LEGACY_STORAGE_MINECART(342, 1), + @Deprecated + LEGACY_POWERED_MINECART(343, 1), + @Deprecated + LEGACY_EGG(344, 16), + @Deprecated + LEGACY_COMPASS(345), + @Deprecated + LEGACY_FISHING_ROD(346, 1, 64), + @Deprecated + LEGACY_WATCH(347), + @Deprecated + LEGACY_GLOWSTONE_DUST(348), + @Deprecated + LEGACY_RAW_FISH(349), + @Deprecated + LEGACY_COOKED_FISH(350), + @Deprecated + LEGACY_INK_SACK(351, org.bukkit.material.Dye.class), + @Deprecated + LEGACY_BONE(352), + @Deprecated + LEGACY_SUGAR(353), + @Deprecated + LEGACY_CAKE(354, 1), + @Deprecated + LEGACY_BED(355, 1), + @Deprecated + LEGACY_DIODE(356), + @Deprecated + LEGACY_COOKIE(357), + /** + * @see org.bukkit.map.MapView + */ + @Deprecated + LEGACY_MAP(358, org.bukkit.material.MaterialData.class), + @Deprecated + LEGACY_SHEARS(359, 1, 238), + @Deprecated + LEGACY_MELON(360), + @Deprecated + LEGACY_PUMPKIN_SEEDS(361), + @Deprecated + LEGACY_MELON_SEEDS(362), + @Deprecated + LEGACY_RAW_BEEF(363), + @Deprecated + LEGACY_COOKED_BEEF(364), + @Deprecated + LEGACY_RAW_CHICKEN(365), + @Deprecated + LEGACY_COOKED_CHICKEN(366), + @Deprecated + LEGACY_ROTTEN_FLESH(367), + @Deprecated + LEGACY_ENDER_PEARL(368, 16), + @Deprecated + LEGACY_BLAZE_ROD(369), + @Deprecated + LEGACY_GHAST_TEAR(370), + @Deprecated + LEGACY_GOLD_NUGGET(371), + @Deprecated + LEGACY_NETHER_STALK(372), + @Deprecated + LEGACY_POTION(373, 1, org.bukkit.material.MaterialData.class), + @Deprecated + LEGACY_GLASS_BOTTLE(374), + @Deprecated + LEGACY_SPIDER_EYE(375), + @Deprecated + LEGACY_FERMENTED_SPIDER_EYE(376), + @Deprecated + LEGACY_BLAZE_POWDER(377), + @Deprecated + LEGACY_MAGMA_CREAM(378), + @Deprecated + LEGACY_BREWING_STAND_ITEM(379), + @Deprecated + LEGACY_CAULDRON_ITEM(380), + @Deprecated + LEGACY_EYE_OF_ENDER(381), + @Deprecated + LEGACY_SPECKLED_MELON(382), + @Deprecated + LEGACY_MONSTER_EGG(383, 64, org.bukkit.material.SpawnEgg.class), + @Deprecated + LEGACY_EXP_BOTTLE(384, 64), + @Deprecated + LEGACY_FIREBALL(385, 64), + @Deprecated + LEGACY_BOOK_AND_QUILL(386, 1), + @Deprecated + LEGACY_WRITTEN_BOOK(387, 16), + @Deprecated + LEGACY_EMERALD(388, 64), + @Deprecated + LEGACY_ITEM_FRAME(389), + @Deprecated + LEGACY_FLOWER_POT_ITEM(390), + @Deprecated + LEGACY_CARROT_ITEM(391), + @Deprecated + LEGACY_POTATO_ITEM(392), + @Deprecated + LEGACY_BAKED_POTATO(393), + @Deprecated + LEGACY_POISONOUS_POTATO(394), + @Deprecated + LEGACY_EMPTY_MAP(395), + @Deprecated + LEGACY_GOLDEN_CARROT(396), + @Deprecated + LEGACY_SKULL_ITEM(397), + @Deprecated + LEGACY_CARROT_STICK(398, 1, 25), + @Deprecated + LEGACY_NETHER_STAR(399), + @Deprecated + LEGACY_PUMPKIN_PIE(400), + @Deprecated + LEGACY_FIREWORK(401), + @Deprecated + LEGACY_FIREWORK_CHARGE(402), + @Deprecated + LEGACY_ENCHANTED_BOOK(403, 1), + @Deprecated + LEGACY_REDSTONE_COMPARATOR(404), + @Deprecated + LEGACY_NETHER_BRICK_ITEM(405), + @Deprecated + LEGACY_QUARTZ(406), + @Deprecated + LEGACY_EXPLOSIVE_MINECART(407, 1), + @Deprecated + LEGACY_HOPPER_MINECART(408, 1), + @Deprecated + LEGACY_PRISMARINE_SHARD(409), + @Deprecated + LEGACY_PRISMARINE_CRYSTALS(410), + @Deprecated + LEGACY_RABBIT(411), + @Deprecated + LEGACY_COOKED_RABBIT(412), + @Deprecated + LEGACY_RABBIT_STEW(413, 1), + @Deprecated + LEGACY_RABBIT_FOOT(414), + @Deprecated + LEGACY_RABBIT_HIDE(415), + @Deprecated + LEGACY_ARMOR_STAND(416, 16), + @Deprecated + LEGACY_IRON_BARDING(417, 1), + @Deprecated + LEGACY_GOLD_BARDING(418, 1), + @Deprecated + LEGACY_DIAMOND_BARDING(419, 1), + @Deprecated + LEGACY_LEASH(420), + @Deprecated + LEGACY_NAME_TAG(421), + @Deprecated + LEGACY_COMMAND_MINECART(422, 1), + @Deprecated + LEGACY_MUTTON(423), + @Deprecated + LEGACY_COOKED_MUTTON(424), + @Deprecated + LEGACY_BANNER(425, 16), + @Deprecated + LEGACY_END_CRYSTAL(426), + @Deprecated + LEGACY_SPRUCE_DOOR_ITEM(427), + @Deprecated + LEGACY_BIRCH_DOOR_ITEM(428), + @Deprecated + LEGACY_JUNGLE_DOOR_ITEM(429), + @Deprecated + LEGACY_ACACIA_DOOR_ITEM(430), + @Deprecated + LEGACY_DARK_OAK_DOOR_ITEM(431), + @Deprecated + LEGACY_CHORUS_FRUIT(432), + @Deprecated + LEGACY_CHORUS_FRUIT_POPPED(433), + @Deprecated + LEGACY_BEETROOT(434), + @Deprecated + LEGACY_BEETROOT_SEEDS(435), + @Deprecated + LEGACY_BEETROOT_SOUP(436, 1), + @Deprecated + LEGACY_DRAGONS_BREATH(437), + @Deprecated + LEGACY_SPLASH_POTION(438, 1), + @Deprecated + LEGACY_SPECTRAL_ARROW(439), + @Deprecated + LEGACY_TIPPED_ARROW(440), + @Deprecated + LEGACY_LINGERING_POTION(441, 1), + @Deprecated + LEGACY_SHIELD(442, 1, 336), + @Deprecated + LEGACY_ELYTRA(443, 1, 431), + @Deprecated + LEGACY_BOAT_SPRUCE(444, 1), + @Deprecated + LEGACY_BOAT_BIRCH(445, 1), + @Deprecated + LEGACY_BOAT_JUNGLE(446, 1), + @Deprecated + LEGACY_BOAT_ACACIA(447, 1), + @Deprecated + LEGACY_BOAT_DARK_OAK(448, 1), + @Deprecated + LEGACY_TOTEM(449, 1), + @Deprecated + LEGACY_SHULKER_SHELL(450), + @Deprecated + LEGACY_IRON_NUGGET(452), + @Deprecated + LEGACY_KNOWLEDGE_BOOK(453, 1), + @Deprecated + LEGACY_GOLD_RECORD(2256, 1), + @Deprecated + LEGACY_GREEN_RECORD(2257, 1), + @Deprecated + LEGACY_RECORD_3(2258, 1), + @Deprecated + LEGACY_RECORD_4(2259, 1), + @Deprecated + LEGACY_RECORD_5(2260, 1), + @Deprecated + LEGACY_RECORD_6(2261, 1), + @Deprecated + LEGACY_RECORD_7(2262, 1), + @Deprecated + LEGACY_RECORD_8(2263, 1), + @Deprecated + LEGACY_RECORD_9(2264, 1), + @Deprecated + LEGACY_RECORD_10(2265, 1), + @Deprecated + LEGACY_RECORD_11(2266, 1), + @Deprecated + LEGACY_RECORD_12(2267, 1), + ; + // + + @Deprecated + public static final String LEGACY_PREFIX = "LEGACY_"; + + private final int id; + private final Constructor ctor; + private final static Map BY_NAME = Maps.newHashMap(); + private final int maxStack; + private final short durability; + public final Class data; + private final boolean legacy; + private final NamespacedKey key; + + private Material(final int id) { + this(id, 64); + } + + private Material(final int id, final int stack) { + this(id, stack, MaterialData.class); + } + + private Material(final int id, final int stack, final int durability) { + this(id, stack, durability, MaterialData.class); + } + + private Material(final int id, /*@NotNull*/ final Class data) { + this(id, 64, data); + } + + private Material(final int id, final int stack, /*@NotNull*/ final Class data) { + this(id, stack, 0, data); + } + + private Material(final int id, final int stack, final int durability, /*@NotNull*/ final Class data) { + this.id = id; + this.durability = (short) durability; + this.maxStack = stack; + this.data = data; + this.legacy = this.name().startsWith(LEGACY_PREFIX); + this.key = NamespacedKey.minecraft(this.name().toLowerCase(Locale.ROOT)); + // try to cache the constructor for this material + try { + if (MaterialData.class.isAssignableFrom(data)) { + this.ctor = (Constructor) data.getConstructor(Material.class, byte.class); + } else { + this.ctor = null; + } + } catch (NoSuchMethodException ex) { + throw new AssertionError(ex); + } catch (SecurityException ex) { + throw new AssertionError(ex); + } + } + + // Paper start + + /** + * @return If the type is either AIR, CAVE_AIR or VOID_AIR + */ + public boolean isEmpty() { + switch (this) { + case AIR: + case CAVE_AIR: + case VOID_AIR: + return true; + } + return false; + } + // Paper end + + /** + * Do not use for any reason. + * + * @return ID of this material + * @deprecated Magic value + */ + @Deprecated + public int getId() { + return id; + } + + /** + * Do not use for any reason. + * + * @return legacy status + */ + @Deprecated + public boolean isLegacy() { + return legacy; + } + + @NotNull + @Override + public NamespacedKey getKey() { + Validate.isTrue(!legacy, "Cannot get key of Legacy Material"); + return key; + } + + /** + * Gets the maximum amount of this material that can be held in a stack + * + * @return Maximum stack size for this material + */ + public int getMaxStackSize() { + return maxStack; + } + + /** + * Gets the maximum durability of this material + * + * @return Maximum durability for this material + */ + public short getMaxDurability() { + return durability; + } + + /** + * Creates a new {@link BlockData} instance for this Material, with all + * properties initialized to unspecified defaults. + * + * @return new data instance + */ + @NotNull + public BlockData createBlockData() { + return Bukkit.createBlockData(this); + } + + /** + * Creates a new {@link BlockData} instance for this Material, with + * all properties initialized to unspecified defaults. + * + * @param consumer consumer to run on new instance before returning + * @return new data instance + */ + @NotNull + public BlockData createBlockData(@Nullable Consumer consumer) { + return Bukkit.createBlockData(this, consumer); + } + + /** + * Creates a new {@link BlockData} instance for this Material, with all + * properties initialized to unspecified defaults, except for those provided + * in data. + * + * @param data data string + * @return new data instance + * @throws IllegalArgumentException if the specified data is not valid + */ + @NotNull + public BlockData createBlockData(@Nullable String data) throws IllegalArgumentException { + return Bukkit.createBlockData(this, data); + } + + /** + * Gets the MaterialData class associated with this Material + * + * @return MaterialData associated with this Material + */ + @NotNull + public Class getData() { + Validate.isTrue(legacy, "Cannot get data class of Modern Material"); + return ctor.getDeclaringClass(); + } + + /** + * Constructs a new MaterialData relevant for this Material, with the + * given initial data + * + * @param raw Initial data to construct the MaterialData with + * @return New MaterialData with the given data + * @deprecated Magic value + */ + @Deprecated + @NotNull + public MaterialData getNewData(final byte raw) { + Validate.isTrue(legacy, "Cannot get new data of Modern Material"); + try { + return ctor.newInstance(this, raw); + } catch (InstantiationException ex) { + final Throwable t = ex.getCause(); + if (t instanceof RuntimeException) { + throw (RuntimeException) t; + } + if (t instanceof Error) { + throw (Error) t; + } + throw new AssertionError(t); + } catch (Throwable t) { + throw new AssertionError(t); + } + } + + /** + * Checks if this Material is a placable block + * + * @return true if this material is a block + */ + public boolean isBlock() { + switch (this) { + // + case ACACIA_BUTTON: + case ACACIA_DOOR: + case ACACIA_FENCE: + case ACACIA_FENCE_GATE: + case ACACIA_LEAVES: + case ACACIA_LOG: + case ACACIA_PLANKS: + case ACACIA_PRESSURE_PLATE: + case ACACIA_SAPLING: + case ACACIA_SLAB: + case ACACIA_STAIRS: + case ACACIA_TRAPDOOR: + case ACACIA_WOOD: + case ACTIVATOR_RAIL: + case AIR: + case ALLIUM: + case ANDESITE: + case ANVIL: + case ATTACHED_MELON_STEM: + case ATTACHED_PUMPKIN_STEM: + case AZURE_BLUET: + case BARRIER: + case BEACON: + case BEDROCK: + case BEETROOTS: + case BIRCH_BUTTON: + case BIRCH_DOOR: + case BIRCH_FENCE: + case BIRCH_FENCE_GATE: + case BIRCH_LEAVES: + case BIRCH_LOG: + case BIRCH_PLANKS: + case BIRCH_PRESSURE_PLATE: + case BIRCH_SAPLING: + case BIRCH_SLAB: + case BIRCH_STAIRS: + case BIRCH_TRAPDOOR: + case BIRCH_WOOD: + case BLACK_BANNER: + case BLACK_BED: + case BLACK_CARPET: + case BLACK_CONCRETE: + case BLACK_CONCRETE_POWDER: + case BLACK_GLAZED_TERRACOTTA: + case BLACK_SHULKER_BOX: + case BLACK_STAINED_GLASS: + case BLACK_STAINED_GLASS_PANE: + case BLACK_TERRACOTTA: + case BLACK_WALL_BANNER: + case BLACK_WOOL: + case BLUE_BANNER: + case BLUE_BED: + case BLUE_CARPET: + case BLUE_CONCRETE: + case BLUE_CONCRETE_POWDER: + case BLUE_GLAZED_TERRACOTTA: + case BLUE_ICE: + case BLUE_ORCHID: + case BLUE_SHULKER_BOX: + case BLUE_STAINED_GLASS: + case BLUE_STAINED_GLASS_PANE: + case BLUE_TERRACOTTA: + case BLUE_WALL_BANNER: + case BLUE_WOOL: + case BONE_BLOCK: + case BOOKSHELF: + case BRAIN_CORAL: + case BRAIN_CORAL_BLOCK: + case BRAIN_CORAL_FAN: + case BRAIN_CORAL_WALL_FAN: + case BREWING_STAND: + case BRICKS: + case BRICK_SLAB: + case BRICK_STAIRS: + case BROWN_BANNER: + case BROWN_BED: + case BROWN_CARPET: + case BROWN_CONCRETE: + case BROWN_CONCRETE_POWDER: + case BROWN_GLAZED_TERRACOTTA: + case BROWN_MUSHROOM: + case BROWN_MUSHROOM_BLOCK: + case BROWN_SHULKER_BOX: + case BROWN_STAINED_GLASS: + case BROWN_STAINED_GLASS_PANE: + case BROWN_TERRACOTTA: + case BROWN_WALL_BANNER: + case BROWN_WOOL: + case BUBBLE_COLUMN: + case BUBBLE_CORAL: + case BUBBLE_CORAL_BLOCK: + case BUBBLE_CORAL_FAN: + case BUBBLE_CORAL_WALL_FAN: + case CACTUS: + case CAKE: + case CARROTS: + case CARVED_PUMPKIN: + case CAULDRON: + case CAVE_AIR: + case CHAIN_COMMAND_BLOCK: + case CHEST: + case CHIPPED_ANVIL: + case CHISELED_QUARTZ_BLOCK: + case CHISELED_RED_SANDSTONE: + case CHISELED_SANDSTONE: + case CHISELED_STONE_BRICKS: + case CHORUS_FLOWER: + case CHORUS_PLANT: + case CLAY: + case COAL_BLOCK: + case COAL_ORE: + case COARSE_DIRT: + case COBBLESTONE: + case COBBLESTONE_SLAB: + case COBBLESTONE_STAIRS: + case COBBLESTONE_WALL: + case COBWEB: + case COCOA: + case COMMAND_BLOCK: + case COMPARATOR: + case CONDUIT: + case CRACKED_STONE_BRICKS: + case CRAFTING_TABLE: + case CREEPER_HEAD: + case CREEPER_WALL_HEAD: + case CUT_RED_SANDSTONE: + case CUT_SANDSTONE: + case CYAN_BANNER: + case CYAN_BED: + case CYAN_CARPET: + case CYAN_CONCRETE: + case CYAN_CONCRETE_POWDER: + case CYAN_GLAZED_TERRACOTTA: + case CYAN_SHULKER_BOX: + case CYAN_STAINED_GLASS: + case CYAN_STAINED_GLASS_PANE: + case CYAN_TERRACOTTA: + case CYAN_WALL_BANNER: + case CYAN_WOOL: + case DAMAGED_ANVIL: + case DANDELION: + case DARK_OAK_BUTTON: + case DARK_OAK_DOOR: + case DARK_OAK_FENCE: + case DARK_OAK_FENCE_GATE: + case DARK_OAK_LEAVES: + case DARK_OAK_LOG: + case DARK_OAK_PLANKS: + case DARK_OAK_PRESSURE_PLATE: + case DARK_OAK_SAPLING: + case DARK_OAK_SLAB: + case DARK_OAK_STAIRS: + case DARK_OAK_TRAPDOOR: + case DARK_OAK_WOOD: + case DARK_PRISMARINE: + case DARK_PRISMARINE_SLAB: + case DARK_PRISMARINE_STAIRS: + case DAYLIGHT_DETECTOR: + case DEAD_BRAIN_CORAL: + case DEAD_BRAIN_CORAL_BLOCK: + case DEAD_BRAIN_CORAL_FAN: + case DEAD_BRAIN_CORAL_WALL_FAN: + case DEAD_BUBBLE_CORAL: + case DEAD_BUBBLE_CORAL_BLOCK: + case DEAD_BUBBLE_CORAL_FAN: + case DEAD_BUBBLE_CORAL_WALL_FAN: + case DEAD_BUSH: + case DEAD_FIRE_CORAL: + case DEAD_FIRE_CORAL_BLOCK: + case DEAD_FIRE_CORAL_FAN: + case DEAD_FIRE_CORAL_WALL_FAN: + case DEAD_HORN_CORAL: + case DEAD_HORN_CORAL_BLOCK: + case DEAD_HORN_CORAL_FAN: + case DEAD_HORN_CORAL_WALL_FAN: + case DEAD_TUBE_CORAL: + case DEAD_TUBE_CORAL_BLOCK: + case DEAD_TUBE_CORAL_FAN: + case DEAD_TUBE_CORAL_WALL_FAN: + case DETECTOR_RAIL: + case DIAMOND_BLOCK: + case DIAMOND_ORE: + case DIORITE: + case DIRT: + case DISPENSER: + case DRAGON_EGG: + case DRAGON_HEAD: + case DRAGON_WALL_HEAD: + case DRIED_KELP_BLOCK: + case DROPPER: + case EMERALD_BLOCK: + case EMERALD_ORE: + case ENCHANTING_TABLE: + case ENDER_CHEST: + case END_GATEWAY: + case END_PORTAL: + case END_PORTAL_FRAME: + case END_ROD: + case END_STONE: + case END_STONE_BRICKS: + case FARMLAND: + case FERN: + case FIRE: + case FIRE_CORAL: + case FIRE_CORAL_BLOCK: + case FIRE_CORAL_FAN: + case FIRE_CORAL_WALL_FAN: + case FLOWER_POT: + case FROSTED_ICE: + case FURNACE: + case GLASS: + case GLASS_PANE: + case GLOWSTONE: + case GOLD_BLOCK: + case GOLD_ORE: + case GRANITE: + case GRASS: + case GRASS_BLOCK: + case GRASS_PATH: + case GRAVEL: + case GRAY_BANNER: + case GRAY_BED: + case GRAY_CARPET: + case GRAY_CONCRETE: + case GRAY_CONCRETE_POWDER: + case GRAY_GLAZED_TERRACOTTA: + case GRAY_SHULKER_BOX: + case GRAY_STAINED_GLASS: + case GRAY_STAINED_GLASS_PANE: + case GRAY_TERRACOTTA: + case GRAY_WALL_BANNER: + case GRAY_WOOL: + case GREEN_BANNER: + case GREEN_BED: + case GREEN_CARPET: + case GREEN_CONCRETE: + case GREEN_CONCRETE_POWDER: + case GREEN_GLAZED_TERRACOTTA: + case GREEN_SHULKER_BOX: + case GREEN_STAINED_GLASS: + case GREEN_STAINED_GLASS_PANE: + case GREEN_TERRACOTTA: + case GREEN_WALL_BANNER: + case GREEN_WOOL: + case HAY_BLOCK: + case HEAVY_WEIGHTED_PRESSURE_PLATE: + case HOPPER: + case HORN_CORAL: + case HORN_CORAL_BLOCK: + case HORN_CORAL_FAN: + case HORN_CORAL_WALL_FAN: + case ICE: + case INFESTED_CHISELED_STONE_BRICKS: + case INFESTED_COBBLESTONE: + case INFESTED_CRACKED_STONE_BRICKS: + case INFESTED_MOSSY_STONE_BRICKS: + case INFESTED_STONE: + case INFESTED_STONE_BRICKS: + case IRON_BARS: + case IRON_BLOCK: + case IRON_DOOR: + case IRON_ORE: + case IRON_TRAPDOOR: + case JACK_O_LANTERN: + case JUKEBOX: + case JUNGLE_BUTTON: + case JUNGLE_DOOR: + case JUNGLE_FENCE: + case JUNGLE_FENCE_GATE: + case JUNGLE_LEAVES: + case JUNGLE_LOG: + case JUNGLE_PLANKS: + case JUNGLE_PRESSURE_PLATE: + case JUNGLE_SAPLING: + case JUNGLE_SLAB: + case JUNGLE_STAIRS: + case JUNGLE_TRAPDOOR: + case JUNGLE_WOOD: + case KELP: + case KELP_PLANT: + case LADDER: + case LAPIS_BLOCK: + case LAPIS_ORE: + case LARGE_FERN: + case LAVA: + case LEVER: + case LIGHT_BLUE_BANNER: + case LIGHT_BLUE_BED: + case LIGHT_BLUE_CARPET: + case LIGHT_BLUE_CONCRETE: + case LIGHT_BLUE_CONCRETE_POWDER: + case LIGHT_BLUE_GLAZED_TERRACOTTA: + case LIGHT_BLUE_SHULKER_BOX: + case LIGHT_BLUE_STAINED_GLASS: + case LIGHT_BLUE_STAINED_GLASS_PANE: + case LIGHT_BLUE_TERRACOTTA: + case LIGHT_BLUE_WALL_BANNER: + case LIGHT_BLUE_WOOL: + case LIGHT_GRAY_BANNER: + case LIGHT_GRAY_BED: + case LIGHT_GRAY_CARPET: + case LIGHT_GRAY_CONCRETE: + case LIGHT_GRAY_CONCRETE_POWDER: + case LIGHT_GRAY_GLAZED_TERRACOTTA: + case LIGHT_GRAY_SHULKER_BOX: + case LIGHT_GRAY_STAINED_GLASS: + case LIGHT_GRAY_STAINED_GLASS_PANE: + case LIGHT_GRAY_TERRACOTTA: + case LIGHT_GRAY_WALL_BANNER: + case LIGHT_GRAY_WOOL: + case LIGHT_WEIGHTED_PRESSURE_PLATE: + case LILAC: + case LILY_PAD: + case LIME_BANNER: + case LIME_BED: + case LIME_CARPET: + case LIME_CONCRETE: + case LIME_CONCRETE_POWDER: + case LIME_GLAZED_TERRACOTTA: + case LIME_SHULKER_BOX: + case LIME_STAINED_GLASS: + case LIME_STAINED_GLASS_PANE: + case LIME_TERRACOTTA: + case LIME_WALL_BANNER: + case LIME_WOOL: + case MAGENTA_BANNER: + case MAGENTA_BED: + case MAGENTA_CARPET: + case MAGENTA_CONCRETE: + case MAGENTA_CONCRETE_POWDER: + case MAGENTA_GLAZED_TERRACOTTA: + case MAGENTA_SHULKER_BOX: + case MAGENTA_STAINED_GLASS: + case MAGENTA_STAINED_GLASS_PANE: + case MAGENTA_TERRACOTTA: + case MAGENTA_WALL_BANNER: + case MAGENTA_WOOL: + case MAGMA_BLOCK: + case MELON: + case MELON_STEM: + case MOSSY_COBBLESTONE: + case MOSSY_COBBLESTONE_WALL: + case MOSSY_STONE_BRICKS: + case MOVING_PISTON: + case MUSHROOM_STEM: + case MYCELIUM: + case NETHERRACK: + case NETHER_BRICKS: + case NETHER_BRICK_FENCE: + case NETHER_BRICK_SLAB: + case NETHER_BRICK_STAIRS: + case NETHER_PORTAL: + case NETHER_QUARTZ_ORE: + case NETHER_WART: + case NETHER_WART_BLOCK: + case NOTE_BLOCK: + case OAK_BUTTON: + case OAK_DOOR: + case OAK_FENCE: + case OAK_FENCE_GATE: + case OAK_LEAVES: + case OAK_LOG: + case OAK_PLANKS: + case OAK_PRESSURE_PLATE: + case OAK_SAPLING: + case OAK_SLAB: + case OAK_STAIRS: + case OAK_TRAPDOOR: + case OAK_WOOD: + case OBSERVER: + case OBSIDIAN: + case ORANGE_BANNER: + case ORANGE_BED: + case ORANGE_CARPET: + case ORANGE_CONCRETE: + case ORANGE_CONCRETE_POWDER: + case ORANGE_GLAZED_TERRACOTTA: + case ORANGE_SHULKER_BOX: + case ORANGE_STAINED_GLASS: + case ORANGE_STAINED_GLASS_PANE: + case ORANGE_TERRACOTTA: + case ORANGE_TULIP: + case ORANGE_WALL_BANNER: + case ORANGE_WOOL: + case OXEYE_DAISY: + case PACKED_ICE: + case PEONY: + case PETRIFIED_OAK_SLAB: + case PINK_BANNER: + case PINK_BED: + case PINK_CARPET: + case PINK_CONCRETE: + case PINK_CONCRETE_POWDER: + case PINK_GLAZED_TERRACOTTA: + case PINK_SHULKER_BOX: + case PINK_STAINED_GLASS: + case PINK_STAINED_GLASS_PANE: + case PINK_TERRACOTTA: + case PINK_TULIP: + case PINK_WALL_BANNER: + case PINK_WOOL: + case PISTON: + case PISTON_HEAD: + case PLAYER_HEAD: + case PLAYER_WALL_HEAD: + case PODZOL: + case POLISHED_ANDESITE: + case POLISHED_DIORITE: + case POLISHED_GRANITE: + case POPPY: + case POTATOES: + case POTTED_ACACIA_SAPLING: + case POTTED_ALLIUM: + case POTTED_AZURE_BLUET: + case POTTED_BIRCH_SAPLING: + case POTTED_BLUE_ORCHID: + case POTTED_BROWN_MUSHROOM: + case POTTED_CACTUS: + case POTTED_DANDELION: + case POTTED_DARK_OAK_SAPLING: + case POTTED_DEAD_BUSH: + case POTTED_FERN: + case POTTED_JUNGLE_SAPLING: + case POTTED_OAK_SAPLING: + case POTTED_ORANGE_TULIP: + case POTTED_OXEYE_DAISY: + case POTTED_PINK_TULIP: + case POTTED_POPPY: + case POTTED_RED_MUSHROOM: + case POTTED_RED_TULIP: + case POTTED_SPRUCE_SAPLING: + case POTTED_WHITE_TULIP: + case POWERED_RAIL: + case PRISMARINE: + case PRISMARINE_BRICKS: + case PRISMARINE_BRICK_SLAB: + case PRISMARINE_BRICK_STAIRS: + case PRISMARINE_SLAB: + case PRISMARINE_STAIRS: + case PUMPKIN: + case PUMPKIN_STEM: + case PURPLE_BANNER: + case PURPLE_BED: + case PURPLE_CARPET: + case PURPLE_CONCRETE: + case PURPLE_CONCRETE_POWDER: + case PURPLE_GLAZED_TERRACOTTA: + case PURPLE_SHULKER_BOX: + case PURPLE_STAINED_GLASS: + case PURPLE_STAINED_GLASS_PANE: + case PURPLE_TERRACOTTA: + case PURPLE_WALL_BANNER: + case PURPLE_WOOL: + case PURPUR_BLOCK: + case PURPUR_PILLAR: + case PURPUR_SLAB: + case PURPUR_STAIRS: + case QUARTZ_BLOCK: + case QUARTZ_PILLAR: + case QUARTZ_SLAB: + case QUARTZ_STAIRS: + case RAIL: + case REDSTONE_BLOCK: + case REDSTONE_LAMP: + case REDSTONE_ORE: + case REDSTONE_TORCH: + case REDSTONE_WALL_TORCH: + case REDSTONE_WIRE: + case RED_BANNER: + case RED_BED: + case RED_CARPET: + case RED_CONCRETE: + case RED_CONCRETE_POWDER: + case RED_GLAZED_TERRACOTTA: + case RED_MUSHROOM: + case RED_MUSHROOM_BLOCK: + case RED_NETHER_BRICKS: + case RED_SAND: + case RED_SANDSTONE: + case RED_SANDSTONE_SLAB: + case RED_SANDSTONE_STAIRS: + case RED_SHULKER_BOX: + case RED_STAINED_GLASS: + case RED_STAINED_GLASS_PANE: + case RED_TERRACOTTA: + case RED_TULIP: + case RED_WALL_BANNER: + case RED_WOOL: + case REPEATER: + case REPEATING_COMMAND_BLOCK: + case ROSE_BUSH: + case SAND: + case SANDSTONE: + case SANDSTONE_SLAB: + case SANDSTONE_STAIRS: + case SEAGRASS: + case SEA_LANTERN: + case SEA_PICKLE: + case SHULKER_BOX: + case SIGN: + case SKELETON_SKULL: + case SKELETON_WALL_SKULL: + case SLIME_BLOCK: + case SMOOTH_QUARTZ: + case SMOOTH_RED_SANDSTONE: + case SMOOTH_SANDSTONE: + case SMOOTH_STONE: + case SNOW: + case SNOW_BLOCK: + case SOUL_SAND: + case SPAWNER: + case SPONGE: + case SPRUCE_BUTTON: + case SPRUCE_DOOR: + case SPRUCE_FENCE: + case SPRUCE_FENCE_GATE: + case SPRUCE_LEAVES: + case SPRUCE_LOG: + case SPRUCE_PLANKS: + case SPRUCE_PRESSURE_PLATE: + case SPRUCE_SAPLING: + case SPRUCE_SLAB: + case SPRUCE_STAIRS: + case SPRUCE_TRAPDOOR: + case SPRUCE_WOOD: + case STICKY_PISTON: + case STONE: + case STONE_BRICKS: + case STONE_BRICK_SLAB: + case STONE_BRICK_STAIRS: + case STONE_BUTTON: + case STONE_PRESSURE_PLATE: + case STONE_SLAB: + case STRIPPED_ACACIA_LOG: + case STRIPPED_ACACIA_WOOD: + case STRIPPED_BIRCH_LOG: + case STRIPPED_BIRCH_WOOD: + case STRIPPED_DARK_OAK_LOG: + case STRIPPED_DARK_OAK_WOOD: + case STRIPPED_JUNGLE_LOG: + case STRIPPED_JUNGLE_WOOD: + case STRIPPED_OAK_LOG: + case STRIPPED_OAK_WOOD: + case STRIPPED_SPRUCE_LOG: + case STRIPPED_SPRUCE_WOOD: + case STRUCTURE_BLOCK: + case STRUCTURE_VOID: + case SUGAR_CANE: + case SUNFLOWER: + case TALL_GRASS: + case TALL_SEAGRASS: + case TERRACOTTA: + case TNT: + case TORCH: + case TRAPPED_CHEST: + case TRIPWIRE: + case TRIPWIRE_HOOK: + case TUBE_CORAL: + case TUBE_CORAL_BLOCK: + case TUBE_CORAL_FAN: + case TUBE_CORAL_WALL_FAN: + case TURTLE_EGG: + case VINE: + case VOID_AIR: + case WALL_SIGN: + case WALL_TORCH: + case WATER: + case WET_SPONGE: + case WHEAT: + case WHITE_BANNER: + case WHITE_BED: + case WHITE_CARPET: + case WHITE_CONCRETE: + case WHITE_CONCRETE_POWDER: + case WHITE_GLAZED_TERRACOTTA: + case WHITE_SHULKER_BOX: + case WHITE_STAINED_GLASS: + case WHITE_STAINED_GLASS_PANE: + case WHITE_TERRACOTTA: + case WHITE_TULIP: + case WHITE_WALL_BANNER: + case WHITE_WOOL: + case WITHER_SKELETON_SKULL: + case WITHER_SKELETON_WALL_SKULL: + case YELLOW_BANNER: + case YELLOW_BED: + case YELLOW_CARPET: + case YELLOW_CONCRETE: + case YELLOW_CONCRETE_POWDER: + case YELLOW_GLAZED_TERRACOTTA: + case YELLOW_SHULKER_BOX: + case YELLOW_STAINED_GLASS: + case YELLOW_STAINED_GLASS_PANE: + case YELLOW_TERRACOTTA: + case YELLOW_WALL_BANNER: + case YELLOW_WOOL: + case ZOMBIE_HEAD: + case ZOMBIE_WALL_HEAD: + // + return true; + default: + return 0 <= id && id < 256; + } + } + + /** + * Checks if this Material is edible. + * + * @return true if this Material is edible. + */ + public boolean isEdible() { + switch (this) { + // + case APPLE: + case BAKED_POTATO: + case BEEF: + case BEETROOT: + case BEETROOT_SOUP: + case BREAD: + case CARROT: + case CHICKEN: + case CHORUS_FRUIT: + case COD: + case COOKED_BEEF: + case COOKED_CHICKEN: + case COOKED_COD: + case COOKED_MUTTON: + case COOKED_PORKCHOP: + case COOKED_RABBIT: + case COOKED_SALMON: + case COOKIE: + case DRIED_KELP: + case ENCHANTED_GOLDEN_APPLE: + case GOLDEN_APPLE: + case GOLDEN_CARROT: + case MELON_SLICE: + case MUSHROOM_STEW: + case MUTTON: + case POISONOUS_POTATO: + case PORKCHOP: + case POTATO: + case PUFFERFISH: + case PUMPKIN_PIE: + case RABBIT: + case RABBIT_STEW: + case ROTTEN_FLESH: + case SALMON: + case SPIDER_EYE: + case TROPICAL_FISH: + // ----- Legacy Separator ----- + case LEGACY_BREAD: + case LEGACY_CARROT_ITEM: + case LEGACY_BAKED_POTATO: + case LEGACY_POTATO_ITEM: + case LEGACY_POISONOUS_POTATO: + case LEGACY_GOLDEN_CARROT: + case LEGACY_PUMPKIN_PIE: + case LEGACY_COOKIE: + case LEGACY_MELON: + case LEGACY_MUSHROOM_SOUP: + case LEGACY_RAW_CHICKEN: + case LEGACY_COOKED_CHICKEN: + case LEGACY_RAW_BEEF: + case LEGACY_COOKED_BEEF: + case LEGACY_RAW_FISH: + case LEGACY_COOKED_FISH: + case LEGACY_PORK: + case LEGACY_GRILLED_PORK: + case LEGACY_APPLE: + case LEGACY_GOLDEN_APPLE: + case LEGACY_ROTTEN_FLESH: + case LEGACY_SPIDER_EYE: + case LEGACY_RABBIT: + case LEGACY_COOKED_RABBIT: + case LEGACY_RABBIT_STEW: + case LEGACY_MUTTON: + case LEGACY_COOKED_MUTTON: + case LEGACY_BEETROOT: + case LEGACY_CHORUS_FRUIT: + case LEGACY_BEETROOT_SOUP: + // + return true; + default: + return false; + } + } + + /** + * Attempts to get the Material with the given name. + *

+ * This is a normal lookup, names must be the precise name they are given + * in the enum. + * + * @param name Name of the material to get + * @return Material if found, or null + */ + @Nullable + public static Material getMaterial(@NotNull final String name) { + return getMaterial(name, false); + } + + /** + * Attempts to get the Material with the given name. + *

+ * This is a normal lookup, names must be the precise name they are given + * in the enum. + * + * @param name Name of the material to get + * @param legacyName whether this is a legacy name + * @return Material if found, or null + */ + @Nullable + public static Material getMaterial(@NotNull String name, boolean legacyName) { + if (legacyName) { + if (!name.startsWith(LEGACY_PREFIX)) { + name = LEGACY_PREFIX + name; + } + + Material match = BY_NAME.get(name); + return Bukkit.getUnsafe().fromLegacy(match); + } + + return BY_NAME.get(name); + } + + /** + * Attempts to match the Material with the given name. + *

+ * This is a match lookup; names will be stripped of the "minecraft:" + * namespace, converted to uppercase, then stripped of special characters in + * an attempt to format it like the enum. + * + * @param name Name of the material to get + * @return Material if found, or null + */ + @Nullable + public static Material matchMaterial(@NotNull final String name) { + return matchMaterial(name, false); + } + + /** + * Attempts to match the Material with the given name. + *

+ * This is a match lookup; names will be stripped of the "minecraft:" + * namespace, converted to uppercase, then stripped of special characters in + * an attempt to format it like the enum. + * + * @param name Name of the material to get + * @param legacyName whether this is a legacy name + * @return Material if found, or null + */ + @Nullable + public static Material matchMaterial(@NotNull final String name, boolean legacyName) { + Validate.notNull(name, "Name cannot be null"); + + String filtered = name; + if (filtered.startsWith(NamespacedKey.MINECRAFT + ":")) { + filtered = filtered.substring((NamespacedKey.MINECRAFT + ":").length()); + } + + filtered = filtered.toUpperCase(java.util.Locale.ENGLISH); + + filtered = filtered.replaceAll("\\s+", "_").replaceAll("\\W", ""); + return getMaterial(filtered, legacyName); + } + + static { + for (Material material : values()) { + BY_NAME.put(material.name(), material); + } + } + + /** + * @return True if this material represents a playable music disk. + */ + public boolean isRecord() { + switch (this) { + // + case MUSIC_DISC_11: + case MUSIC_DISC_13: + case MUSIC_DISC_BLOCKS: + case MUSIC_DISC_CAT: + case MUSIC_DISC_CHIRP: + case MUSIC_DISC_FAR: + case MUSIC_DISC_MALL: + case MUSIC_DISC_MELLOHI: + case MUSIC_DISC_STAL: + case MUSIC_DISC_STRAD: + case MUSIC_DISC_WAIT: + case MUSIC_DISC_WARD: + // + return true; + default: + return id >= LEGACY_GOLD_RECORD.id && id <= LEGACY_RECORD_12.id; + } + } + + /** + * Check if the material is a block and solid (can be built upon) + * + * @return True if this material is a block and solid + */ + public boolean isSolid() { + if (!isBlock() || id == 0) { + return false; + } + switch (this) { + // + case ACACIA_DOOR: + case ACACIA_FENCE: + case ACACIA_FENCE_GATE: + case ACACIA_LEAVES: + case ACACIA_LOG: + case ACACIA_PLANKS: + case ACACIA_PRESSURE_PLATE: + case ACACIA_SLAB: + case ACACIA_STAIRS: + case ACACIA_TRAPDOOR: + case ACACIA_WOOD: + case ANDESITE: + case ANVIL: + case BARRIER: + case BEACON: + case BEDROCK: + case BIRCH_DOOR: + case BIRCH_FENCE: + case BIRCH_FENCE_GATE: + case BIRCH_LEAVES: + case BIRCH_LOG: + case BIRCH_PLANKS: + case BIRCH_PRESSURE_PLATE: + case BIRCH_SLAB: + case BIRCH_STAIRS: + case BIRCH_TRAPDOOR: + case BIRCH_WOOD: + case BLACK_BANNER: + case BLACK_BED: + case BLACK_CONCRETE: + case BLACK_CONCRETE_POWDER: + case BLACK_GLAZED_TERRACOTTA: + case BLACK_SHULKER_BOX: + case BLACK_STAINED_GLASS: + case BLACK_STAINED_GLASS_PANE: + case BLACK_TERRACOTTA: + case BLACK_WALL_BANNER: + case BLACK_WOOL: + case BLUE_BANNER: + case BLUE_BED: + case BLUE_CONCRETE: + case BLUE_CONCRETE_POWDER: + case BLUE_GLAZED_TERRACOTTA: + case BLUE_ICE: + case BLUE_SHULKER_BOX: + case BLUE_STAINED_GLASS: + case BLUE_STAINED_GLASS_PANE: + case BLUE_TERRACOTTA: + case BLUE_WALL_BANNER: + case BLUE_WOOL: + case BONE_BLOCK: + case BOOKSHELF: + case BRAIN_CORAL_BLOCK: + case BREWING_STAND: + case BRICKS: + case BRICK_SLAB: + case BRICK_STAIRS: + case BROWN_BANNER: + case BROWN_BED: + case BROWN_CONCRETE: + case BROWN_CONCRETE_POWDER: + case BROWN_GLAZED_TERRACOTTA: + case BROWN_MUSHROOM_BLOCK: + case BROWN_SHULKER_BOX: + case BROWN_STAINED_GLASS: + case BROWN_STAINED_GLASS_PANE: + case BROWN_TERRACOTTA: + case BROWN_WALL_BANNER: + case BROWN_WOOL: + case BUBBLE_CORAL_BLOCK: + case CACTUS: + case CAKE: + case CARVED_PUMPKIN: + case CAULDRON: + case CHAIN_COMMAND_BLOCK: + case CHEST: + case CHIPPED_ANVIL: + case CHISELED_QUARTZ_BLOCK: + case CHISELED_RED_SANDSTONE: + case CHISELED_SANDSTONE: + case CHISELED_STONE_BRICKS: + case CLAY: + case COAL_BLOCK: + case COAL_ORE: + case COARSE_DIRT: + case COBBLESTONE: + case COBBLESTONE_SLAB: + case COBBLESTONE_STAIRS: + case COBBLESTONE_WALL: + case COMMAND_BLOCK: + case CONDUIT: + case CRACKED_STONE_BRICKS: + case CRAFTING_TABLE: + case CUT_RED_SANDSTONE: + case CUT_SANDSTONE: + case CYAN_BANNER: + case CYAN_BED: + case CYAN_CONCRETE: + case CYAN_CONCRETE_POWDER: + case CYAN_GLAZED_TERRACOTTA: + case CYAN_SHULKER_BOX: + case CYAN_STAINED_GLASS: + case CYAN_STAINED_GLASS_PANE: + case CYAN_TERRACOTTA: + case CYAN_WALL_BANNER: + case CYAN_WOOL: + case DAMAGED_ANVIL: + case DARK_OAK_DOOR: + case DARK_OAK_FENCE: + case DARK_OAK_FENCE_GATE: + case DARK_OAK_LEAVES: + case DARK_OAK_LOG: + case DARK_OAK_PLANKS: + case DARK_OAK_PRESSURE_PLATE: + case DARK_OAK_SLAB: + case DARK_OAK_STAIRS: + case DARK_OAK_TRAPDOOR: + case DARK_OAK_WOOD: + case DARK_PRISMARINE: + case DARK_PRISMARINE_SLAB: + case DARK_PRISMARINE_STAIRS: + case DAYLIGHT_DETECTOR: + case DEAD_BRAIN_CORAL: + case DEAD_BRAIN_CORAL_BLOCK: + case DEAD_BRAIN_CORAL_FAN: + case DEAD_BRAIN_CORAL_WALL_FAN: + case DEAD_BUBBLE_CORAL: + case DEAD_BUBBLE_CORAL_BLOCK: + case DEAD_BUBBLE_CORAL_FAN: + case DEAD_BUBBLE_CORAL_WALL_FAN: + case DEAD_FIRE_CORAL: + case DEAD_FIRE_CORAL_BLOCK: + case DEAD_FIRE_CORAL_FAN: + case DEAD_FIRE_CORAL_WALL_FAN: + case DEAD_HORN_CORAL: + case DEAD_HORN_CORAL_BLOCK: + case DEAD_HORN_CORAL_FAN: + case DEAD_HORN_CORAL_WALL_FAN: + case DEAD_TUBE_CORAL: + case DEAD_TUBE_CORAL_BLOCK: + case DEAD_TUBE_CORAL_FAN: + case DEAD_TUBE_CORAL_WALL_FAN: + case DIAMOND_BLOCK: + case DIAMOND_ORE: + case DIORITE: + case DIRT: + case DISPENSER: + case DRAGON_EGG: + case DRIED_KELP_BLOCK: + case DROPPER: + case EMERALD_BLOCK: + case EMERALD_ORE: + case ENCHANTING_TABLE: + case ENDER_CHEST: + case END_PORTAL_FRAME: + case END_STONE: + case END_STONE_BRICKS: + case FARMLAND: + case FIRE_CORAL_BLOCK: + case FROSTED_ICE: + case FURNACE: + case GLASS: + case GLASS_PANE: + case GLOWSTONE: + case GOLD_BLOCK: + case GOLD_ORE: + case GRANITE: + case GRASS_BLOCK: + case GRASS_PATH: + case GRAVEL: + case GRAY_BANNER: + case GRAY_BED: + case GRAY_CONCRETE: + case GRAY_CONCRETE_POWDER: + case GRAY_GLAZED_TERRACOTTA: + case GRAY_SHULKER_BOX: + case GRAY_STAINED_GLASS: + case GRAY_STAINED_GLASS_PANE: + case GRAY_TERRACOTTA: + case GRAY_WALL_BANNER: + case GRAY_WOOL: + case GREEN_BANNER: + case GREEN_BED: + case GREEN_CONCRETE: + case GREEN_CONCRETE_POWDER: + case GREEN_GLAZED_TERRACOTTA: + case GREEN_SHULKER_BOX: + case GREEN_STAINED_GLASS: + case GREEN_STAINED_GLASS_PANE: + case GREEN_TERRACOTTA: + case GREEN_WALL_BANNER: + case GREEN_WOOL: + case HAY_BLOCK: + case HEAVY_WEIGHTED_PRESSURE_PLATE: + case HOPPER: + case HORN_CORAL_BLOCK: + case ICE: + case INFESTED_CHISELED_STONE_BRICKS: + case INFESTED_COBBLESTONE: + case INFESTED_CRACKED_STONE_BRICKS: + case INFESTED_MOSSY_STONE_BRICKS: + case INFESTED_STONE: + case INFESTED_STONE_BRICKS: + case IRON_BARS: + case IRON_BLOCK: + case IRON_DOOR: + case IRON_ORE: + case IRON_TRAPDOOR: + case JACK_O_LANTERN: + case JUKEBOX: + case JUNGLE_DOOR: + case JUNGLE_FENCE: + case JUNGLE_FENCE_GATE: + case JUNGLE_LEAVES: + case JUNGLE_LOG: + case JUNGLE_PLANKS: + case JUNGLE_PRESSURE_PLATE: + case JUNGLE_SLAB: + case JUNGLE_STAIRS: + case JUNGLE_TRAPDOOR: + case JUNGLE_WOOD: + case LAPIS_BLOCK: + case LAPIS_ORE: + case LIGHT_BLUE_BANNER: + case LIGHT_BLUE_BED: + case LIGHT_BLUE_CONCRETE: + case LIGHT_BLUE_CONCRETE_POWDER: + case LIGHT_BLUE_GLAZED_TERRACOTTA: + case LIGHT_BLUE_SHULKER_BOX: + case LIGHT_BLUE_STAINED_GLASS: + case LIGHT_BLUE_STAINED_GLASS_PANE: + case LIGHT_BLUE_TERRACOTTA: + case LIGHT_BLUE_WALL_BANNER: + case LIGHT_BLUE_WOOL: + case LIGHT_GRAY_BANNER: + case LIGHT_GRAY_BED: + case LIGHT_GRAY_CONCRETE: + case LIGHT_GRAY_CONCRETE_POWDER: + case LIGHT_GRAY_GLAZED_TERRACOTTA: + case LIGHT_GRAY_SHULKER_BOX: + case LIGHT_GRAY_STAINED_GLASS: + case LIGHT_GRAY_STAINED_GLASS_PANE: + case LIGHT_GRAY_TERRACOTTA: + case LIGHT_GRAY_WALL_BANNER: + case LIGHT_GRAY_WOOL: + case LIGHT_WEIGHTED_PRESSURE_PLATE: + case LIME_BANNER: + case LIME_BED: + case LIME_CONCRETE: + case LIME_CONCRETE_POWDER: + case LIME_GLAZED_TERRACOTTA: + case LIME_SHULKER_BOX: + case LIME_STAINED_GLASS: + case LIME_STAINED_GLASS_PANE: + case LIME_TERRACOTTA: + case LIME_WALL_BANNER: + case LIME_WOOL: + case MAGENTA_BANNER: + case MAGENTA_BED: + case MAGENTA_CONCRETE: + case MAGENTA_CONCRETE_POWDER: + case MAGENTA_GLAZED_TERRACOTTA: + case MAGENTA_SHULKER_BOX: + case MAGENTA_STAINED_GLASS: + case MAGENTA_STAINED_GLASS_PANE: + case MAGENTA_TERRACOTTA: + case MAGENTA_WALL_BANNER: + case MAGENTA_WOOL: + case MAGMA_BLOCK: + case MELON: + case MOSSY_COBBLESTONE: + case MOSSY_COBBLESTONE_WALL: + case MOSSY_STONE_BRICKS: + case MOVING_PISTON: + case MUSHROOM_STEM: + case MYCELIUM: + case NETHERRACK: + case NETHER_BRICKS: + case NETHER_BRICK_FENCE: + case NETHER_BRICK_SLAB: + case NETHER_BRICK_STAIRS: + case NETHER_QUARTZ_ORE: + case NETHER_WART_BLOCK: + case NOTE_BLOCK: + case OAK_DOOR: + case OAK_FENCE: + case OAK_FENCE_GATE: + case OAK_LEAVES: + case OAK_LOG: + case OAK_PLANKS: + case OAK_PRESSURE_PLATE: + case OAK_SLAB: + case OAK_STAIRS: + case OAK_TRAPDOOR: + case OAK_WOOD: + case OBSERVER: + case OBSIDIAN: + case ORANGE_BANNER: + case ORANGE_BED: + case ORANGE_CONCRETE: + case ORANGE_CONCRETE_POWDER: + case ORANGE_GLAZED_TERRACOTTA: + case ORANGE_SHULKER_BOX: + case ORANGE_STAINED_GLASS: + case ORANGE_STAINED_GLASS_PANE: + case ORANGE_TERRACOTTA: + case ORANGE_WALL_BANNER: + case ORANGE_WOOL: + case PACKED_ICE: + case PETRIFIED_OAK_SLAB: + case PINK_BANNER: + case PINK_BED: + case PINK_CONCRETE: + case PINK_CONCRETE_POWDER: + case PINK_GLAZED_TERRACOTTA: + case PINK_SHULKER_BOX: + case PINK_STAINED_GLASS: + case PINK_STAINED_GLASS_PANE: + case PINK_TERRACOTTA: + case PINK_WALL_BANNER: + case PINK_WOOL: + case PISTON: + case PISTON_HEAD: + case PODZOL: + case POLISHED_ANDESITE: + case POLISHED_DIORITE: + case POLISHED_GRANITE: + case PRISMARINE: + case PRISMARINE_BRICKS: + case PRISMARINE_BRICK_SLAB: + case PRISMARINE_BRICK_STAIRS: + case PRISMARINE_SLAB: + case PRISMARINE_STAIRS: + case PUMPKIN: + case PURPLE_BANNER: + case PURPLE_BED: + case PURPLE_CONCRETE: + case PURPLE_CONCRETE_POWDER: + case PURPLE_GLAZED_TERRACOTTA: + case PURPLE_SHULKER_BOX: + case PURPLE_STAINED_GLASS: + case PURPLE_STAINED_GLASS_PANE: + case PURPLE_TERRACOTTA: + case PURPLE_WALL_BANNER: + case PURPLE_WOOL: + case PURPUR_BLOCK: + case PURPUR_PILLAR: + case PURPUR_SLAB: + case PURPUR_STAIRS: + case QUARTZ_BLOCK: + case QUARTZ_PILLAR: + case QUARTZ_SLAB: + case QUARTZ_STAIRS: + case REDSTONE_BLOCK: + case REDSTONE_LAMP: + case REDSTONE_ORE: + case RED_BANNER: + case RED_BED: + case RED_CONCRETE: + case RED_CONCRETE_POWDER: + case RED_GLAZED_TERRACOTTA: + case RED_MUSHROOM_BLOCK: + case RED_NETHER_BRICKS: + case RED_SAND: + case RED_SANDSTONE: + case RED_SANDSTONE_SLAB: + case RED_SANDSTONE_STAIRS: + case RED_SHULKER_BOX: + case RED_STAINED_GLASS: + case RED_STAINED_GLASS_PANE: + case RED_TERRACOTTA: + case RED_WALL_BANNER: + case RED_WOOL: + case REPEATING_COMMAND_BLOCK: + case SAND: + case SANDSTONE: + case SANDSTONE_SLAB: + case SANDSTONE_STAIRS: + case SEA_LANTERN: + case SHULKER_BOX: + case SIGN: + case SLIME_BLOCK: + case SMOOTH_QUARTZ: + case SMOOTH_RED_SANDSTONE: + case SMOOTH_SANDSTONE: + case SMOOTH_STONE: + case SNOW_BLOCK: + case SOUL_SAND: + case SPAWNER: + case SPONGE: + case SPRUCE_DOOR: + case SPRUCE_FENCE: + case SPRUCE_FENCE_GATE: + case SPRUCE_LEAVES: + case SPRUCE_LOG: + case SPRUCE_PLANKS: + case SPRUCE_PRESSURE_PLATE: + case SPRUCE_SLAB: + case SPRUCE_STAIRS: + case SPRUCE_TRAPDOOR: + case SPRUCE_WOOD: + case STICKY_PISTON: + case STONE: + case STONE_BRICKS: + case STONE_BRICK_SLAB: + case STONE_BRICK_STAIRS: + case STONE_PRESSURE_PLATE: + case STONE_SLAB: + case STRIPPED_ACACIA_LOG: + case STRIPPED_ACACIA_WOOD: + case STRIPPED_BIRCH_LOG: + case STRIPPED_BIRCH_WOOD: + case STRIPPED_DARK_OAK_LOG: + case STRIPPED_DARK_OAK_WOOD: + case STRIPPED_JUNGLE_LOG: + case STRIPPED_JUNGLE_WOOD: + case STRIPPED_OAK_LOG: + case STRIPPED_OAK_WOOD: + case STRIPPED_SPRUCE_LOG: + case STRIPPED_SPRUCE_WOOD: + case STRUCTURE_BLOCK: + case TERRACOTTA: + case TNT: + case TRAPPED_CHEST: + case TUBE_CORAL_BLOCK: + case TURTLE_EGG: + case WALL_SIGN: + case WET_SPONGE: + case WHITE_BANNER: + case WHITE_BED: + case WHITE_CONCRETE: + case WHITE_CONCRETE_POWDER: + case WHITE_GLAZED_TERRACOTTA: + case WHITE_SHULKER_BOX: + case WHITE_STAINED_GLASS: + case WHITE_STAINED_GLASS_PANE: + case WHITE_TERRACOTTA: + case WHITE_WALL_BANNER: + case WHITE_WOOL: + case YELLOW_BANNER: + case YELLOW_BED: + case YELLOW_CONCRETE: + case YELLOW_CONCRETE_POWDER: + case YELLOW_GLAZED_TERRACOTTA: + case YELLOW_SHULKER_BOX: + case YELLOW_STAINED_GLASS: + case YELLOW_STAINED_GLASS_PANE: + case YELLOW_TERRACOTTA: + case YELLOW_WALL_BANNER: + case YELLOW_WOOL: + // ----- Legacy Separator ----- + case LEGACY_STONE: + case LEGACY_GRASS: + case LEGACY_DIRT: + case LEGACY_COBBLESTONE: + case LEGACY_WOOD: + case LEGACY_BEDROCK: + case LEGACY_SAND: + case LEGACY_GRAVEL: + case LEGACY_GOLD_ORE: + case LEGACY_IRON_ORE: + case LEGACY_COAL_ORE: + case LEGACY_LOG: + case LEGACY_LEAVES: + case LEGACY_SPONGE: + case LEGACY_GLASS: + case LEGACY_LAPIS_ORE: + case LEGACY_LAPIS_BLOCK: + case LEGACY_DISPENSER: + case LEGACY_SANDSTONE: + case LEGACY_NOTE_BLOCK: + case LEGACY_BED_BLOCK: + case LEGACY_PISTON_STICKY_BASE: + case LEGACY_PISTON_BASE: + case LEGACY_PISTON_EXTENSION: + case LEGACY_WOOL: + case LEGACY_PISTON_MOVING_PIECE: + case LEGACY_GOLD_BLOCK: + case LEGACY_IRON_BLOCK: + case LEGACY_DOUBLE_STEP: + case LEGACY_STEP: + case LEGACY_BRICK: + case LEGACY_TNT: + case LEGACY_BOOKSHELF: + case LEGACY_MOSSY_COBBLESTONE: + case LEGACY_OBSIDIAN: + case LEGACY_MOB_SPAWNER: + case LEGACY_WOOD_STAIRS: + case LEGACY_CHEST: + case LEGACY_DIAMOND_ORE: + case LEGACY_DIAMOND_BLOCK: + case LEGACY_WORKBENCH: + case LEGACY_SOIL: + case LEGACY_FURNACE: + case LEGACY_BURNING_FURNACE: + case LEGACY_SIGN_POST: + case LEGACY_WOODEN_DOOR: + case LEGACY_COBBLESTONE_STAIRS: + case LEGACY_WALL_SIGN: + case LEGACY_STONE_PLATE: + case LEGACY_IRON_DOOR_BLOCK: + case LEGACY_WOOD_PLATE: + case LEGACY_REDSTONE_ORE: + case LEGACY_GLOWING_REDSTONE_ORE: + case LEGACY_ICE: + case LEGACY_SNOW_BLOCK: + case LEGACY_CACTUS: + case LEGACY_CLAY: + case LEGACY_JUKEBOX: + case LEGACY_FENCE: + case LEGACY_PUMPKIN: + case LEGACY_NETHERRACK: + case LEGACY_SOUL_SAND: + case LEGACY_GLOWSTONE: + case LEGACY_JACK_O_LANTERN: + case LEGACY_CAKE_BLOCK: + case LEGACY_STAINED_GLASS: + case LEGACY_TRAP_DOOR: + case LEGACY_MONSTER_EGGS: + case LEGACY_SMOOTH_BRICK: + case LEGACY_HUGE_MUSHROOM_1: + case LEGACY_HUGE_MUSHROOM_2: + case LEGACY_IRON_FENCE: + case LEGACY_THIN_GLASS: + case LEGACY_MELON_BLOCK: + case LEGACY_FENCE_GATE: + case LEGACY_BRICK_STAIRS: + case LEGACY_SMOOTH_STAIRS: + case LEGACY_MYCEL: + case LEGACY_NETHER_BRICK: + case LEGACY_NETHER_FENCE: + case LEGACY_NETHER_BRICK_STAIRS: + case LEGACY_ENCHANTMENT_TABLE: + case LEGACY_BREWING_STAND: + case LEGACY_CAULDRON: + case LEGACY_ENDER_PORTAL_FRAME: + case LEGACY_ENDER_STONE: + case LEGACY_DRAGON_EGG: + case LEGACY_REDSTONE_LAMP_OFF: + case LEGACY_REDSTONE_LAMP_ON: + case LEGACY_WOOD_DOUBLE_STEP: + case LEGACY_WOOD_STEP: + case LEGACY_SANDSTONE_STAIRS: + case LEGACY_EMERALD_ORE: + case LEGACY_ENDER_CHEST: + case LEGACY_EMERALD_BLOCK: + case LEGACY_SPRUCE_WOOD_STAIRS: + case LEGACY_BIRCH_WOOD_STAIRS: + case LEGACY_JUNGLE_WOOD_STAIRS: + case LEGACY_COMMAND: + case LEGACY_BEACON: + case LEGACY_COBBLE_WALL: + case LEGACY_ANVIL: + case LEGACY_TRAPPED_CHEST: + case LEGACY_GOLD_PLATE: + case LEGACY_IRON_PLATE: + case LEGACY_DAYLIGHT_DETECTOR: + case LEGACY_REDSTONE_BLOCK: + case LEGACY_QUARTZ_ORE: + case LEGACY_HOPPER: + case LEGACY_QUARTZ_BLOCK: + case LEGACY_QUARTZ_STAIRS: + case LEGACY_DROPPER: + case LEGACY_STAINED_CLAY: + case LEGACY_HAY_BLOCK: + case LEGACY_HARD_CLAY: + case LEGACY_COAL_BLOCK: + case LEGACY_STAINED_GLASS_PANE: + case LEGACY_LEAVES_2: + case LEGACY_LOG_2: + case LEGACY_ACACIA_STAIRS: + case LEGACY_DARK_OAK_STAIRS: + case LEGACY_PACKED_ICE: + case LEGACY_RED_SANDSTONE: + case LEGACY_SLIME_BLOCK: + case LEGACY_BARRIER: + case LEGACY_IRON_TRAPDOOR: + case LEGACY_PRISMARINE: + case LEGACY_SEA_LANTERN: + case LEGACY_DOUBLE_STONE_SLAB2: + case LEGACY_RED_SANDSTONE_STAIRS: + case LEGACY_STONE_SLAB2: + case LEGACY_SPRUCE_FENCE_GATE: + case LEGACY_BIRCH_FENCE_GATE: + case LEGACY_JUNGLE_FENCE_GATE: + case LEGACY_DARK_OAK_FENCE_GATE: + case LEGACY_ACACIA_FENCE_GATE: + case LEGACY_SPRUCE_FENCE: + case LEGACY_BIRCH_FENCE: + case LEGACY_JUNGLE_FENCE: + case LEGACY_DARK_OAK_FENCE: + case LEGACY_ACACIA_FENCE: + case LEGACY_STANDING_BANNER: + case LEGACY_WALL_BANNER: + case LEGACY_DAYLIGHT_DETECTOR_INVERTED: + case LEGACY_SPRUCE_DOOR: + case LEGACY_BIRCH_DOOR: + case LEGACY_JUNGLE_DOOR: + case LEGACY_ACACIA_DOOR: + case LEGACY_DARK_OAK_DOOR: + case LEGACY_PURPUR_BLOCK: + case LEGACY_PURPUR_PILLAR: + case LEGACY_PURPUR_STAIRS: + case LEGACY_PURPUR_DOUBLE_SLAB: + case LEGACY_PURPUR_SLAB: + case LEGACY_END_BRICKS: + case LEGACY_GRASS_PATH: + case LEGACY_STRUCTURE_BLOCK: + case LEGACY_COMMAND_REPEATING: + case LEGACY_COMMAND_CHAIN: + case LEGACY_FROSTED_ICE: + case LEGACY_MAGMA: + case LEGACY_NETHER_WART_BLOCK: + case LEGACY_RED_NETHER_BRICK: + case LEGACY_BONE_BLOCK: + case LEGACY_OBSERVER: + case LEGACY_WHITE_SHULKER_BOX: + case LEGACY_ORANGE_SHULKER_BOX: + case LEGACY_MAGENTA_SHULKER_BOX: + case LEGACY_LIGHT_BLUE_SHULKER_BOX: + case LEGACY_YELLOW_SHULKER_BOX: + case LEGACY_LIME_SHULKER_BOX: + case LEGACY_PINK_SHULKER_BOX: + case LEGACY_GRAY_SHULKER_BOX: + case LEGACY_SILVER_SHULKER_BOX: + case LEGACY_CYAN_SHULKER_BOX: + case LEGACY_PURPLE_SHULKER_BOX: + case LEGACY_BLUE_SHULKER_BOX: + case LEGACY_BROWN_SHULKER_BOX: + case LEGACY_GREEN_SHULKER_BOX: + case LEGACY_RED_SHULKER_BOX: + case LEGACY_BLACK_SHULKER_BOX: + case LEGACY_WHITE_GLAZED_TERRACOTTA: + case LEGACY_ORANGE_GLAZED_TERRACOTTA: + case LEGACY_MAGENTA_GLAZED_TERRACOTTA: + case LEGACY_LIGHT_BLUE_GLAZED_TERRACOTTA: + case LEGACY_YELLOW_GLAZED_TERRACOTTA: + case LEGACY_LIME_GLAZED_TERRACOTTA: + case LEGACY_PINK_GLAZED_TERRACOTTA: + case LEGACY_GRAY_GLAZED_TERRACOTTA: + case LEGACY_SILVER_GLAZED_TERRACOTTA: + case LEGACY_CYAN_GLAZED_TERRACOTTA: + case LEGACY_PURPLE_GLAZED_TERRACOTTA: + case LEGACY_BLUE_GLAZED_TERRACOTTA: + case LEGACY_BROWN_GLAZED_TERRACOTTA: + case LEGACY_GREEN_GLAZED_TERRACOTTA: + case LEGACY_RED_GLAZED_TERRACOTTA: + case LEGACY_BLACK_GLAZED_TERRACOTTA: + case LEGACY_CONCRETE: + case LEGACY_CONCRETE_POWDER: + // + return true; + default: + return false; + } + } + + /** + * Check if the material is a block and does not block any light + * + * @return True if this material is a block and does not block any light + * @deprecated currently does not have an implementation which is well + * linked to the underlying server. Contributions welcome. + */ + @Deprecated + public boolean isTransparent() { + if (!isBlock()) { + return false; + } + switch (this) { + // + case ACACIA_BUTTON: + case ACACIA_SAPLING: + case ACTIVATOR_RAIL: + case AIR: + case ALLIUM: + case ATTACHED_MELON_STEM: + case ATTACHED_PUMPKIN_STEM: + case AZURE_BLUET: + case BARRIER: + case BEETROOTS: + case BIRCH_BUTTON: + case BIRCH_SAPLING: + case BLACK_CARPET: + case BLUE_CARPET: + case BLUE_ORCHID: + case BROWN_CARPET: + case BROWN_MUSHROOM: + case CARROTS: + case CAVE_AIR: + case CHORUS_FLOWER: + case CHORUS_PLANT: + case COCOA: + case COMPARATOR: + case CREEPER_HEAD: + case CREEPER_WALL_HEAD: + case CYAN_CARPET: + case DANDELION: + case DARK_OAK_BUTTON: + case DARK_OAK_SAPLING: + case DEAD_BUSH: + case DETECTOR_RAIL: + case DRAGON_HEAD: + case DRAGON_WALL_HEAD: + case END_GATEWAY: + case END_PORTAL: + case END_ROD: + case FERN: + case FIRE: + case FLOWER_POT: + case GRASS: + case GRAY_CARPET: + case GREEN_CARPET: + case JUNGLE_BUTTON: + case JUNGLE_SAPLING: + case LADDER: + case LARGE_FERN: + case LEVER: + case LIGHT_BLUE_CARPET: + case LIGHT_GRAY_CARPET: + case LILAC: + case LILY_PAD: + case LIME_CARPET: + case MAGENTA_CARPET: + case MELON_STEM: + case NETHER_PORTAL: + case NETHER_WART: + case OAK_BUTTON: + case OAK_SAPLING: + case ORANGE_CARPET: + case ORANGE_TULIP: + case OXEYE_DAISY: + case PEONY: + case PINK_CARPET: + case PINK_TULIP: + case PLAYER_HEAD: + case PLAYER_WALL_HEAD: + case POPPY: + case POTATOES: + case POTTED_ACACIA_SAPLING: + case POTTED_ALLIUM: + case POTTED_AZURE_BLUET: + case POTTED_BIRCH_SAPLING: + case POTTED_BLUE_ORCHID: + case POTTED_BROWN_MUSHROOM: + case POTTED_CACTUS: + case POTTED_DANDELION: + case POTTED_DARK_OAK_SAPLING: + case POTTED_DEAD_BUSH: + case POTTED_FERN: + case POTTED_JUNGLE_SAPLING: + case POTTED_OAK_SAPLING: + case POTTED_ORANGE_TULIP: + case POTTED_OXEYE_DAISY: + case POTTED_PINK_TULIP: + case POTTED_POPPY: + case POTTED_RED_MUSHROOM: + case POTTED_RED_TULIP: + case POTTED_SPRUCE_SAPLING: + case POTTED_WHITE_TULIP: + case POWERED_RAIL: + case PUMPKIN_STEM: + case PURPLE_CARPET: + case RAIL: + case REDSTONE_TORCH: + case REDSTONE_WALL_TORCH: + case REDSTONE_WIRE: + case RED_CARPET: + case RED_MUSHROOM: + case RED_TULIP: + case REPEATER: + case ROSE_BUSH: + case SKELETON_SKULL: + case SKELETON_WALL_SKULL: + case SNOW: + case SPRUCE_BUTTON: + case SPRUCE_SAPLING: + case STONE_BUTTON: + case STRUCTURE_VOID: + case SUGAR_CANE: + case SUNFLOWER: + case TALL_GRASS: + case TORCH: + case TRIPWIRE: + case TRIPWIRE_HOOK: + case VINE: + case VOID_AIR: + case WALL_TORCH: + case WHEAT: + case WHITE_CARPET: + case WHITE_TULIP: + case WITHER_SKELETON_SKULL: + case WITHER_SKELETON_WALL_SKULL: + case YELLOW_CARPET: + case ZOMBIE_HEAD: + case ZOMBIE_WALL_HEAD: + // ----- Legacy Separator ----- + case LEGACY_AIR: + case LEGACY_SAPLING: + case LEGACY_POWERED_RAIL: + case LEGACY_DETECTOR_RAIL: + case LEGACY_LONG_GRASS: + case LEGACY_DEAD_BUSH: + case LEGACY_YELLOW_FLOWER: + case LEGACY_RED_ROSE: + case LEGACY_BROWN_MUSHROOM: + case LEGACY_RED_MUSHROOM: + case LEGACY_TORCH: + case LEGACY_FIRE: + case LEGACY_REDSTONE_WIRE: + case LEGACY_CROPS: + case LEGACY_LADDER: + case LEGACY_RAILS: + case LEGACY_LEVER: + case LEGACY_REDSTONE_TORCH_OFF: + case LEGACY_REDSTONE_TORCH_ON: + case LEGACY_STONE_BUTTON: + case LEGACY_SNOW: + case LEGACY_SUGAR_CANE_BLOCK: + case LEGACY_PORTAL: + case LEGACY_DIODE_BLOCK_OFF: + case LEGACY_DIODE_BLOCK_ON: + case LEGACY_PUMPKIN_STEM: + case LEGACY_MELON_STEM: + case LEGACY_VINE: + case LEGACY_WATER_LILY: + case LEGACY_NETHER_WARTS: + case LEGACY_ENDER_PORTAL: + case LEGACY_COCOA: + case LEGACY_TRIPWIRE_HOOK: + case LEGACY_TRIPWIRE: + case LEGACY_FLOWER_POT: + case LEGACY_CARROT: + case LEGACY_POTATO: + case LEGACY_WOOD_BUTTON: + case LEGACY_SKULL: + case LEGACY_REDSTONE_COMPARATOR_OFF: + case LEGACY_REDSTONE_COMPARATOR_ON: + case LEGACY_ACTIVATOR_RAIL: + case LEGACY_CARPET: + case LEGACY_DOUBLE_PLANT: + case LEGACY_END_ROD: + case LEGACY_CHORUS_PLANT: + case LEGACY_CHORUS_FLOWER: + case LEGACY_BEETROOT_BLOCK: + case LEGACY_END_GATEWAY: + case LEGACY_STRUCTURE_VOID: + // + return true; + default: + return false; + } + } + + /** + * Check if the material is a block and can catch fire + * + * @return True if this material is a block and can catch fire + */ + public boolean isFlammable() { + if (!isBlock()) { + return false; + } + switch (this) { + // + case ACACIA_DOOR: + case ACACIA_FENCE: + case ACACIA_FENCE_GATE: + case ACACIA_LEAVES: + case ACACIA_LOG: + case ACACIA_PLANKS: + case ACACIA_PRESSURE_PLATE: + case ACACIA_SLAB: + case ACACIA_STAIRS: + case ACACIA_TRAPDOOR: + case ACACIA_WOOD: + case BIRCH_DOOR: + case BIRCH_FENCE: + case BIRCH_FENCE_GATE: + case BIRCH_LEAVES: + case BIRCH_LOG: + case BIRCH_PLANKS: + case BIRCH_PRESSURE_PLATE: + case BIRCH_SLAB: + case BIRCH_STAIRS: + case BIRCH_TRAPDOOR: + case BIRCH_WOOD: + case BLACK_BANNER: + case BLACK_BED: + case BLACK_CARPET: + case BLACK_WALL_BANNER: + case BLACK_WOOL: + case BLUE_BANNER: + case BLUE_BED: + case BLUE_CARPET: + case BLUE_WALL_BANNER: + case BLUE_WOOL: + case BOOKSHELF: + case BROWN_BANNER: + case BROWN_BED: + case BROWN_CARPET: + case BROWN_MUSHROOM_BLOCK: + case BROWN_WALL_BANNER: + case BROWN_WOOL: + case CHEST: + case CRAFTING_TABLE: + case CYAN_BANNER: + case CYAN_BED: + case CYAN_CARPET: + case CYAN_WALL_BANNER: + case CYAN_WOOL: + case DARK_OAK_DOOR: + case DARK_OAK_FENCE: + case DARK_OAK_FENCE_GATE: + case DARK_OAK_LEAVES: + case DARK_OAK_LOG: + case DARK_OAK_PLANKS: + case DARK_OAK_PRESSURE_PLATE: + case DARK_OAK_SLAB: + case DARK_OAK_STAIRS: + case DARK_OAK_TRAPDOOR: + case DARK_OAK_WOOD: + case DAYLIGHT_DETECTOR: + case DEAD_BUSH: + case FERN: + case GRASS: + case GRAY_BANNER: + case GRAY_BED: + case GRAY_CARPET: + case GRAY_WALL_BANNER: + case GRAY_WOOL: + case GREEN_BANNER: + case GREEN_BED: + case GREEN_CARPET: + case GREEN_WALL_BANNER: + case GREEN_WOOL: + case JUKEBOX: + case JUNGLE_DOOR: + case JUNGLE_FENCE: + case JUNGLE_FENCE_GATE: + case JUNGLE_LEAVES: + case JUNGLE_LOG: + case JUNGLE_PLANKS: + case JUNGLE_PRESSURE_PLATE: + case JUNGLE_SLAB: + case JUNGLE_STAIRS: + case JUNGLE_TRAPDOOR: + case JUNGLE_WOOD: + case LARGE_FERN: + case LIGHT_BLUE_BANNER: + case LIGHT_BLUE_BED: + case LIGHT_BLUE_CARPET: + case LIGHT_BLUE_WALL_BANNER: + case LIGHT_BLUE_WOOL: + case LIGHT_GRAY_BANNER: + case LIGHT_GRAY_BED: + case LIGHT_GRAY_CARPET: + case LIGHT_GRAY_WALL_BANNER: + case LIGHT_GRAY_WOOL: + case LILAC: + case LIME_BANNER: + case LIME_BED: + case LIME_CARPET: + case LIME_WALL_BANNER: + case LIME_WOOL: + case MAGENTA_BANNER: + case MAGENTA_BED: + case MAGENTA_CARPET: + case MAGENTA_WALL_BANNER: + case MAGENTA_WOOL: + case MUSHROOM_STEM: + case NOTE_BLOCK: + case OAK_DOOR: + case OAK_FENCE: + case OAK_FENCE_GATE: + case OAK_LEAVES: + case OAK_LOG: + case OAK_PLANKS: + case OAK_PRESSURE_PLATE: + case OAK_SLAB: + case OAK_STAIRS: + case OAK_TRAPDOOR: + case OAK_WOOD: + case ORANGE_BANNER: + case ORANGE_BED: + case ORANGE_CARPET: + case ORANGE_WALL_BANNER: + case ORANGE_WOOL: + case PEONY: + case PINK_BANNER: + case PINK_BED: + case PINK_CARPET: + case PINK_WALL_BANNER: + case PINK_WOOL: + case PURPLE_BANNER: + case PURPLE_BED: + case PURPLE_CARPET: + case PURPLE_WALL_BANNER: + case PURPLE_WOOL: + case RED_BANNER: + case RED_BED: + case RED_CARPET: + case RED_MUSHROOM_BLOCK: + case RED_WALL_BANNER: + case RED_WOOL: + case ROSE_BUSH: + case SIGN: + case SPRUCE_DOOR: + case SPRUCE_FENCE: + case SPRUCE_FENCE_GATE: + case SPRUCE_LEAVES: + case SPRUCE_LOG: + case SPRUCE_PLANKS: + case SPRUCE_PRESSURE_PLATE: + case SPRUCE_SLAB: + case SPRUCE_STAIRS: + case SPRUCE_TRAPDOOR: + case SPRUCE_WOOD: + case STRIPPED_ACACIA_LOG: + case STRIPPED_ACACIA_WOOD: + case STRIPPED_BIRCH_LOG: + case STRIPPED_BIRCH_WOOD: + case STRIPPED_DARK_OAK_LOG: + case STRIPPED_DARK_OAK_WOOD: + case STRIPPED_JUNGLE_LOG: + case STRIPPED_JUNGLE_WOOD: + case STRIPPED_OAK_LOG: + case STRIPPED_OAK_WOOD: + case STRIPPED_SPRUCE_LOG: + case STRIPPED_SPRUCE_WOOD: + case SUNFLOWER: + case TALL_GRASS: + case TNT: + case TRAPPED_CHEST: + case VINE: + case WALL_SIGN: + case WHITE_BANNER: + case WHITE_BED: + case WHITE_CARPET: + case WHITE_WALL_BANNER: + case WHITE_WOOL: + case YELLOW_BANNER: + case YELLOW_BED: + case YELLOW_CARPET: + case YELLOW_WALL_BANNER: + case YELLOW_WOOL: + // ----- Legacy Separator ----- + case LEGACY_WOOD: + case LEGACY_LOG: + case LEGACY_LEAVES: + case LEGACY_NOTE_BLOCK: + case LEGACY_BED_BLOCK: + case LEGACY_LONG_GRASS: + case LEGACY_DEAD_BUSH: + case LEGACY_WOOL: + case LEGACY_TNT: + case LEGACY_BOOKSHELF: + case LEGACY_WOOD_STAIRS: + case LEGACY_CHEST: + case LEGACY_WORKBENCH: + case LEGACY_SIGN_POST: + case LEGACY_WOODEN_DOOR: + case LEGACY_WALL_SIGN: + case LEGACY_WOOD_PLATE: + case LEGACY_JUKEBOX: + case LEGACY_FENCE: + case LEGACY_TRAP_DOOR: + case LEGACY_HUGE_MUSHROOM_1: + case LEGACY_HUGE_MUSHROOM_2: + case LEGACY_VINE: + case LEGACY_FENCE_GATE: + case LEGACY_WOOD_DOUBLE_STEP: + case LEGACY_WOOD_STEP: + case LEGACY_SPRUCE_WOOD_STAIRS: + case LEGACY_BIRCH_WOOD_STAIRS: + case LEGACY_JUNGLE_WOOD_STAIRS: + case LEGACY_TRAPPED_CHEST: + case LEGACY_DAYLIGHT_DETECTOR: + case LEGACY_CARPET: + case LEGACY_LEAVES_2: + case LEGACY_LOG_2: + case LEGACY_ACACIA_STAIRS: + case LEGACY_DARK_OAK_STAIRS: + case LEGACY_DOUBLE_PLANT: + case LEGACY_SPRUCE_FENCE_GATE: + case LEGACY_BIRCH_FENCE_GATE: + case LEGACY_JUNGLE_FENCE_GATE: + case LEGACY_DARK_OAK_FENCE_GATE: + case LEGACY_ACACIA_FENCE_GATE: + case LEGACY_SPRUCE_FENCE: + case LEGACY_BIRCH_FENCE: + case LEGACY_JUNGLE_FENCE: + case LEGACY_DARK_OAK_FENCE: + case LEGACY_ACACIA_FENCE: + case LEGACY_STANDING_BANNER: + case LEGACY_WALL_BANNER: + case LEGACY_DAYLIGHT_DETECTOR_INVERTED: + case LEGACY_SPRUCE_DOOR: + case LEGACY_BIRCH_DOOR: + case LEGACY_JUNGLE_DOOR: + case LEGACY_ACACIA_DOOR: + case LEGACY_DARK_OAK_DOOR: + // + return true; + default: + return false; + } + } + + /** + * Check if the material is a block and can burn away + * + * @return True if this material is a block and can burn away + */ + public boolean isBurnable() { + if (!isBlock()) { + return false; + } + switch (this) { + // + case ACACIA_FENCE: + case ACACIA_FENCE_GATE: + case ACACIA_LEAVES: + case ACACIA_LOG: + case ACACIA_PLANKS: + case ACACIA_SLAB: + case ACACIA_STAIRS: + case ACACIA_WOOD: + case ALLIUM: + case AZURE_BLUET: + case BIRCH_FENCE: + case BIRCH_FENCE_GATE: + case BIRCH_LEAVES: + case BIRCH_LOG: + case BIRCH_PLANKS: + case BIRCH_SLAB: + case BIRCH_STAIRS: + case BIRCH_WOOD: + case BLACK_CARPET: + case BLACK_WOOL: + case BLUE_CARPET: + case BLUE_ORCHID: + case BLUE_WOOL: + case BOOKSHELF: + case BROWN_CARPET: + case BROWN_WOOL: + case COAL_BLOCK: + case CYAN_CARPET: + case CYAN_WOOL: + case DANDELION: + case DARK_OAK_FENCE: + case DARK_OAK_FENCE_GATE: + case DARK_OAK_LEAVES: + case DARK_OAK_LOG: + case DARK_OAK_PLANKS: + case DARK_OAK_SLAB: + case DARK_OAK_STAIRS: + case DARK_OAK_WOOD: + case DEAD_BUSH: + case DRIED_KELP_BLOCK: + case FERN: + case GRASS: + case GRAY_CARPET: + case GRAY_WOOL: + case GREEN_CARPET: + case GREEN_WOOL: + case HAY_BLOCK: + case JUNGLE_FENCE: + case JUNGLE_FENCE_GATE: + case JUNGLE_LEAVES: + case JUNGLE_LOG: + case JUNGLE_PLANKS: + case JUNGLE_SLAB: + case JUNGLE_STAIRS: + case JUNGLE_WOOD: + case LARGE_FERN: + case LIGHT_BLUE_CARPET: + case LIGHT_BLUE_WOOL: + case LIGHT_GRAY_CARPET: + case LIGHT_GRAY_WOOL: + case LILAC: + case LIME_CARPET: + case LIME_WOOL: + case MAGENTA_CARPET: + case MAGENTA_WOOL: + case OAK_FENCE: + case OAK_FENCE_GATE: + case OAK_LEAVES: + case OAK_LOG: + case OAK_PLANKS: + case OAK_SLAB: + case OAK_STAIRS: + case OAK_WOOD: + case ORANGE_CARPET: + case ORANGE_TULIP: + case ORANGE_WOOL: + case OXEYE_DAISY: + case PEONY: + case PINK_CARPET: + case PINK_TULIP: + case PINK_WOOL: + case POPPY: + case PURPLE_CARPET: + case PURPLE_WOOL: + case RED_CARPET: + case RED_TULIP: + case RED_WOOL: + case ROSE_BUSH: + case SPRUCE_FENCE: + case SPRUCE_FENCE_GATE: + case SPRUCE_LEAVES: + case SPRUCE_LOG: + case SPRUCE_PLANKS: + case SPRUCE_SLAB: + case SPRUCE_STAIRS: + case SPRUCE_WOOD: + case STRIPPED_ACACIA_LOG: + case STRIPPED_ACACIA_WOOD: + case STRIPPED_BIRCH_LOG: + case STRIPPED_BIRCH_WOOD: + case STRIPPED_DARK_OAK_LOG: + case STRIPPED_DARK_OAK_WOOD: + case STRIPPED_JUNGLE_LOG: + case STRIPPED_JUNGLE_WOOD: + case STRIPPED_OAK_LOG: + case STRIPPED_OAK_WOOD: + case STRIPPED_SPRUCE_LOG: + case STRIPPED_SPRUCE_WOOD: + case SUNFLOWER: + case TALL_GRASS: + case TNT: + case VINE: + case WHITE_CARPET: + case WHITE_TULIP: + case WHITE_WOOL: + case YELLOW_CARPET: + case YELLOW_WOOL: + // ----- Legacy Separator ----- + case LEGACY_WOOD: + case LEGACY_LOG: + case LEGACY_LEAVES: + case LEGACY_LONG_GRASS: + case LEGACY_WOOL: + case LEGACY_YELLOW_FLOWER: + case LEGACY_RED_ROSE: + case LEGACY_TNT: + case LEGACY_BOOKSHELF: + case LEGACY_WOOD_STAIRS: + case LEGACY_FENCE: + case LEGACY_VINE: + case LEGACY_WOOD_DOUBLE_STEP: + case LEGACY_WOOD_STEP: + case LEGACY_SPRUCE_WOOD_STAIRS: + case LEGACY_BIRCH_WOOD_STAIRS: + case LEGACY_JUNGLE_WOOD_STAIRS: + case LEGACY_HAY_BLOCK: + case LEGACY_COAL_BLOCK: + case LEGACY_LEAVES_2: + case LEGACY_LOG_2: + case LEGACY_CARPET: + case LEGACY_DOUBLE_PLANT: + case LEGACY_DEAD_BUSH: + case LEGACY_FENCE_GATE: + case LEGACY_SPRUCE_FENCE_GATE: + case LEGACY_BIRCH_FENCE_GATE: + case LEGACY_JUNGLE_FENCE_GATE: + case LEGACY_DARK_OAK_FENCE_GATE: + case LEGACY_ACACIA_FENCE_GATE: + case LEGACY_SPRUCE_FENCE: + case LEGACY_BIRCH_FENCE: + case LEGACY_JUNGLE_FENCE: + case LEGACY_DARK_OAK_FENCE: + case LEGACY_ACACIA_FENCE: + case LEGACY_ACACIA_STAIRS: + case LEGACY_DARK_OAK_STAIRS: + // + return true; + default: + return false; + } + } + + /** + * Checks if this Material can be used as fuel in a Furnace + * + * @return true if this Material can be used as fuel. + */ + public boolean isFuel() { + switch (this) { + // + case ACACIA_BOAT: + case ACACIA_BUTTON: + case ACACIA_DOOR: + case ACACIA_FENCE: + case ACACIA_FENCE_GATE: + case ACACIA_LOG: + case ACACIA_PLANKS: + case ACACIA_PRESSURE_PLATE: + case ACACIA_SAPLING: + case ACACIA_SLAB: + case ACACIA_STAIRS: + case ACACIA_TRAPDOOR: + case ACACIA_WOOD: + case BIRCH_BOAT: + case BIRCH_BUTTON: + case BIRCH_DOOR: + case BIRCH_FENCE: + case BIRCH_FENCE_GATE: + case BIRCH_LOG: + case BIRCH_PLANKS: + case BIRCH_PRESSURE_PLATE: + case BIRCH_SAPLING: + case BIRCH_SLAB: + case BIRCH_STAIRS: + case BIRCH_TRAPDOOR: + case BIRCH_WOOD: + case BLACK_BANNER: + case BLACK_CARPET: + case BLACK_WOOL: + case BLAZE_ROD: + case BLUE_BANNER: + case BLUE_CARPET: + case BLUE_WOOL: + case BOOKSHELF: + case BOW: + case BOWL: + case BROWN_BANNER: + case BROWN_CARPET: + case BROWN_WOOL: + case CHARCOAL: + case CHEST: + case COAL: + case COAL_BLOCK: + case CRAFTING_TABLE: + case CYAN_BANNER: + case CYAN_CARPET: + case CYAN_WOOL: + case DARK_OAK_BOAT: + case DARK_OAK_BUTTON: + case DARK_OAK_DOOR: + case DARK_OAK_FENCE: + case DARK_OAK_FENCE_GATE: + case DARK_OAK_LOG: + case DARK_OAK_PLANKS: + case DARK_OAK_PRESSURE_PLATE: + case DARK_OAK_SAPLING: + case DARK_OAK_SLAB: + case DARK_OAK_STAIRS: + case DARK_OAK_TRAPDOOR: + case DARK_OAK_WOOD: + case DAYLIGHT_DETECTOR: + case DRIED_KELP_BLOCK: + case FISHING_ROD: + case GRAY_BANNER: + case GRAY_CARPET: + case GRAY_WOOL: + case GREEN_BANNER: + case GREEN_CARPET: + case GREEN_WOOL: + case JUKEBOX: + case JUNGLE_BOAT: + case JUNGLE_BUTTON: + case JUNGLE_DOOR: + case JUNGLE_FENCE: + case JUNGLE_FENCE_GATE: + case JUNGLE_LOG: + case JUNGLE_PLANKS: + case JUNGLE_PRESSURE_PLATE: + case JUNGLE_SAPLING: + case JUNGLE_SLAB: + case JUNGLE_STAIRS: + case JUNGLE_TRAPDOOR: + case JUNGLE_WOOD: + case LADDER: + case LAVA_BUCKET: + case LIGHT_BLUE_BANNER: + case LIGHT_BLUE_CARPET: + case LIGHT_BLUE_WOOL: + case LIGHT_GRAY_BANNER: + case LIGHT_GRAY_CARPET: + case LIGHT_GRAY_WOOL: + case LIME_BANNER: + case LIME_CARPET: + case LIME_WOOL: + case MAGENTA_BANNER: + case MAGENTA_CARPET: + case MAGENTA_WOOL: + case NOTE_BLOCK: + case OAK_BOAT: + case OAK_BUTTON: + case OAK_DOOR: + case OAK_FENCE: + case OAK_FENCE_GATE: + case OAK_LOG: + case OAK_PLANKS: + case OAK_PRESSURE_PLATE: + case OAK_SAPLING: + case OAK_SLAB: + case OAK_STAIRS: + case OAK_TRAPDOOR: + case OAK_WOOD: + case ORANGE_BANNER: + case ORANGE_CARPET: + case ORANGE_WOOL: + case PINK_BANNER: + case PINK_CARPET: + case PINK_WOOL: + case PURPLE_BANNER: + case PURPLE_CARPET: + case PURPLE_WOOL: + case RED_BANNER: + case RED_CARPET: + case RED_WOOL: + case SIGN: + case SPRUCE_BOAT: + case SPRUCE_BUTTON: + case SPRUCE_DOOR: + case SPRUCE_FENCE: + case SPRUCE_FENCE_GATE: + case SPRUCE_LOG: + case SPRUCE_PLANKS: + case SPRUCE_PRESSURE_PLATE: + case SPRUCE_SAPLING: + case SPRUCE_SLAB: + case SPRUCE_STAIRS: + case SPRUCE_TRAPDOOR: + case SPRUCE_WOOD: + case STICK: + case STRIPPED_ACACIA_LOG: + case STRIPPED_ACACIA_WOOD: + case STRIPPED_BIRCH_LOG: + case STRIPPED_BIRCH_WOOD: + case STRIPPED_DARK_OAK_LOG: + case STRIPPED_DARK_OAK_WOOD: + case STRIPPED_JUNGLE_LOG: + case STRIPPED_JUNGLE_WOOD: + case STRIPPED_OAK_LOG: + case STRIPPED_OAK_WOOD: + case STRIPPED_SPRUCE_LOG: + case STRIPPED_SPRUCE_WOOD: + case TRAPPED_CHEST: + case WHITE_BANNER: + case WHITE_CARPET: + case WHITE_WOOL: + case WOODEN_AXE: + case WOODEN_HOE: + case WOODEN_PICKAXE: + case WOODEN_SHOVEL: + case WOODEN_SWORD: + case YELLOW_BANNER: + case YELLOW_CARPET: + case YELLOW_WOOL: + // ----- Legacy Separator ----- + case LEGACY_LAVA_BUCKET: + case LEGACY_COAL_BLOCK: + case LEGACY_BLAZE_ROD: + case LEGACY_COAL: + case LEGACY_BOAT: + case LEGACY_BOAT_ACACIA: + case LEGACY_BOAT_BIRCH: + case LEGACY_BOAT_DARK_OAK: + case LEGACY_BOAT_JUNGLE: + case LEGACY_BOAT_SPRUCE: + case LEGACY_LOG: + case LEGACY_LOG_2: + case LEGACY_WOOD: + case LEGACY_WOOD_PLATE: + case LEGACY_FENCE: + case LEGACY_ACACIA_FENCE: + case LEGACY_BIRCH_FENCE: + case LEGACY_DARK_OAK_FENCE: + case LEGACY_JUNGLE_FENCE: + case LEGACY_SPRUCE_FENCE: + case LEGACY_FENCE_GATE: + case LEGACY_ACACIA_FENCE_GATE: + case LEGACY_BIRCH_FENCE_GATE: + case LEGACY_DARK_OAK_FENCE_GATE: + case LEGACY_JUNGLE_FENCE_GATE: + case LEGACY_SPRUCE_FENCE_GATE: + case LEGACY_WOOD_STAIRS: + case LEGACY_ACACIA_STAIRS: + case LEGACY_BIRCH_WOOD_STAIRS: + case LEGACY_DARK_OAK_STAIRS: + case LEGACY_JUNGLE_WOOD_STAIRS: + case LEGACY_SPRUCE_WOOD_STAIRS: + case LEGACY_TRAP_DOOR: + case LEGACY_WORKBENCH: + case LEGACY_BOOKSHELF: + case LEGACY_CHEST: + case LEGACY_TRAPPED_CHEST: + case LEGACY_DAYLIGHT_DETECTOR: + case LEGACY_JUKEBOX: + case LEGACY_NOTE_BLOCK: + case LEGACY_BANNER: + case LEGACY_FISHING_ROD: + case LEGACY_LADDER: + case LEGACY_WOOD_SWORD: + case LEGACY_WOOD_PICKAXE: + case LEGACY_WOOD_AXE: + case LEGACY_WOOD_SPADE: + case LEGACY_WOOD_HOE: + case LEGACY_BOW: + case LEGACY_SIGN: + case LEGACY_WOOD_DOOR: + case LEGACY_ACACIA_DOOR_ITEM: + case LEGACY_BIRCH_DOOR_ITEM: + case LEGACY_DARK_OAK_DOOR_ITEM: + case LEGACY_JUNGLE_DOOR_ITEM: + case LEGACY_SPRUCE_DOOR_ITEM: + case LEGACY_WOOD_STEP: + case LEGACY_SAPLING: + case LEGACY_STICK: + case LEGACY_WOOD_BUTTON: + case LEGACY_WOOL: + case LEGACY_CARPET: + case LEGACY_BOWL: + // + return true; + default: + return false; + } + } + + /** + * Check if the material is a block and completely blocks vision + * + * @return True if this material is a block and completely blocks vision + */ + public boolean isOccluding() { + if (!isBlock()) { + return false; + } + switch (this) { + // + case ACACIA_LOG: + case ACACIA_PLANKS: + case ACACIA_WOOD: + case ANDESITE: + case BARRIER: + case BEDROCK: + case BIRCH_LOG: + case BIRCH_PLANKS: + case BIRCH_WOOD: + case BLACK_CONCRETE: + case BLACK_CONCRETE_POWDER: + case BLACK_GLAZED_TERRACOTTA: + case BLACK_TERRACOTTA: + case BLACK_WOOL: + case BLUE_CONCRETE: + case BLUE_CONCRETE_POWDER: + case BLUE_GLAZED_TERRACOTTA: + case BLUE_ICE: + case BLUE_TERRACOTTA: + case BLUE_WOOL: + case BONE_BLOCK: + case BOOKSHELF: + case BRAIN_CORAL_BLOCK: + case BRICKS: + case BROWN_CONCRETE: + case BROWN_CONCRETE_POWDER: + case BROWN_GLAZED_TERRACOTTA: + case BROWN_MUSHROOM_BLOCK: + case BROWN_TERRACOTTA: + case BROWN_WOOL: + case BUBBLE_CORAL_BLOCK: + case CARVED_PUMPKIN: + case CHAIN_COMMAND_BLOCK: + case CHISELED_QUARTZ_BLOCK: + case CHISELED_RED_SANDSTONE: + case CHISELED_SANDSTONE: + case CHISELED_STONE_BRICKS: + case CLAY: + case COAL_BLOCK: + case COAL_ORE: + case COARSE_DIRT: + case COBBLESTONE: + case COMMAND_BLOCK: + case CRACKED_STONE_BRICKS: + case CRAFTING_TABLE: + case CUT_RED_SANDSTONE: + case CUT_SANDSTONE: + case CYAN_CONCRETE: + case CYAN_CONCRETE_POWDER: + case CYAN_GLAZED_TERRACOTTA: + case CYAN_TERRACOTTA: + case CYAN_WOOL: + case DARK_OAK_LOG: + case DARK_OAK_PLANKS: + case DARK_OAK_WOOD: + case DARK_PRISMARINE: + case DEAD_BRAIN_CORAL_BLOCK: + case DEAD_BUBBLE_CORAL_BLOCK: + case DEAD_FIRE_CORAL_BLOCK: + case DEAD_HORN_CORAL_BLOCK: + case DEAD_TUBE_CORAL_BLOCK: + case DIAMOND_BLOCK: + case DIAMOND_ORE: + case DIORITE: + case DIRT: + case DISPENSER: + case DRIED_KELP_BLOCK: + case DROPPER: + case EMERALD_BLOCK: + case EMERALD_ORE: + case END_STONE: + case END_STONE_BRICKS: + case FIRE_CORAL_BLOCK: + case FURNACE: + case GOLD_BLOCK: + case GOLD_ORE: + case GRANITE: + case GRASS_BLOCK: + case GRAVEL: + case GRAY_CONCRETE: + case GRAY_CONCRETE_POWDER: + case GRAY_GLAZED_TERRACOTTA: + case GRAY_TERRACOTTA: + case GRAY_WOOL: + case GREEN_CONCRETE: + case GREEN_CONCRETE_POWDER: + case GREEN_GLAZED_TERRACOTTA: + case GREEN_TERRACOTTA: + case GREEN_WOOL: + case HAY_BLOCK: + case HORN_CORAL_BLOCK: + case INFESTED_CHISELED_STONE_BRICKS: + case INFESTED_COBBLESTONE: + case INFESTED_CRACKED_STONE_BRICKS: + case INFESTED_MOSSY_STONE_BRICKS: + case INFESTED_STONE: + case INFESTED_STONE_BRICKS: + case IRON_BLOCK: + case IRON_ORE: + case JACK_O_LANTERN: + case JUKEBOX: + case JUNGLE_LOG: + case JUNGLE_PLANKS: + case JUNGLE_WOOD: + case LAPIS_BLOCK: + case LAPIS_ORE: + case LIGHT_BLUE_CONCRETE: + case LIGHT_BLUE_CONCRETE_POWDER: + case LIGHT_BLUE_GLAZED_TERRACOTTA: + case LIGHT_BLUE_TERRACOTTA: + case LIGHT_BLUE_WOOL: + case LIGHT_GRAY_CONCRETE: + case LIGHT_GRAY_CONCRETE_POWDER: + case LIGHT_GRAY_GLAZED_TERRACOTTA: + case LIGHT_GRAY_TERRACOTTA: + case LIGHT_GRAY_WOOL: + case LIME_CONCRETE: + case LIME_CONCRETE_POWDER: + case LIME_GLAZED_TERRACOTTA: + case LIME_TERRACOTTA: + case LIME_WOOL: + case MAGENTA_CONCRETE: + case MAGENTA_CONCRETE_POWDER: + case MAGENTA_GLAZED_TERRACOTTA: + case MAGENTA_TERRACOTTA: + case MAGENTA_WOOL: + case MAGMA_BLOCK: + case MELON: + case MOSSY_COBBLESTONE: + case MOSSY_STONE_BRICKS: + case MUSHROOM_STEM: + case MYCELIUM: + case NETHERRACK: + case NETHER_BRICKS: + case NETHER_QUARTZ_ORE: + case NETHER_WART_BLOCK: + case NOTE_BLOCK: + case OAK_LOG: + case OAK_PLANKS: + case OAK_WOOD: + case OBSIDIAN: + case ORANGE_CONCRETE: + case ORANGE_CONCRETE_POWDER: + case ORANGE_GLAZED_TERRACOTTA: + case ORANGE_TERRACOTTA: + case ORANGE_WOOL: + case PACKED_ICE: + case PINK_CONCRETE: + case PINK_CONCRETE_POWDER: + case PINK_GLAZED_TERRACOTTA: + case PINK_TERRACOTTA: + case PINK_WOOL: + case PODZOL: + case POLISHED_ANDESITE: + case POLISHED_DIORITE: + case POLISHED_GRANITE: + case PRISMARINE: + case PRISMARINE_BRICKS: + case PUMPKIN: + case PURPLE_CONCRETE: + case PURPLE_CONCRETE_POWDER: + case PURPLE_GLAZED_TERRACOTTA: + case PURPLE_TERRACOTTA: + case PURPLE_WOOL: + case PURPUR_BLOCK: + case PURPUR_PILLAR: + case QUARTZ_BLOCK: + case QUARTZ_PILLAR: + case REDSTONE_LAMP: + case REDSTONE_ORE: + case RED_CONCRETE: + case RED_CONCRETE_POWDER: + case RED_GLAZED_TERRACOTTA: + case RED_MUSHROOM_BLOCK: + case RED_NETHER_BRICKS: + case RED_SAND: + case RED_SANDSTONE: + case RED_TERRACOTTA: + case RED_WOOL: + case REPEATING_COMMAND_BLOCK: + case SAND: + case SANDSTONE: + case SLIME_BLOCK: + case SMOOTH_QUARTZ: + case SMOOTH_RED_SANDSTONE: + case SMOOTH_SANDSTONE: + case SMOOTH_STONE: + case SNOW_BLOCK: + case SOUL_SAND: + case SPAWNER: + case SPONGE: + case SPRUCE_LOG: + case SPRUCE_PLANKS: + case SPRUCE_WOOD: + case STONE: + case STONE_BRICKS: + case STRIPPED_ACACIA_LOG: + case STRIPPED_ACACIA_WOOD: + case STRIPPED_BIRCH_LOG: + case STRIPPED_BIRCH_WOOD: + case STRIPPED_DARK_OAK_LOG: + case STRIPPED_DARK_OAK_WOOD: + case STRIPPED_JUNGLE_LOG: + case STRIPPED_JUNGLE_WOOD: + case STRIPPED_OAK_LOG: + case STRIPPED_OAK_WOOD: + case STRIPPED_SPRUCE_LOG: + case STRIPPED_SPRUCE_WOOD: + case STRUCTURE_BLOCK: + case TERRACOTTA: + case TUBE_CORAL_BLOCK: + case WET_SPONGE: + case WHITE_CONCRETE: + case WHITE_CONCRETE_POWDER: + case WHITE_GLAZED_TERRACOTTA: + case WHITE_TERRACOTTA: + case WHITE_WOOL: + case YELLOW_CONCRETE: + case YELLOW_CONCRETE_POWDER: + case YELLOW_GLAZED_TERRACOTTA: + case YELLOW_TERRACOTTA: + case YELLOW_WOOL: + // ----- Legacy Separator ----- + case LEGACY_STONE: + case LEGACY_GRASS: + case LEGACY_DIRT: + case LEGACY_COBBLESTONE: + case LEGACY_WOOD: + case LEGACY_BEDROCK: + case LEGACY_SAND: + case LEGACY_GRAVEL: + case LEGACY_GOLD_ORE: + case LEGACY_IRON_ORE: + case LEGACY_COAL_ORE: + case LEGACY_LOG: + case LEGACY_SPONGE: + case LEGACY_LAPIS_ORE: + case LEGACY_LAPIS_BLOCK: + case LEGACY_DISPENSER: + case LEGACY_SANDSTONE: + case LEGACY_NOTE_BLOCK: + case LEGACY_WOOL: + case LEGACY_GOLD_BLOCK: + case LEGACY_IRON_BLOCK: + case LEGACY_DOUBLE_STEP: + case LEGACY_BRICK: + case LEGACY_BOOKSHELF: + case LEGACY_MOSSY_COBBLESTONE: + case LEGACY_OBSIDIAN: + case LEGACY_MOB_SPAWNER: + case LEGACY_DIAMOND_ORE: + case LEGACY_DIAMOND_BLOCK: + case LEGACY_WORKBENCH: + case LEGACY_FURNACE: + case LEGACY_BURNING_FURNACE: + case LEGACY_REDSTONE_ORE: + case LEGACY_GLOWING_REDSTONE_ORE: + case LEGACY_SNOW_BLOCK: + case LEGACY_CLAY: + case LEGACY_JUKEBOX: + case LEGACY_PUMPKIN: + case LEGACY_NETHERRACK: + case LEGACY_SOUL_SAND: + case LEGACY_JACK_O_LANTERN: + case LEGACY_MONSTER_EGGS: + case LEGACY_SMOOTH_BRICK: + case LEGACY_HUGE_MUSHROOM_1: + case LEGACY_HUGE_MUSHROOM_2: + case LEGACY_MELON_BLOCK: + case LEGACY_MYCEL: + case LEGACY_NETHER_BRICK: + case LEGACY_ENDER_STONE: + case LEGACY_REDSTONE_LAMP_OFF: + case LEGACY_REDSTONE_LAMP_ON: + case LEGACY_WOOD_DOUBLE_STEP: + case LEGACY_EMERALD_ORE: + case LEGACY_EMERALD_BLOCK: + case LEGACY_COMMAND: + case LEGACY_QUARTZ_ORE: + case LEGACY_QUARTZ_BLOCK: + case LEGACY_DROPPER: + case LEGACY_STAINED_CLAY: + case LEGACY_HAY_BLOCK: + case LEGACY_HARD_CLAY: + case LEGACY_COAL_BLOCK: + case LEGACY_LOG_2: + case LEGACY_PACKED_ICE: + case LEGACY_SLIME_BLOCK: + case LEGACY_BARRIER: + case LEGACY_PRISMARINE: + case LEGACY_RED_SANDSTONE: + case LEGACY_DOUBLE_STONE_SLAB2: + case LEGACY_PURPUR_BLOCK: + case LEGACY_PURPUR_PILLAR: + case LEGACY_PURPUR_DOUBLE_SLAB: + case LEGACY_END_BRICKS: + case LEGACY_STRUCTURE_BLOCK: + case LEGACY_COMMAND_REPEATING: + case LEGACY_COMMAND_CHAIN: + case LEGACY_MAGMA: + case LEGACY_NETHER_WART_BLOCK: + case LEGACY_RED_NETHER_BRICK: + case LEGACY_BONE_BLOCK: + case LEGACY_WHITE_GLAZED_TERRACOTTA: + case LEGACY_ORANGE_GLAZED_TERRACOTTA: + case LEGACY_MAGENTA_GLAZED_TERRACOTTA: + case LEGACY_LIGHT_BLUE_GLAZED_TERRACOTTA: + case LEGACY_YELLOW_GLAZED_TERRACOTTA: + case LEGACY_LIME_GLAZED_TERRACOTTA: + case LEGACY_PINK_GLAZED_TERRACOTTA: + case LEGACY_GRAY_GLAZED_TERRACOTTA: + case LEGACY_SILVER_GLAZED_TERRACOTTA: + case LEGACY_CYAN_GLAZED_TERRACOTTA: + case LEGACY_PURPLE_GLAZED_TERRACOTTA: + case LEGACY_BLUE_GLAZED_TERRACOTTA: + case LEGACY_BROWN_GLAZED_TERRACOTTA: + case LEGACY_GREEN_GLAZED_TERRACOTTA: + case LEGACY_RED_GLAZED_TERRACOTTA: + case LEGACY_BLACK_GLAZED_TERRACOTTA: + case LEGACY_CONCRETE: + case LEGACY_CONCRETE_POWDER: + // + return true; + default: + return false; + } + } + + /** + * @return True if this material is affected by gravity. + */ + public boolean hasGravity() { + if (!isBlock()) { + return false; + } + switch (this) { + // + case ANVIL: + case BLACK_CONCRETE_POWDER: + case BLUE_CONCRETE_POWDER: + case BROWN_CONCRETE_POWDER: + case CHIPPED_ANVIL: + case CYAN_CONCRETE_POWDER: + case DAMAGED_ANVIL: + case DRAGON_EGG: + case GRAVEL: + case GRAY_CONCRETE_POWDER: + case GREEN_CONCRETE_POWDER: + case LIGHT_BLUE_CONCRETE_POWDER: + case LIGHT_GRAY_CONCRETE_POWDER: + case LIME_CONCRETE_POWDER: + case MAGENTA_CONCRETE_POWDER: + case ORANGE_CONCRETE_POWDER: + case PINK_CONCRETE_POWDER: + case PURPLE_CONCRETE_POWDER: + case RED_CONCRETE_POWDER: + case RED_SAND: + case SAND: + case WHITE_CONCRETE_POWDER: + case YELLOW_CONCRETE_POWDER: + // ----- Legacy Separator ----- + case LEGACY_SAND: + case LEGACY_GRAVEL: + case LEGACY_ANVIL: + case LEGACY_CONCRETE_POWDER: + // + return true; + default: + return false; + } + } + + /** + * Checks if this Material is an obtainable item. + * + * @return true if this material is an item + */ + public boolean isItem() { + switch (this) { + // + case ATTACHED_MELON_STEM: + case ATTACHED_PUMPKIN_STEM: + case BEETROOTS: + case BLACK_WALL_BANNER: + case BLUE_WALL_BANNER: + case BRAIN_CORAL_WALL_FAN: + case BROWN_WALL_BANNER: + case BUBBLE_COLUMN: + case BUBBLE_CORAL_WALL_FAN: + case CARROTS: + case CAVE_AIR: + case COCOA: + case CREEPER_WALL_HEAD: + case CYAN_WALL_BANNER: + case DEAD_BRAIN_CORAL_WALL_FAN: + case DEAD_BUBBLE_CORAL_WALL_FAN: + case DEAD_FIRE_CORAL_WALL_FAN: + case DEAD_HORN_CORAL_WALL_FAN: + case DEAD_TUBE_CORAL_WALL_FAN: + case DRAGON_WALL_HEAD: + case END_GATEWAY: + case END_PORTAL: + case FIRE: + case FIRE_CORAL_WALL_FAN: + case FROSTED_ICE: + case GRAY_WALL_BANNER: + case GREEN_WALL_BANNER: + case HORN_CORAL_WALL_FAN: + case KELP_PLANT: + case LAVA: + case LIGHT_BLUE_WALL_BANNER: + case LIGHT_GRAY_WALL_BANNER: + case LIME_WALL_BANNER: + case MAGENTA_WALL_BANNER: + case MELON_STEM: + case MOVING_PISTON: + case NETHER_PORTAL: + case ORANGE_WALL_BANNER: + case PINK_WALL_BANNER: + case PISTON_HEAD: + case PLAYER_WALL_HEAD: + case POTATOES: + case POTTED_ACACIA_SAPLING: + case POTTED_ALLIUM: + case POTTED_AZURE_BLUET: + case POTTED_BIRCH_SAPLING: + case POTTED_BLUE_ORCHID: + case POTTED_BROWN_MUSHROOM: + case POTTED_CACTUS: + case POTTED_DANDELION: + case POTTED_DARK_OAK_SAPLING: + case POTTED_DEAD_BUSH: + case POTTED_FERN: + case POTTED_JUNGLE_SAPLING: + case POTTED_OAK_SAPLING: + case POTTED_ORANGE_TULIP: + case POTTED_OXEYE_DAISY: + case POTTED_PINK_TULIP: + case POTTED_POPPY: + case POTTED_RED_MUSHROOM: + case POTTED_RED_TULIP: + case POTTED_SPRUCE_SAPLING: + case POTTED_WHITE_TULIP: + case PUMPKIN_STEM: + case PURPLE_WALL_BANNER: + case REDSTONE_WALL_TORCH: + case REDSTONE_WIRE: + case RED_WALL_BANNER: + case SKELETON_WALL_SKULL: + case TALL_SEAGRASS: + case TRIPWIRE: + case TUBE_CORAL_WALL_FAN: + case VOID_AIR: + case WALL_SIGN: + case WALL_TORCH: + case WATER: + case WHITE_WALL_BANNER: + case WITHER_SKELETON_WALL_SKULL: + case YELLOW_WALL_BANNER: + case ZOMBIE_WALL_HEAD: + // ----- Legacy Separator ----- + case LEGACY_ACACIA_DOOR: + case LEGACY_BED_BLOCK: + case LEGACY_BEETROOT_BLOCK: + case LEGACY_BIRCH_DOOR: + case LEGACY_BREWING_STAND: + case LEGACY_BURNING_FURNACE: + case LEGACY_CAKE_BLOCK: + case LEGACY_CARROT: + case LEGACY_CAULDRON: + case LEGACY_COCOA: + case LEGACY_CROPS: + case LEGACY_DARK_OAK_DOOR: + case LEGACY_DAYLIGHT_DETECTOR_INVERTED: + case LEGACY_DIODE_BLOCK_OFF: + case LEGACY_DIODE_BLOCK_ON: + case LEGACY_DOUBLE_STEP: + case LEGACY_DOUBLE_STONE_SLAB2: + case LEGACY_ENDER_PORTAL: + case LEGACY_END_GATEWAY: + case LEGACY_FIRE: + case LEGACY_FLOWER_POT: + case LEGACY_FROSTED_ICE: + case LEGACY_GLOWING_REDSTONE_ORE: + case LEGACY_IRON_DOOR_BLOCK: + case LEGACY_JUNGLE_DOOR: + case LEGACY_LAVA: + case LEGACY_MELON_STEM: + case LEGACY_NETHER_WARTS: + case LEGACY_PISTON_EXTENSION: + case LEGACY_PISTON_MOVING_PIECE: + case LEGACY_PORTAL: + case LEGACY_POTATO: + case LEGACY_PUMPKIN_STEM: + case LEGACY_PURPUR_DOUBLE_SLAB: + case LEGACY_REDSTONE_COMPARATOR_OFF: + case LEGACY_REDSTONE_COMPARATOR_ON: + case LEGACY_REDSTONE_LAMP_ON: + case LEGACY_REDSTONE_TORCH_OFF: + case LEGACY_REDSTONE_WIRE: + case LEGACY_SIGN_POST: + case LEGACY_SKULL: + case LEGACY_SPRUCE_DOOR: + case LEGACY_STANDING_BANNER: + case LEGACY_STATIONARY_LAVA: + case LEGACY_STATIONARY_WATER: + case LEGACY_SUGAR_CANE_BLOCK: + case LEGACY_TRIPWIRE: + case LEGACY_WALL_BANNER: + case LEGACY_WALL_SIGN: + case LEGACY_WATER: + case LEGACY_WOODEN_DOOR: + case LEGACY_WOOD_DOUBLE_STEP: + // + return false; + default: + return true; + } + } + + /** + * Checks if this Material can be interacted with. + * + * Interactable materials include those with functionality when they are + * interacted with by a player such as chests, furnaces, etc. + * + * Some blocks such as piston heads and stairs are considered interactable + * though may not perform any additional functionality. + * + * Note that the interactability of some materials may be dependant on their + * state as well. This method will return true if there is at least one + * state in which additional interact handling is performed for the + * material. + * + * @return true if this material can be interacted with. + */ + public boolean isInteractable() { + switch (this) { + // + case ACACIA_BUTTON: + case ACACIA_DOOR: + case ACACIA_FENCE: + case ACACIA_FENCE_GATE: + case ACACIA_STAIRS: + case ACACIA_TRAPDOOR: + case ANVIL: + case BEACON: + case BIRCH_BUTTON: + case BIRCH_DOOR: + case BIRCH_FENCE: + case BIRCH_FENCE_GATE: + case BIRCH_STAIRS: + case BIRCH_TRAPDOOR: + case BLACK_BED: + case BLACK_SHULKER_BOX: + case BLUE_BED: + case BLUE_SHULKER_BOX: + case BREWING_STAND: + case BRICK_STAIRS: + case BROWN_BED: + case BROWN_SHULKER_BOX: + case CAKE: + case CAULDRON: + case CHAIN_COMMAND_BLOCK: + case CHEST: + case CHIPPED_ANVIL: + case COBBLESTONE_STAIRS: + case COMMAND_BLOCK: + case COMPARATOR: + case CRAFTING_TABLE: + case CYAN_BED: + case CYAN_SHULKER_BOX: + case DAMAGED_ANVIL: + case DARK_OAK_BUTTON: + case DARK_OAK_DOOR: + case DARK_OAK_FENCE: + case DARK_OAK_FENCE_GATE: + case DARK_OAK_STAIRS: + case DARK_OAK_TRAPDOOR: + case DARK_PRISMARINE_STAIRS: + case DAYLIGHT_DETECTOR: + case DISPENSER: + case DRAGON_EGG: + case DROPPER: + case ENCHANTING_TABLE: + case ENDER_CHEST: + case FLOWER_POT: + case FURNACE: + case GRAY_BED: + case GRAY_SHULKER_BOX: + case GREEN_BED: + case GREEN_SHULKER_BOX: + case HOPPER: + case IRON_DOOR: + case IRON_TRAPDOOR: + case JUKEBOX: + case JUNGLE_BUTTON: + case JUNGLE_DOOR: + case JUNGLE_FENCE: + case JUNGLE_FENCE_GATE: + case JUNGLE_STAIRS: + case JUNGLE_TRAPDOOR: + case LEVER: + case LIGHT_BLUE_BED: + case LIGHT_BLUE_SHULKER_BOX: + case LIGHT_GRAY_BED: + case LIGHT_GRAY_SHULKER_BOX: + case LIME_BED: + case LIME_SHULKER_BOX: + case MAGENTA_BED: + case MAGENTA_SHULKER_BOX: + case MOVING_PISTON: + case NETHER_BRICK_FENCE: + case NETHER_BRICK_STAIRS: + case NOTE_BLOCK: + case OAK_BUTTON: + case OAK_DOOR: + case OAK_FENCE: + case OAK_FENCE_GATE: + case OAK_STAIRS: + case OAK_TRAPDOOR: + case ORANGE_BED: + case ORANGE_SHULKER_BOX: + case PINK_BED: + case PINK_SHULKER_BOX: + case POTTED_ACACIA_SAPLING: + case POTTED_ALLIUM: + case POTTED_AZURE_BLUET: + case POTTED_BIRCH_SAPLING: + case POTTED_BLUE_ORCHID: + case POTTED_BROWN_MUSHROOM: + case POTTED_CACTUS: + case POTTED_DANDELION: + case POTTED_DARK_OAK_SAPLING: + case POTTED_DEAD_BUSH: + case POTTED_FERN: + case POTTED_JUNGLE_SAPLING: + case POTTED_OAK_SAPLING: + case POTTED_ORANGE_TULIP: + case POTTED_OXEYE_DAISY: + case POTTED_PINK_TULIP: + case POTTED_POPPY: + case POTTED_RED_MUSHROOM: + case POTTED_RED_TULIP: + case POTTED_SPRUCE_SAPLING: + case POTTED_WHITE_TULIP: + case PRISMARINE_BRICK_STAIRS: + case PRISMARINE_STAIRS: + case PUMPKIN: + case PURPLE_BED: + case PURPLE_SHULKER_BOX: + case PURPUR_STAIRS: + case QUARTZ_STAIRS: + case REDSTONE_ORE: + case RED_BED: + case RED_SANDSTONE_STAIRS: + case RED_SHULKER_BOX: + case REPEATER: + case REPEATING_COMMAND_BLOCK: + case SANDSTONE_STAIRS: + case SHULKER_BOX: + case SIGN: + case SPRUCE_BUTTON: + case SPRUCE_DOOR: + case SPRUCE_FENCE: + case SPRUCE_FENCE_GATE: + case SPRUCE_STAIRS: + case SPRUCE_TRAPDOOR: + case STONE_BRICK_STAIRS: + case STONE_BUTTON: + case STRUCTURE_BLOCK: + case TNT: + case TRAPPED_CHEST: + case WALL_SIGN: + case WHITE_BED: + case WHITE_SHULKER_BOX: + case YELLOW_BED: + case YELLOW_SHULKER_BOX: + // + return true; + default: + return false; + } + } + + /** + * Obtains the block's hardness level (also known as "strength"). + *
+ * This number is used to calculate the time required to break each block. + *
+ * Only available when {@link #isBlock()} is true. + * + * @return the hardness of that material. + */ + public float getHardness() { + Validate.isTrue(isBlock(), "The Material is not a block!"); + switch (this) { + // + case BARRIER: + case BEDROCK: + case CHAIN_COMMAND_BLOCK: + case COMMAND_BLOCK: + case END_GATEWAY: + case END_PORTAL: + case END_PORTAL_FRAME: + case MOVING_PISTON: + case NETHER_PORTAL: + case REPEATING_COMMAND_BLOCK: + case STRUCTURE_BLOCK: + return -1.0F; + case BLACK_CARPET: + case BLUE_CARPET: + case BROWN_CARPET: + case CYAN_CARPET: + case GRAY_CARPET: + case GREEN_CARPET: + case LIGHT_BLUE_CARPET: + case LIGHT_GRAY_CARPET: + case LIME_CARPET: + case MAGENTA_CARPET: + case ORANGE_CARPET: + case PINK_CARPET: + case PURPLE_CARPET: + case RED_CARPET: + case SNOW: + case WHITE_CARPET: + case YELLOW_CARPET: + return 0.1F; + case ACACIA_LEAVES: + case BIRCH_LEAVES: + case BLACK_BED: + case BLUE_BED: + case BROWN_BED: + case BROWN_MUSHROOM_BLOCK: + case COCOA: + case CYAN_BED: + case DARK_OAK_LEAVES: + case DAYLIGHT_DETECTOR: + case GRAY_BED: + case GREEN_BED: + case JUNGLE_LEAVES: + case LIGHT_BLUE_BED: + case LIGHT_GRAY_BED: + case LIME_BED: + case MAGENTA_BED: + case MUSHROOM_STEM: + case OAK_LEAVES: + case ORANGE_BED: + case PINK_BED: + case PURPLE_BED: + case RED_BED: + case RED_MUSHROOM_BLOCK: + case SNOW_BLOCK: + case SPRUCE_LEAVES: + case VINE: + case WHITE_BED: + case YELLOW_BED: + return 0.2F; + case BLACK_STAINED_GLASS: + case BLACK_STAINED_GLASS_PANE: + case BLUE_STAINED_GLASS: + case BLUE_STAINED_GLASS_PANE: + case BROWN_STAINED_GLASS: + case BROWN_STAINED_GLASS_PANE: + case CYAN_STAINED_GLASS: + case CYAN_STAINED_GLASS_PANE: + case GLASS: + case GLASS_PANE: + case GLOWSTONE: + case GRAY_STAINED_GLASS: + case GRAY_STAINED_GLASS_PANE: + case GREEN_STAINED_GLASS: + case GREEN_STAINED_GLASS_PANE: + case LIGHT_BLUE_STAINED_GLASS: + case LIGHT_BLUE_STAINED_GLASS_PANE: + case LIGHT_GRAY_STAINED_GLASS: + case LIGHT_GRAY_STAINED_GLASS_PANE: + case LIME_STAINED_GLASS: + case LIME_STAINED_GLASS_PANE: + case MAGENTA_STAINED_GLASS: + case MAGENTA_STAINED_GLASS_PANE: + case ORANGE_STAINED_GLASS: + case ORANGE_STAINED_GLASS_PANE: + case PINK_STAINED_GLASS: + case PINK_STAINED_GLASS_PANE: + case PURPLE_STAINED_GLASS: + case PURPLE_STAINED_GLASS_PANE: + case REDSTONE_LAMP: + case RED_STAINED_GLASS: + case RED_STAINED_GLASS_PANE: + case SEA_LANTERN: + case WHITE_STAINED_GLASS: + case WHITE_STAINED_GLASS_PANE: + case YELLOW_STAINED_GLASS: + case YELLOW_STAINED_GLASS_PANE: + return 0.3F; + case CACTUS: + case CHORUS_FLOWER: + case CHORUS_PLANT: + case LADDER: + case NETHERRACK: + return 0.4F; + case ACACIA_BUTTON: + case ACACIA_PRESSURE_PLATE: + case BIRCH_BUTTON: + case BIRCH_PRESSURE_PLATE: + case BLACK_CONCRETE_POWDER: + case BLUE_CONCRETE_POWDER: + case BREWING_STAND: + case BROWN_CONCRETE_POWDER: + case CAKE: + case COARSE_DIRT: + case CYAN_CONCRETE_POWDER: + case DARK_OAK_BUTTON: + case DARK_OAK_PRESSURE_PLATE: + case DIRT: + case DRIED_KELP_BLOCK: + case FROSTED_ICE: + case GRAY_CONCRETE_POWDER: + case GREEN_CONCRETE_POWDER: + case HAY_BLOCK: + case HEAVY_WEIGHTED_PRESSURE_PLATE: + case ICE: + case JUNGLE_BUTTON: + case JUNGLE_PRESSURE_PLATE: + case LEVER: + case LIGHT_BLUE_CONCRETE_POWDER: + case LIGHT_GRAY_CONCRETE_POWDER: + case LIGHT_WEIGHTED_PRESSURE_PLATE: + case LIME_CONCRETE_POWDER: + case MAGENTA_CONCRETE_POWDER: + case MAGMA_BLOCK: + case OAK_BUTTON: + case OAK_PRESSURE_PLATE: + case ORANGE_CONCRETE_POWDER: + case PACKED_ICE: + case PINK_CONCRETE_POWDER: + case PISTON: + case PISTON_HEAD: + case PODZOL: + case PURPLE_CONCRETE_POWDER: + case RED_CONCRETE_POWDER: + case RED_SAND: + case SAND: + case SOUL_SAND: + case SPRUCE_BUTTON: + case SPRUCE_PRESSURE_PLATE: + case STICKY_PISTON: + case STONE_BUTTON: + case STONE_PRESSURE_PLATE: + case TURTLE_EGG: + case WHITE_CONCRETE_POWDER: + case YELLOW_CONCRETE_POWDER: + return 0.5F; + case CLAY: + case FARMLAND: + case GRASS_BLOCK: + case GRAVEL: + case MYCELIUM: + case SPONGE: + case WET_SPONGE: + return 0.6F; + case GRASS_PATH: + return 0.65F; + case ACTIVATOR_RAIL: + case DETECTOR_RAIL: + case POWERED_RAIL: + case RAIL: + return 0.7F; + case BLACK_WOOL: + case BLUE_WOOL: + case BROWN_WOOL: + case CHISELED_QUARTZ_BLOCK: + case CHISELED_RED_SANDSTONE: + case CHISELED_SANDSTONE: + case CUT_RED_SANDSTONE: + case CUT_SANDSTONE: + case CYAN_WOOL: + case END_STONE_BRICKS: + case GRAY_WOOL: + case GREEN_WOOL: + case LIGHT_BLUE_WOOL: + case LIGHT_GRAY_WOOL: + case LIME_WOOL: + case MAGENTA_WOOL: + case NOTE_BLOCK: + case ORANGE_WOOL: + case PINK_WOOL: + case PURPLE_WOOL: + case QUARTZ_BLOCK: + case QUARTZ_PILLAR: + case QUARTZ_STAIRS: + case RED_SANDSTONE: + case RED_SANDSTONE_STAIRS: + case RED_WOOL: + case SANDSTONE: + case SANDSTONE_STAIRS: + case WHITE_WOOL: + case YELLOW_WOOL: + return 0.8F; + case BLACK_BANNER: + case BLACK_WALL_BANNER: + case BLUE_BANNER: + case BLUE_WALL_BANNER: + case BROWN_BANNER: + case BROWN_WALL_BANNER: + case CARVED_PUMPKIN: + case CREEPER_HEAD: + case CREEPER_WALL_HEAD: + case CYAN_BANNER: + case CYAN_WALL_BANNER: + case DRAGON_HEAD: + case DRAGON_WALL_HEAD: + case GRAY_BANNER: + case GRAY_WALL_BANNER: + case GREEN_BANNER: + case GREEN_WALL_BANNER: + case JACK_O_LANTERN: + case LIGHT_BLUE_BANNER: + case LIGHT_BLUE_WALL_BANNER: + case LIGHT_GRAY_BANNER: + case LIGHT_GRAY_WALL_BANNER: + case LIME_BANNER: + case LIME_WALL_BANNER: + case MAGENTA_BANNER: + case MAGENTA_WALL_BANNER: + case MELON: + case NETHER_WART_BLOCK: + case ORANGE_BANNER: + case ORANGE_WALL_BANNER: + case PINK_BANNER: + case PINK_WALL_BANNER: + case PLAYER_HEAD: + case PLAYER_WALL_HEAD: + case PUMPKIN: + case PURPLE_BANNER: + case PURPLE_WALL_BANNER: + case RED_BANNER: + case RED_WALL_BANNER: + case SIGN: + case SKELETON_SKULL: + case SKELETON_WALL_SKULL: + case WALL_SIGN: + case WHITE_BANNER: + case WHITE_WALL_BANNER: + case WITHER_SKELETON_SKULL: + case WITHER_SKELETON_WALL_SKULL: + case YELLOW_BANNER: + case YELLOW_WALL_BANNER: + case ZOMBIE_HEAD: + case ZOMBIE_WALL_HEAD: + return 1.0F; + case BLACK_TERRACOTTA: + case BLUE_TERRACOTTA: + case BROWN_TERRACOTTA: + case CYAN_TERRACOTTA: + case GRAY_TERRACOTTA: + case GREEN_TERRACOTTA: + case LIGHT_BLUE_TERRACOTTA: + case LIGHT_GRAY_TERRACOTTA: + case LIME_TERRACOTTA: + case MAGENTA_TERRACOTTA: + case ORANGE_TERRACOTTA: + case PINK_TERRACOTTA: + case PURPLE_TERRACOTTA: + case RED_TERRACOTTA: + case TERRACOTTA: + case WHITE_TERRACOTTA: + case YELLOW_TERRACOTTA: + return 1.25F; + case BLACK_GLAZED_TERRACOTTA: + case BLUE_GLAZED_TERRACOTTA: + case BROWN_GLAZED_TERRACOTTA: + case CYAN_GLAZED_TERRACOTTA: + case GRAY_GLAZED_TERRACOTTA: + case GREEN_GLAZED_TERRACOTTA: + case LIGHT_BLUE_GLAZED_TERRACOTTA: + case LIGHT_GRAY_GLAZED_TERRACOTTA: + case LIME_GLAZED_TERRACOTTA: + case MAGENTA_GLAZED_TERRACOTTA: + case ORANGE_GLAZED_TERRACOTTA: + case PINK_GLAZED_TERRACOTTA: + case PURPLE_GLAZED_TERRACOTTA: + case RED_GLAZED_TERRACOTTA: + case WHITE_GLAZED_TERRACOTTA: + case YELLOW_GLAZED_TERRACOTTA: + return 1.4F; + case ANDESITE: + case BOOKSHELF: + case BRAIN_CORAL_BLOCK: + case BUBBLE_CORAL_BLOCK: + case CHISELED_STONE_BRICKS: + case CRACKED_STONE_BRICKS: + case DARK_PRISMARINE: + case DARK_PRISMARINE_SLAB: + case DARK_PRISMARINE_STAIRS: + case DEAD_BRAIN_CORAL_BLOCK: + case DEAD_BUBBLE_CORAL_BLOCK: + case DEAD_FIRE_CORAL_BLOCK: + case DEAD_HORN_CORAL_BLOCK: + case DEAD_TUBE_CORAL_BLOCK: + case DIORITE: + case FIRE_CORAL_BLOCK: + case GRANITE: + case HORN_CORAL_BLOCK: + case MOSSY_STONE_BRICKS: + case POLISHED_ANDESITE: + case POLISHED_DIORITE: + case POLISHED_GRANITE: + case PRISMARINE: + case PRISMARINE_BRICKS: + case PRISMARINE_BRICK_SLAB: + case PRISMARINE_BRICK_STAIRS: + case PRISMARINE_SLAB: + case PRISMARINE_STAIRS: + case PURPUR_BLOCK: + case PURPUR_PILLAR: + case PURPUR_STAIRS: + case STONE: + case STONE_BRICKS: + case STONE_BRICK_STAIRS: + case TUBE_CORAL_BLOCK: + return 1.5F; + case BLACK_CONCRETE: + case BLUE_CONCRETE: + case BROWN_CONCRETE: + case CYAN_CONCRETE: + case GRAY_CONCRETE: + case GREEN_CONCRETE: + case LIGHT_BLUE_CONCRETE: + case LIGHT_GRAY_CONCRETE: + case LIME_CONCRETE: + case MAGENTA_CONCRETE: + case ORANGE_CONCRETE: + case PINK_CONCRETE: + case PURPLE_CONCRETE: + case RED_CONCRETE: + case WHITE_CONCRETE: + case YELLOW_CONCRETE: + return 1.8F; + case ACACIA_FENCE: + case ACACIA_FENCE_GATE: + case ACACIA_LOG: + case ACACIA_PLANKS: + case ACACIA_SLAB: + case ACACIA_STAIRS: + case ACACIA_WOOD: + case BIRCH_FENCE: + case BIRCH_FENCE_GATE: + case BIRCH_LOG: + case BIRCH_PLANKS: + case BIRCH_SLAB: + case BIRCH_STAIRS: + case BIRCH_WOOD: + case BLACK_SHULKER_BOX: + case BLUE_SHULKER_BOX: + case BONE_BLOCK: + case BRICKS: + case BRICK_SLAB: + case BRICK_STAIRS: + case BROWN_SHULKER_BOX: + case CAULDRON: + case COBBLESTONE: + case COBBLESTONE_SLAB: + case COBBLESTONE_STAIRS: + case COBBLESTONE_WALL: + case CYAN_SHULKER_BOX: + case DARK_OAK_FENCE: + case DARK_OAK_FENCE_GATE: + case DARK_OAK_LOG: + case DARK_OAK_PLANKS: + case DARK_OAK_SLAB: + case DARK_OAK_STAIRS: + case DARK_OAK_WOOD: + case GRAY_SHULKER_BOX: + case GREEN_SHULKER_BOX: + case JUKEBOX: + case JUNGLE_FENCE: + case JUNGLE_FENCE_GATE: + case JUNGLE_LOG: + case JUNGLE_PLANKS: + case JUNGLE_SLAB: + case JUNGLE_STAIRS: + case JUNGLE_WOOD: + case LIGHT_BLUE_SHULKER_BOX: + case LIGHT_GRAY_SHULKER_BOX: + case LIME_SHULKER_BOX: + case MAGENTA_SHULKER_BOX: + case MOSSY_COBBLESTONE: + case MOSSY_COBBLESTONE_WALL: + case NETHER_BRICKS: + case NETHER_BRICK_FENCE: + case NETHER_BRICK_SLAB: + case NETHER_BRICK_STAIRS: + case OAK_FENCE: + case OAK_FENCE_GATE: + case OAK_LOG: + case OAK_PLANKS: + case OAK_SLAB: + case OAK_STAIRS: + case OAK_WOOD: + case ORANGE_SHULKER_BOX: + case PETRIFIED_OAK_SLAB: + case PINK_SHULKER_BOX: + case PURPLE_SHULKER_BOX: + case PURPUR_SLAB: + case QUARTZ_SLAB: + case RED_NETHER_BRICKS: + case RED_SANDSTONE_SLAB: + case RED_SHULKER_BOX: + case SANDSTONE_SLAB: + case SHULKER_BOX: + case SMOOTH_QUARTZ: + case SMOOTH_RED_SANDSTONE: + case SMOOTH_SANDSTONE: + case SMOOTH_STONE: + case SPRUCE_FENCE: + case SPRUCE_FENCE_GATE: + case SPRUCE_LOG: + case SPRUCE_PLANKS: + case SPRUCE_SLAB: + case SPRUCE_STAIRS: + case SPRUCE_WOOD: + case STONE_BRICK_SLAB: + case STONE_SLAB: + case STRIPPED_ACACIA_LOG: + case STRIPPED_ACACIA_WOOD: + case STRIPPED_BIRCH_LOG: + case STRIPPED_BIRCH_WOOD: + case STRIPPED_DARK_OAK_LOG: + case STRIPPED_DARK_OAK_WOOD: + case STRIPPED_JUNGLE_LOG: + case STRIPPED_JUNGLE_WOOD: + case STRIPPED_OAK_LOG: + case STRIPPED_OAK_WOOD: + case STRIPPED_SPRUCE_LOG: + case STRIPPED_SPRUCE_WOOD: + case WHITE_SHULKER_BOX: + case YELLOW_SHULKER_BOX: + return 2.0F; + case CHEST: + case CRAFTING_TABLE: + case TRAPPED_CHEST: + return 2.5F; + case BLUE_ICE: + return 2.8F; + case ACACIA_DOOR: + case ACACIA_TRAPDOOR: + case BEACON: + case BIRCH_DOOR: + case BIRCH_TRAPDOOR: + case COAL_ORE: + case CONDUIT: + case DARK_OAK_DOOR: + case DARK_OAK_TRAPDOOR: + case DIAMOND_ORE: + case DRAGON_EGG: + case EMERALD_ORE: + case END_STONE: + case GOLD_BLOCK: + case GOLD_ORE: + case HOPPER: + case IRON_ORE: + case JUNGLE_DOOR: + case JUNGLE_TRAPDOOR: + case LAPIS_BLOCK: + case LAPIS_ORE: + case NETHER_QUARTZ_ORE: + case OAK_DOOR: + case OAK_TRAPDOOR: + case OBSERVER: + case REDSTONE_ORE: + case SPRUCE_DOOR: + case SPRUCE_TRAPDOOR: + return 3.0F; + case DISPENSER: + case DROPPER: + case FURNACE: + return 3.5F; + case COBWEB: + return 4.0F; + case ANVIL: + case CHIPPED_ANVIL: + case COAL_BLOCK: + case DAMAGED_ANVIL: + case DIAMOND_BLOCK: + case EMERALD_BLOCK: + case ENCHANTING_TABLE: + case IRON_BARS: + case IRON_BLOCK: + case IRON_DOOR: + case IRON_TRAPDOOR: + case REDSTONE_BLOCK: + case SPAWNER: + return 5.0F; + case ENDER_CHEST: + return 22.5F; + case OBSIDIAN: + return 50.0F; + case LAVA: + case WATER: + return 100.0F; + default: + return 0F; + // + } + } + + /** + * Obtains the blast resistance value (also known as block "durability"). + *
+ * This value is used in explosions to calculate whether a block should be + * broken or not. + *
+ * Only available when {@link #isBlock()} is true. + * + * @return the blast resistance of that material. + */ + public float getBlastResistance() { + Validate.isTrue(isBlock(), "The Material is not a block!"); + switch (this) { + // + case BLACK_CARPET: + case BLUE_CARPET: + case BROWN_CARPET: + case CYAN_CARPET: + case GRAY_CARPET: + case GREEN_CARPET: + case LIGHT_BLUE_CARPET: + case LIGHT_GRAY_CARPET: + case LIME_CARPET: + case MAGENTA_CARPET: + case ORANGE_CARPET: + case PINK_CARPET: + case PURPLE_CARPET: + case RED_CARPET: + case SNOW: + case WHITE_CARPET: + case YELLOW_CARPET: + return 0.1F; + case ACACIA_LEAVES: + case BIRCH_LEAVES: + case BLACK_BED: + case BLUE_BED: + case BROWN_BED: + case BROWN_MUSHROOM_BLOCK: + case CYAN_BED: + case DARK_OAK_LEAVES: + case DAYLIGHT_DETECTOR: + case GRAY_BED: + case GREEN_BED: + case JUNGLE_LEAVES: + case LIGHT_BLUE_BED: + case LIGHT_GRAY_BED: + case LIME_BED: + case MAGENTA_BED: + case MUSHROOM_STEM: + case OAK_LEAVES: + case ORANGE_BED: + case PINK_BED: + case PURPLE_BED: + case RED_BED: + case RED_MUSHROOM_BLOCK: + case SNOW_BLOCK: + case SPRUCE_LEAVES: + case VINE: + case WHITE_BED: + case YELLOW_BED: + return 0.2F; + case BLACK_STAINED_GLASS: + case BLACK_STAINED_GLASS_PANE: + case BLUE_STAINED_GLASS: + case BLUE_STAINED_GLASS_PANE: + case BROWN_STAINED_GLASS: + case BROWN_STAINED_GLASS_PANE: + case CYAN_STAINED_GLASS: + case CYAN_STAINED_GLASS_PANE: + case GLASS: + case GLASS_PANE: + case GLOWSTONE: + case GRAY_STAINED_GLASS: + case GRAY_STAINED_GLASS_PANE: + case GREEN_STAINED_GLASS: + case GREEN_STAINED_GLASS_PANE: + case LIGHT_BLUE_STAINED_GLASS: + case LIGHT_BLUE_STAINED_GLASS_PANE: + case LIGHT_GRAY_STAINED_GLASS: + case LIGHT_GRAY_STAINED_GLASS_PANE: + case LIME_STAINED_GLASS: + case LIME_STAINED_GLASS_PANE: + case MAGENTA_STAINED_GLASS: + case MAGENTA_STAINED_GLASS_PANE: + case ORANGE_STAINED_GLASS: + case ORANGE_STAINED_GLASS_PANE: + case PINK_STAINED_GLASS: + case PINK_STAINED_GLASS_PANE: + case PURPLE_STAINED_GLASS: + case PURPLE_STAINED_GLASS_PANE: + case REDSTONE_LAMP: + case RED_STAINED_GLASS: + case RED_STAINED_GLASS_PANE: + case SEA_LANTERN: + case WHITE_STAINED_GLASS: + case WHITE_STAINED_GLASS_PANE: + case YELLOW_STAINED_GLASS: + case YELLOW_STAINED_GLASS_PANE: + return 0.3F; + case CACTUS: + case CHORUS_FLOWER: + case CHORUS_PLANT: + case LADDER: + case NETHERRACK: + return 0.4F; + case ACACIA_BUTTON: + case ACACIA_PRESSURE_PLATE: + case BIRCH_BUTTON: + case BIRCH_PRESSURE_PLATE: + case BLACK_CONCRETE_POWDER: + case BLUE_CONCRETE_POWDER: + case BREWING_STAND: + case BROWN_CONCRETE_POWDER: + case CAKE: + case COARSE_DIRT: + case CYAN_CONCRETE_POWDER: + case DARK_OAK_BUTTON: + case DARK_OAK_PRESSURE_PLATE: + case DIRT: + case FROSTED_ICE: + case GRAY_CONCRETE_POWDER: + case GREEN_CONCRETE_POWDER: + case HAY_BLOCK: + case HEAVY_WEIGHTED_PRESSURE_PLATE: + case ICE: + case JUNGLE_BUTTON: + case JUNGLE_PRESSURE_PLATE: + case LEVER: + case LIGHT_BLUE_CONCRETE_POWDER: + case LIGHT_GRAY_CONCRETE_POWDER: + case LIGHT_WEIGHTED_PRESSURE_PLATE: + case LIME_CONCRETE_POWDER: + case MAGENTA_CONCRETE_POWDER: + case MAGMA_BLOCK: + case OAK_BUTTON: + case OAK_PRESSURE_PLATE: + case ORANGE_CONCRETE_POWDER: + case PACKED_ICE: + case PINK_CONCRETE_POWDER: + case PISTON: + case PISTON_HEAD: + case PODZOL: + case PURPLE_CONCRETE_POWDER: + case RED_CONCRETE_POWDER: + case RED_SAND: + case SAND: + case SOUL_SAND: + case SPRUCE_BUTTON: + case SPRUCE_PRESSURE_PLATE: + case STICKY_PISTON: + case STONE_BUTTON: + case STONE_PRESSURE_PLATE: + case TURTLE_EGG: + case WHITE_CONCRETE_POWDER: + case YELLOW_CONCRETE_POWDER: + return 0.5F; + case CLAY: + case FARMLAND: + case GRASS_BLOCK: + case GRAVEL: + case MYCELIUM: + case SPONGE: + case WET_SPONGE: + return 0.6F; + case GRASS_PATH: + return 0.65F; + case ACTIVATOR_RAIL: + case DETECTOR_RAIL: + case POWERED_RAIL: + case RAIL: + return 0.7F; + case INFESTED_CHISELED_STONE_BRICKS: + case INFESTED_COBBLESTONE: + case INFESTED_CRACKED_STONE_BRICKS: + case INFESTED_MOSSY_STONE_BRICKS: + case INFESTED_STONE: + case INFESTED_STONE_BRICKS: + return 0.75F; + case BLACK_WOOL: + case BLUE_WOOL: + case BROWN_WOOL: + case CHISELED_QUARTZ_BLOCK: + case CHISELED_RED_SANDSTONE: + case CHISELED_SANDSTONE: + case CUT_RED_SANDSTONE: + case CUT_SANDSTONE: + case CYAN_WOOL: + case END_STONE_BRICKS: + case GRAY_WOOL: + case GREEN_WOOL: + case LIGHT_BLUE_WOOL: + case LIGHT_GRAY_WOOL: + case LIME_WOOL: + case MAGENTA_WOOL: + case NOTE_BLOCK: + case ORANGE_WOOL: + case PINK_WOOL: + case PURPLE_WOOL: + case QUARTZ_BLOCK: + case QUARTZ_PILLAR: + case QUARTZ_STAIRS: + case RED_SANDSTONE: + case RED_SANDSTONE_STAIRS: + case RED_WOOL: + case SANDSTONE: + case SANDSTONE_STAIRS: + case WHITE_WOOL: + case YELLOW_WOOL: + return 0.8F; + case BLACK_BANNER: + case BLACK_WALL_BANNER: + case BLUE_BANNER: + case BLUE_WALL_BANNER: + case BROWN_BANNER: + case BROWN_WALL_BANNER: + case CARVED_PUMPKIN: + case CREEPER_HEAD: + case CREEPER_WALL_HEAD: + case CYAN_BANNER: + case CYAN_WALL_BANNER: + case DRAGON_HEAD: + case DRAGON_WALL_HEAD: + case GRAY_BANNER: + case GRAY_WALL_BANNER: + case GREEN_BANNER: + case GREEN_WALL_BANNER: + case JACK_O_LANTERN: + case LIGHT_BLUE_BANNER: + case LIGHT_BLUE_WALL_BANNER: + case LIGHT_GRAY_BANNER: + case LIGHT_GRAY_WALL_BANNER: + case LIME_BANNER: + case LIME_WALL_BANNER: + case MAGENTA_BANNER: + case MAGENTA_WALL_BANNER: + case MELON: + case NETHER_WART_BLOCK: + case ORANGE_BANNER: + case ORANGE_WALL_BANNER: + case PINK_BANNER: + case PINK_WALL_BANNER: + case PLAYER_HEAD: + case PLAYER_WALL_HEAD: + case PUMPKIN: + case PURPLE_BANNER: + case PURPLE_WALL_BANNER: + case RED_BANNER: + case RED_WALL_BANNER: + case SIGN: + case SKELETON_SKULL: + case SKELETON_WALL_SKULL: + case WALL_SIGN: + case WHITE_BANNER: + case WHITE_WALL_BANNER: + case WITHER_SKELETON_SKULL: + case WITHER_SKELETON_WALL_SKULL: + case YELLOW_BANNER: + case YELLOW_WALL_BANNER: + case ZOMBIE_HEAD: + case ZOMBIE_WALL_HEAD: + return 1.0F; + case BLACK_GLAZED_TERRACOTTA: + case BLUE_GLAZED_TERRACOTTA: + case BROWN_GLAZED_TERRACOTTA: + case CYAN_GLAZED_TERRACOTTA: + case GRAY_GLAZED_TERRACOTTA: + case GREEN_GLAZED_TERRACOTTA: + case LIGHT_BLUE_GLAZED_TERRACOTTA: + case LIGHT_GRAY_GLAZED_TERRACOTTA: + case LIME_GLAZED_TERRACOTTA: + case MAGENTA_GLAZED_TERRACOTTA: + case ORANGE_GLAZED_TERRACOTTA: + case PINK_GLAZED_TERRACOTTA: + case PURPLE_GLAZED_TERRACOTTA: + case RED_GLAZED_TERRACOTTA: + case WHITE_GLAZED_TERRACOTTA: + case YELLOW_GLAZED_TERRACOTTA: + return 1.4F; + case BOOKSHELF: + return 1.5F; + case BLACK_CONCRETE: + case BLUE_CONCRETE: + case BROWN_CONCRETE: + case CYAN_CONCRETE: + case GRAY_CONCRETE: + case GREEN_CONCRETE: + case LIGHT_BLUE_CONCRETE: + case LIGHT_GRAY_CONCRETE: + case LIME_CONCRETE: + case MAGENTA_CONCRETE: + case ORANGE_CONCRETE: + case PINK_CONCRETE: + case PURPLE_CONCRETE: + case RED_CONCRETE: + case WHITE_CONCRETE: + case YELLOW_CONCRETE: + return 1.8F; + case ACACIA_LOG: + case ACACIA_WOOD: + case BIRCH_LOG: + case BIRCH_WOOD: + case BLACK_SHULKER_BOX: + case BLUE_SHULKER_BOX: + case BONE_BLOCK: + case BROWN_SHULKER_BOX: + case CAULDRON: + case CYAN_SHULKER_BOX: + case DARK_OAK_LOG: + case DARK_OAK_WOOD: + case GRAY_SHULKER_BOX: + case GREEN_SHULKER_BOX: + case JUNGLE_LOG: + case JUNGLE_WOOD: + case LIGHT_BLUE_SHULKER_BOX: + case LIGHT_GRAY_SHULKER_BOX: + case LIME_SHULKER_BOX: + case MAGENTA_SHULKER_BOX: + case OAK_LOG: + case OAK_WOOD: + case ORANGE_SHULKER_BOX: + case PINK_SHULKER_BOX: + case PURPLE_SHULKER_BOX: + case RED_SHULKER_BOX: + case SHULKER_BOX: + case SPRUCE_LOG: + case SPRUCE_WOOD: + case STRIPPED_ACACIA_LOG: + case STRIPPED_ACACIA_WOOD: + case STRIPPED_BIRCH_LOG: + case STRIPPED_BIRCH_WOOD: + case STRIPPED_DARK_OAK_LOG: + case STRIPPED_DARK_OAK_WOOD: + case STRIPPED_JUNGLE_LOG: + case STRIPPED_JUNGLE_WOOD: + case STRIPPED_OAK_LOG: + case STRIPPED_OAK_WOOD: + case STRIPPED_SPRUCE_LOG: + case STRIPPED_SPRUCE_WOOD: + case WHITE_SHULKER_BOX: + case YELLOW_SHULKER_BOX: + return 2.0F; + case CHEST: + case CRAFTING_TABLE: + case DRIED_KELP_BLOCK: + case TRAPPED_CHEST: + return 2.5F; + case BLUE_ICE: + return 2.8F; + case ACACIA_DOOR: + case ACACIA_FENCE: + case ACACIA_FENCE_GATE: + case ACACIA_PLANKS: + case ACACIA_SLAB: + case ACACIA_STAIRS: + case ACACIA_TRAPDOOR: + case BEACON: + case BIRCH_DOOR: + case BIRCH_FENCE: + case BIRCH_FENCE_GATE: + case BIRCH_PLANKS: + case BIRCH_SLAB: + case BIRCH_STAIRS: + case BIRCH_TRAPDOOR: + case COAL_ORE: + case COCOA: + case CONDUIT: + case DARK_OAK_DOOR: + case DARK_OAK_FENCE: + case DARK_OAK_FENCE_GATE: + case DARK_OAK_PLANKS: + case DARK_OAK_SLAB: + case DARK_OAK_STAIRS: + case DARK_OAK_TRAPDOOR: + case DIAMOND_ORE: + case EMERALD_ORE: + case GOLD_ORE: + case IRON_ORE: + case JUNGLE_DOOR: + case JUNGLE_FENCE: + case JUNGLE_FENCE_GATE: + case JUNGLE_PLANKS: + case JUNGLE_SLAB: + case JUNGLE_STAIRS: + case JUNGLE_TRAPDOOR: + case LAPIS_BLOCK: + case LAPIS_ORE: + case NETHER_QUARTZ_ORE: + case OAK_DOOR: + case OAK_FENCE: + case OAK_FENCE_GATE: + case OAK_PLANKS: + case OAK_SLAB: + case OAK_STAIRS: + case OAK_TRAPDOOR: + case OBSERVER: + case REDSTONE_ORE: + case SPRUCE_DOOR: + case SPRUCE_FENCE: + case SPRUCE_FENCE_GATE: + case SPRUCE_PLANKS: + case SPRUCE_SLAB: + case SPRUCE_STAIRS: + case SPRUCE_TRAPDOOR: + return 3.0F; + case DISPENSER: + case DROPPER: + case FURNACE: + return 3.5F; + case COBWEB: + return 4.0F; + case BLACK_TERRACOTTA: + case BLUE_TERRACOTTA: + case BROWN_TERRACOTTA: + case CYAN_TERRACOTTA: + case GRAY_TERRACOTTA: + case GREEN_TERRACOTTA: + case LIGHT_BLUE_TERRACOTTA: + case LIGHT_GRAY_TERRACOTTA: + case LIME_TERRACOTTA: + case MAGENTA_TERRACOTTA: + case ORANGE_TERRACOTTA: + case PINK_TERRACOTTA: + case PURPLE_TERRACOTTA: + case RED_TERRACOTTA: + case TERRACOTTA: + case WHITE_TERRACOTTA: + case YELLOW_TERRACOTTA: + return 4.2F; + case HOPPER: + return 4.8F; + case IRON_DOOR: + case IRON_TRAPDOOR: + case SPAWNER: + return 5.0F; + case ANDESITE: + case BRAIN_CORAL_BLOCK: + case BRICKS: + case BRICK_SLAB: + case BRICK_STAIRS: + case BUBBLE_CORAL_BLOCK: + case CHISELED_STONE_BRICKS: + case COAL_BLOCK: + case COBBLESTONE: + case COBBLESTONE_SLAB: + case COBBLESTONE_STAIRS: + case COBBLESTONE_WALL: + case CRACKED_STONE_BRICKS: + case DARK_PRISMARINE: + case DARK_PRISMARINE_SLAB: + case DARK_PRISMARINE_STAIRS: + case DEAD_BRAIN_CORAL_BLOCK: + case DEAD_BUBBLE_CORAL_BLOCK: + case DEAD_FIRE_CORAL_BLOCK: + case DEAD_HORN_CORAL_BLOCK: + case DEAD_TUBE_CORAL_BLOCK: + case DIAMOND_BLOCK: + case DIORITE: + case EMERALD_BLOCK: + case FIRE_CORAL_BLOCK: + case GOLD_BLOCK: + case GRANITE: + case HORN_CORAL_BLOCK: + case IRON_BARS: + case IRON_BLOCK: + case JUKEBOX: + case MOSSY_COBBLESTONE: + case MOSSY_COBBLESTONE_WALL: + case MOSSY_STONE_BRICKS: + case NETHER_BRICKS: + case NETHER_BRICK_FENCE: + case NETHER_BRICK_SLAB: + case NETHER_BRICK_STAIRS: + case PETRIFIED_OAK_SLAB: + case POLISHED_ANDESITE: + case POLISHED_DIORITE: + case POLISHED_GRANITE: + case PRISMARINE: + case PRISMARINE_BRICKS: + case PRISMARINE_BRICK_SLAB: + case PRISMARINE_BRICK_STAIRS: + case PRISMARINE_SLAB: + case PRISMARINE_STAIRS: + case PURPUR_BLOCK: + case PURPUR_PILLAR: + case PURPUR_SLAB: + case PURPUR_STAIRS: + case QUARTZ_SLAB: + case REDSTONE_BLOCK: + case RED_NETHER_BRICKS: + case RED_SANDSTONE_SLAB: + case SANDSTONE_SLAB: + case SMOOTH_QUARTZ: + case SMOOTH_RED_SANDSTONE: + case SMOOTH_SANDSTONE: + case SMOOTH_STONE: + case STONE: + case STONE_BRICKS: + case STONE_BRICK_SLAB: + case STONE_BRICK_STAIRS: + case STONE_SLAB: + case TUBE_CORAL_BLOCK: + return 6.0F; + case DRAGON_EGG: + case END_STONE: + return 9.0F; + case LAVA: + case WATER: + return 100.0F; + case ENDER_CHEST: + return 600.0F; + case ANVIL: + case CHIPPED_ANVIL: + case DAMAGED_ANVIL: + case ENCHANTING_TABLE: + case OBSIDIAN: + return 1200.0F; + case BEDROCK: + case CHAIN_COMMAND_BLOCK: + case COMMAND_BLOCK: + case END_GATEWAY: + case END_PORTAL: + case END_PORTAL_FRAME: + case REPEATING_COMMAND_BLOCK: + case STRUCTURE_BLOCK: + return 3600000.0F; + case BARRIER: + return 3600000.8F; + default: + return 0; + // + } + } +} diff --git a/api/src/main/java/org/bukkit/Nameable.java b/api/src/main/java/org/bukkit/Nameable.java new file mode 100644 index 000000000..fee814e01 --- /dev/null +++ b/api/src/main/java/org/bukkit/Nameable.java @@ -0,0 +1,31 @@ +package org.bukkit; + +import org.jetbrains.annotations.Nullable; + +public interface Nameable { + + /** + * Gets the custom name on a mob or block. If there is no name this method + * will return null. + *

+ * This value has no effect on players, they will always use their real + * name. + * + * @return name of the mob/block or null + */ + @Nullable + public String getCustomName(); + + /** + * Sets a custom name on a mob or block. This name will be used in death + * messages and can be sent to the client as a nameplate over the mob. + *

+ * Setting the name to null or an empty string will clear it. + *

+ * This value has no effect on players, they will always use their real + * name. + * + * @param name the name to set + */ + public void setCustomName(@Nullable String name); +} diff --git a/api/src/main/java/org/bukkit/NamespacedKey.java b/api/src/main/java/org/bukkit/NamespacedKey.java new file mode 100644 index 000000000..8648e8fb6 --- /dev/null +++ b/api/src/main/java/org/bukkit/NamespacedKey.java @@ -0,0 +1,144 @@ +package org.bukkit; + +import com.google.common.base.Preconditions; +import java.util.Locale; +import java.util.UUID; +import java.util.regex.Pattern; +import org.bukkit.plugin.Plugin; +import org.jetbrains.annotations.NotNull; + +/** + * Represents a String based key which consists of two components - a namespace + * and a key. + * + * Namespaces may only contain lowercase alphanumeric characters, periods, + * underscores, and hyphens. + *

+ * Keys may only contain lowercase alphanumeric characters, periods, + * underscores, hyphens, and forward slashes. + * + */ +public final class NamespacedKey implements com.destroystokyo.paper.Namespaced { // Paper - implement namespaced + + /** + * The namespace representing all inbuilt keys. + */ + public static final String MINECRAFT = "minecraft"; + /** + * The namespace representing all keys generated by Bukkit for backwards + * compatibility measures. + */ + public static final String BUKKIT = "bukkit"; + // + private static final Pattern VALID_NAMESPACE = Pattern.compile("[a-z0-9._-]+"); + private static final Pattern VALID_KEY = Pattern.compile("[a-z0-9/._-]+"); + // + private final String namespace; + private final String key; + + /** + * Create a key in a specific namespace. + * + * @param namespace String representing a grouping of keys + * @param key Name for this specific key + * @deprecated should never be used by plugins, for internal use only!! + */ + @Deprecated + public NamespacedKey(@NotNull String namespace, @NotNull String key) { + Preconditions.checkArgument(namespace != null && VALID_NAMESPACE.matcher(namespace).matches(), "Invalid namespace. Must be [a-z0-9._-]: %s", namespace); + Preconditions.checkArgument(key != null && VALID_KEY.matcher(key).matches(), "Invalid key. Must be [a-z0-9/._-]: %s", key); + + this.namespace = namespace; + this.key = key; + + String string = toString(); + Preconditions.checkArgument(string.length() < 256, "NamespacedKey must be less than 256 characters", string); + } + + /** + * Create a key in the plugin's namespace. + *

+ * Namespaces may only contain lowercase alphanumeric characters, periods, + * underscores, and hyphens. + *

+ * Keys may only contain lowercase alphanumeric characters, periods, + * underscores, hyphens, and forward slashes. + * + * @param plugin the plugin to use for the namespace + * @param key the key to create + */ + public NamespacedKey(@NotNull Plugin plugin, @NotNull String key) { + Preconditions.checkArgument(plugin != null, "Plugin cannot be null"); + Preconditions.checkArgument(key != null, "Key cannot be null"); + + this.namespace = plugin.getName().toLowerCase(Locale.ROOT); + this.key = key.toLowerCase().toLowerCase(Locale.ROOT); + + // Check validity after normalization + Preconditions.checkArgument(VALID_NAMESPACE.matcher(this.namespace).matches(), "Invalid namespace. Must be [a-z0-9._-]: %s", this.namespace); + Preconditions.checkArgument(VALID_KEY.matcher(this.key).matches(), "Invalid key. Must be [a-z0-9/._-]: %s", this.key); + + String string = toString(); + Preconditions.checkArgument(string.length() < 256, "NamespacedKey must be less than 256 characters (%s)", string); + } + + @NotNull + @Override // Paper + public String getNamespace() { + return namespace; + } + + @NotNull + @Override // Paper + public String getKey() { + return key; + } + + @Override + public int hashCode() { + int hash = 5; + hash = 47 * hash + this.namespace.hashCode(); + hash = 47 * hash + this.key.hashCode(); + return hash; + } + + @Override + public boolean equals(Object obj) { + if (obj == null) { + return false; + } + if (getClass() != obj.getClass()) { + return false; + } + final NamespacedKey other = (NamespacedKey) obj; + return this.namespace.equals(other.namespace) && this.key.equals(other.key); + } + + @Override + public String toString() { + return this.namespace + ":" + this.key; + } + + /** + * Return a new random key in the {@link #BUKKIT} namespace. + * + * @return new key + * @deprecated should never be used by plugins, for internal use only!! + */ + @Deprecated + @NotNull + public static NamespacedKey randomKey() { + return new NamespacedKey(BUKKIT, UUID.randomUUID().toString()); + } + + /** + * Get a key in the Minecraft namespace. + * + * @param key the key to use + * @return new key in the Minecraft namespace + */ + @NotNull + public static NamespacedKey minecraft(@NotNull String key) { + return new NamespacedKey(MINECRAFT, key); + } +} diff --git a/api/src/main/java/org/bukkit/NetherWartsState.java b/api/src/main/java/org/bukkit/NetherWartsState.java new file mode 100644 index 000000000..f43209cf7 --- /dev/null +++ b/api/src/main/java/org/bukkit/NetherWartsState.java @@ -0,0 +1,21 @@ +package org.bukkit; + +public enum NetherWartsState { + + /** + * State when first seeded + */ + SEEDED, + /** + * First growth stage + */ + STAGE_ONE, + /** + * Second growth stage + */ + STAGE_TWO, + /** + * Ready to harvest + */ + RIPE; +} diff --git a/api/src/main/java/org/bukkit/Note.java b/api/src/main/java/org/bukkit/Note.java new file mode 100644 index 000000000..6aa02542b --- /dev/null +++ b/api/src/main/java/org/bukkit/Note.java @@ -0,0 +1,285 @@ +package org.bukkit; + +import java.util.Map; + +import org.apache.commons.lang.Validate; + +import com.google.common.collect.Maps; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +/** + * A note class to store a specific note. + */ +public class Note { + + /** + * An enum holding tones. + */ + public enum Tone { + G(0x1, true), + A(0x3, true), + B(0x5, false), + C(0x6, true), + D(0x8, true), + E(0xA, false), + F(0xB, true); + + private final boolean sharpable; + private final byte id; + + private static final Map BY_DATA = Maps.newHashMap(); + /** The number of tones including sharped tones. */ + public static final byte TONES_COUNT = 12; + + private Tone(int id, boolean sharpable) { + this.id = (byte) (id % TONES_COUNT); + this.sharpable = sharpable; + } + + /** + * Returns the not sharped id of this tone. + * + * @return the not sharped id of this tone. + * @deprecated Magic value + */ + @Deprecated + public byte getId() { + return getId(false); + } + + /** + * Returns the id of this tone. These method allows to return the + * sharped id of the tone. If the tone couldn't be sharped it always + * return the not sharped id of this tone. + * + * @param sharped Set to true to return the sharped id. + * @return the id of this tone. + * @deprecated Magic value + */ + @Deprecated + public byte getId(boolean sharped) { + byte id = (byte) (sharped && sharpable ? this.id + 1 : this.id); + + return (byte) (id % TONES_COUNT); + } + + /** + * Returns if this tone could be sharped. + * + * @return if this tone could be sharped. + */ + public boolean isSharpable() { + return sharpable; + } + + /** + * Returns if this tone id is the sharped id of the tone. + * + * @param id the id of the tone. + * @return if the tone id is the sharped id of the tone. + * @throws IllegalArgumentException if neither the tone nor the + * semitone have the id. + * @deprecated Magic value + */ + @Deprecated + public boolean isSharped(byte id) { + if (id == getId(false)) { + return false; + } else if (id == getId(true)) { + return true; + } else { + // The id isn't matching to the tone! + throw new IllegalArgumentException("The id isn't matching to the tone."); + } + } + + /** + * Returns the tone to id. Also returning the semitones. + * + * @param id the id of the tone. + * @return the tone to id. + * @deprecated Magic value + */ + @Deprecated + @Nullable + public static Tone getById(byte id) { + return BY_DATA.get(id); + } + + static { + for (Tone tone : values()) { + int id = tone.id % TONES_COUNT; + BY_DATA.put((byte) id, tone); + + if (tone.isSharpable()) { + id = (id + 1) % TONES_COUNT; + BY_DATA.put((byte) id, tone); + } + } + } + } + + private final byte note; + + /** + * Creates a new note. + * + * @param note Internal note id. {@link #getId()} always return this + * value. The value has to be in the interval [0; 24]. + */ + public Note(int note) { + Validate.isTrue(note >= 0 && note <= 24, "The note value has to be between 0 and 24."); + + this.note = (byte) note; + } + + /** + * Creates a new note. + * + * @param octave The octave where the note is in. Has to be 0 - 2. + * @param tone The tone within the octave. If the octave is 2 the note has + * to be F#. + * @param sharped Set if the tone is sharped (e.g. for F#). + */ + public Note(int octave, @NotNull Tone tone, boolean sharped) { + if (sharped && !tone.isSharpable()) { + tone = Tone.values()[tone.ordinal() + 1]; + sharped = false; + } + if (octave < 0 || octave > 2 || (octave == 2 && !(tone == Tone.F && sharped))) { + throw new IllegalArgumentException("Tone and octave have to be between F#0 and F#2"); + } + + this.note = (byte) (octave * Tone.TONES_COUNT + tone.getId(sharped)); + } + + /** + * Creates a new note for a flat tone, such as A-flat. + * + * @param octave The octave where the note is in. Has to be 0 - 1. + * @param tone The tone within the octave. + * @return The new note. + */ + @NotNull + public static Note flat(int octave, @NotNull Tone tone) { + Validate.isTrue(octave != 2, "Octave cannot be 2 for flats"); + tone = tone == Tone.G ? Tone.F : Tone.values()[tone.ordinal() - 1]; + return new Note(octave, tone, tone.isSharpable()); + } + + /** + * Creates a new note for a sharp tone, such as A-sharp. + * + * @param octave The octave where the note is in. Has to be 0 - 2. + * @param tone The tone within the octave. If the octave is 2 the note has + * to be F#. + * @return The new note. + */ + @NotNull + public static Note sharp(int octave, @NotNull Tone tone) { + return new Note(octave, tone, true); + } + + /** + * Creates a new note for a natural tone, such as A-natural. + * + * @param octave The octave where the note is in. Has to be 0 - 1. + * @param tone The tone within the octave. + * @return The new note. + */ + @NotNull + public static Note natural(int octave, @NotNull Tone tone) { + Validate.isTrue(octave != 2, "Octave cannot be 2 for naturals"); + return new Note(octave, tone, false); + } + + /** + * @return The note a semitone above this one. + */ + @NotNull + public Note sharped() { + Validate.isTrue(note < 24, "This note cannot be sharped because it is the highest known note!"); + return new Note(note + 1); + } + + /** + * @return The note a semitone below this one. + */ + @NotNull + public Note flattened() { + Validate.isTrue(note > 0, "This note cannot be flattened because it is the lowest known note!"); + return new Note(note - 1); + } + + /** + * Returns the internal id of this note. + * + * @return the internal id of this note. + * @deprecated Magic value + */ + @Deprecated + public byte getId() { + return note; + } + + /** + * Returns the octave of this note. + * + * @return the octave of this note. + */ + public int getOctave() { + return note / Tone.TONES_COUNT; + } + + private byte getToneByte() { + return (byte) (note % Tone.TONES_COUNT); + } + + /** + * Returns the tone of this note. + * + * @return the tone of this note. + */ + @NotNull + public Tone getTone() { + return Tone.getById(getToneByte()); + } + + /** + * Returns if this note is sharped. + * + * @return if this note is sharped. + */ + public boolean isSharped() { + byte note = getToneByte(); + return Tone.getById(note).isSharped(note); + } + + @Override + public int hashCode() { + final int prime = 31; + int result = 1; + result = prime * result + note; + return result; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) + return true; + if (obj == null) + return false; + if (getClass() != obj.getClass()) + return false; + Note other = (Note) obj; + if (note != other.note) + return false; + return true; + } + + @Override + public String toString() { + return "Note{" + getTone().toString() + (isSharped() ? "#" : "") + "}"; + } +} diff --git a/api/src/main/java/org/bukkit/OfflinePlayer.java b/api/src/main/java/org/bukkit/OfflinePlayer.java new file mode 100644 index 000000000..30195c045 --- /dev/null +++ b/api/src/main/java/org/bukkit/OfflinePlayer.java @@ -0,0 +1,195 @@ +package org.bukkit; + +import java.util.UUID; + +import org.bukkit.configuration.serialization.ConfigurationSerializable; +import org.bukkit.entity.AnimalTamer; +import org.bukkit.entity.Player; +import org.bukkit.permissions.ServerOperator; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +public interface OfflinePlayer extends ServerOperator, AnimalTamer, ConfigurationSerializable { + + /** + * Checks if this player is currently online + * + * @return true if they are online + */ + public boolean isOnline(); + + /** + * Returns the name of this player + *

+ * Names are no longer unique past a single game session. For persistent storage + * it is recommended that you use {@link #getUniqueId()} instead. + * + * @return Player name or null if we have not seen a name for this player yet + */ + @Nullable + public String getName(); + + /** + * Returns the UUID of this player + * + * @return Player UUID + */ + @NotNull + public UUID getUniqueId(); + + /** + * Checks if this player is banned or not + * + * @return true if banned, otherwise false + */ + public boolean isBanned(); + // Paper start + + /** + * Permanently Bans this player from the server + * + * @param reason Reason for Ban + * @return Ban Entry + */ + @NotNull + public default BanEntry banPlayer(@Nullable String reason) { + return banPlayer(reason, null, null); + } + + /** + * Permanently Bans this player from the server + * @param reason Reason for Ban + * @param source Source of the ban, or null for default + * @return Ban Entry + */ + @NotNull + public default BanEntry banPlayer(@Nullable String reason, @Nullable String source) { + return banPlayer(reason, null, source); + } + + /** + * Bans this player from the server + * @param reason Reason for Ban + * @param expires When to expire the ban + * @return Ban Entry + */ + @NotNull + public default BanEntry banPlayer(@Nullable String reason, @Nullable java.util.Date expires) { + return banPlayer(reason, expires, null); + } + + /** + * Bans this player from the server + * @param reason Reason for Ban + * @param expires When to expire the ban + * @param source Source of the ban or null for default + * @return Ban Entry + */ + @NotNull + public default BanEntry banPlayer(@Nullable String reason, @Nullable java.util.Date expires, @Nullable String source) { + return banPlayer(reason, expires, source, true); + } + @NotNull + public default BanEntry banPlayer(@Nullable String reason, @Nullable java.util.Date expires, @Nullable String source, boolean kickIfOnline) { + BanEntry banEntry = Bukkit.getServer().getBanList(BanList.Type.NAME).addBan(getName(), reason, expires, source); + if (kickIfOnline && isOnline()) { + getPlayer().kickPlayer(reason); + } + return banEntry; + } + // Paper end + + /** + * Checks if this player is whitelisted or not + * + * @return true if whitelisted + */ + public boolean isWhitelisted(); + + /** + * Sets if this player is whitelisted or not + * + * @param value true if whitelisted + */ + public void setWhitelisted(boolean value); + + /** + * Gets a {@link Player} object that this represents, if there is one + *

+ * If the player is online, this will return that player. Otherwise, + * it will return null. + * + * @return Online player + */ + @Nullable + public Player getPlayer(); + + /** + * Gets the first date and time that this player was witnessed on this + * server. + *

+ * If the player has never played before, this will return 0. Otherwise, + * it will be the amount of milliseconds since midnight, January 1, 1970 + * UTC. + * + * @return Date of first log-in for this player, or 0 + */ + public long getFirstPlayed(); + + /** + * Gets the last date and time that this player was witnessed on this + * server. + *

+ * If the player has never played before, this will return 0. Otherwise, + * it will be the amount of milliseconds since midnight, January 1, 1970 + * UTC. + * + * @return Date of last log-in for this player, or 0 + * @deprecated The API contract is ambiguous and the implementation may or may not return the correct value given this API ambiguity. It is instead recommended use {@link #getLastLogin()} or {@link #getLastSeen()} depending on your needs. + */ + @Deprecated + public long getLastPlayed(); + + /** + * Checks if this player has played on this server before. + * + * @return True if the player has played before, otherwise false + */ + public boolean hasPlayedBefore(); + + /** + * Gets the Location where the player will spawn at their bed, null if + * they have not slept in one or their current bed spawn is invalid. + * + * @return Bed Spawn Location if bed exists, otherwise null. + */ + @Nullable + public Location getBedSpawnLocation(); + + // Paper start + + /** + * Gets the last date and time that this player logged into the server. + *

+ * If the player has never played before, this will return 0. Otherwise, + * it will be the amount of milliseconds since midnight, January 1, 1970 + * UTC. + * + * @return last login time + */ + public long getLastLogin(); + + /** + * Gets the last date and time that this player was seen on the server. + *

+ * If the player has never played before, this will return 0. If the + * player is currently online, this will return the current time. + * Otherwise it will be the amount of milliseconds since midnight, + * January 1, 1970 UTC. + * + * @return last seen time + */ + public long getLastSeen(); + // Paper end + +} diff --git a/api/src/main/java/org/bukkit/Particle.java b/api/src/main/java/org/bukkit/Particle.java new file mode 100644 index 000000000..a919c6d5b --- /dev/null +++ b/api/src/main/java/org/bukkit/Particle.java @@ -0,0 +1,132 @@ +package org.bukkit; + +import com.google.common.base.Preconditions; +import org.bukkit.block.data.BlockData; +import org.bukkit.inventory.ItemStack; +import org.bukkit.material.MaterialData; +import org.jetbrains.annotations.NotNull; + +public enum Particle { + EXPLOSION_NORMAL, + EXPLOSION_LARGE, + EXPLOSION_HUGE, + FIREWORKS_SPARK, + WATER_BUBBLE, + WATER_SPLASH, + WATER_WAKE, + SUSPENDED, + SUSPENDED_DEPTH, + CRIT, + CRIT_MAGIC, + SMOKE_NORMAL, + SMOKE_LARGE, + SPELL, + SPELL_INSTANT, + SPELL_MOB, + SPELL_MOB_AMBIENT, + SPELL_WITCH, + DRIP_WATER, + DRIP_LAVA, + VILLAGER_ANGRY, + VILLAGER_HAPPY, + TOWN_AURA, + NOTE, + PORTAL, + ENCHANTMENT_TABLE, + FLAME, + LAVA, + CLOUD, + REDSTONE(DustOptions.class), + SNOWBALL, + SNOW_SHOVEL, + SLIME, + HEART, + BARRIER, + ITEM_CRACK(ItemStack.class), + BLOCK_CRACK(BlockData.class), + BLOCK_DUST(BlockData.class), + WATER_DROP, + MOB_APPEARANCE, + DRAGON_BREATH, + END_ROD, + DAMAGE_INDICATOR, + SWEEP_ATTACK, + FALLING_DUST(BlockData.class), + TOTEM, + SPIT, + SQUID_INK, + BUBBLE_POP, + CURRENT_DOWN, + BUBBLE_COLUMN_UP, + NAUTILUS, + DOLPHIN, + // ----- Legacy Separator ----- + LEGACY_BLOCK_CRACK(MaterialData.class), + LEGACY_BLOCK_DUST(MaterialData.class), + LEGACY_FALLING_DUST(MaterialData.class); + + private final Class dataType; + + Particle() { + dataType = Void.class; + } + + Particle(/*@NotNull*/ Class data) { + dataType = data; + } + + /** + * Returns the required data type for the particle + * @return the required data type + */ + @NotNull + public Class getDataType() { + return dataType; + } + + // Paper start - Particle API expansion + /** + * Creates a {@link com.destroystokyo.paper.ParticleBuilder} + * + * @return a {@link com.destroystokyo.paper.ParticleBuilder} for the particle + */ + @NotNull + public com.destroystokyo.paper.ParticleBuilder builder() { + return new com.destroystokyo.paper.ParticleBuilder(this); + } + // Paper end + /** + * Options which can be applied to redstone dust particles - a particle + * color and size. + */ + public static class DustOptions { + + private final Color color; + private final float size; + + public DustOptions(@NotNull Color color, float size) { + Preconditions.checkArgument(color != null, "color"); + this.color = color; + this.size = size; + } + + /** + * The color of the particles to be displayed. + * + * @return particle color + */ + @NotNull + public Color getColor() { + return color; + } + + /** + * Relative size of the particle. + * + * @return relative particle size + */ + public float getSize() { + return size; + } + } +} diff --git a/api/src/main/java/org/bukkit/PortalType.java b/api/src/main/java/org/bukkit/PortalType.java new file mode 100644 index 000000000..427cfbb8b --- /dev/null +++ b/api/src/main/java/org/bukkit/PortalType.java @@ -0,0 +1,22 @@ +package org.bukkit; + +/** + * Represents various types of portals that can be made in a world. + */ +public enum PortalType { + + /** + * This is a Nether portal, made of obsidian. + */ + NETHER, + + /** + * This is an Ender portal. + */ + ENDER, + + /** + * This is a custom Plugin portal. + */ + CUSTOM; +} diff --git a/api/src/main/java/org/bukkit/Rotation.java b/api/src/main/java/org/bukkit/Rotation.java new file mode 100644 index 000000000..78708fe39 --- /dev/null +++ b/api/src/main/java/org/bukkit/Rotation.java @@ -0,0 +1,67 @@ +package org.bukkit; + +import org.jetbrains.annotations.NotNull; + +/** + * An enum to specify a rotation based orientation, like that on a clock. + *

+ * It represents how something is viewed, as opposed to cardinal directions. + */ +public enum Rotation { + + /** + * No rotation + */ + NONE, + /** + * Rotated clockwise by 45 degrees + */ + CLOCKWISE_45, + /** + * Rotated clockwise by 90 degrees + */ + CLOCKWISE, + /** + * Rotated clockwise by 135 degrees + */ + CLOCKWISE_135, + /** + * Flipped upside-down, a 180 degree rotation + */ + FLIPPED, + /** + * Flipped upside-down + 45 degree rotation + */ + FLIPPED_45, + /** + * Rotated counter-clockwise by 90 degrees + */ + COUNTER_CLOCKWISE, + /** + * Rotated counter-clockwise by 45 degrees + */ + COUNTER_CLOCKWISE_45 + ; + + private static final Rotation[] rotations = values(); + + /** + * Rotate clockwise by 90 degrees. + * + * @return the relative rotation + */ + @NotNull + public Rotation rotateClockwise() { + return rotations[(this.ordinal() + 1) & 0x7]; + } + + /** + * Rotate counter-clockwise by 90 degrees. + * + * @return the relative rotation + */ + @NotNull + public Rotation rotateCounterClockwise() { + return rotations[(this.ordinal() - 1) & 0x7]; + } +} diff --git a/api/src/main/java/org/bukkit/SandstoneType.java b/api/src/main/java/org/bukkit/SandstoneType.java new file mode 100644 index 000000000..3b3a4a7cc --- /dev/null +++ b/api/src/main/java/org/bukkit/SandstoneType.java @@ -0,0 +1,53 @@ +package org.bukkit; + +import java.util.Map; + +import com.google.common.collect.Maps; +import org.jetbrains.annotations.Nullable; + +/** + * Represents the three different types of Sandstone + */ +public enum SandstoneType { + CRACKED(0x0), + GLYPHED(0x1), + SMOOTH(0x2); + + private final byte data; + private final static Map BY_DATA = Maps.newHashMap(); + + private SandstoneType(final int data) { + this.data = (byte) data; + } + + /** + * Gets the associated data value representing this type of sandstone + * + * @return A byte containing the data value of this sandstone type + * @deprecated Magic value + */ + @Deprecated + public byte getData() { + return data; + } + + /** + * Gets the type of sandstone with the given data value + * + * @param data Data value to fetch + * @return The {@link SandstoneType} representing the given value, or null + * if it doesn't exist + * @deprecated Magic value + */ + @Deprecated + @Nullable + public static SandstoneType getByData(final byte data) { + return BY_DATA.get(data); + } + + static { + for (SandstoneType type : values()) { + BY_DATA.put(type.data, type); + } + } +} diff --git a/api/src/main/java/org/bukkit/Server.java b/api/src/main/java/org/bukkit/Server.java new file mode 100644 index 000000000..eb23417b7 --- /dev/null +++ b/api/src/main/java/org/bukkit/Server.java @@ -0,0 +1,1430 @@ +package org.bukkit; + +import java.awt.image.BufferedImage; +import java.io.File; +import java.io.Serializable; +import java.util.Collection; +import java.util.Collections; +import java.util.Iterator; +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.UUID; +import java.util.function.Consumer; +import java.util.logging.Logger; + +import org.bukkit.Warning.WarningState; +import org.bukkit.block.data.BlockData; +import org.bukkit.boss.BarColor; +import org.bukkit.boss.BarFlag; +import org.bukkit.boss.BarStyle; +import org.bukkit.boss.BossBar; +import org.bukkit.boss.KeyedBossBar; +import org.bukkit.command.CommandException; +import org.bukkit.command.CommandSender; +import org.bukkit.command.ConsoleCommandSender; +import org.bukkit.command.PluginCommand; +import org.bukkit.entity.Entity; +import org.bukkit.entity.Player; +import org.bukkit.event.inventory.InventoryType; +import org.bukkit.event.server.ServerListPingEvent; +import org.bukkit.help.HelpMap; +import org.bukkit.inventory.Inventory; +import org.bukkit.inventory.InventoryHolder; +import org.bukkit.inventory.ItemStack; +import org.bukkit.inventory.Merchant; +import org.bukkit.inventory.Recipe; +import org.bukkit.loot.LootTable; +import org.bukkit.map.MapView; +import org.bukkit.permissions.Permissible; +import org.bukkit.plugin.PluginManager; +import org.bukkit.plugin.ServicesManager; +import org.bukkit.plugin.messaging.Messenger; +import org.bukkit.plugin.messaging.PluginMessageRecipient; +import org.bukkit.scheduler.BukkitScheduler; +import org.bukkit.scoreboard.ScoreboardManager; +import org.bukkit.util.CachedServerIcon; + +import com.google.common.collect.ImmutableList; +import org.bukkit.advancement.Advancement; +import org.bukkit.generator.ChunkGenerator; + +import org.bukkit.inventory.ItemFactory; +import org.bukkit.inventory.meta.ItemMeta; +import org.jetbrains.annotations.Contract; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +/** + * Represents a server implementation. + */ +public interface Server extends PluginMessageRecipient { + + /** + * Used for all administrative messages, such as an operator using a + * command. + *

+ * For use in {@link #broadcast(java.lang.String, java.lang.String)}. + */ + public static final String BROADCAST_CHANNEL_ADMINISTRATIVE = "bukkit.broadcast.admin"; + + /** + * Used for all announcement messages, such as informing users that a + * player has joined. + *

+ * For use in {@link #broadcast(java.lang.String, java.lang.String)}. + */ + public static final String BROADCAST_CHANNEL_USERS = "bukkit.broadcast.user"; + + /** + * Gets the name of this server implementation. + * + * @return name of this server implementation + */ + @NotNull + public String getName(); + + /** + * Gets the version string of this server implementation. + * + * @return version of this server implementation + */ + @NotNull + public String getVersion(); + + /** + * Gets the Bukkit version that this server is running. + * + * @return version of Bukkit + */ + @NotNull + public String getBukkitVersion(); + + /** + * Gets a view of all currently logged in players. This {@linkplain + * Collections#unmodifiableCollection(Collection) view} is a reused + * object, making some operations like {@link Collection#size()} + * zero-allocation. + *

+ * The collection is a view backed by the internal representation, such + * that, changes to the internal state of the server will be reflected + * immediately. However, the reuse of the returned collection (identity) + * is not strictly guaranteed for future or all implementations. Casting + * the collection, or relying on interface implementations (like {@link + * Serializable} or {@link List}), is deprecated. + *

+ * Iteration behavior is undefined outside of self-contained main-thread + * uses. Normal and immediate iterator use without consequences that + * affect the collection are fully supported. The effects following + * (non-exhaustive) {@link Entity#teleport(Location) teleportation}, + * {@link Player#setHealth(double) death}, and {@link Player#kickPlayer( + * String) kicking} are undefined. Any use of this collection from + * asynchronous threads is unsafe. + *

+ * For safe consequential iteration or mimicking the old array behavior, + * using {@link Collection#toArray(Object[])} is recommended. For making + * snapshots, {@link ImmutableList#copyOf(Collection)} is recommended. + * + * @return a view of currently online players. + */ + @NotNull + public Collection getOnlinePlayers(); + + /** + * Get the maximum amount of players which can login to this server. + * + * @return the amount of players this server allows + */ + public int getMaxPlayers(); + + /** + * Get the game port that the server runs on. + * + * @return the port number of this server + */ + public int getPort(); + + /** + * Get the view distance from this server. + * + * @return the view distance from this server. + */ + public int getViewDistance(); + + /** + * Get the IP that this server is bound to, or empty string if not + * specified. + * + * @return the IP string that this server is bound to, otherwise empty + * string + */ + @NotNull + public String getIp(); + + /** + * Get the name of this server. + * + * @return the name of this server + * @deprecated not a standard server property + */ + @Deprecated + @NotNull + public String getServerName(); + + /** + * Get an ID of this server. The ID is a simple generally alphanumeric ID + * that can be used for uniquely identifying this server. + * + * @return the ID of this server + * @deprecated not a standard server property + */ + @Deprecated + @NotNull + public String getServerId(); + + /** + * Get world type (level-type setting) for default world. + * + * @return the value of level-type (e.g. DEFAULT, FLAT, DEFAULT_1_1) + */ + @NotNull + public String getWorldType(); + + /** + * Get generate-structures setting. + * + * @return true if structure generation is enabled, false otherwise + */ + public boolean getGenerateStructures(); + + /** + * Gets whether this server allows the End or not. + * + * @return whether this server allows the End or not + */ + public boolean getAllowEnd(); + + /** + * Gets whether this server allows the Nether or not. + * + * @return whether this server allows the Nether or not + */ + public boolean getAllowNether(); + + /** + * Gets whether this server has a whitelist or not. + * + * @return whether this server has a whitelist or not + */ + public boolean hasWhitelist(); + + /** + * Sets if the server is whitelisted. + * + * @param value true for whitelist on, false for off + */ + public void setWhitelist(boolean value); + + /** + * Gets a list of whitelisted players. + * + * @return a set containing all whitelisted players + */ + @NotNull + public Set getWhitelistedPlayers(); + + /** + * Reloads the whitelist from disk. + */ + public void reloadWhitelist(); + + /** + * Broadcast a message to all players. + *

+ * This is the same as calling {@link #broadcast(java.lang.String, + * java.lang.String)} to {@link #BROADCAST_CHANNEL_USERS} + * + * @param message the message + * @return the number of players + */ + public int broadcastMessage(@NotNull String message); + + // Paper start + /** + * Sends the component to all online players. + * + * @param component the component to send + */ + public default void broadcast(@NotNull net.md_5.bungee.api.chat.BaseComponent component) { + spigot().broadcast(component); + } + + /** + * Sends an array of components as a single message to all online players. + * + * @param components the components to send + */ + public default void broadcast(@NotNull net.md_5.bungee.api.chat.BaseComponent... components) { + spigot().broadcast(components); + } + // Paper end + + /** + * Gets the name of the update folder. The update folder is used to safely + * update plugins at the right moment on a plugin load. + *

+ * The update folder name is relative to the plugins folder. + * + * @return the name of the update folder + */ + @NotNull + public String getUpdateFolder(); + + /** + * Gets the update folder. The update folder is used to safely update + * plugins at the right moment on a plugin load. + * + * @return the update folder + */ + @NotNull + public File getUpdateFolderFile(); + + /** + * Gets the value of the connection throttle setting. + * + * @return the value of the connection throttle setting + */ + public long getConnectionThrottle(); + + /** + * Gets default ticks per animal spawns value. + *

+ * Example Usage: + *

    + *
  • A value of 1 will mean the server will attempt to spawn monsters + * every tick. + *
  • A value of 400 will mean the server will attempt to spawn monsters + * every 400th tick. + *
  • A value below 0 will be reset back to Minecraft's default. + *
+ *

+ * Note: If set to 0, animal spawning will be disabled. We + * recommend using spawn-animals to control this instead. + *

+ * Minecraft default: 400. + * + * @return the default ticks per animal spawns value + */ + public int getTicksPerAnimalSpawns(); + + /** + * Gets the default ticks per monster spawns value. + *

+ * Example Usage: + *

    + *
  • A value of 1 will mean the server will attempt to spawn monsters + * every tick. + *
  • A value of 400 will mean the server will attempt to spawn monsters + * every 400th tick. + *
  • A value below 0 will be reset back to Minecraft's default. + *
+ *

+ * Note: If set to 0, monsters spawning will be disabled. We + * recommend using spawn-monsters to control this instead. + *

+ * Minecraft default: 1. + * + * @return the default ticks per monsters spawn value + */ + public int getTicksPerMonsterSpawns(); + + /** + * Gets a player object by the given username. + *

+ * This method may not return objects for offline players. + * + * @param name the name to look up + * @return a player if one was found, null otherwise + */ + @Nullable + public Player getPlayer(@NotNull String name); + + /** + * Gets the player with the exact given name, case insensitive. + * + * @param name Exact name of the player to retrieve + * @return a player object if one was found, null otherwise + */ + @Nullable + public Player getPlayerExact(@NotNull String name); + + /** + * Attempts to match any players with the given name, and returns a list + * of all possibly matches. + *

+ * This list is not sorted in any particular order. If an exact match is + * found, the returned list will only contain a single result. + * + * @param name the (partial) name to match + * @return list of all possible players + */ + @NotNull + public List matchPlayer(@NotNull String name); + + /** + * Gets the player with the given UUID. + * + * @param id UUID of the player to retrieve + * @return a player object if one was found, null otherwise + */ + @Nullable + public Player getPlayer(@NotNull UUID id); + + // Paper start + /** + * Gets the unique ID of the player currently known as the specified player name + * In Offline Mode, will return an Offline UUID + * + * @param playerName the player name to look up the unique ID for + * @return A UUID, or null if that player name is not registered with Minecraft and the server is in online mode + */ + @Nullable + public UUID getPlayerUniqueId(@NotNull String playerName); + // Paper end + + /** + * Gets the plugin manager for interfacing with plugins. + * + * @return a plugin manager for this Server instance + */ + @NotNull + public PluginManager getPluginManager(); + + /** + * Gets the scheduler for managing scheduled events. + * + * @return a scheduling service for this server + */ + @NotNull + public BukkitScheduler getScheduler(); + + /** + * Gets a services manager. + * + * @return s services manager + */ + @NotNull + public ServicesManager getServicesManager(); + + /** + * Gets a list of all worlds on this server. + * + * @return a list of worlds + */ + @NotNull + public List getWorlds(); + + /** + * Creates or loads a world with the given name using the specified + * options. + *

+ * If the world is already loaded, it will just return the equivalent of + * getWorld(creator.name()). + * + * @param creator the options to use when creating the world + * @return newly created or loaded world + */ + @Nullable + public World createWorld(@NotNull WorldCreator creator); + + /** + * Unloads a world with the given name. + * + * @param name Name of the world to unload + * @param save whether to save the chunks before unloading + * @return true if successful, false otherwise + */ + public boolean unloadWorld(@NotNull String name, boolean save); + + /** + * Unloads the given world. + * + * @param world the world to unload + * @param save whether to save the chunks before unloading + * @return true if successful, false otherwise + */ + public boolean unloadWorld(@NotNull World world, boolean save); + + /** + * Gets the world with the given name. + * + * @param name the name of the world to retrieve + * @return a world with the given name, or null if none exists + */ + @Nullable + public World getWorld(@NotNull String name); + + /** + * Gets the world from the given Unique ID. + * + * @param uid a unique-id of the world to retrieve + * @return a world with the given Unique ID, or null if none exists + */ + @Nullable + public World getWorld(@NotNull UUID uid); + + /** + * Gets the map from the given item ID. + * + * @param id the id of the map to get + * @return a map view if it exists, or null otherwise + * @deprecated Magic value + */ + @Deprecated + @Nullable + public MapView getMap(int id); + + /** + * Create a new map with an automatically assigned ID. + * + * @param world the world the map will belong to + * @return a newly created map view + */ + @NotNull + public MapView createMap(@NotNull World world); + + /** + * Create a new explorer map targeting the closest nearby structure of a + * given {@link StructureType}. + *
+ * This method uses implementation default values for radius and + * findUnexplored (usually 100, true). + * + * @param world the world the map will belong to + * @param location the origin location to find the nearest structure + * @param structureType the type of structure to find + * @return a newly created item stack + * + * @see World#locateNearestStructure(org.bukkit.Location, + * org.bukkit.StructureType, int, boolean) + */ + @NotNull + public ItemStack createExplorerMap(@NotNull World world, @NotNull Location location, @NotNull StructureType structureType); + + /** + * Create a new explorer map targeting the closest nearby structure of a + * given {@link StructureType}. + *
+ * This method uses implementation default values for radius and + * findUnexplored (usually 100, true). + * + * @param world the world the map will belong to + * @param location the origin location to find the nearest structure + * @param structureType the type of structure to find + * @param radius radius to search, see World#locateNearestStructure for more + * information + * @param findUnexplored whether to find unexplored structures + * @return the newly created item stack + * + * @see World#locateNearestStructure(org.bukkit.Location, + * org.bukkit.StructureType, int, boolean) + */ + @NotNull + public ItemStack createExplorerMap(@NotNull World world, @NotNull Location location, @NotNull StructureType structureType, int radius, boolean findUnexplored); + + /** + * Reloads the server, refreshing settings and plugin information. + */ + public void reload(); + + /** + * Reload only the Minecraft data for the server. This includes custom + * advancements and loot tables. + */ + public void reloadData(); + + /** + * Returns the primary logger associated with this server instance. + * + * @return Logger associated with this server + */ + @NotNull + public Logger getLogger(); + + /** + * Gets a {@link PluginCommand} with the given name or alias. + * + * @param name the name of the command to retrieve + * @return a plugin command if found, null otherwise + */ + @Nullable + public PluginCommand getPluginCommand(@NotNull String name); + + /** + * Writes loaded players to disk. + */ + public void savePlayers(); + + /** + * Dispatches a command on this server, and executes it if found. + * + * @param sender the apparent sender of the command + * @param commandLine the command + arguments. Example: test abc + * 123 + * @return returns false if no target is found + * @throws CommandException thrown when the executor for the given command + * fails with an unhandled exception + */ + public boolean dispatchCommand(@NotNull CommandSender sender, @NotNull String commandLine) throws CommandException; + + /** + * Adds a recipe to the crafting manager. + * + * @param recipe the recipe to add + * @return true if the recipe was added, false if it wasn't for some + * reason + */ + @Contract("null -> false") + public boolean addRecipe(@Nullable Recipe recipe); + + /** + * Get a list of all recipes for a given item. The stack size is ignored + * in comparisons. If the durability is -1, it will match any data value. + * + * @param result the item to match against recipe results + * @return a list of recipes with the given result + */ + @NotNull + public List getRecipesFor(@NotNull ItemStack result); + + /** + * Get an iterator through the list of crafting recipes. + * + * @return an iterator + */ + @NotNull + public Iterator recipeIterator(); + + /** + * Clears the list of crafting recipes. + */ + public void clearRecipes(); + + /** + * Resets the list of crafting recipes to the default. + */ + public void resetRecipes(); + + /** + * Gets a list of command aliases defined in the server properties. + * + * @return a map of aliases to command names + */ + @NotNull + public Map getCommandAliases(); + + /** + * Gets the radius, in blocks, around each worlds spawn point to protect. + * + * @return spawn radius, or 0 if none + */ + public int getSpawnRadius(); + + /** + * Sets the radius, in blocks, around each worlds spawn point to protect. + * + * @param value new spawn radius, or 0 if none + */ + public void setSpawnRadius(int value); + + /** + * Gets whether the Server is in online mode or not. + * + * @return true if the server authenticates clients, false otherwise + */ + public boolean getOnlineMode(); + + /** + * Gets whether this server allows flying or not. + * + * @return true if the server allows flight, false otherwise + */ + public boolean getAllowFlight(); + + /** + * Gets whether the server is in hardcore mode or not. + * + * @return true if the server mode is hardcore, false otherwise + */ + public boolean isHardcore(); + + /** + * Shutdowns the server, stopping everything. + */ + public void shutdown(); + + /** + * Broadcasts the specified message to every user with the given + * permission name. + * + * @param message message to broadcast + * @param permission the required permission {@link Permissible + * permissibles} must have to receive the broadcast + * @return number of message recipients + */ + public int broadcast(@NotNull String message, @NotNull String permission); + + /** + * Gets the player by the given name, regardless if they are offline or + * online. + *

+ * This method may involve a blocking web request to get the UUID for the + * given name. + *

+ * This will return an object even if the player does not exist. To this + * method, all players will exist. + * + * @deprecated Persistent storage of users should be by UUID as names are no longer + * unique past a single session. + * @param name the name the player to retrieve + * @return an offline player + * @see #getOfflinePlayer(java.util.UUID) + */ + @Deprecated + @NotNull + public OfflinePlayer getOfflinePlayer(@NotNull String name); + + /** + * Gets the player by the given UUID, regardless if they are offline or + * online. + *

+ * This will return an object even if the player does not exist. To this + * method, all players will exist. + * + * @param id the UUID of the player to retrieve + * @return an offline player + */ + @NotNull + public OfflinePlayer getOfflinePlayer(@NotNull UUID id); + + /** + * Gets a set containing all current IPs that are banned. + * + * @return a set containing banned IP addresses + */ + @NotNull + public Set getIPBans(); + + /** + * Bans the specified address from the server. + * + * @param address the IP address to ban + */ + public void banIP(@NotNull String address); + + /** + * Unbans the specified address from the server. + * + * @param address the IP address to unban + */ + public void unbanIP(@NotNull String address); + + /** + * Gets a set containing all banned players. + * + * @return a set containing banned players + */ + @NotNull + public Set getBannedPlayers(); + + /** + * Gets a ban list for the supplied type. + *

+ * Bans by name are no longer supported and this method will return + * null when trying to request them. The replacement is bans by UUID. + * + * @param type the type of list to fetch, cannot be null + * @return a ban list of the specified type + */ + @NotNull + public BanList getBanList(@NotNull BanList.Type type); + + /** + * Gets a set containing all player operators. + * + * @return a set containing player operators + */ + @NotNull + public Set getOperators(); + + /** + * Gets the default {@link GameMode} for new players. + * + * @return the default game mode + */ + @NotNull + public GameMode getDefaultGameMode(); + + /** + * Sets the default {@link GameMode} for new players. + * + * @param mode the new game mode + */ + public void setDefaultGameMode(@NotNull GameMode mode); + + /** + * Gets a {@link ConsoleCommandSender} that may be used as an input source + * for this server. + * + * @return a console command sender + */ + @NotNull + public ConsoleCommandSender getConsoleSender(); + + /** + * Gets the folder that contains all of the various {@link World}s. + * + * @return folder that contains all worlds + */ + @NotNull + public File getWorldContainer(); + + /** + * Gets every player that has ever played on this server. + * + * @return an array containing all previous players + */ + @NotNull + public OfflinePlayer[] getOfflinePlayers(); + + /** + * Gets the {@link Messenger} responsible for this server. + * + * @return messenger responsible for this server + */ + @NotNull + public Messenger getMessenger(); + + /** + * Gets the {@link HelpMap} providing help topics for this server. + * + * @return a help map for this server + */ + @NotNull + public HelpMap getHelpMap(); + + /** + * Creates an empty inventory with the specified type and title. If the type + * is {@link InventoryType#CHEST}, the new inventory has a size of 27; + * otherwise the new inventory has the normal size for its type.
+ * It should be noted that some inventory types do not support titles and + * may not render with said titles on the Minecraft client. + *
+ * {@link InventoryType#WORKBENCH} will not process crafting recipes if + * created with this method. Use + * {@link Player#openWorkbench(Location, boolean)} instead. + *
+ * {@link InventoryType#ENCHANTING} will not process {@link ItemStack}s + * for possible enchanting results. Use + * {@link Player#openEnchanting(Location, boolean)} instead. + * + * @param owner the holder of the inventory, or null to indicate no holder + * @param type the type of inventory to create + * @return a new inventory + * @throws IllegalArgumentException if the {@link InventoryType} cannot be + * viewed. + * + * @see InventoryType#isCreatable() + */ + @NotNull + Inventory createInventory(@Nullable InventoryHolder owner, @NotNull InventoryType type); + + /** + * Creates an empty inventory with the specified type and title. If the type + * is {@link InventoryType#CHEST}, the new inventory has a size of 27; + * otherwise the new inventory has the normal size for its type.
+ * It should be noted that some inventory types do not support titles and + * may not render with said titles on the Minecraft client. + *
+ * {@link InventoryType#WORKBENCH} will not process crafting recipes if + * created with this method. Use + * {@link Player#openWorkbench(Location, boolean)} instead. + *
+ * {@link InventoryType#ENCHANTING} will not process {@link ItemStack}s + * for possible enchanting results. Use + * {@link Player#openEnchanting(Location, boolean)} instead. + * + * @param owner The holder of the inventory; can be null if there's no holder. + * @param type The type of inventory to create. + * @param title The title of the inventory, to be displayed when it is viewed. + * @return The new inventory. + * @throws IllegalArgumentException if the {@link InventoryType} cannot be + * viewed. + * + * @see InventoryType#isCreatable() + */ + @NotNull + Inventory createInventory(@Nullable InventoryHolder owner, @NotNull InventoryType type, @NotNull String title); + + /** + * Creates an empty inventory of type {@link InventoryType#CHEST} with the + * specified size. + * + * @param owner the holder of the inventory, or null to indicate no holder + * @param size a multiple of 9 as the size of inventory to create + * @return a new inventory + * @throws IllegalArgumentException if the size is not a multiple of 9 + */ + @NotNull + Inventory createInventory(@Nullable InventoryHolder owner, int size) throws IllegalArgumentException; + + /** + * Creates an empty inventory of type {@link InventoryType#CHEST} with the + * specified size and title. + * + * @param owner the holder of the inventory, or null to indicate no holder + * @param size a multiple of 9 as the size of inventory to create + * @param title the title of the inventory, displayed when inventory is + * viewed + * @return a new inventory + * @throws IllegalArgumentException if the size is not a multiple of 9 + */ + @NotNull + Inventory createInventory(@Nullable InventoryHolder owner, int size, @NotNull String title) throws IllegalArgumentException; + + /** + * Creates an empty merchant. + * + * @param title the title of the corresponding merchant inventory, displayed + * when the merchant inventory is viewed + * @return a new merchant + */ + @NotNull + Merchant createMerchant(@Nullable String title); + + /** + * Gets user-specified limit for number of monsters that can spawn in a + * chunk. + * + * @return the monster spawn limit + */ + int getMonsterSpawnLimit(); + + /** + * Gets user-specified limit for number of animals that can spawn in a + * chunk. + * + * @return the animal spawn limit + */ + int getAnimalSpawnLimit(); + + /** + * Gets user-specified limit for number of water animals that can spawn in + * a chunk. + * + * @return the water animal spawn limit + */ + int getWaterAnimalSpawnLimit(); + + /** + * Gets user-specified limit for number of ambient mobs that can spawn in + * a chunk. + * + * @return the ambient spawn limit + */ + int getAmbientSpawnLimit(); + + /** + * Checks the current thread against the expected primary thread for the + * server. + *

+ * Note: this method should not be used to indicate the current + * synchronized state of the runtime. A current thread matching the main + * thread indicates that it is synchronized, but a mismatch does not + * preclude the same assumption. + * + * @return true if the current thread matches the expected primary thread, + * false otherwise + */ + boolean isPrimaryThread(); + + /** + * Gets the message that is displayed on the server list. + * + * @return the servers MOTD + */ + @NotNull + String getMotd(); + + /** + * Gets the default message that is displayed when the server is stopped. + * + * @return the shutdown message + */ + @Nullable + String getShutdownMessage(); + + /** + * Gets the current warning state for the server. + * + * @return the configured warning state + */ + @NotNull + public WarningState getWarningState(); + + /** + * Gets the instance of the item factory (for {@link ItemMeta}). + * + * @return the item factory + * @see ItemFactory + */ + @NotNull + ItemFactory getItemFactory(); + + /** + * Gets the instance of the scoreboard manager. + *

+ * This will only exist after the first world has loaded. + * + * @return the scoreboard manager or null if no worlds are loaded. + */ + @NotNull // Paper + ScoreboardManager getScoreboardManager(); + + /** + * Gets an instance of the server's default server-icon. + * + * @return the default server-icon; null values may be used by the + * implementation to indicate no defined icon, but this behavior is + * not guaranteed + */ + @Nullable + CachedServerIcon getServerIcon(); + + /** + * Loads an image from a file, and returns a cached image for the specific + * server-icon. + *

+ * Size and type are implementation defined. An incompatible file is + * guaranteed to throw an implementation-defined {@link Exception}. + * + * @param file the file to load the from + * @throws IllegalArgumentException if image is null + * @throws Exception if the image does not meet current server server-icon + * specifications + * @return a cached server-icon that can be used for a {@link + * ServerListPingEvent#setServerIcon(CachedServerIcon)} + */ + @NotNull + CachedServerIcon loadServerIcon(@NotNull File file) throws IllegalArgumentException, Exception; + + /** + * Creates a cached server-icon for the specific image. + *

+ * Size and type are implementation defined. An incompatible file is + * guaranteed to throw an implementation-defined {@link Exception}. + * + * @param image the image to use + * @throws IllegalArgumentException if image is null + * @throws Exception if the image does not meet current server + * server-icon specifications + * @return a cached server-icon that can be used for a {@link + * ServerListPingEvent#setServerIcon(CachedServerIcon)} + */ + @NotNull + CachedServerIcon loadServerIcon(@NotNull BufferedImage image) throws IllegalArgumentException, Exception; + + /** + * Set the idle kick timeout. Any players idle for the specified amount of + * time will be automatically kicked. + *

+ * A value of 0 will disable the idle kick timeout. + * + * @param threshold the idle timeout in minutes + */ + public void setIdleTimeout(int threshold); + + /** + * Gets the idle kick timeout. + * + * @return the idle timeout in minutes + */ + public int getIdleTimeout(); + + /** + * Create a ChunkData for use in a generator. + * + * See {@link ChunkGenerator#generateChunkData(org.bukkit.World, java.util.Random, int, int, org.bukkit.generator.ChunkGenerator.BiomeGrid)} + * + * @param world the world to create the ChunkData for + * @return a new ChunkData for the world + * + */ + @NotNull + public ChunkGenerator.ChunkData createChunkData(@NotNull World world); + + /** + * Creates a boss bar instance to display to players. The progress + * defaults to 1.0 + * + * @param title the title of the boss bar + * @param color the color of the boss bar + * @param style the style of the boss bar + * @param flags an optional list of flags to set on the boss bar + * @return the created boss bar + */ + @NotNull + BossBar createBossBar(@Nullable String title, @NotNull BarColor color, @NotNull BarStyle style, @NotNull BarFlag... flags); + + /** + * Creates a boss bar instance to display to players. The progress defaults + * to 1.0. + *
+ * This instance is added to the persistent storage of the server and will + * be editable by commands and restored after restart. + * + * @param key the key of the boss bar that is used to access the boss bar + * @param title the title of the boss bar + * @param color the color of the boss bar + * @param style the style of the boss bar + * @param flags an optional list of flags to set on the boss bar + * @return the created boss bar + */ + @NotNull + KeyedBossBar createBossBar(@NotNull NamespacedKey key, @Nullable String title, @NotNull BarColor color, @NotNull BarStyle style, @NotNull BarFlag... flags); + + /** + * Gets an unmodifiable iterator through all persistent bossbars. + *

    + *
  • not bound to a {@link org.bukkit.entity.Boss}
  • + *
  • + * not created using + * {@link #createBossBar(String, BarColor, BarStyle, BarFlag...)} + *
  • + *
+ * + * e.g. bossbars created using the bossbar command + * + * @return a bossbar iterator + */ + @NotNull + Iterator getBossBars(); + + /** + * Gets the {@link KeyedBossBar} specified by this key. + *
    + *
  • not bound to a {@link org.bukkit.entity.Boss}
  • + *
  • + * not created using + * {@link #createBossBar(String, BarColor, BarStyle, BarFlag...)} + *
  • + *
+ * + * e.g. bossbars created using the bossbar command + * + * @param key unique bossbar key + * @return bossbar or null if not exists + */ + @Nullable + KeyedBossBar getBossBar(@NotNull NamespacedKey key); + + /** + * Removes a {@link KeyedBossBar} specified by this key. + *
    + *
  • not bound to a {@link org.bukkit.entity.Boss}
  • + *
  • + * not created using + * {@link #createBossBar(String, BarColor, BarStyle, BarFlag...)} + *
  • + *
+ * + * e.g. bossbars created using the bossbar command + * + * @param key unique bossbar key + * @return true if removal succeeded or false + */ + boolean removeBossBar(@NotNull NamespacedKey key); + + /** + * Gets an entity on the server by its UUID + * + * @param uuid the UUID of the entity + * @return the entity with the given UUID, or null if it isn't found + */ + @Nullable + Entity getEntity(@NotNull UUID uuid); + + // Paper start + /** + * Gets the current server TPS + * + * @return current server TPS (1m, 5m, 15m in Paper-Server) + */ + @NotNull + public double[] getTPS(); + // Paper end + + // Paper start + /** + * Gets the active {@link org.bukkit.command.CommandMap} + * + * @return the active command map + */ + @NotNull + org.bukkit.command.CommandMap getCommandMap(); + + /** + * Get the advancement specified by this key. + * + * @param key unique advancement key + * @return advancement or null if not exists + */ + @Nullable + Advancement getAdvancement(@NotNull NamespacedKey key); + + /** + * Get an iterator through all advancements. Advancements cannot be removed + * from this iterator, + * + * @return an advancement iterator + */ + @NotNull + Iterator advancementIterator(); + + /** + * Creates a new {@link BlockData} instance for the specified Material, with + * all properties initialized to unspecified defaults. + * + * @param material the material + * @return new data instance + */ + @NotNull + BlockData createBlockData(@NotNull Material material); + + /** + * Creates a new {@link BlockData} instance for the specified Material, with + * all properties initialized to unspecified defaults. + * + * @param material the material + * @param consumer consumer to run on new instance before returning + * @return new data instance + */ + @NotNull + public BlockData createBlockData(@NotNull Material material, @Nullable Consumer consumer); + + /** + * Creates a new {@link BlockData} instance with material and properties + * parsed from provided data. + * + * @param data data string + * @return new data instance + * @throws IllegalArgumentException if the specified data is not valid + */ + @NotNull + BlockData createBlockData(@NotNull String data) throws IllegalArgumentException; + + /** + * Creates a new {@link BlockData} instance for the specified Material, with + * all properties initialized to unspecified defaults, except for those + * provided in data. + *
+ * If material is specified, then the data string must not also + * contain the material. + * + * @param material the material + * @param data data string + * @return new data instance + * @throws IllegalArgumentException if the specified data is not valid + */ + @NotNull + @Contract("null, null -> fail") + BlockData createBlockData(@Nullable Material material, @Nullable String data) throws IllegalArgumentException; + + /** + * Gets a tag which has already been defined within the server. Plugins are + * suggested to use the concrete tags in {@link Tag} rather than this method + * which makes no guarantees about which tags are available, and may also be + * less performant due to lack of caching. + *
+ * Tags will be searched for in an implementation specific manner, but a + * path consisting of namespace/tags/registry/key is expected. + *
+ * Server implementations are allowed to handle only the registries + * indicated in {@link Tag}. + * + * @param type of the tag + * @param registry the tag registry to look at + * @param tag the name of the tag + * @param clazz the class of the tag entries + * @return the tag or null + */ + @UndefinedNullability + Tag getTag(@NotNull String registry, @NotNull NamespacedKey tag, @NotNull Class clazz); + + /** + * Gets a all tags which have been defined within the server. + *
+ * Server implementations are allowed to handle only the registries + * indicated in {@link Tag}. + *
+ * No guarantees are made about the mutability of the returned iterator. + * + * @param type of the tag + * @param registry the tag registry to look at + * @param clazz the class of the tag entries + * @return all defined tags + */ + @NotNull + Iterable> getTags(@NotNull String registry, @NotNull Class clazz); + + /** + * Gets the specified {@link LootTable}. + * + * @param key the name of the LootTable + * @return the LootTable, or null if no LootTable is found with that name + */ + @Nullable + LootTable getLootTable(@NotNull NamespacedKey key); + + /** + * Selects entities using the given Vanilla selector. + *
+ * No guarantees are made about the selector format, other than they match + * the Vanilla format for the active Minecraft version. + *
+ * Usually a selector will start with '@', unless selecting a Player in + * which case it may simply be the Player's name or UUID. + *
+ * Note that in Vanilla, elevated permissions are usually required to use + * '@' selectors, but this method should not check such permissions from the + * sender. + * + * @param sender the sender to execute as, must be provided + * @param selector the selection string + * @return a list of the selected entities. The list will not be null, but + * no further guarantees are made. + * @throws IllegalArgumentException if the selector is malformed in any way + * or a parameter is null + * @deprecated draft API + */ + @Deprecated + @NotNull + List selectEntities(@NotNull CommandSender sender, @NotNull String selector) throws IllegalArgumentException; + + /** + * @see UnsafeValues + * @return the unsafe values instance + */ + @Deprecated + @NotNull + UnsafeValues getUnsafe(); + + // Spigot start + public class Spigot + { + + @NotNull + public org.bukkit.configuration.file.YamlConfiguration getConfig() + { + throw new UnsupportedOperationException( "Not supported yet." ); + } + + // Paper start + @NotNull + public org.bukkit.configuration.file.YamlConfiguration getBukkitConfig() + { + throw new UnsupportedOperationException( "Not supported yet." ); + } + + @NotNull + public org.bukkit.configuration.file.YamlConfiguration getSpigotConfig() + { + throw new UnsupportedOperationException("Not supported yet."); + } + + @NotNull + public org.bukkit.configuration.file.YamlConfiguration getPaperConfig() + { + throw new UnsupportedOperationException("Not supported yet."); + } + // Paper end + + /** + * Sends the component to the player + * + * @param component the components to send + */ + public void broadcast(@NotNull net.md_5.bungee.api.chat.BaseComponent component) { + throw new UnsupportedOperationException("Not supported yet."); + } + + /** + * Sends an array of components as a single message to the player + * + * @param components the components to send + */ + public void broadcast(@NotNull net.md_5.bungee.api.chat.BaseComponent... components) { + throw new UnsupportedOperationException("Not supported yet."); + } + + /** + * Restart the server. If the server administrator has not configured restarting, the server will stop. + */ + public void restart() { + throw new UnsupportedOperationException("Not supported yet."); + } + } + + @NotNull + Spigot spigot(); + // Spigot end + + void reloadPermissions(); // Paper + + boolean reloadCommandAliases(); // Paper + + // Paper start - allow preventing player name suggestions by default + /** + * Checks if player names should be suggested when a command returns {@code null} as + * their tab completion result. + * + * @return true if player names should be suggested + */ + boolean suggestPlayerNamesWhenNullTabCompletions(); + + /** + * + * @return the default no permission message used on the server + */ + @NotNull + String getPermissionMessage(); + + /** + * Creates a PlayerProfile for the specified uuid, with name as null + * @param uuid UUID to create profile for + * @return A PlayerProfile object + */ + @NotNull + com.destroystokyo.paper.profile.PlayerProfile createProfile(@NotNull UUID uuid); + + /** + * Creates a PlayerProfile for the specified name, with UUID as null + * @param name Name to create profile for + * @return A PlayerProfile object + */ + @NotNull + com.destroystokyo.paper.profile.PlayerProfile createProfile(@NotNull String name); + + /** + * Creates a PlayerProfile for the specified name/uuid + * + * Both UUID and Name can not be null at same time. One must be supplied. + * + * @param uuid UUID to create profile for + * @param name Name to create profile for + * @return A PlayerProfile object + */ + @NotNull + com.destroystokyo.paper.profile.PlayerProfile createProfile(@Nullable UUID uuid, @Nullable String name); + // Paper end +} diff --git a/api/src/main/java/org/bukkit/SkullType.java b/api/src/main/java/org/bukkit/SkullType.java new file mode 100644 index 000000000..1c3d9ae9b --- /dev/null +++ b/api/src/main/java/org/bukkit/SkullType.java @@ -0,0 +1,15 @@ +package org.bukkit; + +/** + * Represents the different types of skulls. + * @deprecated check {@link Material} instead + */ +@Deprecated +public enum SkullType { + SKELETON, + WITHER, + ZOMBIE, + PLAYER, + CREEPER, + DRAGON; +} diff --git a/api/src/main/java/org/bukkit/Sound.java b/api/src/main/java/org/bukkit/Sound.java new file mode 100644 index 000000000..d84ad5b90 --- /dev/null +++ b/api/src/main/java/org/bukkit/Sound.java @@ -0,0 +1,674 @@ +package org.bukkit; + +/** + * An Enum of Sounds the server is able to send to players. + *

+ * WARNING: At any time, sounds may be added/removed from this Enum or even + * MineCraft itself! There is no guarantee the sounds will play. There is no + * guarantee values will not be removed from this Enum. As such, you should not + * depend on the ordinal values of this class. + */ +public enum Sound { + AMBIENT_CAVE, + AMBIENT_UNDERWATER_ENTER, + AMBIENT_UNDERWATER_EXIT, + AMBIENT_UNDERWATER_LOOP, + AMBIENT_UNDERWATER_LOOP_ADDITIONS, + AMBIENT_UNDERWATER_LOOP_ADDITIONS_RARE, + AMBIENT_UNDERWATER_LOOP_ADDITIONS_ULTRA_RARE, + BLOCK_ANVIL_BREAK, + BLOCK_ANVIL_DESTROY, + BLOCK_ANVIL_FALL, + BLOCK_ANVIL_HIT, + BLOCK_ANVIL_LAND, + BLOCK_ANVIL_PLACE, + BLOCK_ANVIL_STEP, + BLOCK_ANVIL_USE, + BLOCK_BEACON_ACTIVATE, + BLOCK_BEACON_AMBIENT, + BLOCK_BEACON_DEACTIVATE, + BLOCK_BEACON_POWER_SELECT, + BLOCK_BREWING_STAND_BREW, + BLOCK_BUBBLE_COLUMN_BUBBLE_POP, + BLOCK_BUBBLE_COLUMN_UPWARDS_AMBIENT, + BLOCK_BUBBLE_COLUMN_UPWARDS_INSIDE, + BLOCK_BUBBLE_COLUMN_WHIRLPOOL_AMBIENT, + BLOCK_BUBBLE_COLUMN_WHIRLPOOL_INSIDE, + BLOCK_CHEST_CLOSE, + BLOCK_CHEST_LOCKED, + BLOCK_CHEST_OPEN, + BLOCK_CHORUS_FLOWER_DEATH, + BLOCK_CHORUS_FLOWER_GROW, + BLOCK_COMPARATOR_CLICK, + BLOCK_CONDUIT_ACTIVATE, + BLOCK_CONDUIT_AMBIENT, + BLOCK_CONDUIT_AMBIENT_SHORT, + BLOCK_CONDUIT_ATTACK_TARGET, + BLOCK_CONDUIT_DEACTIVATE, + BLOCK_CORAL_BLOCK_BREAK, + BLOCK_CORAL_BLOCK_FALL, + BLOCK_CORAL_BLOCK_HIT, + BLOCK_CORAL_BLOCK_PLACE, + BLOCK_CORAL_BLOCK_STEP, + BLOCK_DISPENSER_DISPENSE, + BLOCK_DISPENSER_FAIL, + BLOCK_DISPENSER_LAUNCH, + BLOCK_ENCHANTMENT_TABLE_USE, + BLOCK_ENDER_CHEST_CLOSE, + BLOCK_ENDER_CHEST_OPEN, + BLOCK_END_GATEWAY_SPAWN, + BLOCK_END_PORTAL_FRAME_FILL, + BLOCK_END_PORTAL_SPAWN, + BLOCK_FENCE_GATE_CLOSE, + BLOCK_FENCE_GATE_OPEN, + BLOCK_FIRE_AMBIENT, + BLOCK_FIRE_EXTINGUISH, + BLOCK_FURNACE_FIRE_CRACKLE, + BLOCK_GLASS_BREAK, + BLOCK_GLASS_FALL, + BLOCK_GLASS_HIT, + BLOCK_GLASS_PLACE, + BLOCK_GLASS_STEP, + BLOCK_GRASS_BREAK, + BLOCK_GRASS_FALL, + BLOCK_GRASS_HIT, + BLOCK_GRASS_PLACE, + BLOCK_GRASS_STEP, + BLOCK_GRAVEL_BREAK, + BLOCK_GRAVEL_FALL, + BLOCK_GRAVEL_HIT, + BLOCK_GRAVEL_PLACE, + BLOCK_GRAVEL_STEP, + BLOCK_IRON_DOOR_CLOSE, + BLOCK_IRON_DOOR_OPEN, + BLOCK_IRON_TRAPDOOR_CLOSE, + BLOCK_IRON_TRAPDOOR_OPEN, + BLOCK_LADDER_BREAK, + BLOCK_LADDER_FALL, + BLOCK_LADDER_HIT, + BLOCK_LADDER_PLACE, + BLOCK_LADDER_STEP, + BLOCK_LAVA_AMBIENT, + BLOCK_LAVA_EXTINGUISH, + BLOCK_LAVA_POP, + BLOCK_LEVER_CLICK, + BLOCK_LILY_PAD_PLACE, + BLOCK_METAL_BREAK, + BLOCK_METAL_FALL, + BLOCK_METAL_HIT, + BLOCK_METAL_PLACE, + BLOCK_METAL_PRESSURE_PLATE_CLICK_OFF, + BLOCK_METAL_PRESSURE_PLATE_CLICK_ON, + BLOCK_METAL_STEP, + BLOCK_NOTE_BLOCK_BASEDRUM, + BLOCK_NOTE_BLOCK_BASS, + BLOCK_NOTE_BLOCK_BELL, + BLOCK_NOTE_BLOCK_CHIME, + BLOCK_NOTE_BLOCK_FLUTE, + BLOCK_NOTE_BLOCK_GUITAR, + BLOCK_NOTE_BLOCK_HARP, + BLOCK_NOTE_BLOCK_HAT, + BLOCK_NOTE_BLOCK_PLING, + BLOCK_NOTE_BLOCK_SNARE, + BLOCK_NOTE_BLOCK_XYLOPHONE, + BLOCK_PISTON_CONTRACT, + BLOCK_PISTON_EXTEND, + BLOCK_PORTAL_AMBIENT, + BLOCK_PORTAL_TRAVEL, + BLOCK_PORTAL_TRIGGER, + BLOCK_PUMPKIN_CARVE, + BLOCK_REDSTONE_TORCH_BURNOUT, + BLOCK_SAND_BREAK, + BLOCK_SAND_FALL, + BLOCK_SAND_HIT, + BLOCK_SAND_PLACE, + BLOCK_SAND_STEP, + BLOCK_SHULKER_BOX_CLOSE, + BLOCK_SHULKER_BOX_OPEN, + BLOCK_SLIME_BLOCK_BREAK, + BLOCK_SLIME_BLOCK_FALL, + BLOCK_SLIME_BLOCK_HIT, + BLOCK_SLIME_BLOCK_PLACE, + BLOCK_SLIME_BLOCK_STEP, + BLOCK_SNOW_BREAK, + BLOCK_SNOW_FALL, + BLOCK_SNOW_HIT, + BLOCK_SNOW_PLACE, + BLOCK_SNOW_STEP, + BLOCK_STONE_BREAK, + BLOCK_STONE_BUTTON_CLICK_OFF, + BLOCK_STONE_BUTTON_CLICK_ON, + BLOCK_STONE_FALL, + BLOCK_STONE_HIT, + BLOCK_STONE_PLACE, + BLOCK_STONE_PRESSURE_PLATE_CLICK_OFF, + BLOCK_STONE_PRESSURE_PLATE_CLICK_ON, + BLOCK_STONE_STEP, + BLOCK_TRIPWIRE_ATTACH, + BLOCK_TRIPWIRE_CLICK_OFF, + BLOCK_TRIPWIRE_CLICK_ON, + BLOCK_TRIPWIRE_DETACH, + BLOCK_WATER_AMBIENT, + BLOCK_WET_GRASS_BREAK, + BLOCK_WET_GRASS_FALL, + BLOCK_WET_GRASS_HIT, + BLOCK_WET_GRASS_PLACE, + BLOCK_WET_GRASS_STEP, + BLOCK_WOODEN_BUTTON_CLICK_OFF, + BLOCK_WOODEN_BUTTON_CLICK_ON, + BLOCK_WOODEN_DOOR_CLOSE, + BLOCK_WOODEN_DOOR_OPEN, + BLOCK_WOODEN_PRESSURE_PLATE_CLICK_OFF, + BLOCK_WOODEN_PRESSURE_PLATE_CLICK_ON, + BLOCK_WOODEN_TRAPDOOR_CLOSE, + BLOCK_WOODEN_TRAPDOOR_OPEN, + BLOCK_WOOD_BREAK, + BLOCK_WOOD_FALL, + BLOCK_WOOD_HIT, + BLOCK_WOOD_PLACE, + BLOCK_WOOD_STEP, + BLOCK_WOOL_BREAK, + BLOCK_WOOL_FALL, + BLOCK_WOOL_HIT, + BLOCK_WOOL_PLACE, + BLOCK_WOOL_STEP, + ENCHANT_THORNS_HIT, + ENTITY_ARMOR_STAND_BREAK, + ENTITY_ARMOR_STAND_FALL, + ENTITY_ARMOR_STAND_HIT, + ENTITY_ARMOR_STAND_PLACE, + ENTITY_ARROW_HIT, + ENTITY_ARROW_HIT_PLAYER, + ENTITY_ARROW_SHOOT, + ENTITY_BAT_AMBIENT, + ENTITY_BAT_DEATH, + ENTITY_BAT_HURT, + ENTITY_BAT_LOOP, + ENTITY_BAT_TAKEOFF, + ENTITY_BLAZE_AMBIENT, + ENTITY_BLAZE_BURN, + ENTITY_BLAZE_DEATH, + ENTITY_BLAZE_HURT, + ENTITY_BLAZE_SHOOT, + ENTITY_BOAT_PADDLE_LAND, + ENTITY_BOAT_PADDLE_WATER, + ENTITY_CAT_AMBIENT, + ENTITY_CAT_DEATH, + ENTITY_CAT_HISS, + ENTITY_CAT_HURT, + ENTITY_CAT_PURR, + ENTITY_CAT_PURREOW, + ENTITY_CHICKEN_AMBIENT, + ENTITY_CHICKEN_DEATH, + ENTITY_CHICKEN_EGG, + ENTITY_CHICKEN_HURT, + ENTITY_CHICKEN_STEP, + ENTITY_COD_AMBIENT, + ENTITY_COD_DEATH, + ENTITY_COD_FLOP, + ENTITY_COD_HURT, + ENTITY_COW_AMBIENT, + ENTITY_COW_DEATH, + ENTITY_COW_HURT, + ENTITY_COW_MILK, + ENTITY_COW_STEP, + ENTITY_CREEPER_DEATH, + ENTITY_CREEPER_HURT, + ENTITY_CREEPER_PRIMED, + ENTITY_DOLPHIN_AMBIENT, + ENTITY_DOLPHIN_AMBIENT_WATER, + ENTITY_DOLPHIN_ATTACK, + ENTITY_DOLPHIN_DEATH, + ENTITY_DOLPHIN_EAT, + ENTITY_DOLPHIN_HURT, + ENTITY_DOLPHIN_JUMP, + ENTITY_DOLPHIN_PLAY, + ENTITY_DOLPHIN_SPLASH, + ENTITY_DOLPHIN_SWIM, + ENTITY_DONKEY_AMBIENT, + ENTITY_DONKEY_ANGRY, + ENTITY_DONKEY_CHEST, + ENTITY_DONKEY_DEATH, + ENTITY_DONKEY_HURT, + ENTITY_DRAGON_FIREBALL_EXPLODE, + ENTITY_DROWNED_AMBIENT, + ENTITY_DROWNED_AMBIENT_WATER, + ENTITY_DROWNED_DEATH, + ENTITY_DROWNED_DEATH_WATER, + ENTITY_DROWNED_HURT, + ENTITY_DROWNED_HURT_WATER, + ENTITY_DROWNED_SHOOT, + ENTITY_DROWNED_STEP, + ENTITY_DROWNED_SWIM, + ENTITY_EGG_THROW, + ENTITY_ELDER_GUARDIAN_AMBIENT, + ENTITY_ELDER_GUARDIAN_AMBIENT_LAND, + ENTITY_ELDER_GUARDIAN_CURSE, + ENTITY_ELDER_GUARDIAN_DEATH, + ENTITY_ELDER_GUARDIAN_DEATH_LAND, + ENTITY_ELDER_GUARDIAN_FLOP, + ENTITY_ELDER_GUARDIAN_HURT, + ENTITY_ELDER_GUARDIAN_HURT_LAND, + ENTITY_ENDERMAN_AMBIENT, + ENTITY_ENDERMAN_DEATH, + ENTITY_ENDERMAN_HURT, + ENTITY_ENDERMAN_SCREAM, + ENTITY_ENDERMAN_STARE, + ENTITY_ENDERMAN_TELEPORT, + ENTITY_ENDERMITE_AMBIENT, + ENTITY_ENDERMITE_DEATH, + ENTITY_ENDERMITE_HURT, + ENTITY_ENDERMITE_STEP, + ENTITY_ENDER_DRAGON_AMBIENT, + ENTITY_ENDER_DRAGON_DEATH, + ENTITY_ENDER_DRAGON_FLAP, + ENTITY_ENDER_DRAGON_GROWL, + ENTITY_ENDER_DRAGON_HURT, + ENTITY_ENDER_DRAGON_SHOOT, + ENTITY_ENDER_EYE_DEATH, + ENTITY_ENDER_EYE_LAUNCH, + ENTITY_ENDER_PEARL_THROW, + ENTITY_EVOKER_AMBIENT, + ENTITY_EVOKER_CAST_SPELL, + ENTITY_EVOKER_DEATH, + ENTITY_EVOKER_FANGS_ATTACK, + ENTITY_EVOKER_HURT, + ENTITY_EVOKER_PREPARE_ATTACK, + ENTITY_EVOKER_PREPARE_SUMMON, + ENTITY_EVOKER_PREPARE_WOLOLO, + ENTITY_EXPERIENCE_BOTTLE_THROW, + ENTITY_EXPERIENCE_ORB_PICKUP, + ENTITY_FIREWORK_ROCKET_BLAST, + ENTITY_FIREWORK_ROCKET_BLAST_FAR, + ENTITY_FIREWORK_ROCKET_LARGE_BLAST, + ENTITY_FIREWORK_ROCKET_LARGE_BLAST_FAR, + ENTITY_FIREWORK_ROCKET_LAUNCH, + ENTITY_FIREWORK_ROCKET_SHOOT, + ENTITY_FIREWORK_ROCKET_TWINKLE, + ENTITY_FIREWORK_ROCKET_TWINKLE_FAR, + ENTITY_FISHING_BOBBER_RETRIEVE, + ENTITY_FISHING_BOBBER_SPLASH, + ENTITY_FISHING_BOBBER_THROW, + ENTITY_FISH_SWIM, + ENTITY_GENERIC_BIG_FALL, + ENTITY_GENERIC_BURN, + ENTITY_GENERIC_DEATH, + ENTITY_GENERIC_DRINK, + ENTITY_GENERIC_EAT, + ENTITY_GENERIC_EXPLODE, + ENTITY_GENERIC_EXTINGUISH_FIRE, + ENTITY_GENERIC_HURT, + ENTITY_GENERIC_SMALL_FALL, + ENTITY_GENERIC_SPLASH, + ENTITY_GENERIC_SWIM, + ENTITY_GHAST_AMBIENT, + ENTITY_GHAST_DEATH, + ENTITY_GHAST_HURT, + ENTITY_GHAST_SCREAM, + ENTITY_GHAST_SHOOT, + ENTITY_GHAST_WARN, + ENTITY_GUARDIAN_AMBIENT, + ENTITY_GUARDIAN_AMBIENT_LAND, + ENTITY_GUARDIAN_ATTACK, + ENTITY_GUARDIAN_DEATH, + ENTITY_GUARDIAN_DEATH_LAND, + ENTITY_GUARDIAN_FLOP, + ENTITY_GUARDIAN_HURT, + ENTITY_GUARDIAN_HURT_LAND, + ENTITY_HORSE_AMBIENT, + ENTITY_HORSE_ANGRY, + ENTITY_HORSE_ARMOR, + ENTITY_HORSE_BREATHE, + ENTITY_HORSE_DEATH, + ENTITY_HORSE_EAT, + ENTITY_HORSE_GALLOP, + ENTITY_HORSE_HURT, + ENTITY_HORSE_JUMP, + ENTITY_HORSE_LAND, + ENTITY_HORSE_SADDLE, + ENTITY_HORSE_STEP, + ENTITY_HORSE_STEP_WOOD, + ENTITY_HOSTILE_BIG_FALL, + ENTITY_HOSTILE_DEATH, + ENTITY_HOSTILE_HURT, + ENTITY_HOSTILE_SMALL_FALL, + ENTITY_HOSTILE_SPLASH, + ENTITY_HOSTILE_SWIM, + ENTITY_HUSK_AMBIENT, + ENTITY_HUSK_CONVERTED_TO_ZOMBIE, + ENTITY_HUSK_DEATH, + ENTITY_HUSK_HURT, + ENTITY_HUSK_STEP, + ENTITY_ILLUSIONER_AMBIENT, + ENTITY_ILLUSIONER_CAST_SPELL, + ENTITY_ILLUSIONER_DEATH, + ENTITY_ILLUSIONER_HURT, + ENTITY_ILLUSIONER_MIRROR_MOVE, + ENTITY_ILLUSIONER_PREPARE_BLINDNESS, + ENTITY_ILLUSIONER_PREPARE_MIRROR, + ENTITY_IRON_GOLEM_ATTACK, + ENTITY_IRON_GOLEM_DEATH, + ENTITY_IRON_GOLEM_HURT, + ENTITY_IRON_GOLEM_STEP, + ENTITY_ITEM_BREAK, + ENTITY_ITEM_FRAME_ADD_ITEM, + ENTITY_ITEM_FRAME_BREAK, + ENTITY_ITEM_FRAME_PLACE, + ENTITY_ITEM_FRAME_REMOVE_ITEM, + ENTITY_ITEM_FRAME_ROTATE_ITEM, + ENTITY_ITEM_PICKUP, + ENTITY_LEASH_KNOT_BREAK, + ENTITY_LEASH_KNOT_PLACE, + ENTITY_LIGHTNING_BOLT_IMPACT, + ENTITY_LIGHTNING_BOLT_THUNDER, + ENTITY_LINGERING_POTION_THROW, + ENTITY_LLAMA_AMBIENT, + ENTITY_LLAMA_ANGRY, + ENTITY_LLAMA_CHEST, + ENTITY_LLAMA_DEATH, + ENTITY_LLAMA_EAT, + ENTITY_LLAMA_HURT, + ENTITY_LLAMA_SPIT, + ENTITY_LLAMA_STEP, + ENTITY_LLAMA_SWAG, + ENTITY_MAGMA_CUBE_DEATH, + ENTITY_MAGMA_CUBE_DEATH_SMALL, + ENTITY_MAGMA_CUBE_HURT, + ENTITY_MAGMA_CUBE_HURT_SMALL, + ENTITY_MAGMA_CUBE_JUMP, + ENTITY_MAGMA_CUBE_SQUISH, + ENTITY_MAGMA_CUBE_SQUISH_SMALL, + ENTITY_MINECART_INSIDE, + ENTITY_MINECART_RIDING, + ENTITY_MOOSHROOM_SHEAR, + ENTITY_MULE_AMBIENT, + ENTITY_MULE_CHEST, + ENTITY_MULE_DEATH, + ENTITY_MULE_HURT, + ENTITY_PAINTING_BREAK, + ENTITY_PAINTING_PLACE, + ENTITY_PARROT_AMBIENT, + ENTITY_PARROT_DEATH, + ENTITY_PARROT_EAT, + ENTITY_PARROT_FLY, + ENTITY_PARROT_HURT, + ENTITY_PARROT_IMITATE_BLAZE, + ENTITY_PARROT_IMITATE_CREEPER, + ENTITY_PARROT_IMITATE_DROWNED, + ENTITY_PARROT_IMITATE_ELDER_GUARDIAN, + ENTITY_PARROT_IMITATE_ENDERMAN, + ENTITY_PARROT_IMITATE_ENDERMITE, + ENTITY_PARROT_IMITATE_ENDER_DRAGON, + ENTITY_PARROT_IMITATE_EVOKER, + ENTITY_PARROT_IMITATE_GHAST, + ENTITY_PARROT_IMITATE_HUSK, + ENTITY_PARROT_IMITATE_ILLUSIONER, + ENTITY_PARROT_IMITATE_MAGMA_CUBE, + ENTITY_PARROT_IMITATE_PHANTOM, + ENTITY_PARROT_IMITATE_POLAR_BEAR, + ENTITY_PARROT_IMITATE_SHULKER, + ENTITY_PARROT_IMITATE_SILVERFISH, + ENTITY_PARROT_IMITATE_SKELETON, + ENTITY_PARROT_IMITATE_SLIME, + ENTITY_PARROT_IMITATE_SPIDER, + ENTITY_PARROT_IMITATE_STRAY, + ENTITY_PARROT_IMITATE_VEX, + ENTITY_PARROT_IMITATE_VINDICATOR, + ENTITY_PARROT_IMITATE_WITCH, + ENTITY_PARROT_IMITATE_WITHER, + ENTITY_PARROT_IMITATE_WITHER_SKELETON, + ENTITY_PARROT_IMITATE_WOLF, + ENTITY_PARROT_IMITATE_ZOMBIE, + ENTITY_PARROT_IMITATE_ZOMBIE_PIGMAN, + ENTITY_PARROT_IMITATE_ZOMBIE_VILLAGER, + ENTITY_PARROT_STEP, + ENTITY_PHANTOM_AMBIENT, + ENTITY_PHANTOM_BITE, + ENTITY_PHANTOM_DEATH, + ENTITY_PHANTOM_FLAP, + ENTITY_PHANTOM_HURT, + ENTITY_PHANTOM_SWOOP, + ENTITY_PIG_AMBIENT, + ENTITY_PIG_DEATH, + ENTITY_PIG_HURT, + ENTITY_PIG_SADDLE, + ENTITY_PIG_STEP, + ENTITY_PLAYER_ATTACK_CRIT, + ENTITY_PLAYER_ATTACK_KNOCKBACK, + ENTITY_PLAYER_ATTACK_NODAMAGE, + ENTITY_PLAYER_ATTACK_STRONG, + ENTITY_PLAYER_ATTACK_SWEEP, + ENTITY_PLAYER_ATTACK_WEAK, + ENTITY_PLAYER_BIG_FALL, + ENTITY_PLAYER_BREATH, + ENTITY_PLAYER_BURP, + ENTITY_PLAYER_DEATH, + ENTITY_PLAYER_HURT, + ENTITY_PLAYER_HURT_DROWN, + ENTITY_PLAYER_HURT_ON_FIRE, + ENTITY_PLAYER_LEVELUP, + ENTITY_PLAYER_SMALL_FALL, + ENTITY_PLAYER_SPLASH, + ENTITY_PLAYER_SPLASH_HIGH_SPEED, + ENTITY_PLAYER_SWIM, + ENTITY_POLAR_BEAR_AMBIENT, + ENTITY_POLAR_BEAR_AMBIENT_BABY, + ENTITY_POLAR_BEAR_DEATH, + ENTITY_POLAR_BEAR_HURT, + ENTITY_POLAR_BEAR_STEP, + ENTITY_POLAR_BEAR_WARNING, + ENTITY_PUFFER_FISH_AMBIENT, + ENTITY_PUFFER_FISH_BLOW_OUT, + ENTITY_PUFFER_FISH_BLOW_UP, + ENTITY_PUFFER_FISH_DEATH, + ENTITY_PUFFER_FISH_FLOP, + ENTITY_PUFFER_FISH_HURT, + ENTITY_PUFFER_FISH_STING, + ENTITY_RABBIT_AMBIENT, + ENTITY_RABBIT_ATTACK, + ENTITY_RABBIT_DEATH, + ENTITY_RABBIT_HURT, + ENTITY_RABBIT_JUMP, + ENTITY_SALMON_AMBIENT, + ENTITY_SALMON_DEATH, + ENTITY_SALMON_FLOP, + ENTITY_SALMON_HURT, + ENTITY_SHEEP_AMBIENT, + ENTITY_SHEEP_DEATH, + ENTITY_SHEEP_HURT, + ENTITY_SHEEP_SHEAR, + ENTITY_SHEEP_STEP, + ENTITY_SHULKER_AMBIENT, + ENTITY_SHULKER_BULLET_HIT, + ENTITY_SHULKER_BULLET_HURT, + ENTITY_SHULKER_CLOSE, + ENTITY_SHULKER_DEATH, + ENTITY_SHULKER_HURT, + ENTITY_SHULKER_HURT_CLOSED, + ENTITY_SHULKER_OPEN, + ENTITY_SHULKER_SHOOT, + ENTITY_SHULKER_TELEPORT, + ENTITY_SILVERFISH_AMBIENT, + ENTITY_SILVERFISH_DEATH, + ENTITY_SILVERFISH_HURT, + ENTITY_SILVERFISH_STEP, + ENTITY_SKELETON_AMBIENT, + ENTITY_SKELETON_DEATH, + ENTITY_SKELETON_HORSE_AMBIENT, + ENTITY_SKELETON_HORSE_AMBIENT_WATER, + ENTITY_SKELETON_HORSE_DEATH, + ENTITY_SKELETON_HORSE_GALLOP_WATER, + ENTITY_SKELETON_HORSE_HURT, + ENTITY_SKELETON_HORSE_JUMP_WATER, + ENTITY_SKELETON_HORSE_STEP_WATER, + ENTITY_SKELETON_HORSE_SWIM, + ENTITY_SKELETON_HURT, + ENTITY_SKELETON_SHOOT, + ENTITY_SKELETON_STEP, + ENTITY_SLIME_ATTACK, + ENTITY_SLIME_DEATH, + ENTITY_SLIME_DEATH_SMALL, + ENTITY_SLIME_HURT, + ENTITY_SLIME_HURT_SMALL, + ENTITY_SLIME_JUMP, + ENTITY_SLIME_JUMP_SMALL, + ENTITY_SLIME_SQUISH, + ENTITY_SLIME_SQUISH_SMALL, + ENTITY_SNOWBALL_THROW, + ENTITY_SNOW_GOLEM_AMBIENT, + ENTITY_SNOW_GOLEM_DEATH, + ENTITY_SNOW_GOLEM_HURT, + ENTITY_SNOW_GOLEM_SHOOT, + ENTITY_SPIDER_AMBIENT, + ENTITY_SPIDER_DEATH, + ENTITY_SPIDER_HURT, + ENTITY_SPIDER_STEP, + ENTITY_SPLASH_POTION_BREAK, + ENTITY_SPLASH_POTION_THROW, + ENTITY_SQUID_AMBIENT, + ENTITY_SQUID_DEATH, + ENTITY_SQUID_HURT, + ENTITY_SQUID_SQUIRT, + ENTITY_STRAY_AMBIENT, + ENTITY_STRAY_DEATH, + ENTITY_STRAY_HURT, + ENTITY_STRAY_STEP, + ENTITY_TNT_PRIMED, + ENTITY_TROPICAL_FISH_AMBIENT, + ENTITY_TROPICAL_FISH_DEATH, + ENTITY_TROPICAL_FISH_FLOP, + ENTITY_TROPICAL_FISH_HURT, + ENTITY_TURTLE_AMBIENT_LAND, + ENTITY_TURTLE_DEATH, + ENTITY_TURTLE_DEATH_BABY, + ENTITY_TURTLE_EGG_BREAK, + ENTITY_TURTLE_EGG_CRACK, + ENTITY_TURTLE_EGG_HATCH, + ENTITY_TURTLE_HURT, + ENTITY_TURTLE_HURT_BABY, + ENTITY_TURTLE_LAY_EGG, + ENTITY_TURTLE_SHAMBLE, + ENTITY_TURTLE_SHAMBLE_BABY, + ENTITY_TURTLE_SWIM, + ENTITY_VEX_AMBIENT, + ENTITY_VEX_CHARGE, + ENTITY_VEX_DEATH, + ENTITY_VEX_HURT, + ENTITY_VILLAGER_AMBIENT, + ENTITY_VILLAGER_DEATH, + ENTITY_VILLAGER_HURT, + ENTITY_VILLAGER_NO, + ENTITY_VILLAGER_TRADE, + ENTITY_VILLAGER_YES, + ENTITY_VINDICATOR_AMBIENT, + ENTITY_VINDICATOR_DEATH, + ENTITY_VINDICATOR_HURT, + ENTITY_WITCH_AMBIENT, + ENTITY_WITCH_DEATH, + ENTITY_WITCH_DRINK, + ENTITY_WITCH_HURT, + ENTITY_WITCH_THROW, + ENTITY_WITHER_AMBIENT, + ENTITY_WITHER_BREAK_BLOCK, + ENTITY_WITHER_DEATH, + ENTITY_WITHER_HURT, + ENTITY_WITHER_SHOOT, + ENTITY_WITHER_SKELETON_AMBIENT, + ENTITY_WITHER_SKELETON_DEATH, + ENTITY_WITHER_SKELETON_HURT, + ENTITY_WITHER_SKELETON_STEP, + ENTITY_WITHER_SPAWN, + ENTITY_WOLF_AMBIENT, + ENTITY_WOLF_DEATH, + ENTITY_WOLF_GROWL, + ENTITY_WOLF_HOWL, + ENTITY_WOLF_HURT, + ENTITY_WOLF_PANT, + ENTITY_WOLF_SHAKE, + ENTITY_WOLF_STEP, + ENTITY_WOLF_WHINE, + ENTITY_ZOMBIE_AMBIENT, + ENTITY_ZOMBIE_ATTACK_IRON_DOOR, + ENTITY_ZOMBIE_ATTACK_WOODEN_DOOR, + ENTITY_ZOMBIE_BREAK_WOODEN_DOOR, + ENTITY_ZOMBIE_CONVERTED_TO_DROWNED, + ENTITY_ZOMBIE_DEATH, + ENTITY_ZOMBIE_DESTROY_EGG, + ENTITY_ZOMBIE_HORSE_AMBIENT, + ENTITY_ZOMBIE_HORSE_DEATH, + ENTITY_ZOMBIE_HORSE_HURT, + ENTITY_ZOMBIE_HURT, + ENTITY_ZOMBIE_INFECT, + ENTITY_ZOMBIE_PIGMAN_AMBIENT, + ENTITY_ZOMBIE_PIGMAN_ANGRY, + ENTITY_ZOMBIE_PIGMAN_DEATH, + ENTITY_ZOMBIE_PIGMAN_HURT, + ENTITY_ZOMBIE_STEP, + ENTITY_ZOMBIE_VILLAGER_AMBIENT, + ENTITY_ZOMBIE_VILLAGER_CONVERTED, + ENTITY_ZOMBIE_VILLAGER_CURE, + ENTITY_ZOMBIE_VILLAGER_DEATH, + ENTITY_ZOMBIE_VILLAGER_HURT, + ENTITY_ZOMBIE_VILLAGER_STEP, + ITEM_ARMOR_EQUIP_CHAIN, + ITEM_ARMOR_EQUIP_DIAMOND, + ITEM_ARMOR_EQUIP_ELYTRA, + ITEM_ARMOR_EQUIP_GENERIC, + ITEM_ARMOR_EQUIP_GOLD, + ITEM_ARMOR_EQUIP_IRON, + ITEM_ARMOR_EQUIP_LEATHER, + ITEM_ARMOR_EQUIP_TURTLE, + ITEM_AXE_STRIP, + ITEM_BOTTLE_EMPTY, + ITEM_BOTTLE_FILL, + ITEM_BOTTLE_FILL_DRAGONBREATH, + ITEM_BUCKET_EMPTY, + ITEM_BUCKET_EMPTY_FISH, + ITEM_BUCKET_EMPTY_LAVA, + ITEM_BUCKET_FILL, + ITEM_BUCKET_FILL_FISH, + ITEM_BUCKET_FILL_LAVA, + ITEM_CHORUS_FRUIT_TELEPORT, + ITEM_ELYTRA_FLYING, + ITEM_FIRECHARGE_USE, + ITEM_FLINTANDSTEEL_USE, + ITEM_HOE_TILL, + ITEM_SHIELD_BLOCK, + ITEM_SHIELD_BREAK, + ITEM_SHOVEL_FLATTEN, + ITEM_TOTEM_USE, + ITEM_TRIDENT_HIT, + ITEM_TRIDENT_HIT_GROUND, + ITEM_TRIDENT_RETURN, + ITEM_TRIDENT_RIPTIDE_1, + ITEM_TRIDENT_RIPTIDE_2, + ITEM_TRIDENT_RIPTIDE_3, + ITEM_TRIDENT_THROW, + ITEM_TRIDENT_THUNDER, + MUSIC_CREATIVE, + MUSIC_CREDITS, + MUSIC_DISC_11, + MUSIC_DISC_13, + MUSIC_DISC_BLOCKS, + MUSIC_DISC_CAT, + MUSIC_DISC_CHIRP, + MUSIC_DISC_FAR, + MUSIC_DISC_MALL, + MUSIC_DISC_MELLOHI, + MUSIC_DISC_STAL, + MUSIC_DISC_STRAD, + MUSIC_DISC_WAIT, + MUSIC_DISC_WARD, + MUSIC_DRAGON, + MUSIC_END, + MUSIC_GAME, + MUSIC_MENU, + MUSIC_NETHER, + MUSIC_UNDER_WATER, + UI_BUTTON_CLICK, + UI_TOAST_CHALLENGE_COMPLETE, + UI_TOAST_IN, + UI_TOAST_OUT, + WEATHER_RAIN, + WEATHER_RAIN_ABOVE, +} diff --git a/api/src/main/java/org/bukkit/SoundCategory.java b/api/src/main/java/org/bukkit/SoundCategory.java new file mode 100644 index 000000000..ac5e263d7 --- /dev/null +++ b/api/src/main/java/org/bukkit/SoundCategory.java @@ -0,0 +1,18 @@ +package org.bukkit; + +/** + * An Enum of categories for sounds. + */ +public enum SoundCategory { + + MASTER, + MUSIC, + RECORDS, + WEATHER, + BLOCKS, + HOSTILE, + NEUTRAL, + PLAYERS, + AMBIENT, + VOICE; +} diff --git a/api/src/main/java/org/bukkit/Statistic.java b/api/src/main/java/org/bukkit/Statistic.java new file mode 100644 index 000000000..f5a834fea --- /dev/null +++ b/api/src/main/java/org/bukkit/Statistic.java @@ -0,0 +1,151 @@ +package org.bukkit; + +import org.jetbrains.annotations.NotNull; + +/** + * Represents a countable statistic, which is tracked by the server. + */ +public enum Statistic { + DAMAGE_DEALT, + DAMAGE_TAKEN, + DEATHS, + MOB_KILLS, + PLAYER_KILLS, + FISH_CAUGHT, + ANIMALS_BRED, + LEAVE_GAME, + JUMP, + DROP_COUNT, + DROP(Type.ITEM), + PICKUP(Type.ITEM), + /** + * Name is misleading, actually records ticks played. + */ + PLAY_ONE_MINUTE, + WALK_ONE_CM, + WALK_ON_WATER_ONE_CM, + FALL_ONE_CM, + SNEAK_TIME, + CLIMB_ONE_CM, + FLY_ONE_CM, + WALK_UNDER_WATER_ONE_CM, + MINECART_ONE_CM, + BOAT_ONE_CM, + PIG_ONE_CM, + HORSE_ONE_CM, + SPRINT_ONE_CM, + CROUCH_ONE_CM, + AVIATE_ONE_CM, + MINE_BLOCK(Type.BLOCK), + USE_ITEM(Type.ITEM), + BREAK_ITEM(Type.ITEM), + CRAFT_ITEM(Type.ITEM), + KILL_ENTITY(Type.ENTITY), + ENTITY_KILLED_BY(Type.ENTITY), + TIME_SINCE_DEATH, + TALKED_TO_VILLAGER, + TRADED_WITH_VILLAGER, + CAKE_SLICES_EATEN, + CAULDRON_FILLED, + CAULDRON_USED, + ARMOR_CLEANED, + BANNER_CLEANED, + BREWINGSTAND_INTERACTION, + BEACON_INTERACTION, + DROPPER_INSPECTED, + HOPPER_INSPECTED, + DISPENSER_INSPECTED, + NOTEBLOCK_PLAYED, + NOTEBLOCK_TUNED, + FLOWER_POTTED, + TRAPPED_CHEST_TRIGGERED, + ENDERCHEST_OPENED, + ITEM_ENCHANTED, + RECORD_PLAYED, + FURNACE_INTERACTION, + CRAFTING_TABLE_INTERACTION, + CHEST_OPENED, + SLEEP_IN_BED, + SHULKER_BOX_OPENED, + TIME_SINCE_REST, + SWIM_ONE_CM, + DAMAGE_DEALT_ABSORBED, + DAMAGE_DEALT_RESISTED, + DAMAGE_BLOCKED_BY_SHIELD, + DAMAGE_ABSORBED, + DAMAGE_RESISTED, + CLEAN_SHULKER_BOX; + + private final Type type; + + private Statistic() { + this(Type.UNTYPED); + } + + private Statistic(/*@NotNull*/ Type type) { + this.type = type; + } + + /** + * Gets the type of this statistic. + * + * @return the type of this statistic + */ + @NotNull + public Type getType() { + return type; + } + + /** + * Checks if this is a substatistic. + *

+ * A substatistic exists en masse for each block, item, or entitytype, depending on + * {@link #getType()}. + *

+ * This is a redundant method and equivalent to checking + * getType() != Type.UNTYPED + * + * @return true if this is a substatistic + */ + public boolean isSubstatistic() { + return type != Type.UNTYPED; + } + + /** + * Checks if this is a substatistic dealing with blocks. + *

+ * This is a redundant method and equivalent to checking + * getType() == Type.BLOCK + * + * @return true if this deals with blocks + */ + public boolean isBlock() { + return type == Type.BLOCK; + } + + /** + * The type of statistic. + * + */ + public enum Type { + /** + * Statistics of this type do not require a qualifier. + */ + UNTYPED, + + /** + * Statistics of this type require an Item Material qualifier. + */ + ITEM, + + /** + * Statistics of this type require a Block Material qualifier. + */ + BLOCK, + + /** + * Statistics of this type require an EntityType qualifier. + */ + ENTITY; + } +} diff --git a/api/src/main/java/org/bukkit/StructureType.java b/api/src/main/java/org/bukkit/StructureType.java new file mode 100644 index 000000000..02d795451 --- /dev/null +++ b/api/src/main/java/org/bukkit/StructureType.java @@ -0,0 +1,228 @@ +package org.bukkit; + +import com.google.common.base.Preconditions; +import com.google.common.collect.ImmutableMap; +import org.apache.commons.lang.Validate; +import org.bukkit.map.MapCursor; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +import java.util.HashMap; +import java.util.Map; +import java.util.Objects; + +/** + * This class handles the creation and storage of all structure types for + * Bukkit. Structure Types are the different kinds of structures that can be + * generated during world/chunk generation. These include Villages, Mineshafts, + * Mansions, etc. + *
+ * The registration of new {@link StructureType}s is case-sensitive. + */ +// Order is retrieved from WorldGenFactory +public class StructureType { + + private static final Map structureTypeMap = new HashMap<>(); + + /** + * Mineshafts are underground structures which consist of branching mining + * tunnels with wooden supports and broken rails. + *
+ * They are the only place where cave spider spawners and minecarts with + * chests can be found naturally. + */ + public static final StructureType MINESHAFT = register(new StructureType("Mineshaft", MapCursor.Type.RED_X)); + + /** + * Villages are naturally generating structures that form above ground. + *
+ * They are usually generated in desert, plains, taiga, and savanna biomes + * and are a site for villager spawns, with whom the player can trade. + */ + public static final StructureType VILLAGE = register(new StructureType("Village", MapCursor.Type.MANSION)); + + /** + * Nether fortresses are very large complexes that mainly consist of + * netherbricks. + *
+ * They contain blaze spawners, nether wart farms, and loot chests. They are + * only generated in the nether dimension. + */ + public static final StructureType NETHER_FORTRESS = register(new StructureType("Fortress", MapCursor.Type.RED_X)); + + /** + * Strongholds are underground structures that consist of many rooms, + * libraries, and an end portal room. + *
+ * They can be found using an {@link Material#ENDER_EYE}. + */ + public static final StructureType STRONGHOLD = register(new StructureType("Stronghold", MapCursor.Type.MANSION)); + + /** + * Jungle pyramids (also known as jungle temples) are found in jungles. + *
+ * They are usually composed of cobblestone and mossy cobblestone. They + * consist of three floors, with the bottom floor containing treasure + * chests. + */ + public static final StructureType JUNGLE_PYRAMID = register(new StructureType("Jungle_Pyramid", MapCursor.Type.RED_X)); + + /** + * Ocean ruins are clusters of many different blocks that generate + * underwater in ocean biomes (as well as on the surface of beaches). + *
+ * They come in my different variations. The cold variants consist primarily + * of stone brick, and the warm variants consist of sandstone. + */ + public static final StructureType OCEAN_RUIN = register(new StructureType("Ocean_Ruin", MapCursor.Type.TEMPLE)); + + /** + * Desert pyramids (also known as desert temples) are found in deserts. + *
+ * They are usually composed of sandstone and stained terracotta. + */ + public static final StructureType DESERT_PYRAMID = register(new StructureType("Desert_Pyramid", MapCursor.Type.RED_X)); + + /** + * Igloos are structures that generate in snowy biomes. + *
+ * They consist of the house, as well as a basement. + */ + public static final StructureType IGLOO = register(new StructureType("Igloo", MapCursor.Type.RED_X)); + + /** + * Swamp huts (also known as witch huts) generate in swamp biomes and have + * the ability to spawn witches. + */ + public static final StructureType SWAMP_HUT = register(new StructureType("Swamp_Hut", MapCursor.Type.RED_X)); + + /** + * Ocean monuments are underwater structures. + *
+ * They are usually composed on all three different prismarine types and sea + * lanterns. They are the only place guardians and elder guardians spawn + * naturally. + */ + public static final StructureType OCEAN_MONUMENT = register(new StructureType("Monument", MapCursor.Type.TEMPLE)); + + /** + * End Cities are tall castle-like structures that generate in the outer + * island of the End dimension. + *
+ * They consist primarily of end stone bricks, purpur blocks, and end rods. + * They are the only place where shulkers can be found. + */ + public static final StructureType END_CITY = register(new StructureType("EndCity", MapCursor.Type.RED_X)); + + /** + * Mansions (also known as woodland mansions) are massive house structures + * that generate in dark forests, containing a wide variety of rooms. + *
+ * They are the only place where evokers, vindicators, and vexes spawn + * naturally (but only once) + */ + public static final StructureType WOODLAND_MANSION = register(new StructureType("Mansion", MapCursor.Type.MANSION)); + + /** + * Buried treasure consists of a single chest buried in the beach sand or + * gravel, with random loot in it. + */ + public static final StructureType BURIED_TREASURE = register(new StructureType("Buried_Treasure", MapCursor.Type.RED_X)); + + /** + * Shipwrecks are structures that generate on the floor of oceans or + * beaches. + *
+ * They are made up of wood materials, and contain 1-3 loot chests. They can + * generate sideways, upside-down, or upright. + */ + public static final StructureType SHIPWRECK = register(new StructureType("Shipwreck", MapCursor.Type.RED_X)); + + /* **************** + * STRUCTURE TYPES REGISTERED ABOVE THIS + * **************** + */ + private final String name; + private final MapCursor.Type mapCursor; + + /** + * Create a new StructureType with a given name and map cursor. To indicate + * this structure should not be compatible with explorer maps, use null for + * mapIcon. + * + * @param name the name of the structure, case-sensitive + * @param mapIcon the {@link org.bukkit.map.MapCursor.Type} this structure type should use + * when creating explorer maps. Use null to indicate this structure should + * not be compatible with explorer maps. + */ + private StructureType(@NotNull String name, @Nullable MapCursor.Type mapIcon) { + Validate.notEmpty(name, "Structure name cannot be empty"); + this.name = name; + this.mapCursor = mapIcon; + } + + /** + * Get the name of this structure. This is case-sensitive when used in + * commands. + * + * @return the name of this structure + */ + @NotNull + public String getName() { + return name; + } + + /** + * Get the {@link org.bukkit.map.MapCursor.Type} that this structure can use on maps. If + * this is null, this structure will not appear on explorer maps. + * + * @return the {@link org.bukkit.map.MapCursor.Type} or null. + */ + @Nullable + public MapCursor.Type getMapIcon() { + return mapCursor; + } + + @Override + public boolean equals(Object other) { + if (this == other) { + return true; + } + if (!(other instanceof StructureType)) { + return false; + } + StructureType that = (StructureType) other; + return this.name.equals(that.name) && this.mapCursor == that.mapCursor; + } + + @Override + public int hashCode() { + int hash = 7; + hash = 71 * hash + Objects.hashCode(this.name); + hash = 71 * hash + Objects.hashCode(this.mapCursor); + return hash; + } + + @Override + public String toString() { + return "StructureType{name=" + this.name + ", cursor=" + this.mapCursor + "}"; + } + + @NotNull + private static T register(@NotNull T type) { + Preconditions.checkNotNull(type, "Cannot register null StructureType."); + Preconditions.checkArgument(!structureTypeMap.containsKey(type.getName()), "Cannot register same StructureType twice. %s", type.getName()); + StructureType.structureTypeMap.put(type.getName(), type); + return type; + } + + /** + * Get all registered {@link StructureType}s. + * + * @return an immutable copy of registered structure types. + */ + @NotNull + public static Map getStructureTypes() { + return ImmutableMap.copyOf(structureTypeMap); + } +} diff --git a/api/src/main/java/org/bukkit/Tag.java b/api/src/main/java/org/bukkit/Tag.java new file mode 100644 index 000000000..03d1f2583 --- /dev/null +++ b/api/src/main/java/org/bukkit/Tag.java @@ -0,0 +1,211 @@ +package org.bukkit; + +import org.jetbrains.annotations.NotNull; + +import java.util.Set; + +/** + * Represents a tag that may be defined by the server or a resource pack to + * group like things together. + * + * Note that whilst all tags defined within this interface must be present in + * implementations, their existence is not guaranteed across future versions. + * + * @param the type of things grouped by this tag + */ +public interface Tag extends Keyed { + + /** + * Key for the built in block registry. + */ + String REGISTRY_BLOCKS = "blocks"; + /** + * Vanilla block tag representing all colors of wool. + */ + Tag WOOL = Bukkit.getTag(REGISTRY_BLOCKS, NamespacedKey.minecraft("wool"), Material.class); + /** + * Vanilla block tag representing all plank variants. + */ + Tag PLANKS = Bukkit.getTag(REGISTRY_BLOCKS, NamespacedKey.minecraft("planks"), Material.class); + /** + * Vanilla block tag representing all regular/mossy/cracked/chiseled stone + * bricks. + */ + Tag STONE_BRICKS = Bukkit.getTag(REGISTRY_BLOCKS, NamespacedKey.minecraft("stone_bricks"), Material.class); + /** + * Vanilla block tag representing all wooden buttons. + */ + Tag WOODEN_BUTTONS = Bukkit.getTag(REGISTRY_BLOCKS, NamespacedKey.minecraft("wooden_buttons"), Material.class); + /** + * Vanilla block tag representing all buttons (inherits from + * {@link #WOODEN_BUTTONS}. + */ + Tag BUTTONS = Bukkit.getTag(REGISTRY_BLOCKS, NamespacedKey.minecraft("buttons"), Material.class); + /** + * Vanilla block tag representing all colors of carpet. + */ + Tag CARPETS = Bukkit.getTag(REGISTRY_BLOCKS, NamespacedKey.minecraft("carpets"), Material.class); + /** + * Vanilla block tag representing all wooden doors. + */ + Tag WOODEN_DOORS = Bukkit.getTag(REGISTRY_BLOCKS, NamespacedKey.minecraft("wooden_doors"), Material.class); + /** + * Vanilla block tag representing all wooden stairs. + */ + Tag WOODEN_STAIRS = Bukkit.getTag(REGISTRY_BLOCKS, NamespacedKey.minecraft("wooden_stairs"), Material.class); + /** + * Vanilla block tag representing all wooden slabs. + */ + Tag WOODEN_SLABS = Bukkit.getTag(REGISTRY_BLOCKS, NamespacedKey.minecraft("wooden_slabs"), Material.class); + /** + * Vanilla block tag representing all wooden pressure plates. + */ + Tag WOODEN_PRESSURE_PLATES = Bukkit.getTag(REGISTRY_BLOCKS, NamespacedKey.minecraft("wooden_pressure_plates"), Material.class); + /** + * Vanilla block tag representing all wooden trapdoors. + */ + Tag WOODEN_TRAPDOORS = Bukkit.getTag(REGISTRY_BLOCKS, NamespacedKey.minecraft("wooden_trapdoors"), Material.class); + /** + * Vanilla block tag representing all doors (inherits from + * {@link #WOODEN_DOORS}. + */ + Tag DOORS = Bukkit.getTag(REGISTRY_BLOCKS, NamespacedKey.minecraft("doors"), Material.class); + /** + * Vanilla block tag representing all sapling variants. + */ + Tag SAPLINGS = Bukkit.getTag(REGISTRY_BLOCKS, NamespacedKey.minecraft("saplings"), Material.class); + /** + * Vanilla block tag representing all log and bark variants. + */ + Tag LOGS = Bukkit.getTag(REGISTRY_BLOCKS, NamespacedKey.minecraft("logs"), Material.class); + /** + * Vanilla block tag representing all dark oak log and bark variants. + */ + Tag DARK_OAK_LOGS = Bukkit.getTag(REGISTRY_BLOCKS, NamespacedKey.minecraft("dark_oak_logs"), Material.class); + /** + * Vanilla block tag representing all oak log and bark variants. + */ + Tag OAK_LOGS = Bukkit.getTag(REGISTRY_BLOCKS, NamespacedKey.minecraft("oak_logs"), Material.class); + /** + * Vanilla block tag representing all birch log and bark variants. + */ + Tag BIRCH_LOGS = Bukkit.getTag(REGISTRY_BLOCKS, NamespacedKey.minecraft("birch_logs"), Material.class); + /** + * Vanilla block tag representing all acacia log and bark variants. + */ + Tag ACACIA_LOGS = Bukkit.getTag(REGISTRY_BLOCKS, NamespacedKey.minecraft("acacia_logs"), Material.class); + /** + * Vanilla block tag representing all jungle log and bark variants. + */ + Tag JUNGLE_LOGS = Bukkit.getTag(REGISTRY_BLOCKS, NamespacedKey.minecraft("jungle_logs"), Material.class); + /** + * Vanilla block tag representing all spruce log and bark variants. + */ + Tag SPRUCE_LOGS = Bukkit.getTag(REGISTRY_BLOCKS, NamespacedKey.minecraft("spruce_logs"), Material.class); + /** + * Vanilla block tag representing all banner blocks. + */ + Tag BANNERS = Bukkit.getTag(REGISTRY_BLOCKS, NamespacedKey.minecraft("banners"), Material.class); + /** + * Vanilla block tag representing all sand blocks. + */ + Tag SAND = Bukkit.getTag(REGISTRY_BLOCKS, NamespacedKey.minecraft("sand"), Material.class); + /** + * Vanilla block tag representing all stairs. + */ + Tag STAIRS = Bukkit.getTag(REGISTRY_BLOCKS, NamespacedKey.minecraft("stairs"), Material.class); + /** + * Vanilla block tag representing all slabs. + */ + Tag SLABS = Bukkit.getTag(REGISTRY_BLOCKS, NamespacedKey.minecraft("slabs"), Material.class); + /** + * Vanilla block tag representing all damaged and undamaged anvils. + */ + Tag ANVIL = Bukkit.getTag(REGISTRY_BLOCKS, NamespacedKey.minecraft("anvil"), Material.class); + /** + * Vanilla block tag representing all Minecart rails. + */ + Tag RAILS = Bukkit.getTag(REGISTRY_BLOCKS, NamespacedKey.minecraft("rails"), Material.class); + /** + * Vanilla block tag representing all leaves fans. + */ + Tag LEAVES = Bukkit.getTag(REGISTRY_BLOCKS, NamespacedKey.minecraft("leaves"), Material.class); + /** + * Vanilla block tag representing all trapdoors (inherits from + * {@link #WOODEN_TRAPDOORS}. + */ + Tag TRAPDOORS = Bukkit.getTag(REGISTRY_BLOCKS, NamespacedKey.minecraft("trapdoors"), Material.class); + /** + * Vanilla block tag representing all empty and filled flower pots. + */ + Tag FLOWER_POTS = Bukkit.getTag(REGISTRY_BLOCKS, NamespacedKey.minecraft("flower_pots"), Material.class); + /** + * Vanilla block tag denoting blocks that enderman may pick up and hold. + */ + Tag ENDERMAN_HOLDABLE = Bukkit.getTag(REGISTRY_BLOCKS, NamespacedKey.minecraft("enderman_holdable"), Material.class); + /** + * Vanilla block tag denoting ice blocks. + */ + Tag ICE = Bukkit.getTag(REGISTRY_BLOCKS, NamespacedKey.minecraft("ice"), Material.class); + /** + * Vanilla block tag denoting all valid mob spawn positions. + */ + Tag VALID_SPAWN = Bukkit.getTag(REGISTRY_BLOCKS, NamespacedKey.minecraft("valid_spawn"), Material.class); + /** + * Vanilla block tag denoting impermeable blocks which do not drip fluids. + */ + Tag IMPERMEABLE = Bukkit.getTag(REGISTRY_BLOCKS, NamespacedKey.minecraft("impermeable"), Material.class); + /** + * Vanilla block tag denoting all underwater blocks which may be bonemealed. + */ + Tag UNDERWATER_BONEMEALS = Bukkit.getTag(REGISTRY_BLOCKS, NamespacedKey.minecraft("underwater_bonemeals"), Material.class); + /** + * Vanilla block tag representing all coral blocks. + */ + Tag CORAL_BLOCKS = Bukkit.getTag(REGISTRY_BLOCKS, NamespacedKey.minecraft("coral_blocks"), Material.class); + /** + * Vanilla block tag representing all wall corals. + */ + Tag WALL_CORALS = Bukkit.getTag(REGISTRY_BLOCKS, NamespacedKey.minecraft("wall_corals"), Material.class); + /** + * Vanilla block tag representing all coral plants. + */ + Tag CORAL_PLANTS = Bukkit.getTag(REGISTRY_BLOCKS, NamespacedKey.minecraft("coral_plants"), Material.class); + /** + * Vanilla block tag representing all coral. + */ + Tag CORALS = Bukkit.getTag(REGISTRY_BLOCKS, NamespacedKey.minecraft("corals"), Material.class); + /** + * Key for the built in item registry. + */ + String REGISTRY_ITEMS = "items"; + /** + * Vanilla item tag representing all banner items. + */ + Tag ITEMS_BANNERS = Bukkit.getTag(REGISTRY_ITEMS, NamespacedKey.minecraft("banners"), Material.class); + /** + * Vanilla item tag representing all boat items. + */ + Tag ITEMS_BOATS = Bukkit.getTag(REGISTRY_ITEMS, NamespacedKey.minecraft("boats"), Material.class); + /** + * Vanilla item tag representing all fish items. + */ + Tag ITEMS_FISHES = Bukkit.getTag(REGISTRY_ITEMS, NamespacedKey.minecraft("fishes"), Material.class); + + /** + * Returns whether or not this tag has an entry for the specified item. + * + * @param item to check + * @return if it is tagged + */ + boolean isTagged(@NotNull T item); + + /** + * Gets an immutable set of all tagged items. + * + * @return set of tagged items + */ + @NotNull + Set getValues(); + +} diff --git a/api/src/main/java/org/bukkit/TravelAgent.java b/api/src/main/java/org/bukkit/TravelAgent.java new file mode 100644 index 000000000..f1a89e7c8 --- /dev/null +++ b/api/src/main/java/org/bukkit/TravelAgent.java @@ -0,0 +1,100 @@ +package org.bukkit; + +import org.jetbrains.annotations.NotNull; + +/** + * The Travel Agent handles the creation and the research of Nether and End + * portals when Entities try to use one. + *

+ * It is used in {@link org.bukkit.event.entity.EntityPortalEvent} and in + * {@link org.bukkit.event.player.PlayerPortalEvent} to help developers + * reproduce and/or modify Vanilla behaviour. + */ +public interface TravelAgent { + + /** + * Set the Block radius to search in for available portals. + * + * @param radius the radius in which to search for a portal from the + * location + * @return this travel agent + */ + @NotNull + public TravelAgent setSearchRadius(int radius); + + /** + * Gets the search radius value for finding an available portal. + * + * @return the currently set search radius + */ + public int getSearchRadius(); + + /** + * Sets the maximum radius from the given location to create a portal. + * + * @param radius the radius in which to create a portal from the location + * @return this travel agent + */ + @NotNull + public TravelAgent setCreationRadius(int radius); + + /** + * Gets the maximum radius from the given location to create a portal. + * + * @return the currently set creation radius + */ + public int getCreationRadius(); + + /** + * Returns whether the TravelAgent will attempt to create a destination + * portal or not. + * + * @return whether the TravelAgent should create a destination portal or + * not + */ + public boolean getCanCreatePortal(); + + /** + * Sets whether the TravelAgent should attempt to create a destination + * portal or not. + * + * @param create Sets whether the TravelAgent should create a destination + * portal or not + */ + public void setCanCreatePortal(boolean create); + + /** + * Attempt to find a portal near the given location, if a portal is not + * found it will attempt to create one. + * + * @param location the location where the search for a portal should begin + * @return the location of a portal which has been found or returns the + * location passed to the method if unsuccessful + * @see #createPortal(Location) + */ + @NotNull + public Location findOrCreate(@NotNull Location location); + + /** + * Attempt to find a portal near the given location. + * + * @param location the desired location of the portal + * @return the location of the nearest portal to the location + */ + @NotNull + public Location findPortal(@NotNull Location location); + + /** + * Attempt to create a portal near the given location. + *

+ * In the case of a Nether portal teleportation, this will attempt to + * create a Nether portal. + *

+ * In the case of an Ender portal teleportation, this will (re-)create the + * obsidian platform and clean blocks above it. + * + * @param location the desired location of the portal + * @return true if a portal was successfully created + */ + public boolean createPortal(@NotNull Location location); +} diff --git a/api/src/main/java/org/bukkit/TreeSpecies.java b/api/src/main/java/org/bukkit/TreeSpecies.java new file mode 100644 index 000000000..a18e23a4d --- /dev/null +++ b/api/src/main/java/org/bukkit/TreeSpecies.java @@ -0,0 +1,76 @@ +package org.bukkit; + +import java.util.Map; + +import com.google.common.collect.Maps; +import org.jetbrains.annotations.Nullable; + +/** + * Represents the different species of trees regardless of size. + */ +public enum TreeSpecies { + + /** + * Represents the common tree species. + */ + GENERIC(0x0), + /** + * Represents the darker barked/leaved tree species. + */ + REDWOOD(0x1), + /** + * Represents birches. + */ + BIRCH(0x2), + /** + * Represents jungle trees. + */ + JUNGLE(0x3), + /** + * Represents acacia trees. + */ + ACACIA(0x4), + /** + * Represents dark oak trees. + */ + DARK_OAK(0x5), + ; + + private final byte data; + private final static Map BY_DATA = Maps.newHashMap(); + + private TreeSpecies(final int data) { + this.data = (byte) data; + } + + /** + * Gets the associated data value representing this species + * + * @return A byte containing the data value of this tree species + * @deprecated Magic value + */ + @Deprecated + public byte getData() { + return data; + } + + /** + * Gets the TreeSpecies with the given data value + * + * @param data Data value to fetch + * @return The {@link TreeSpecies} representing the given value, or null + * if it doesn't exist + * @deprecated Magic value + */ + @Deprecated + @Nullable + public static TreeSpecies getByData(final byte data) { + return BY_DATA.get(data); + } + + static { + for (TreeSpecies species : values()) { + BY_DATA.put(species.data, species); + } + } +} diff --git a/api/src/main/java/org/bukkit/TreeType.java b/api/src/main/java/org/bukkit/TreeType.java new file mode 100644 index 000000000..dba084fa9 --- /dev/null +++ b/api/src/main/java/org/bukkit/TreeType.java @@ -0,0 +1,76 @@ +package org.bukkit; + +/** + * Tree and organic structure types. + */ +public enum TreeType { + + /** + * Regular tree, no branches + */ + TREE, + /** + * Regular tree, extra tall with branches + */ + BIG_TREE, + /** + * Redwood tree, shaped like a pine tree + */ + REDWOOD, + /** + * Tall redwood tree with just a few leaves at the top + */ + TALL_REDWOOD, + /** + * Birch tree + */ + BIRCH, + /** + * Standard jungle tree; 4 blocks wide and tall + */ + JUNGLE, + /** + * Smaller jungle tree; 1 block wide + */ + SMALL_JUNGLE, + /** + * Jungle tree with cocoa plants; 1 block wide + */ + COCOA_TREE, + /** + * Small bush that grows in the jungle + */ + JUNGLE_BUSH, + /** + * Big red mushroom; short and fat + */ + RED_MUSHROOM, + /** + * Big brown mushroom; tall and umbrella-like + */ + BROWN_MUSHROOM, + /** + * Swamp tree (regular with vines on the side) + */ + SWAMP, + /** + * Acacia tree. + */ + ACACIA, + /** + * Dark Oak tree. + */ + DARK_OAK, + /** + * Mega redwood tree; 4 blocks wide and tall + */ + MEGA_REDWOOD, + /** + * Tall birch tree + */ + TALL_BIRCH, + /** + * Large plant native to The End + */ + CHORUS_PLANT, +} diff --git a/api/src/main/java/org/bukkit/UndefinedNullability.java b/api/src/main/java/org/bukkit/UndefinedNullability.java new file mode 100644 index 000000000..f465ea001 --- /dev/null +++ b/api/src/main/java/org/bukkit/UndefinedNullability.java @@ -0,0 +1,26 @@ +package org.bukkit; + +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; + +/** + * Annotation for types, whose nullability is not well defined, so + * {@link org.jetbrains.annotations.NotNull} nor + * {@link org.jetbrains.annotations.Nullable} is applicable. For example when + * interface defines a method, whose nullability depends on the implementation. + * + * @deprecated This should generally not be used in any new API code as it + * suggests a bad API design. + */ +@Retention(RetentionPolicy.CLASS) +@Deprecated +public @interface UndefinedNullability { + + /** + * Human readable description of the circumstances, in which the type is + * nullable. + * + * @return description + */ + String value() default ""; +} diff --git a/api/src/main/java/org/bukkit/UnsafeValues.java b/api/src/main/java/org/bukkit/UnsafeValues.java new file mode 100644 index 000000000..f462b631b --- /dev/null +++ b/api/src/main/java/org/bukkit/UnsafeValues.java @@ -0,0 +1,76 @@ +package org.bukkit; + +import org.bukkit.advancement.Advancement; +import org.bukkit.block.data.BlockData; +import org.bukkit.inventory.ItemStack; +import org.bukkit.material.MaterialData; +import org.bukkit.plugin.InvalidPluginException; +import org.bukkit.plugin.PluginDescriptionFile; + +/** + * This interface provides value conversions that may be specific to a + * runtime, or have arbitrary meaning (read: magic values). + *

+ * Their existence and behavior is not guaranteed across future versions. They + * may be poorly named, throw exceptions, have misleading parameters, or any + * other bad programming practice. + */ +@Deprecated +public interface UnsafeValues { + + Material toLegacy(Material material); + + Material fromLegacy(Material material); + + Material fromLegacy(MaterialData material); + + Material fromLegacy(MaterialData material, boolean itemPriority); + + BlockData fromLegacy(Material material, byte data); + + int getDataVersion(); + + ItemStack modifyItemStack(ItemStack stack, String arguments); + + void checkSupported(PluginDescriptionFile pdf) throws InvalidPluginException; + + byte[] processClass(PluginDescriptionFile pdf, String path, byte[] clazz); + + /** + * Load an advancement represented by the specified string into the server. + * The advancement format is governed by Minecraft and has no specified + * layout. + *
+ * It is currently a JSON object, as described by the Minecraft Wiki: + * http://minecraft.gamepedia.com/Advancements + *
+ * Loaded advancements will be stored and persisted across server restarts + * and reloads. + *
+ * Callers should be prepared for {@link Exception} to be thrown. + * + * @param key the unique advancement key + * @param advancement representation of the advancement + * @return the loaded advancement or null if an error occurred + */ + Advancement loadAdvancement(NamespacedKey key, String advancement); + + /** + * Delete an advancement which was loaded and saved by + * {@link #loadAdvancement(org.bukkit.NamespacedKey, java.lang.String)}. + *
+ * This method will only remove advancement from persistent storage. It + * should be accompanied by a call to {@link Server#reloadData()} in order + * to fully remove it from the running instance. + * + * @param key the unique advancement key + * @return true if a file matching this key was found and deleted + */ + boolean removeAdvancement(NamespacedKey key); + + // Paper start - Add legacy check util + static boolean isLegacyPlugin(org.bukkit.plugin.Plugin plugin) { + return !("1.13".equals(plugin.getDescription().getAPIVersion())); + } + // Paper end +} diff --git a/api/src/main/java/org/bukkit/Utility.java b/api/src/main/java/org/bukkit/Utility.java new file mode 100644 index 000000000..da66853ef --- /dev/null +++ b/api/src/main/java/org/bukkit/Utility.java @@ -0,0 +1,18 @@ +package org.bukkit; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +/** + * This annotation indicates a method (and sometimes constructor) will chain + * its internal operations. + *

+ * This is solely meant for identifying methods that don't need to be + * overridden / handled manually. + */ +@Target({ElementType.CONSTRUCTOR, ElementType.METHOD}) +@Retention(RetentionPolicy.SOURCE) +public @interface Utility { +} diff --git a/api/src/main/java/org/bukkit/Warning.java b/api/src/main/java/org/bukkit/Warning.java new file mode 100644 index 000000000..f86f25a0d --- /dev/null +++ b/api/src/main/java/org/bukkit/Warning.java @@ -0,0 +1,112 @@ +package org.bukkit; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; +import java.util.Map; + +import com.google.common.collect.ImmutableMap; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +/** + * This designates the warning state for a specific item. + *

+ * When the server settings dictate 'default' warnings, warnings are printed + * if the {@link #value()} is true. + */ +@Target({ElementType.CONSTRUCTOR, ElementType.METHOD, ElementType.TYPE}) +@Retention(RetentionPolicy.RUNTIME) +public @interface Warning { + + /** + * This represents the states that server verbose for warnings may be. + */ + public enum WarningState { + + /** + * Indicates all warnings should be printed for deprecated items. + */ + ON, + /** + * Indicates no warnings should be printed for deprecated items. + */ + OFF, + /** + * Indicates each warning would default to the configured {@link + * Warning} annotation, or always if annotation not found. + */ + DEFAULT; + + private static final Map values = ImmutableMap.builder() + .put("off", OFF) + .put("false", OFF) + .put("f", OFF) + .put("no", OFF) + .put("n", OFF) + .put("on", ON) + .put("true", ON) + .put("t", ON) + .put("yes", ON) + .put("y", ON) + .put("", DEFAULT) + .put("d", DEFAULT) + .put("default", DEFAULT) + .build(); + + /** + * This method checks the provided warning should be printed for this + * state + * + * @param warning The warning annotation added to a deprecated item + * @return

    + *
  • ON is always True + *
  • OFF is always false + *
  • DEFAULT is false if and only if annotation is not null and + * specifies false for {@link Warning#value()}, true otherwise. + *
+ */ + public boolean printFor(@Nullable Warning warning) { + if (this == DEFAULT) { + return warning == null || warning.value(); + } + return this == ON; + } + + /** + * This method returns the corresponding warning state for the given + * string value. + * + * @param value The string value to check + * @return {@link #DEFAULT} if not found, or the respective + * WarningState + */ + @NotNull + public static WarningState value(@Nullable final String value) { + if (value == null) { + return DEFAULT; + } + WarningState state = values.get(value.toLowerCase()); + if (state == null) { + return DEFAULT; + } + return state; + } + } + + /** + * This sets if the deprecation warnings when registering events gets + * printed when the setting is in the default state. + * + * @return false normally, or true to encourage warning printout + */ + boolean value() default false; + + /** + * This can provide detailed information on why the event is deprecated. + * + * @return The reason an event is deprecated + */ + String reason() default ""; +} diff --git a/api/src/main/java/org/bukkit/WeatherType.java b/api/src/main/java/org/bukkit/WeatherType.java new file mode 100644 index 000000000..36b993f1c --- /dev/null +++ b/api/src/main/java/org/bukkit/WeatherType.java @@ -0,0 +1,17 @@ +package org.bukkit; + +/** + * An enum of all current weather types + */ +public enum WeatherType { + + /** + * Raining or snowing depending on biome. + */ + DOWNFALL, + /** + * Clear weather, clouds but no rain. + */ + CLEAR, + ; +} diff --git a/api/src/main/java/org/bukkit/World.java b/api/src/main/java/org/bukkit/World.java new file mode 100644 index 000000000..107f41735 --- /dev/null +++ b/api/src/main/java/org/bukkit/World.java @@ -0,0 +1,2832 @@ +package org.bukkit; + +import java.io.File; +import org.bukkit.generator.ChunkGenerator; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.UUID; +import java.util.function.Predicate; + +import org.bukkit.block.Biome; +import org.bukkit.block.Block; +import org.bukkit.block.data.BlockData; +import org.bukkit.entity.*; +import org.bukkit.generator.BlockPopulator; +import org.bukkit.inventory.ItemStack; +import org.bukkit.material.MaterialData; +import org.bukkit.metadata.Metadatable; +import org.bukkit.plugin.messaging.PluginMessageRecipient; +import org.bukkit.util.BoundingBox; +import org.bukkit.util.Consumer; +import org.bukkit.util.RayTraceResult; +import org.bukkit.util.Vector; +import org.jetbrains.annotations.Contract; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +/** + * Represents a world, which may contain entities, chunks and blocks + */ +public interface World extends PluginMessageRecipient, Metadatable { + + // Paper start + /** + * @return The amount of Entities in this world + */ + int getEntityCount(); + + /** + * @return The amount of Tile Entities in this world + */ + int getTileEntityCount(); + + /** + * @return The amount of Tickable Tile Entities in this world + */ + int getTickableTileEntityCount(); + + /** + * @return The amount of Chunks in this world + */ + int getChunkCount(); + + /** + * @return The amount of Players in this world + */ + int getPlayerCount(); + // Paper end + + /** + * Gets the {@link Block} at the given coordinates + * + * @param x X-coordinate of the block + * @param y Y-coordinate of the block + * @param z Z-coordinate of the block + * @return Block at the given coordinates + */ + @NotNull + public Block getBlockAt(int x, int y, int z); + + /** + * Gets the {@link Block} at the given {@link Location} + * + * @param location Location of the block + * @return Block at the given location + */ + @NotNull + public Block getBlockAt(@NotNull Location location); + + // Paper start + /** + * Gets the {@link Block} at the given block key + * + * @param key The block key. See {@link Block#getBlockKey()} + * @return Block at the key + * @see Block#getBlockKey(int, int, int) + */ + @NotNull + public default Block getBlockAtKey(long key) { + int x = Block.getBlockKeyX(key); + int y = Block.getBlockKeyY(key); + int z = Block.getBlockKeyZ(key); + return getBlockAt(x, y, z); + } + + /** + * Gets the {@link Location} at the given block key + * + * @param key The block key. See {@link Location#toBlockKey()} + * @return Location at the key + * @see Block#getBlockKey(int, int, int) + */ + @NotNull + public default Location getLocationAtKey(long key) { + int x = Block.getBlockKeyX(key); + int y = Block.getBlockKeyY(key); + int z = Block.getBlockKeyZ(key); + return new Location(this, x, y, z); + } + // Paper end + + /** + * Gets the y coordinate of the lowest block at this position such that the + * block and all blocks above it are transparent for lighting purposes. + * + * @param x X-coordinate of the blocks + * @param z Z-coordinate of the blocks + * @return Y-coordinate of the described block + */ + public int getHighestBlockYAt(int x, int z); + + /** + * Gets the y coordinate of the lowest block at the given {@link Location} + * such that the block and all blocks above it are transparent for lighting + * purposes. + * + * @param location Location of the blocks + * @return Y-coordinate of the highest non-air block + */ + public int getHighestBlockYAt(@NotNull Location location); + + /** + * Gets the lowest block at the given coordinates such that the block and + * all blocks above it are transparent for lighting purposes. + * + * @param x X-coordinate of the block + * @param z Z-coordinate of the block + * @return Highest non-empty block + */ + @NotNull + public Block getHighestBlockAt(int x, int z); + + /** + * Gets the lowest block at the given {@link Location} such that the block + * and all blocks above it are transparent for lighting purposes. + * + * @param location Coordinates to get the highest block + * @return Highest non-empty block + */ + @NotNull + public Block getHighestBlockAt(@NotNull Location location); + + /** + * Gets the {@link Chunk} at the given coordinates + * + * @param x X-coordinate of the chunk + * @param z Z-coordinate of the chunk + * @return Chunk at the given coordinates + */ + @NotNull + public Chunk getChunkAt(int x, int z); + + /** + * Gets the {@link Chunk} at the given {@link Location} + * + * @param location Location of the chunk + * @return Chunk at the given location + */ + @NotNull + public Chunk getChunkAt(@NotNull Location location); + + /** + * Gets the {@link Chunk} that contains the given {@link Block} + * + * @param block Block to get the containing chunk from + * @return The chunk that contains the given block + */ + @NotNull + public Chunk getChunkAt(@NotNull Block block); + + // Paper start + /** + * Gets the chunk at the specified chunk key, which is the X and Z packed into a long. + * + * See {@link Chunk#getChunkKey()} for easy access to the key, or you may calculate it as: + * long chunkKey = (long) chunkX & 0xffffffffL | ((long) chunkZ & 0xffffffffL) >> 32; + * + * @param chunkKey The Chunk Key to look up the chunk by + * @return The chunk at the specified key + */ + @NotNull + public default Chunk getChunkAt(long chunkKey) { + return getChunkAt((int) chunkKey, (int) (chunkKey >> 32)); + } + + /** + * Checks if a {@link Chunk} has been generated at the specified chunk key, + * which is the X and Z packed into a long. + * + * @param chunkKey The Chunk Key to look up the chunk by + * @return true if the chunk has been generated, otherwise false + */ + public default boolean isChunkGenerated(long chunkKey) { + return isChunkGenerated((int) chunkKey, (int) (chunkKey >> 32)); + } + + /** + * This is the Legacy API before Java 8 was supported. Java 8 Consumer is provided, + * as well as future support + * + * Used by {@link World#getChunkAtAsync(Location,ChunkLoadCallback)} methods + * to request a {@link Chunk} to be loaded, with this callback receiving + * the chunk when it is finished. + * + * This callback will be executed on synchronously on the main thread. + * + * Timing and order this callback is fired is intentionally not defined and + * and subject to change. + * + * @deprecated Use either the Future or the Consumer based methods + */ + @Deprecated + public static interface ChunkLoadCallback extends java.util.function.Consumer { + public void onLoad(@NotNull Chunk chunk); + + // backwards compat to old api + @Override + default void accept(@NotNull Chunk chunk) { + onLoad(chunk); + } + } + + /** + * Requests a {@link Chunk} to be loaded at the given coordinates + * + * This method makes no guarantee on how fast the chunk will load, + * and will return the chunk to the callback at a later time. + * + * You should use this method if you need a chunk but do not need it + * immediately, and you wish to let the server control the speed + * of chunk loads, keeping performance in mind. + * + * The {@link ChunkLoadCallback} will always be executed synchronously + * on the main Server Thread. + * + * @deprecated Use either the Future or the Consumer based methods + * @param x Chunk X-coordinate of the chunk - (world coordinate / 16) + * @param z Chunk Z-coordinate of the chunk - (world coordinate / 16) + * @param cb Callback to receive the chunk when it is loaded. + * will be executed synchronously + */ + @Deprecated + public default void getChunkAtAsync(int x, int z, @NotNull ChunkLoadCallback cb) { + getChunkAtAsync(x, z, true).thenAccept(cb::onLoad); + } + + /** + * Requests a {@link Chunk} to be loaded at the given {@link Location} + * + * This method makes no guarantee on how fast the chunk will load, + * and will return the chunk to the callback at a later time. + * + * You should use this method if you need a chunk but do not need it + * immediately, and you wish to let the server control the speed + * of chunk loads, keeping performance in mind. + * + * The {@link ChunkLoadCallback} will always be executed synchronously + * on the main Server Thread. + * + * @deprecated Use either the Future or the Consumer based methods + * @param loc Location of the chunk + * @param cb Callback to receive the chunk when it is loaded. + * will be executed synchronously + */ + @Deprecated + public default void getChunkAtAsync(@NotNull Location loc, @NotNull ChunkLoadCallback cb) { + getChunkAtAsync(loc, true).thenAccept(cb::onLoad); + } + + /** + * Requests {@link Chunk} to be loaded that contains the given {@link Block} + * + * This method makes no guarantee on how fast the chunk will load, + * and will return the chunk to the callback at a later time. + * + * You should use this method if you need a chunk but do not need it + * immediately, and you wish to let the server control the speed + * of chunk loads, keeping performance in mind. + * + * The {@link ChunkLoadCallback} will always be executed synchronously + * on the main Server Thread. + * + * @deprecated Use either the Future or the Consumer based methods + * @param block Block to get the containing chunk from + * @param cb Callback to receive the chunk when it is loaded. + * will be executed synchronously + */ + @Deprecated + public default void getChunkAtAsync(@NotNull Block block, @NotNull ChunkLoadCallback cb) { + getChunkAtAsync(block, true).thenAccept(cb::onLoad); + } + + /** + * Requests a {@link Chunk} to be loaded at the given coordinates + * + * This method makes no guarantee on how fast the chunk will load, + * and will return the chunk to the callback at a later time. + * + * You should use this method if you need a chunk but do not need it + * immediately, and you wish to let the server control the speed + * of chunk loads, keeping performance in mind. + * + * The {@link java.util.function.Consumer} will always be executed synchronously + * on the main Server Thread. + * + * @param x Chunk X-coordinate of the chunk - (world coordinate / 16) + * @param z Chunk Z-coordinate of the chunk - (world coordinate / 16) + * @param cb Callback to receive the chunk when it is loaded. + * will be executed synchronously + */ + public default void getChunkAtAsync(int x, int z, @NotNull java.util.function.Consumer cb) { + getChunkAtAsync(x, z, true).thenAccept(cb); + } + + /** + * Requests a {@link Chunk} to be loaded at the given coordinates + * + * This method makes no guarantee on how fast the chunk will load, + * and will return the chunk to the callback at a later time. + * + * You should use this method if you need a chunk but do not need it + * immediately, and you wish to let the server control the speed + * of chunk loads, keeping performance in mind. + * + * The {@link java.util.function.Consumer} will always be executed synchronously + * on the main Server Thread. + * + * @param x Chunk X-coordinate of the chunk - (world coordinate / 16) + * @param z Chunk Z-coordinate of the chunk - (world coordinate / 16) + * @param gen Should we generate a chunk if it doesn't exists or not + * @param cb Callback to receive the chunk when it is loaded. + * will be executed synchronously + */ + public default void getChunkAtAsync(int x, int z, boolean gen, @NotNull java.util.function.Consumer cb) { + getChunkAtAsync(x, z, gen).thenAccept(cb); + } + + /** + * Requests a {@link Chunk} to be loaded at the given {@link Location} + * + * This method makes no guarantee on how fast the chunk will load, + * and will return the chunk to the callback at a later time. + * + * You should use this method if you need a chunk but do not need it + * immediately, and you wish to let the server control the speed + * of chunk loads, keeping performance in mind. + * + * The {@link java.util.function.Consumer} will always be executed synchronously + * on the main Server Thread. + * + * @param loc Location of the chunk + * @param cb Callback to receive the chunk when it is loaded. + * will be executed synchronously + */ + public default void getChunkAtAsync(@NotNull Location loc, @NotNull java.util.function.Consumer cb) { + getChunkAtAsync((int)Math.floor(loc.getX()) >> 4, (int)Math.floor(loc.getZ()) >> 4, true, cb); + } + + /** + * Requests a {@link Chunk} to be loaded at the given {@link Location} + * + * This method makes no guarantee on how fast the chunk will load, + * and will return the chunk to the callback at a later time. + * + * You should use this method if you need a chunk but do not need it + * immediately, and you wish to let the server control the speed + * of chunk loads, keeping performance in mind. + * + * The {@link java.util.function.Consumer} will always be executed synchronously + * on the main Server Thread. + * + * @param loc Location of the chunk + * @param gen Should the chunk generate + * @param cb Callback to receive the chunk when it is loaded. + * will be executed synchronously + */ + public default void getChunkAtAsync(@NotNull Location loc, boolean gen, @NotNull java.util.function.Consumer cb) { + getChunkAtAsync((int)Math.floor(loc.getX()) >> 4, (int)Math.floor(loc.getZ()) >> 4, gen, cb); + } + + /** + * Requests {@link Chunk} to be loaded that contains the given {@link Block} + * + * This method makes no guarantee on how fast the chunk will load, + * and will return the chunk to the callback at a later time. + * + * You should use this method if you need a chunk but do not need it + * immediately, and you wish to let the server control the speed + * of chunk loads, keeping performance in mind. + * + * The {@link java.util.function.Consumer} will always be executed synchronously + * on the main Server Thread. + * + * @param block Block to get the containing chunk from + * @param cb Callback to receive the chunk when it is loaded. + * will be executed synchronously + */ + public default void getChunkAtAsync(@NotNull Block block, @NotNull java.util.function.Consumer cb) { + getChunkAtAsync(block.getX() >> 4, block.getZ() >> 4, true, cb); + } + + /** + * Requests {@link Chunk} to be loaded that contains the given {@link Block} + * + * This method makes no guarantee on how fast the chunk will load, + * and will return the chunk to the callback at a later time. + * + * You should use this method if you need a chunk but do not need it + * immediately, and you wish to let the server control the speed + * of chunk loads, keeping performance in mind. + * + * The {@link java.util.function.Consumer} will always be executed synchronously + * on the main Server Thread. + * + * @param block Block to get the containing chunk from + * @param gen Should the chunk generate + * @param cb Callback to receive the chunk when it is loaded. + * will be executed synchronously + */ + public default void getChunkAtAsync(@NotNull Block block, boolean gen, @NotNull java.util.function.Consumer cb) { + getChunkAtAsync(block.getX() >> 4, block.getZ() >> 4, gen, cb); + } + + /** + * Requests a {@link Chunk} to be loaded at the given coordinates + * + * This method makes no guarantee on how fast the chunk will load, + * and will return the chunk to the callback at a later time. + * + * You should use this method if you need a chunk but do not need it + * immediately, and you wish to let the server control the speed + * of chunk loads, keeping performance in mind. + * + * The future will always be executed synchronously + * on the main Server Thread. + * @param loc Location to load the corresponding chunk from + * @return Future that will resolve when the chunk is loaded + */ + @NotNull + public default java.util.concurrent.CompletableFuture getChunkAtAsync(@NotNull Location loc) { + return getChunkAtAsync((int)Math.floor(loc.getX()) >> 4, (int)Math.floor(loc.getZ()) >> 4, true); + } + + /** + * Requests a {@link Chunk} to be loaded at the given coordinates + * + * This method makes no guarantee on how fast the chunk will load, + * and will return the chunk to the callback at a later time. + * + * You should use this method if you need a chunk but do not need it + * immediately, and you wish to let the server control the speed + * of chunk loads, keeping performance in mind. + * + * The future will always be executed synchronously + * on the main Server Thread. + * @param loc Location to load the corresponding chunk from + * @param gen Should the chunk generate + * @return Future that will resolve when the chunk is loaded + */ + @NotNull + public default java.util.concurrent.CompletableFuture getChunkAtAsync(@NotNull Location loc, boolean gen) { + return getChunkAtAsync((int)Math.floor(loc.getX()) >> 4, (int)Math.floor(loc.getZ()) >> 4, gen); + } + + /** + * Requests a {@link Chunk} to be loaded at the given coordinates + * + * This method makes no guarantee on how fast the chunk will load, + * and will return the chunk to the callback at a later time. + * + * You should use this method if you need a chunk but do not need it + * immediately, and you wish to let the server control the speed + * of chunk loads, keeping performance in mind. + * + * The future will always be executed synchronously + * on the main Server Thread. + * @param block Block to load the corresponding chunk from + * @return Future that will resolve when the chunk is loaded + */ + @NotNull + public default java.util.concurrent.CompletableFuture getChunkAtAsync(@NotNull Block block) { + return getChunkAtAsync(block.getX() >> 4, block.getZ() >> 4, true); + } + + /** + * Requests a {@link Chunk} to be loaded at the given coordinates + * + * This method makes no guarantee on how fast the chunk will load, + * and will return the chunk to the callback at a later time. + * + * You should use this method if you need a chunk but do not need it + * immediately, and you wish to let the server control the speed + * of chunk loads, keeping performance in mind. + * + * The future will always be executed synchronously + * on the main Server Thread. + * @param block Block to load the corresponding chunk from + * @param gen Should the chunk generate + * @return Future that will resolve when the chunk is loaded + */ + @NotNull + public default java.util.concurrent.CompletableFuture getChunkAtAsync(@NotNull Block block, boolean gen) { + return getChunkAtAsync(block.getX() >> 4, block.getZ() >> 4, gen); + } + + /** + * Requests a {@link Chunk} to be loaded at the given coordinates + * + * This method makes no guarantee on how fast the chunk will load, + * and will return the chunk to the callback at a later time. + * + * You should use this method if you need a chunk but do not need it + * immediately, and you wish to let the server control the speed + * of chunk loads, keeping performance in mind. + * + * The future will always be executed synchronously + * on the main Server Thread. + * + * @param x X Coord + * @param z Z Coord + * @return Future that will resolve when the chunk is loaded + */ + @NotNull + public default java.util.concurrent.CompletableFuture getChunkAtAsync(int x, int z) { + return getChunkAtAsync(x, z, true); + } + + /** + * Requests a {@link Chunk} to be loaded at the given coordinates + * + * This method makes no guarantee on how fast the chunk will load, + * and will return the chunk to the callback at a later time. + * + * You should use this method if you need a chunk but do not need it + * immediately, and you wish to let the server control the speed + * of chunk loads, keeping performance in mind. + * + * The future will always be executed synchronously + * on the main Server Thread. + * + * @param x Chunk X-coordinate of the chunk - (world coordinate / 16) + * @param z Chunk Z-coordinate of the chunk - (world coordinate / 16) + * @param gen Should we generate a chunk if it doesn't exists or not + * @return Future that will resolve when the chunk is loaded + */ + @NotNull + public java.util.concurrent.CompletableFuture getChunkAtAsync(int x, int z, boolean gen); + // Paper end + + /** + * Checks if the specified {@link Chunk} is loaded + * + * @param chunk The chunk to check + * @return true if the chunk is loaded, otherwise false + */ + public boolean isChunkLoaded(@NotNull Chunk chunk); + + /** + * Gets an array of all loaded {@link Chunk}s + * + * @return Chunk[] containing all loaded chunks + */ + @NotNull + public Chunk[] getLoadedChunks(); + + /** + * Loads the specified {@link Chunk} + * + * @param chunk The chunk to load + */ + public void loadChunk(@NotNull Chunk chunk); + + /** + * Checks if the {@link Chunk} at the specified coordinates is loaded + * + * @param x X-coordinate of the chunk + * @param z Z-coordinate of the chunk + * @return true if the chunk is loaded, otherwise false + */ + public boolean isChunkLoaded(int x, int z); + + /** + * Checks if the {@link Chunk} at the specified coordinates is generated + * + * @param x X-coordinate of the chunk + * @param z Z-coordinate of the chunk + * @return true if the chunk is generated, otherwise false + */ + public boolean isChunkGenerated(int x, int z); + + /** + * Checks if the {@link Chunk} at the specified coordinates is loaded and + * in use by one or more players + * + * @param x X-coordinate of the chunk + * @param z Z-coordinate of the chunk + * @return true if the chunk is loaded and in use by one or more players, + * otherwise false + */ + public boolean isChunkInUse(int x, int z); + + /** + * Loads the {@link Chunk} at the specified coordinates + *

+ * If the chunk does not exist, it will be generated. + *

+ * This method is analogous to {@link #loadChunk(int, int, boolean)} where + * generate is true. + * + * @param x X-coordinate of the chunk + * @param z Z-coordinate of the chunk + */ + public void loadChunk(int x, int z); + + /** + * Loads the {@link Chunk} at the specified coordinates + * + * @param x X-coordinate of the chunk + * @param z Z-coordinate of the chunk + * @param generate Whether or not to generate a chunk if it doesn't + * already exist + * @return true if the chunk has loaded successfully, otherwise false + */ + public boolean loadChunk(int x, int z, boolean generate); + + /** + * Safely unloads and saves the {@link Chunk} at the specified coordinates + *

+ * This method is analogous to {@link #unloadChunk(int, int, boolean, + * boolean)} where safe and save is true + * + * @param chunk the chunk to unload + * @return true if the chunk has unloaded successfully, otherwise false + */ + public boolean unloadChunk(@NotNull Chunk chunk); + + /** + * Safely unloads and saves the {@link Chunk} at the specified coordinates + *

+ * This method is analogous to {@link #unloadChunk(int, int, boolean, + * boolean)} where safe and saveis true + * + * @param x X-coordinate of the chunk + * @param z Z-coordinate of the chunk + * @return true if the chunk has unloaded successfully, otherwise false + */ + public boolean unloadChunk(int x, int z); + + /** + * Safely unloads and optionally saves the {@link Chunk} at the specified + * coordinates + *

+ * This method is analogous to {@link #unloadChunk(int, int, boolean, + * boolean)} where save is true + * + * @param x X-coordinate of the chunk + * @param z Z-coordinate of the chunk + * @param save Whether or not to save the chunk + * @return true if the chunk has unloaded successfully, otherwise false + */ + public boolean unloadChunk(int x, int z, boolean save); + + /** + * Unloads and optionally saves the {@link Chunk} at the specified + * coordinates + * + * @param x X-coordinate of the chunk + * @param z Z-coordinate of the chunk + * @param save Controls whether the chunk is saved + * @param safe Controls whether to unload the chunk when players are + * nearby + * @return true if the chunk has unloaded successfully, otherwise false + * @deprecated it is never safe to remove a chunk in use + */ + @Deprecated + public boolean unloadChunk(int x, int z, boolean save, boolean safe); + + /** + * Safely queues the {@link Chunk} at the specified coordinates for + * unloading + *

+ * This method is analogous to {@link #unloadChunkRequest(int, int, + * boolean)} where safe is true + * + * @param x X-coordinate of the chunk + * @param z Z-coordinate of the chunk + * @return true is the queue attempt was successful, otherwise false + */ + public boolean unloadChunkRequest(int x, int z); + + /** + * Queues the {@link Chunk} at the specified coordinates for unloading + * + * @param x X-coordinate of the chunk + * @param z Z-coordinate of the chunk + * @param safe Controls whether to queue the chunk when players are nearby + * @return Whether the chunk was actually queued + * @deprecated it is never safe to remove a chunk in use + */ + @Deprecated + public boolean unloadChunkRequest(int x, int z, boolean safe); + + /** + * Regenerates the {@link Chunk} at the specified coordinates + * + * @param x X-coordinate of the chunk + * @param z Z-coordinate of the chunk + * @return Whether the chunk was actually regenerated + * + * @deprecated regenerating a single chunk is not likely to produce the same + * chunk as before as terrain decoration may be spread across chunks. Use of + * this method should be avoided as it is known to produce buggy results. + */ + @Deprecated + public boolean regenerateChunk(int x, int z); + + /** + * Resends the {@link Chunk} to all clients + * + * @param x X-coordinate of the chunk + * @param z Z-coordinate of the chunk + * @return Whether the chunk was actually refreshed + * + * @deprecated This method is not guaranteed to work suitably across all client implementations. + */ + @Deprecated + public boolean refreshChunk(int x, int z); + + /** + * Gets whether the chunk at the specified chunk coordinates is force + * loaded. + *

+ * A force loaded chunk will not be unloaded due to lack of player activity. + * + * @param x X-coordinate of the chunk + * @param z Z-coordinate of the chunk + * @return force load status + */ + public boolean isChunkForceLoaded(int x, int z); + + /** + * Sets whether the chunk at the specified chunk coordinates is force + * loaded. + *

+ * A force loaded chunk will not be unloaded due to lack of player activity. + * + * @param x X-coordinate of the chunk + * @param z Z-coordinate of the chunk + * @param forced + */ + public void setChunkForceLoaded(int x, int z, boolean forced); + + /** + * Returns all force loaded chunks in this world. + *

+ * A force loaded chunk will not be unloaded due to lack of player activity. + * + * @return unmodifiable collection of force loaded chunks + */ + @NotNull + public Collection getForceLoadedChunks(); + + /** + * Drops an item at the specified {@link Location} + * + * @param location Location to drop the item + * @param item ItemStack to drop + * @return ItemDrop entity created as a result of this method + */ + @NotNull + public Item dropItem(@NotNull Location location, @NotNull ItemStack item); + + /** + * Drops an item at the specified {@link Location} with a random offset + * + * @param location Location to drop the item + * @param item ItemStack to drop + * @return ItemDrop entity created as a result of this method + */ + @NotNull + public Item dropItemNaturally(@NotNull Location location, @NotNull ItemStack item); + + /** + * Creates an {@link Arrow} entity at the given {@link Location} + * + * @param location Location to spawn the arrow + * @param direction Direction to shoot the arrow in + * @param speed Speed of the arrow. A recommend speed is 0.6 + * @param spread Spread of the arrow. A recommend spread is 12 + * @return Arrow entity spawned as a result of this method + */ + @NotNull + public Arrow spawnArrow(@NotNull Location location, @NotNull Vector direction, float speed, float spread); + + /** + * Creates an arrow entity of the given class at the given {@link Location} + * + * @param type of arrow to spawn + * @param location Location to spawn the arrow + * @param direction Direction to shoot the arrow in + * @param speed Speed of the arrow. A recommend speed is 0.6 + * @param spread Spread of the arrow. A recommend spread is 12 + * @param clazz the Entity class for the arrow + * {@link org.bukkit.entity.SpectralArrow},{@link org.bukkit.entity.Arrow},{@link org.bukkit.entity.TippedArrow} + * @return Arrow entity spawned as a result of this method + */ + @NotNull + public T spawnArrow(@NotNull Location location, @NotNull Vector direction, float speed, float spread, @NotNull Class clazz); + + /** + * Creates a tree at the given {@link Location} + * + * @param location Location to spawn the tree + * @param type Type of the tree to create + * @return true if the tree was created successfully, otherwise false + */ + public boolean generateTree(@NotNull Location location, @NotNull TreeType type); + + /** + * Creates a tree at the given {@link Location} + * + * @param loc Location to spawn the tree + * @param type Type of the tree to create + * @param delegate A class to call for each block changed as a result of + * this method + * @return true if the tree was created successfully, otherwise false + */ + public boolean generateTree(@NotNull Location loc, @NotNull TreeType type, @NotNull BlockChangeDelegate delegate); + + /** + * Creates a entity at the given {@link Location} + * + * @param loc The location to spawn the entity + * @param type The entity to spawn + * @return Resulting Entity of this method, or null if it was unsuccessful + */ + @NotNull + public Entity spawnEntity(@NotNull Location loc, @NotNull EntityType type); + + /** + * Strikes lightning at the given {@link Location} + * + * @param loc The location to strike lightning + * @return The lightning entity. + */ + @NotNull + public LightningStrike strikeLightning(@NotNull Location loc); + + /** + * Strikes lightning at the given {@link Location} without doing damage + * + * @param loc The location to strike lightning + * @return The lightning entity. + */ + @NotNull + public LightningStrike strikeLightningEffect(@NotNull Location loc); + + /** + * Get a list of all entities in this World + * + * @return A List of all Entities currently residing in this world + */ + @NotNull + public List getEntities(); + + /** + * Get a list of all living entities in this World + * + * @return A List of all LivingEntities currently residing in this world + */ + @NotNull + public List getLivingEntities(); + + /** + * Get a collection of all entities in this World matching the given + * class/interface + * + * @param an entity subclass + * @param classes The classes representing the types of entity to match + * @return A List of all Entities currently residing in this world that + * match the given class/interface + */ + @Deprecated + @NotNull + public Collection getEntitiesByClass(@NotNull Class... classes); + + /** + * Get a collection of all entities in this World matching the given + * class/interface + * + * @param an entity subclass + * @param cls The class representing the type of entity to match + * @return A List of all Entities currently residing in this world that + * match the given class/interface + */ + @NotNull + public Collection getEntitiesByClass(@NotNull Class cls); + + /** + * Get a collection of all entities in this World matching any of the + * given classes/interfaces + * + * @param classes The classes representing the types of entity to match + * @return A List of all Entities currently residing in this world that + * match one or more of the given classes/interfaces + */ + @NotNull + public Collection getEntitiesByClasses(@NotNull Class... classes); + + // Paper start + /** + * Gets nearby players within the specified radius (bounding box) + * @param loc Center location + * @param radius Radius + * @return the collection of entities near location. This will always be a non-null collection. + */ + @NotNull + public default Collection getNearbyLivingEntities(@NotNull Location loc, double radius) { + return getNearbyEntitiesByType(org.bukkit.entity.LivingEntity.class, loc, radius, radius, radius); + } + + /** + * Gets nearby players within the specified radius (bounding box) + * @param loc Center location + * @param xzRadius X/Z Radius + * @param yRadius Y Radius + * @return the collection of entities near location. This will always be a non-null collection. + */ + @NotNull + public default Collection getNearbyLivingEntities(@NotNull Location loc, double xzRadius, double yRadius) { + return getNearbyEntitiesByType(org.bukkit.entity.LivingEntity.class, loc, xzRadius, yRadius, xzRadius); + } + + /** + * Gets nearby players within the specified radius (bounding box) + * @param loc Center location + * @param xRadius X Radius + * @param yRadius Y Radius + * @param zRadius Z radius + * @return the collection of entities near location. This will always be a non-null collection. + */ + @NotNull + public default Collection getNearbyLivingEntities(@NotNull Location loc, double xRadius, double yRadius, double zRadius) { + return getNearbyEntitiesByType(org.bukkit.entity.LivingEntity.class, loc, xRadius, yRadius, zRadius); + } + + /** + * Gets nearby players within the specified radius (bounding box) + * @param loc Center location + * @param radius X Radius + * @param predicate a predicate used to filter results + * @return the collection of living entities near location. This will always be a non-null collection + */ + @NotNull + public default Collection getNearbyLivingEntities(@NotNull Location loc, double radius, @Nullable Predicate predicate) { + return getNearbyEntitiesByType(org.bukkit.entity.LivingEntity.class, loc, radius, radius, radius, predicate); + } + + /** + * Gets nearby players within the specified radius (bounding box) + * @param loc Center location + * @param xzRadius X/Z Radius + * @param yRadius Y Radius + * @param predicate a predicate used to filter results + * @return the collection of living entities near location. This will always be a non-null collection + */ + @NotNull + public default Collection getNearbyLivingEntities(@NotNull Location loc, double xzRadius, double yRadius, @Nullable Predicate predicate) { + return getNearbyEntitiesByType(org.bukkit.entity.LivingEntity.class, loc, xzRadius, yRadius, xzRadius, predicate); + } + + /** + * Gets nearby players within the specified radius (bounding box) + * @param loc Center location + * @param xRadius X Radius + * @param yRadius Y Radius + * @param zRadius Z radius + * @param predicate a predicate used to filter results + * @return the collection of living entities near location. This will always be a non-null collection. + */ + @NotNull + public default Collection getNearbyLivingEntities(@NotNull Location loc, double xRadius, double yRadius, double zRadius, @Nullable Predicate predicate) { + return getNearbyEntitiesByType(org.bukkit.entity.LivingEntity.class, loc, xRadius, yRadius, zRadius, predicate); + } + + /** + * Gets nearby players within the specified radius (bounding box) + * @param loc Center location + * @param radius X/Y/Z Radius + * @return the collection of living entities near location. This will always be a non-null collection. + */ + @NotNull + public default Collection getNearbyPlayers(@NotNull Location loc, double radius) { + return getNearbyEntitiesByType(org.bukkit.entity.Player.class, loc, radius, radius, radius); + } + + /** + * Gets nearby players within the specified radius (bounding box) + * @param loc Center location + * @param xzRadius X/Z Radius + * @param yRadius Y Radius + * @return the collection of living entities near location. This will always be a non-null collection. + */ + @NotNull + public default Collection getNearbyPlayers(@NotNull Location loc, double xzRadius, double yRadius) { + return getNearbyEntitiesByType(org.bukkit.entity.Player.class, loc, xzRadius, yRadius, xzRadius); + } + + /** + * Gets nearby players within the specified radius (bounding box) + * @param loc Center location + * @param xRadius X Radius + * @param yRadius Y Radius + * @param zRadius Z Radius + * @return the collection of players near location. This will always be a non-null collection. + */ + @NotNull + public default Collection getNearbyPlayers(@NotNull Location loc, double xRadius, double yRadius, double zRadius) { + return getNearbyEntitiesByType(org.bukkit.entity.Player.class, loc, xRadius, yRadius, zRadius); + } + + /** + * Gets nearby players within the specified radius (bounding box) + * @param loc Center location + * @param radius X/Y/Z Radius + * @param predicate a predicate used to filter results + * @return the collection of players near location. This will always be a non-null collection. + */ + @NotNull + public default Collection getNearbyPlayers(@NotNull Location loc, double radius, @Nullable Predicate predicate) { + return getNearbyEntitiesByType(org.bukkit.entity.Player.class, loc, radius, radius, radius, predicate); + } + + /** + * Gets nearby players within the specified radius (bounding box) + * @param loc Center location + * @param xzRadius X/Z Radius + * @param yRadius Y Radius + * @param predicate a predicate used to filter results + * @return the collection of players near location. This will always be a non-null collection. + */ + @NotNull + public default Collection getNearbyPlayers(@NotNull Location loc, double xzRadius, double yRadius, @Nullable Predicate predicate) { + return getNearbyEntitiesByType(org.bukkit.entity.Player.class, loc, xzRadius, yRadius, xzRadius, predicate); + } + + /** + * Gets nearby players within the specified radius (bounding box) + * @param loc Center location + * @param xRadius X Radius + * @param yRadius Y Radius + * @param zRadius Z Radius + * @param predicate a predicate used to filter results + * @return the collection of players near location. This will always be a non-null collection. + */ + @NotNull + public default Collection getNearbyPlayers(@NotNull Location loc, double xRadius, double yRadius, double zRadius, @Nullable Predicate predicate) { + return getNearbyEntitiesByType(org.bukkit.entity.Player.class, loc, xRadius, yRadius, zRadius, predicate); + } + + /** + * Gets all nearby entities of the specified type, within the specified radius (bounding box) + * @param clazz Type to filter by + * @param loc Center location + * @param radius X/Y/Z radius to search within + * @param the entity type + * @return the collection of entities near location. This will always be a non-null collection. + */ + @NotNull + public default Collection getNearbyEntitiesByType(@Nullable Class clazz, @NotNull Location loc, double radius) { + return getNearbyEntitiesByType(clazz, loc, radius, radius, radius, null); + } + + /** + * Gets all nearby entities of the specified type, within the specified radius, with x and x radius matching (bounding box) + * @param clazz Type to filter by + * @param loc Center location + * @param xzRadius X/Z radius to search within + * @param yRadius Y radius to search within + * @param the entity type + * @return the collection of entities near location. This will always be a non-null collection. + */ + @NotNull + public default Collection getNearbyEntitiesByType(@Nullable Class clazz, @NotNull Location loc, double xzRadius, double yRadius) { + return getNearbyEntitiesByType(clazz, loc, xzRadius, yRadius, xzRadius, null); + } + + /** + * Gets all nearby entities of the specified type, within the specified radius (bounding box) + * @param clazz Type to filter by + * @param loc Center location + * @param xRadius X Radius + * @param yRadius Y Radius + * @param zRadius Z Radius + * @param the entity type + * @return the collection of entities near location. This will always be a non-null collection. + */ + @NotNull + public default Collection getNearbyEntitiesByType(@Nullable Class clazz, @NotNull Location loc, double xRadius, double yRadius, double zRadius) { + return getNearbyEntitiesByType(clazz, loc, xRadius, yRadius, zRadius, null); + } + + /** + * Gets all nearby entities of the specified type, within the specified radius (bounding box) + * @param clazz Type to filter by + * @param loc Center location + * @param radius X/Y/Z radius to search within + * @param predicate a predicate used to filter results + * @param the entity type + * @return the collection of entities near location. This will always be a non-null collection. + */ + @NotNull + public default Collection getNearbyEntitiesByType(@Nullable Class clazz, @NotNull Location loc, double radius, @Nullable Predicate predicate) { + return getNearbyEntitiesByType(clazz, loc, radius, radius, radius, predicate); + } + + /** + * Gets all nearby entities of the specified type, within the specified radius, with x and x radius matching (bounding box) + * @param clazz Type to filter by + * @param loc Center location + * @param xzRadius X/Z radius to search within + * @param yRadius Y radius to search within + * @param predicate a predicate used to filter results + * @param the entity type + * @return the collection of entities near location. This will always be a non-null collection. + */ + @NotNull + public default Collection getNearbyEntitiesByType(@Nullable Class clazz, @NotNull Location loc, double xzRadius, double yRadius, @Nullable Predicate predicate) { + return getNearbyEntitiesByType(clazz, loc, xzRadius, yRadius, xzRadius, predicate); + } + + /** + * Gets all nearby entities of the specified type, within the specified radius (bounding box) + * @param clazz Type to filter by + * @param loc Center location + * @param xRadius X Radius + * @param yRadius Y Radius + * @param zRadius Z Radius + * @param predicate a predicate used to filter results + * @param the entity type + * @return the collection of entities near location. This will always be a non-null collection. + */ + @NotNull + public default Collection getNearbyEntitiesByType(@Nullable Class clazz, @NotNull Location loc, double xRadius, double yRadius, double zRadius, @Nullable Predicate predicate) { + if (clazz == null) { + clazz = Entity.class; + } + List nearby = new ArrayList<>(); + for (Entity bukkitEntity : getNearbyEntities(loc, xRadius, yRadius, zRadius)) { + //noinspection unchecked + if (clazz.isAssignableFrom(bukkitEntity.getClass()) && (predicate == null || predicate.test((T) bukkitEntity))) { + //noinspection unchecked + nearby.add((T) bukkitEntity); + } + } + return nearby; + } + // Paper end + + /** + * Get a list of all players in this World + * + * @return A list of all Players currently residing in this world + */ + @NotNull + public List getPlayers(); + + /** + * Returns a list of entities within a bounding box centered around a + * Location. + *

+ * This may not consider entities in currently unloaded chunks. Some + * implementations may impose artificial restrictions on the size of the + * search bounding box. + * + * @param location The center of the bounding box + * @param x 1/2 the size of the box along x axis + * @param y 1/2 the size of the box along y axis + * @param z 1/2 the size of the box along z axis + * @return the collection of entities near location. This will always be a + * non-null collection. + */ + @NotNull + public Collection getNearbyEntities(@NotNull Location location, double x, double y, double z); + + // Paper start - getEntity by UUID API + /** + * Gets an entity in this world by its UUID + * + * @param uuid the UUID of the entity + * @return the entity with the given UUID, or null if it isn't found + */ + @Nullable + public Entity getEntity(@NotNull UUID uuid); + // Paper end + + /** + * Returns a list of entities within a bounding box centered around a + * Location. + *

+ * This may not consider entities in currently unloaded chunks. Some + * implementations may impose artificial restrictions on the size of the + * search bounding box. + * + * @param location The center of the bounding box + * @param x 1/2 the size of the box along x axis + * @param y 1/2 the size of the box along y axis + * @param z 1/2 the size of the box along z axis + * @param filter only entities that fulfill this predicate are considered, + * or null to consider all entities + * @return the collection of entities near location. This will always be a + * non-null collection. + */ + @NotNull + public Collection getNearbyEntities(@NotNull Location location, double x, double y, double z, @Nullable Predicate filter); + + /** + * Returns a list of entities within the given bounding box. + *

+ * This may not consider entities in currently unloaded chunks. Some + * implementations may impose artificial restrictions on the size of the + * search bounding box. + * + * @param boundingBox the bounding box + * @return the collection of entities within the bounding box, will always + * be a non-null collection + */ + @NotNull + public Collection getNearbyEntities(@NotNull BoundingBox boundingBox); + + /** + * Returns a list of entities within the given bounding box. + *

+ * This may not consider entities in currently unloaded chunks. Some + * implementations may impose artificial restrictions on the size of the + * search bounding box. + * + * @param boundingBox the bounding box + * @param filter only entities that fulfill this predicate are considered, + * or null to consider all entities + * @return the collection of entities within the bounding box, will always + * be a non-null collection + */ + @NotNull + public Collection getNearbyEntities(@NotNull BoundingBox boundingBox, @Nullable Predicate filter); + + /** + * Performs a ray trace that checks for entity collisions. + *

+ * This may not consider entities in currently unloaded chunks. Some + * implementations may impose artificial restrictions on the maximum + * distance. + * + * @param start the start position + * @param direction the ray direction + * @param maxDistance the maximum distance + * @return the closest ray trace hit result, or null if there + * is no hit + * @see #rayTraceEntities(Location, Vector, double, double, Predicate) + */ + @Nullable + public RayTraceResult rayTraceEntities(@NotNull Location start, @NotNull Vector direction, double maxDistance); + + /** + * Performs a ray trace that checks for entity collisions. + *

+ * This may not consider entities in currently unloaded chunks. Some + * implementations may impose artificial restrictions on the maximum + * distance. + * + * @param start the start position + * @param direction the ray direction + * @param maxDistance the maximum distance + * @param raySize entity bounding boxes will be uniformly expanded (or + * shrinked) by this value before doing collision checks + * @return the closest ray trace hit result, or null if there + * is no hit + * @see #rayTraceEntities(Location, Vector, double, double, Predicate) + */ + @Nullable + public RayTraceResult rayTraceEntities(@NotNull Location start, @NotNull Vector direction, double maxDistance, double raySize); + + /** + * Performs a ray trace that checks for entity collisions. + *

+ * This may not consider entities in currently unloaded chunks. Some + * implementations may impose artificial restrictions on the maximum + * distance. + * + * @param start the start position + * @param direction the ray direction + * @param maxDistance the maximum distance + * @param filter only entities that fulfill this predicate are considered, + * or null to consider all entities + * @return the closest ray trace hit result, or null if there + * is no hit + * @see #rayTraceEntities(Location, Vector, double, double, Predicate) + */ + @Nullable + public RayTraceResult rayTraceEntities(@NotNull Location start, @NotNull Vector direction, double maxDistance, @Nullable Predicate filter); + + /** + * Performs a ray trace that checks for entity collisions. + *

+ * This may not consider entities in currently unloaded chunks. Some + * implementations may impose artificial restrictions on the maximum + * distance. + * + * @param start the start position + * @param direction the ray direction + * @param maxDistance the maximum distance + * @param raySize entity bounding boxes will be uniformly expanded (or + * shrinked) by this value before doing collision checks + * @param filter only entities that fulfill this predicate are considered, + * or null to consider all entities + * @return the closest ray trace hit result, or null if there + * is no hit + */ + @Nullable + public RayTraceResult rayTraceEntities(@NotNull Location start, @NotNull Vector direction, double maxDistance, double raySize, @Nullable Predicate filter); + + /** + * Performs a ray trace that checks for block collisions using the blocks' + * precise collision shapes. + *

+ * This takes collisions with passable blocks into account, but ignores + * fluids. + *

+ * This may cause loading of chunks! Some implementations may impose + * artificial restrictions on the maximum distance. + * + * @param start the start location + * @param direction the ray direction + * @param maxDistance the maximum distance + * @return the ray trace hit result, or null if there is no hit + * @see #rayTraceBlocks(Location, Vector, double, FluidCollisionMode, boolean) + */ + @Nullable + public RayTraceResult rayTraceBlocks(@NotNull Location start, @NotNull Vector direction, double maxDistance); + + /** + * Performs a ray trace that checks for block collisions using the blocks' + * precise collision shapes. + *

+ * This takes collisions with passable blocks into account. + *

+ * This may cause loading of chunks! Some implementations may impose + * artificial restrictions on the maximum distance. + * + * @param start the start location + * @param direction the ray direction + * @param maxDistance the maximum distance + * @param fluidCollisionMode the fluid collision mode + * @return the ray trace hit result, or null if there is no hit + * @see #rayTraceBlocks(Location, Vector, double, FluidCollisionMode, boolean) + */ + @Nullable + public RayTraceResult rayTraceBlocks(@NotNull Location start, @NotNull Vector direction, double maxDistance, @NotNull FluidCollisionMode fluidCollisionMode); + + /** + * Performs a ray trace that checks for block collisions using the blocks' + * precise collision shapes. + *

+ * If collisions with passable blocks are ignored, fluid collisions are + * ignored as well regardless of the fluid collision mode. + *

+ * Portal blocks are only considered passable if the ray starts within + * them. Apart from that collisions with portal blocks will be considered + * even if collisions with passable blocks are otherwise ignored. + *

+ * This may cause loading of chunks! Some implementations may impose + * artificial restrictions on the maximum distance. + * + * @param start the start location + * @param direction the ray direction + * @param maxDistance the maximum distance + * @param fluidCollisionMode the fluid collision mode + * @param ignorePassableBlocks whether to ignore passable but collidable + * blocks (ex. tall grass, signs, fluids, ..) + * @return the ray trace hit result, or null if there is no hit + */ + @Nullable + public RayTraceResult rayTraceBlocks(@NotNull Location start, @NotNull Vector direction, double maxDistance, @NotNull FluidCollisionMode fluidCollisionMode, boolean ignorePassableBlocks); + + /** + * Performs a ray trace that checks for both block and entity collisions. + *

+ * Block collisions use the blocks' precise collision shapes. The + * raySize parameter is only taken into account for entity + * collision checks. + *

+ * If collisions with passable blocks are ignored, fluid collisions are + * ignored as well regardless of the fluid collision mode. + *

+ * Portal blocks are only considered passable if the ray starts within them. + * Apart from that collisions with portal blocks will be considered even if + * collisions with passable blocks are otherwise ignored. + *

+ * This may cause loading of chunks! Some implementations may impose + * artificial restrictions on the maximum distance. + * + * @param start the start location + * @param direction the ray direction + * @param maxDistance the maximum distance + * @param fluidCollisionMode the fluid collision mode + * @param ignorePassableBlocks whether to ignore passable but collidable + * blocks (ex. tall grass, signs, fluids, ..) + * @param raySize entity bounding boxes will be uniformly expanded (or + * shrinked) by this value before doing collision checks + * @param filter only entities that fulfill this predicate are considered, + * or null to consider all entities + * @return the closest ray trace hit result with either a block or an + * entity, or null if there is no hit + */ + @Nullable + public RayTraceResult rayTrace(@NotNull Location start, @NotNull Vector direction, double maxDistance, @NotNull FluidCollisionMode fluidCollisionMode, boolean ignorePassableBlocks, double raySize, @Nullable Predicate filter); + + /** + * Gets the unique name of this world + * + * @return Name of this world + */ + @NotNull + public String getName(); + + /** + * Gets the Unique ID of this world + * + * @return Unique ID of this world. + */ + @NotNull + public UUID getUID(); + + /** + * Gets the default spawn {@link Location} of this world + * + * @return The spawn location of this world + */ + @NotNull + public Location getSpawnLocation(); + + /** + * Sets the spawn location of the world. + *
+ * The location provided must be equal to this world. + * + * @param location The {@link Location} to set the spawn for this world at. + * @return True if it was successfully set. + */ + @NotNull + public boolean setSpawnLocation(@NotNull Location location); + + /** + * Sets the spawn location of the world + * + * @param x X coordinate + * @param y Y coordinate + * @param z Z coordinate + * @return True if it was successfully set. + */ + public boolean setSpawnLocation(int x, int y, int z); + + /** + * Gets the relative in-game time of this world. + *

+ * The relative time is analogous to hours * 1000 + * + * @return The current relative time + * @see #getFullTime() Returns an absolute time of this world + */ + public long getTime(); + + /** + * Sets the relative in-game time on the server. + *

+ * The relative time is analogous to hours * 1000 + *

+ * Note that setting the relative time below the current relative time + * will actually move the clock forward a day. If you require to rewind + * time, please see {@link #setFullTime(long)} + * + * @param time The new relative time to set the in-game time to (in + * hours*1000) + * @see #setFullTime(long) Sets the absolute time of this world + */ + public void setTime(long time); + + /** + * Gets the full in-game time on this world + * + * @return The current absolute time + * @see #getTime() Returns a relative time of this world + */ + public long getFullTime(); + + /** + * Sets the in-game time on the server + *

+ * Note that this sets the full time of the world, which may cause adverse + * effects such as breaking redstone clocks and any scheduled events + * + * @param time The new absolute time to set this world to + * @see #setTime(long) Sets the relative time of this world + */ + public void setFullTime(long time); + + // Paper start + + /** + * Check if it is currently daytime in this world + * + * @return True if it is daytime + */ + public boolean isDayTime(); + // Paper end + + /** + * Returns whether the world has an ongoing storm. + * + * @return Whether there is an ongoing storm + */ + public boolean hasStorm(); + + /** + * Set whether there is a storm. A duration will be set for the new + * current conditions. + * + * @param hasStorm Whether there is rain and snow + */ + public void setStorm(boolean hasStorm); + + /** + * Get the remaining time in ticks of the current conditions. + * + * @return Time in ticks + */ + public int getWeatherDuration(); + + /** + * Set the remaining time in ticks of the current conditions. + * + * @param duration Time in ticks + */ + public void setWeatherDuration(int duration); + + /** + * Returns whether there is thunder. + * + * @return Whether there is thunder + */ + public boolean isThundering(); + + /** + * Set whether it is thundering. + * + * @param thundering Whether it is thundering + */ + public void setThundering(boolean thundering); + + /** + * Get the thundering duration. + * + * @return Duration in ticks + */ + public int getThunderDuration(); + + /** + * Set the thundering duration. + * + * @param duration Duration in ticks + */ + public void setThunderDuration(int duration); + + /** + * Creates explosion at given coordinates with given power + * + * @param x X coordinate + * @param y Y coordinate + * @param z Z coordinate + * @param power The power of explosion, where 4F is TNT + * @return false if explosion was canceled, otherwise true + */ + public boolean createExplosion(double x, double y, double z, float power); + + /** + * Creates explosion at given coordinates with given power and optionally + * setting blocks on fire. + * + * @param x X coordinate + * @param y Y coordinate + * @param z Z coordinate + * @param power The power of explosion, where 4F is TNT + * @param setFire Whether or not to set blocks on fire + * @return false if explosion was canceled, otherwise true + */ + public boolean createExplosion(double x, double y, double z, float power, boolean setFire); + + /** + * Creates explosion at given coordinates with given power and optionally + * setting blocks on fire or breaking blocks. + * + * @param x X coordinate + * @param y Y coordinate + * @param z Z coordinate + * @param power The power of explosion, where 4F is TNT + * @param setFire Whether or not to set blocks on fire + * @param breakBlocks Whether or not to have blocks be destroyed + * @return false if explosion was canceled, otherwise true + */ + public boolean createExplosion(double x, double y, double z, float power, boolean setFire, boolean breakBlocks); + + /** + * Creates explosion at given coordinates with given power + * + * @param loc Location to blow up + * @param power The power of explosion, where 4F is TNT + * @return false if explosion was canceled, otherwise true + */ + public boolean createExplosion(@NotNull Location loc, float power); + + /** + * Creates explosion at given coordinates with given power and optionally + * setting blocks on fire. + * + * @param loc Location to blow up + * @param power The power of explosion, where 4F is TNT + * @param setFire Whether or not to set blocks on fire + * @return false if explosion was canceled, otherwise true + */ + public boolean createExplosion(@NotNull Location loc, float power, boolean setFire); + + // Paper start + /** + * Creates explosion at given location with given power and optionally + * setting blocks on fire, with the specified entity as the source. + * + * @param source The source entity of the explosion + * @param loc Location to blow up + * @param power The power of explosion, where 4F is TNT + * @param setFire Whether or not to set blocks on fire + * @param breakBlocks Whether or not to have blocks be destroyed + * @return false if explosion was canceled, otherwise true + */ + public boolean createExplosion(@Nullable Entity source, @NotNull Location loc, float power, boolean setFire, boolean breakBlocks); + + /** + * Creates explosion at given location with given power and optionally + * setting blocks on fire, with the specified entity as the source. + * + * Will destroy other blocks + * + * @param source The source entity of the explosion + * @param loc Location to blow up + * @param power The power of explosion, where 4F is TNT + * @param setFire Whether or not to set blocks on fire + * @return false if explosion was canceled, otherwise true + */ + public default boolean createExplosion(@Nullable Entity source, @NotNull Location loc, float power, boolean setFire) { + return createExplosion(source, loc, power, setFire, true); + } + /** + * Creates explosion at given location with given power, with the specified entity as the source. + * Will set blocks on fire and destroy blocks. + * + * @param source The source entity of the explosion + * @param loc Location to blow up + * @param power The power of explosion, where 4F is TNT + * @return false if explosion was canceled, otherwise true + */ + public default boolean createExplosion(@Nullable Entity source, @NotNull Location loc, float power) { + return createExplosion(source, loc, power, true, true); + } + /** + * Creates explosion at given entities location with given power and optionally + * setting blocks on fire, with the specified entity as the source. + * + * @param source The source entity of the explosion + * @param power The power of explosion, where 4F is TNT + * @param setFire Whether or not to set blocks on fire + * @param breakBlocks Whether or not to have blocks be destroyed + * @return false if explosion was canceled, otherwise true + */ + public default boolean createExplosion(@NotNull Entity source, float power, boolean setFire, boolean breakBlocks) { + return createExplosion(source, source.getLocation(), power, setFire, breakBlocks); + } + /** + * Creates explosion at given entities location with given power and optionally + * setting blocks on fire, with the specified entity as the source. + * + * Will destroy blocks. + * + * @param source The source entity of the explosion + * @param power The power of explosion, where 4F is TNT + * @param setFire Whether or not to set blocks on fire + * @return false if explosion was canceled, otherwise true + */ + public default boolean createExplosion(@NotNull Entity source, float power, boolean setFire) { + return createExplosion(source, source.getLocation(), power, setFire, true); + } + + /** + * Creates explosion at given entities location with given power and optionally + * setting blocks on fire, with the specified entity as the source. + * + * @param source The source entity of the explosion + * @param power The power of explosion, where 4F is TNT + * @return false if explosion was canceled, otherwise true + */ + public default boolean createExplosion(@NotNull Entity source, float power) { + return createExplosion(source, source.getLocation(), power, true, true); + } + + /** + * Creates explosion at given location with given power and optionally + * setting blocks on fire or breaking blocks. + * + * @param loc Location to blow up + * @param power The power of explosion, where 4F is TNT + * @param setFire Whether or not to set blocks on fire + * @param breakBlocks Whether or not to have blocks be destroyed + * @return false if explosion was canceled, otherwise true + */ + public default boolean createExplosion(@NotNull Location loc, float power, boolean setFire, boolean breakBlocks) { + return createExplosion(loc.getX(), loc.getY(), loc.getZ(), power, setFire, breakBlocks); + } + // Paper end + + /** + * Gets the {@link Environment} type of this world + * + * @return This worlds Environment type + */ + @NotNull + public Environment getEnvironment(); + + /** + * Gets the Seed for this world. + * + * @return This worlds Seed + */ + public long getSeed(); + + /** + * Gets the current PVP setting for this world. + * + * @return True if PVP is enabled + */ + public boolean getPVP(); + + /** + * Sets the PVP setting for this world. + * + * @param pvp True/False whether PVP should be Enabled. + */ + public void setPVP(boolean pvp); + + /** + * Gets the chunk generator for this world + * + * @return ChunkGenerator associated with this world + */ + @Nullable + public ChunkGenerator getGenerator(); + + /** + * Saves world to disk + */ + public void save(); + + /** + * Gets a list of all applied {@link BlockPopulator}s for this World + * + * @return List containing any or none BlockPopulators + */ + @NotNull + public List getPopulators(); + + /** + * Spawn an entity of a specific class at the given {@link Location} + * + * @param location the {@link Location} to spawn the entity at + * @param clazz the class of the {@link Entity} to spawn + * @param the class of the {@link Entity} to spawn + * @return an instance of the spawned {@link Entity} + * @throws IllegalArgumentException if either parameter is null or the + * {@link Entity} requested cannot be spawned + */ + @NotNull + public T spawn(@NotNull Location location, @NotNull Class clazz) throws IllegalArgumentException; + + /** + * Spawn an entity of a specific class at the given {@link Location}, with + * the supplied function run before the entity is added to the world. + *
+ * Note that when the function is run, the entity will not be actually in + * the world. Any operation involving such as teleporting the entity is undefined + * until after this function returns. + * + * @param location the {@link Location} to spawn the entity at + * @param clazz the class of the {@link Entity} to spawn + * @param function the function to be run before the entity is spawned. + * @param the class of the {@link Entity} to spawn + * @return an instance of the spawned {@link Entity} + * @throws IllegalArgumentException if either parameter is null or the + * {@link Entity} requested cannot be spawned + */ + @NotNull + public T spawn(@NotNull Location location, @NotNull Class clazz, @Nullable Consumer function) throws IllegalArgumentException; + + /** + * Spawn a {@link FallingBlock} entity at the given {@link Location} of + * the specified {@link Material}. The material dictates what is falling. + * When the FallingBlock hits the ground, it will place that block. + *

+ * The Material must be a block type, check with {@link Material#isBlock() + * material.isBlock()}. The Material may not be air. + * + * @param location The {@link Location} to spawn the FallingBlock + * @param data The block data + * @return The spawned {@link FallingBlock} instance + * @throws IllegalArgumentException if {@link Location} or {@link + * MaterialData} are null or {@link Material} of the {@link MaterialData} is not a block + */ + @NotNull + public FallingBlock spawnFallingBlock(@NotNull Location location, @NotNull MaterialData data) throws IllegalArgumentException; + + /** + * Spawn a {@link FallingBlock} entity at the given {@link Location} of + * the specified {@link Material}. The material dictates what is falling. + * When the FallingBlock hits the ground, it will place that block. + *

+ * The Material must be a block type, check with {@link Material#isBlock() + * material.isBlock()}. The Material may not be air. + * + * @param location The {@link Location} to spawn the FallingBlock + * @param data The block data + * @return The spawned {@link FallingBlock} instance + * @throws IllegalArgumentException if {@link Location} or {@link + * BlockData} are null + */ + @NotNull + public FallingBlock spawnFallingBlock(@NotNull Location location, @NotNull BlockData data) throws IllegalArgumentException; + + /** + * Spawn a {@link FallingBlock} entity at the given {@link Location} of the + * specified {@link Material}. The material dictates what is falling. + * When the FallingBlock hits the ground, it will place that block. + *

+ * The Material must be a block type, check with {@link Material#isBlock() + * material.isBlock()}. The Material may not be air. + * + * @param location The {@link Location} to spawn the FallingBlock + * @param material The block {@link Material} type + * @param data The block data + * @return The spawned {@link FallingBlock} instance + * @throws IllegalArgumentException if {@link Location} or {@link + * Material} are null or {@link Material} is not a block + * @deprecated Magic value + */ + @Deprecated + @NotNull + public FallingBlock spawnFallingBlock(@NotNull Location location, @NotNull Material material, byte data) throws IllegalArgumentException; + + /** + * Plays an effect to all players within a default radius around a given + * location. + * + * @param location the {@link Location} around which players must be to + * hear the sound + * @param effect the {@link Effect} + * @param data a data bit needed for some effects + */ + public void playEffect(@NotNull Location location, @NotNull Effect effect, int data); + + /** + * Plays an effect to all players within a given radius around a location. + * + * @param location the {@link Location} around which players must be to + * hear the effect + * @param effect the {@link Effect} + * @param data a data bit needed for some effects + * @param radius the radius around the location + */ + public void playEffect(@NotNull Location location, @NotNull Effect effect, int data, int radius); + + /** + * Plays an effect to all players within a default radius around a given + * location. + * + * @param data dependant on the type of effect + * @param location the {@link Location} around which players must be to + * hear the sound + * @param effect the {@link Effect} + * @param data a data bit needed for some effects + */ + public void playEffect(@NotNull Location location, @NotNull Effect effect, @Nullable T data); + + /** + * Plays an effect to all players within a given radius around a location. + * + * @param data dependant on the type of effect + * @param location the {@link Location} around which players must be to + * hear the effect + * @param effect the {@link Effect} + * @param data a data bit needed for some effects + * @param radius the radius around the location + */ + public void playEffect(@NotNull Location location, @NotNull Effect effect, @Nullable T data, int radius); + + /** + * Get empty chunk snapshot (equivalent to all air blocks), optionally + * including valid biome data. Used for representing an ungenerated chunk, + * or for fetching only biome data without loading a chunk. + * + * @param x - chunk x coordinate + * @param z - chunk z coordinate + * @param includeBiome - if true, snapshot includes per-coordinate biome + * type + * @param includeBiomeTemp - if true, snapshot includes per-coordinate + * raw biome temperature + * @return The empty snapshot. + */ + @NotNull + public ChunkSnapshot getEmptyChunkSnapshot(int x, int z, boolean includeBiome, boolean includeBiomeTemp); + + /** + * Sets the spawn flags for this. + * + * @param allowMonsters - if true, monsters are allowed to spawn in this + * world. + * @param allowAnimals - if true, animals are allowed to spawn in this + * world. + */ + public void setSpawnFlags(boolean allowMonsters, boolean allowAnimals); + + /** + * Gets whether animals can spawn in this world. + * + * @return whether animals can spawn in this world. + */ + public boolean getAllowAnimals(); + + /** + * Gets whether monsters can spawn in this world. + * + * @return whether monsters can spawn in this world. + */ + public boolean getAllowMonsters(); + + /** + * Gets the biome for the given block coordinates. + * + * @param x X coordinate of the block + * @param z Z coordinate of the block + * @return Biome of the requested block + */ + @NotNull + Biome getBiome(int x, int z); + + /** + * Sets the biome for the given block coordinates + * + * @param x X coordinate of the block + * @param z Z coordinate of the block + * @param bio new Biome type for this block + */ + void setBiome(int x, int z, @NotNull Biome bio); + + /** + * Gets the temperature for the given block coordinates. + *

+ * It is safe to run this method when the block does not exist, it will + * not create the block. + *

+ * This method will return the raw temperature without adjusting for block + * height effects. + * + * @param x X coordinate of the block + * @param z Z coordinate of the block + * @return Temperature of the requested block + */ + public double getTemperature(int x, int z); + + /** + * Gets the humidity for the given block coordinates. + *

+ * It is safe to run this method when the block does not exist, it will + * not create the block. + * + * @param x X coordinate of the block + * @param z Z coordinate of the block + * @return Humidity of the requested block + */ + public double getHumidity(int x, int z); + + /** + * Gets the maximum height of this world. + *

+ * If the max height is 100, there are only blocks from y=0 to y=99. + * + * @return Maximum height of the world + */ + public int getMaxHeight(); + + /** + * Gets the sea level for this world. + *

+ * This is often half of {@link #getMaxHeight()} + * + * @return Sea level + */ + public int getSeaLevel(); + + /** + * Gets whether the world's spawn area should be kept loaded into memory + * or not. + * + * @return true if the world's spawn area will be kept loaded into memory. + */ + public boolean getKeepSpawnInMemory(); + + /** + * Sets whether the world's spawn area should be kept loaded into memory + * or not. + * + * @param keepLoaded if true then the world's spawn area will be kept + * loaded into memory. + */ + public void setKeepSpawnInMemory(boolean keepLoaded); + + /** + * Gets whether or not the world will automatically save + * + * @return true if the world will automatically save, otherwise false + */ + public boolean isAutoSave(); + + /** + * Sets whether or not the world will automatically save + * + * @param value true if the world should automatically save, otherwise + * false + */ + public void setAutoSave(boolean value); + + /** + * Sets the Difficulty of the world. + * + * @param difficulty the new difficulty you want to set the world to + */ + public void setDifficulty(@NotNull Difficulty difficulty); + + /** + * Gets the Difficulty of the world. + * + * @return The difficulty of the world. + */ + @NotNull + public Difficulty getDifficulty(); + + /** + * Gets the folder of this world on disk. + * + * @return The folder of this world. + */ + @NotNull + public File getWorldFolder(); + + /** + * Gets the type of this world. + * + * @return Type of this world. + */ + @Nullable + public WorldType getWorldType(); + + /** + * Gets whether or not structures are being generated. + * + * @return True if structures are being generated. + */ + public boolean canGenerateStructures(); + + /** + * Gets the world's ticks per animal spawns value + *

+ * This value determines how many ticks there are between attempts to + * spawn animals. + *

+ * Example Usage: + *

    + *
  • A value of 1 will mean the server will attempt to spawn animals in + * this world every tick. + *
  • A value of 400 will mean the server will attempt to spawn animals + * in this world every 400th tick. + *
  • A value below 0 will be reset back to Minecraft's default. + *
+ *

+ * Note: + * If set to 0, animal spawning will be disabled for this world. We + * recommend using {@link #setSpawnFlags(boolean, boolean)} to control + * this instead. + *

+ * Minecraft default: 400. + * + * @return The world's ticks per animal spawns value + */ + public long getTicksPerAnimalSpawns(); + + /** + * Sets the world's ticks per animal spawns value + *

+ * This value determines how many ticks there are between attempts to + * spawn animals. + *

+ * Example Usage: + *

    + *
  • A value of 1 will mean the server will attempt to spawn animals in + * this world every tick. + *
  • A value of 400 will mean the server will attempt to spawn animals + * in this world every 400th tick. + *
  • A value below 0 will be reset back to Minecraft's default. + *
+ *

+ * Note: + * If set to 0, animal spawning will be disabled for this world. We + * recommend using {@link #setSpawnFlags(boolean, boolean)} to control + * this instead. + *

+ * Minecraft default: 400. + * + * @param ticksPerAnimalSpawns the ticks per animal spawns value you want + * to set the world to + */ + public void setTicksPerAnimalSpawns(int ticksPerAnimalSpawns); + + /** + * Gets the world's ticks per monster spawns value + *

+ * This value determines how many ticks there are between attempts to + * spawn monsters. + *

+ * Example Usage: + *

    + *
  • A value of 1 will mean the server will attempt to spawn monsters in + * this world every tick. + *
  • A value of 400 will mean the server will attempt to spawn monsters + * in this world every 400th tick. + *
  • A value below 0 will be reset back to Minecraft's default. + *
+ *

+ * Note: + * If set to 0, monsters spawning will be disabled for this world. We + * recommend using {@link #setSpawnFlags(boolean, boolean)} to control + * this instead. + *

+ * Minecraft default: 1. + * + * @return The world's ticks per monster spawns value + */ + public long getTicksPerMonsterSpawns(); + + /** + * Sets the world's ticks per monster spawns value + *

+ * This value determines how many ticks there are between attempts to + * spawn monsters. + *

+ * Example Usage: + *

    + *
  • A value of 1 will mean the server will attempt to spawn monsters in + * this world on every tick. + *
  • A value of 400 will mean the server will attempt to spawn monsters + * in this world every 400th tick. + *
  • A value below 0 will be reset back to Minecraft's default. + *
+ *

+ * Note: + * If set to 0, monsters spawning will be disabled for this world. We + * recommend using {@link #setSpawnFlags(boolean, boolean)} to control + * this instead. + *

+ * Minecraft default: 1. + * + * @param ticksPerMonsterSpawns the ticks per monster spawns value you + * want to set the world to + */ + public void setTicksPerMonsterSpawns(int ticksPerMonsterSpawns); + + /** + * Gets limit for number of monsters that can spawn in a chunk in this + * world + * + * @return The monster spawn limit + */ + int getMonsterSpawnLimit(); + + /** + * Sets the limit for number of monsters that can spawn in a chunk in this + * world + *

+ * Note: If set to a negative number the world will use the + * server-wide spawn limit instead. + * + * @param limit the new mob limit + */ + void setMonsterSpawnLimit(int limit); + + /** + * Gets the limit for number of animals that can spawn in a chunk in this + * world + * + * @return The animal spawn limit + */ + int getAnimalSpawnLimit(); + + /** + * Sets the limit for number of animals that can spawn in a chunk in this + * world + *

+ * Note: If set to a negative number the world will use the + * server-wide spawn limit instead. + * + * @param limit the new mob limit + */ + void setAnimalSpawnLimit(int limit); + + /** + * Gets the limit for number of water animals that can spawn in a chunk in + * this world + * + * @return The water animal spawn limit + */ + int getWaterAnimalSpawnLimit(); + + /** + * Sets the limit for number of water animals that can spawn in a chunk in + * this world + *

+ * Note: If set to a negative number the world will use the + * server-wide spawn limit instead. + * + * @param limit the new mob limit + */ + void setWaterAnimalSpawnLimit(int limit); + + /** + * Gets the limit for number of ambient mobs that can spawn in a chunk in + * this world + * + * @return The ambient spawn limit + */ + int getAmbientSpawnLimit(); + + /** + * Sets the limit for number of ambient mobs that can spawn in a chunk in + * this world + *

+ * Note: If set to a negative number the world will use the + * server-wide spawn limit instead. + * + * @param limit the new mob limit + */ + void setAmbientSpawnLimit(int limit); + + /** + * Play a Sound at the provided Location in the World + *

+ * This function will fail silently if Location or Sound are null. + * + * @param location The location to play the sound + * @param sound The sound to play + * @param volume The volume of the sound + * @param pitch The pitch of the sound + */ + void playSound(@NotNull Location location, @NotNull Sound sound, float volume, float pitch); + + /** + * Play a Sound at the provided Location in the World. + *

+ * This function will fail silently if Location or Sound are null. No + * sound will be heard by the players if their clients do not have the + * respective sound for the value passed. + * + * @param location the location to play the sound + * @param sound the internal sound name to play + * @param volume the volume of the sound + * @param pitch the pitch of the sound + */ + void playSound(@NotNull Location location, @NotNull String sound, float volume, float pitch); + + /** + * Play a Sound at the provided Location in the World. + *

+ * This function will fail silently if Location or Sound are null. + * + * @param location The location to play the sound + * @param sound The sound to play + * @param category the category of the sound + * @param volume The volume of the sound + * @param pitch The pitch of the sound + */ + void playSound(@NotNull Location location, @NotNull Sound sound, @NotNull SoundCategory category, float volume, float pitch); + + /** + * Play a Sound at the provided Location in the World. + *

+ * This function will fail silently if Location or Sound are null. No sound + * will be heard by the players if their clients do not have the respective + * sound for the value passed. + * + * @param location the location to play the sound + * @param sound the internal sound name to play + * @param category the category of the sound + * @param volume the volume of the sound + * @param pitch the pitch of the sound + */ + void playSound(@NotNull Location location, @NotNull String sound, @NotNull SoundCategory category, float volume, float pitch); + + /** + * Get an array containing the names of all the {@link GameRule}s. + * + * @return An array of {@link GameRule} names. + */ + @NotNull + public String[] getGameRules(); + + /** + * Gets the current state of the specified rule + *

+ * Will return null if rule passed is null + * + * @param rule Rule to look up value of + * @return String value of rule + * @deprecated use {@link #getGameRuleValue(GameRule)} instead + */ + @Deprecated + @Contract("null -> null; !null -> !null") + @Nullable + public String getGameRuleValue(@Nullable String rule); + + /** + * Set the specified gamerule to specified value. + *

+ * The rule may attempt to validate the value passed, will return true if + * value was set. + *

+ * If rule is null, the function will return false. + * + * @param rule Rule to set + * @param value Value to set rule to + * @return True if rule was set + * @deprecated use {@link #setGameRule(GameRule, Object)} instead. + */ + @Deprecated + public boolean setGameRuleValue(@NotNull String rule, @NotNull String value); + + /** + * Checks if string is a valid game rule + * + * @param rule Rule to check + * @return True if rule exists + */ + public boolean isGameRule(@NotNull String rule); + + /** + * Get the current value for a given {@link GameRule}. + * + * @param rule the GameRule to check + * @param the GameRule's type + * @return the current value + */ + @Nullable + public T getGameRuleValue(@NotNull GameRule rule); + + /** + * Get the default value for a given {@link GameRule}. This value is not + * guaranteed to match the current value. + * + * @param rule the rule to return a default value for + * @param the type of GameRule + * @return the default value + */ + @Nullable + public T getGameRuleDefault(@NotNull GameRule rule); + + /** + * Set the given {@link GameRule}'s new value. + * + * @param rule the GameRule to update + * @param newValue the new value + * @param the value type of the GameRule + * @return true if the value was successfully set + */ + public boolean setGameRule(@NotNull GameRule rule, @NotNull T newValue); + + /** + * Gets the world border for this world. + * + * @return The world border for this world. + */ + @NotNull + public WorldBorder getWorldBorder(); + + /** + * Spawns the particle (the number of times specified by count) + * at the target location. + * + * @param particle the particle to spawn + * @param location the location to spawn at + * @param count the number of particles + */ + public void spawnParticle(@NotNull Particle particle, @NotNull Location location, int count); + + /** + * Spawns the particle (the number of times specified by count) + * at the target location. + * + * @param particle the particle to spawn + * @param x the position on the x axis to spawn at + * @param y the position on the y axis to spawn at + * @param z the position on the z axis to spawn at + * @param count the number of particles + */ + public void spawnParticle(@NotNull Particle particle, double x, double y, double z, int count); + + /** + * Spawns the particle (the number of times specified by count) + * at the target location. + * + * @param particle the particle to spawn + * @param location the location to spawn at + * @param count the number of particles + * @param data the data to use for the particle or null, + * the type of this depends on {@link Particle#getDataType()} + * @param Type + */ + public void spawnParticle(@NotNull Particle particle, @NotNull Location location, int count, @Nullable T data); + + + /** + * Spawns the particle (the number of times specified by count) + * at the target location. + * + * @param particle the particle to spawn + * @param x the position on the x axis to spawn at + * @param y the position on the y axis to spawn at + * @param z the position on the z axis to spawn at + * @param count the number of particles + * @param data the data to use for the particle or null, + * the type of this depends on {@link Particle#getDataType()} + * @param Type + */ + public void spawnParticle(@NotNull Particle particle, double x, double y, double z, int count, @Nullable T data); + + /** + * Spawns the particle (the number of times specified by count) + * at the target location. The position of each particle will be + * randomized positively and negatively by the offset parameters + * on each axis. + * + * @param particle the particle to spawn + * @param location the location to spawn at + * @param count the number of particles + * @param offsetX the maximum random offset on the X axis + * @param offsetY the maximum random offset on the Y axis + * @param offsetZ the maximum random offset on the Z axis + */ + public void spawnParticle(@NotNull Particle particle, @NotNull Location location, int count, double offsetX, double offsetY, double offsetZ); + + /** + * Spawns the particle (the number of times specified by count) + * at the target location. The position of each particle will be + * randomized positively and negatively by the offset parameters + * on each axis. + * + * @param particle the particle to spawn + * @param x the position on the x axis to spawn at + * @param y the position on the y axis to spawn at + * @param z the position on the z axis to spawn at + * @param count the number of particles + * @param offsetX the maximum random offset on the X axis + * @param offsetY the maximum random offset on the Y axis + * @param offsetZ the maximum random offset on the Z axis + */ + public void spawnParticle(@NotNull Particle particle, double x, double y, double z, int count, double offsetX, double offsetY, double offsetZ); + + /** + * Spawns the particle (the number of times specified by count) + * at the target location. The position of each particle will be + * randomized positively and negatively by the offset parameters + * on each axis. + * + * @param particle the particle to spawn + * @param location the location to spawn at + * @param count the number of particles + * @param offsetX the maximum random offset on the X axis + * @param offsetY the maximum random offset on the Y axis + * @param offsetZ the maximum random offset on the Z axis + * @param data the data to use for the particle or null, + * the type of this depends on {@link Particle#getDataType()} + * @param Type + */ + public void spawnParticle(@NotNull Particle particle, @NotNull Location location, int count, double offsetX, double offsetY, double offsetZ, @Nullable T data); + + /** + * Spawns the particle (the number of times specified by count) + * at the target location. The position of each particle will be + * randomized positively and negatively by the offset parameters + * on each axis. + * + * @param particle the particle to spawn + * @param x the position on the x axis to spawn at + * @param y the position on the y axis to spawn at + * @param z the position on the z axis to spawn at + * @param count the number of particles + * @param offsetX the maximum random offset on the X axis + * @param offsetY the maximum random offset on the Y axis + * @param offsetZ the maximum random offset on the Z axis + * @param data the data to use for the particle or null, + * the type of this depends on {@link Particle#getDataType()} + * @param Type + */ + public void spawnParticle(@NotNull Particle particle, double x, double y, double z, int count, double offsetX, double offsetY, double offsetZ, @Nullable T data); + + /** + * Spawns the particle (the number of times specified by count) + * at the target location. The position of each particle will be + * randomized positively and negatively by the offset parameters + * on each axis. + * + * @param particle the particle to spawn + * @param location the location to spawn at + * @param count the number of particles + * @param offsetX the maximum random offset on the X axis + * @param offsetY the maximum random offset on the Y axis + * @param offsetZ the maximum random offset on the Z axis + * @param extra the extra data for this particle, depends on the + * particle used (normally speed) + */ + public void spawnParticle(@NotNull Particle particle, @NotNull Location location, int count, double offsetX, double offsetY, double offsetZ, double extra); + + /** + * Spawns the particle (the number of times specified by count) + * at the target location. The position of each particle will be + * randomized positively and negatively by the offset parameters + * on each axis. + * + * @param particle the particle to spawn + * @param x the position on the x axis to spawn at + * @param y the position on the y axis to spawn at + * @param z the position on the z axis to spawn at + * @param count the number of particles + * @param offsetX the maximum random offset on the X axis + * @param offsetY the maximum random offset on the Y axis + * @param offsetZ the maximum random offset on the Z axis + * @param extra the extra data for this particle, depends on the + * particle used (normally speed) + */ + public void spawnParticle(@NotNull Particle particle, double x, double y, double z, int count, double offsetX, double offsetY, double offsetZ, double extra); + + /** + * Spawns the particle (the number of times specified by count) + * at the target location. The position of each particle will be + * randomized positively and negatively by the offset parameters + * on each axis. + * + * @param particle the particle to spawn + * @param location the location to spawn at + * @param count the number of particles + * @param offsetX the maximum random offset on the X axis + * @param offsetY the maximum random offset on the Y axis + * @param offsetZ the maximum random offset on the Z axis + * @param extra the extra data for this particle, depends on the + * particle used (normally speed) + * @param data the data to use for the particle or null, + * the type of this depends on {@link Particle#getDataType()} + * @param Type + */ + public void spawnParticle(@NotNull Particle particle, @NotNull Location location, int count, double offsetX, double offsetY, double offsetZ, double extra, @Nullable T data); + + /** + * Spawns the particle (the number of times specified by count) + * at the target location. The position of each particle will be + * randomized positively and negatively by the offset parameters + * on each axis. + * + * @param particle the particle to spawn + * @param x the position on the x axis to spawn at + * @param y the position on the y axis to spawn at + * @param z the position on the z axis to spawn at + * @param count the number of particles + * @param offsetX the maximum random offset on the X axis + * @param offsetY the maximum random offset on the Y axis + * @param offsetZ the maximum random offset on the Z axis + * @param extra the extra data for this particle, depends on the + * particle used (normally speed) + * @param data the data to use for the particle or null, + * the type of this depends on {@link Particle#getDataType()} + * @param Type + */ + public default void spawnParticle(@NotNull Particle particle, double x, double y, double z, int count, double offsetX, double offsetY, double offsetZ, double extra, @Nullable T data) { spawnParticle(particle, null, null, x, y, z, count, offsetX, offsetY, offsetZ, extra, data, true); }// Paper start - Expand Particle API + /** + * Spawns the particle (the number of times specified by count) + * at the target location. The position of each particle will be + * randomized positively and negatively by the offset parameters + * on each axis. + * + * @param particle the particle to spawn + * @param receivers List of players to receive the particles, or null for all in world + * @param source Source of the particles to be used in visibility checks, or null if no player source + * @param x the position on the x axis to spawn at + * @param y the position on the y axis to spawn at + * @param z the position on the z axis to spawn at + * @param count the number of particles + * @param offsetX the maximum random offset on the X axis + * @param offsetY the maximum random offset on the Y axis + * @param offsetZ the maximum random offset on the Z axis + * @param extra the extra data for this particle, depends on the + * particle used (normally speed) + * @param data the data to use for the particle or null, + * the type of this depends on {@link Particle#getDataType()} + * @param Type + */ + public default void spawnParticle(@NotNull Particle particle, @Nullable List receivers, @NotNull Player source, double x, double y, double z, int count, double offsetX, double offsetY, double offsetZ, double extra, @Nullable T data) { spawnParticle(particle, receivers, source, x, y, z, count, offsetX, offsetY, offsetZ, extra, data, true); } + /** + * Spawns the particle (the number of times specified by count) + * at the target location. The position of each particle will be + * randomized positively and negatively by the offset parameters + * on each axis. + * + * @param particle the particle to spawn + * @param receivers List of players to receive the particles, or null for all in world + * @param source Source of the particles to be used in visibility checks, or null if no player source + * @param x the position on the x axis to spawn at + * @param y the position on the y axis to spawn at + * @param z the position on the z axis to spawn at + * @param count the number of particles + * @param offsetX the maximum random offset on the X axis + * @param offsetY the maximum random offset on the Y axis + * @param offsetZ the maximum random offset on the Z axis + * @param extra the extra data for this particle, depends on the + * particle used (normally speed) + * @param data the data to use for the particle or null, + * the type of this depends on {@link Particle#getDataType()} + * @param Type + * @param force allows the particle to be seen further away from the player + * and shows to players using any vanilla client particle settings + */ + public void spawnParticle(@NotNull Particle particle, @Nullable List receivers, @Nullable Player source, double x, double y, double z, int count, double offsetX, double offsetY, double offsetZ, double extra, @Nullable T data, boolean force); + // Paper end + + + /** + * Spawns the particle (the number of times specified by count) + * at the target location. The position of each particle will be + * randomized positively and negatively by the offset parameters + * on each axis. + * + * @param particle the particle to spawn + * @param location the location to spawn at + * @param count the number of particles + * @param offsetX the maximum random offset on the X axis + * @param offsetY the maximum random offset on the Y axis + * @param offsetZ the maximum random offset on the Z axis + * @param extra the extra data for this particle, depends on the + * particle used (normally speed) + * @param data the data to use for the particle or null, + * the type of this depends on {@link Particle#getDataType()} + * @param force whether to send the particle to players within an extended + * range and encourage their client to render it regardless of + * settings + * @param Particle data type + */ + public void spawnParticle(@NotNull Particle particle, @NotNull Location location, int count, double offsetX, double offsetY, double offsetZ, double extra, @Nullable T data, boolean force); + + /** + * Spawns the particle (the number of times specified by count) + * at the target location. The position of each particle will be + * randomized positively and negatively by the offset parameters + * on each axis. + * + * @param particle the particle to spawn + * @param x the position on the x axis to spawn at + * @param y the position on the y axis to spawn at + * @param z the position on the z axis to spawn at + * @param count the number of particles + * @param offsetX the maximum random offset on the X axis + * @param offsetY the maximum random offset on the Y axis + * @param offsetZ the maximum random offset on the Z axis + * @param extra the extra data for this particle, depends on the + * particle used (normally speed) + * @param data the data to use for the particle or null, + * the type of this depends on {@link Particle#getDataType()} + * @param force whether to send the particle to players within an extended + * range and encourage their client to render it regardless of + * settings + * @param Particle data type + */ + public void spawnParticle(@NotNull Particle particle, double x, double y, double z, int count, double offsetX, double offsetY, double offsetZ, double extra, @Nullable T data, boolean force); + + /** + * Find the closest nearby structure of a given {@link StructureType}. + * Finding unexplored structures can, and will, block if the world is + * looking in chunks that gave not generated yet. This can lead to the world + * temporarily freezing while locating an unexplored structure. + *

+ * The {@code radius} is not a rigid square radius. Each structure may alter + * how many chunks to check for each iteration. Do not assume that only a + * radius x radius chunk area will be checked. For example, + * {@link StructureType#WOODLAND_MANSION} can potentially check up to 20,000 + * blocks away (or more) regardless of the radius used. + *

+ * This will not load or generate chunks. This can also lead to + * instances where the server can hang if you are only looking for + * unexplored structures. This is because it will keep looking further and + * further out in order to find the structure. + * + * @param origin where to start looking for a structure + * @param structureType the type of structure to find + * @param radius the radius, in chunks, around which to search + * @param findUnexplored true to only find unexplored structures + * @return the closest {@link Location}, or null if no structure of the + * specified type exists. + */ + @Nullable + public Location locateNearestStructure(@NotNull Location origin, @NotNull StructureType structureType, int radius, boolean findUnexplored); + + // Spigot start + public class Spigot + { + + /** + * Strikes lightning at the given {@link Location} and possibly without sound + * + * @param loc The location to strike lightning + * @param isSilent Whether this strike makes no sound + * @return The lightning entity. + */ + @NotNull + public LightningStrike strikeLightning(@NotNull Location loc, boolean isSilent) + { + throw new UnsupportedOperationException( "Not supported yet." ); + } + + /** + * Strikes lightning at the given {@link Location} without doing damage and possibly without sound + * + * @param loc The location to strike lightning + * @param isSilent Whether this strike makes no sound + * @return The lightning entity. + */ + @NotNull + public LightningStrike strikeLightningEffect(@NotNull Location loc, boolean isSilent) + { + throw new UnsupportedOperationException( "Not supported yet." ); + } + } + + @NotNull + Spigot spigot(); + // Spigot end + + /** + * Represents various map environment types that a world may be + */ + public enum Environment { + + /** + * Represents the "normal"/"surface world" map + */ + NORMAL(0), + /** + * Represents a nether based map ("hell") + */ + NETHER(-1), + /** + * Represents the "end" map + */ + THE_END(1); + + private final int id; + private static final Map lookup = new HashMap(); + + private Environment(int id) { + this.id = id; + } + + /** + * Gets the dimension ID of this environment + * + * @return dimension ID + * @deprecated Magic value + */ + @Deprecated + public int getId() { + return id; + } + + /** + * Get an environment by ID + * + * @param id The ID of the environment + * @return The environment + * @deprecated Magic value + */ + @Deprecated + @Nullable + public static Environment getEnvironment(int id) { + return lookup.get(id); + } + + static { + for (Environment env : values()) { + lookup.put(env.getId(), env); + } + } + } +} diff --git a/api/src/main/java/org/bukkit/WorldBorder.java b/api/src/main/java/org/bukkit/WorldBorder.java new file mode 100644 index 000000000..afb7b136b --- /dev/null +++ b/api/src/main/java/org/bukkit/WorldBorder.java @@ -0,0 +1,134 @@ +package org.bukkit; + +import org.jetbrains.annotations.NotNull; + +public interface WorldBorder { + + /** + * Resets the border to default values. + */ + public void reset(); + + /** + * Gets the current side length of the border. + * + * @return The current side length of the border. + */ + public double getSize(); + + /** + * Sets the border to a square region with the specified side length in blocks. + * + * @param newSize The new size of the border. + */ + public void setSize(double newSize); + + /** + * Sets the border to a square region with the specified side length in blocks. + * + * @param newSize The new side length of the border. + * @param seconds The time in seconds in which the border grows or shrinks from the previous size to that being set. + */ + public void setSize(double newSize, long seconds); + + /** + * Gets the current border center. + * + * @return The current border center. + */ + @NotNull + public Location getCenter(); + + /** + * Sets the new border center. + * + * @param x The new center x-coordinate. + * @param z The new center z-coordinate. + */ + public void setCenter(double x, double z); + + /** + * Sets the new border center. + * + * @param location The new location of the border center. (Only x/z used) + */ + public void setCenter(@NotNull Location location); + + /** + * Gets the current border damage buffer. + * + * @return The current border damage buffer. + */ + public double getDamageBuffer(); + + /** + * Sets the amount of blocks a player may safely be outside the border before taking damage. + * + * @param blocks The amount of blocks. (The default is 5 blocks.) + */ + public void setDamageBuffer(double blocks); + + /** + * Gets the current border damage amount. + * + * @return The current border damage amount. + */ + public double getDamageAmount(); + + /** + * Sets the amount of damage a player takes when outside the border plus the border buffer. + * + * @param damage The amount of damage. (The default is 0.2 damage per second per block.) + */ + public void setDamageAmount(double damage); + + /** + * Gets the current border warning time in seconds. + * + * @return The current border warning time in seconds. + */ + public int getWarningTime(); + + /** + * Sets the warning time that causes the screen to be tinted red when a contracting border will reach the player within the specified time. + * + * @param seconds The amount of time in seconds. (The default is 15 seconds.) + */ + public void setWarningTime(int seconds); + + /** + * Gets the current border warning distance. + * + * @return The current border warning distance. + */ + public int getWarningDistance(); + + /** + * Sets the warning distance that causes the screen to be tinted red when the player is within the specified number of blocks from the border. + * + * @param distance The distance in blocks. (The default is 5 blocks.) + */ + public void setWarningDistance(int distance); + + /** + * Check if the specified location is inside this border. + * + * @param location the location to check + * @return if this location is inside the border or not + */ + public boolean isInside(@NotNull Location location); + + // Paper start + /** + * Checks if the location is within the boundaries of this border. + * + * @param location specific location to check + * @return true if the location is within the bounds of this border, false otherwise. + * @deprecated use {@link #isInside(Location)} for an upstream compatible replacement + */ + @Deprecated + public default boolean isInBounds(@NotNull Location location) { + return this.isInside(location); + } + // Paper end +} diff --git a/api/src/main/java/org/bukkit/WorldCreator.java b/api/src/main/java/org/bukkit/WorldCreator.java new file mode 100644 index 000000000..a9b29b6e2 --- /dev/null +++ b/api/src/main/java/org/bukkit/WorldCreator.java @@ -0,0 +1,337 @@ +package org.bukkit; + +import java.util.Random; +import org.bukkit.command.CommandSender; +import org.bukkit.generator.ChunkGenerator; +import org.bukkit.plugin.Plugin; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +/** + * Represents various types of options that may be used to create a world. + */ +public class WorldCreator { + private final String name; + private long seed; + private World.Environment environment = World.Environment.NORMAL; + private ChunkGenerator generator = null; + private WorldType type = WorldType.NORMAL; + private boolean generateStructures = true; + private String generatorSettings = ""; + + /** + * Creates an empty WorldCreationOptions for the given world name + * + * @param name Name of the world that will be created + */ + public WorldCreator(@NotNull String name) { + if (name == null) { + throw new IllegalArgumentException("World name cannot be null"); + } + + this.name = name; + this.seed = (new Random()).nextLong(); + } + + /** + * Copies the options from the specified world + * + * @param world World to copy options from + * @return This object, for chaining + */ + @NotNull + public WorldCreator copy(@NotNull World world) { + if (world == null) { + throw new IllegalArgumentException("World cannot be null"); + } + + seed = world.getSeed(); + environment = world.getEnvironment(); + generator = world.getGenerator(); + + return this; + } + + /** + * Copies the options from the specified {@link WorldCreator} + * + * @param creator World creator to copy options from + * @return This object, for chaining + */ + @NotNull + public WorldCreator copy(@NotNull WorldCreator creator) { + if (creator == null) { + throw new IllegalArgumentException("Creator cannot be null"); + } + + seed = creator.seed(); + environment = creator.environment(); + generator = creator.generator(); + + return this; + } + + /** + * Gets the name of the world that is to be loaded or created. + * + * @return World name + */ + @NotNull + public String name() { + return name; + } + + /** + * Gets the seed that will be used to create this world + * + * @return World seed + */ + public long seed() { + return seed; + } + + /** + * Sets the seed that will be used to create this world + * + * @param seed World seed + * @return This object, for chaining + */ + @NotNull + public WorldCreator seed(long seed) { + this.seed = seed; + + return this; + } + + /** + * Gets the environment that will be used to create or load the world + * + * @return World environment + */ + @NotNull + public World.Environment environment() { + return environment; + } + + /** + * Sets the environment that will be used to create or load the world + * + * @param env World environment + * @return This object, for chaining + */ + @NotNull + public WorldCreator environment(@NotNull World.Environment env) { + this.environment = env; + + return this; + } + + /** + * Gets the type of the world that will be created or loaded + * + * @return World type + */ + @NotNull + public WorldType type() { + return type; + } + + /** + * Sets the type of the world that will be created or loaded + * + * @param type World type + * @return This object, for chaining + */ + @NotNull + public WorldCreator type(@NotNull WorldType type) { + this.type = type; + + return this; + } + + /** + * Gets the generator that will be used to create or load the world. + *

+ * This may be null, in which case the "natural" generator for this + * environment will be used. + * + * @return Chunk generator + */ + @Nullable + public ChunkGenerator generator() { + return generator; + } + + /** + * Sets the generator that will be used to create or load the world. + *

+ * This may be null, in which case the "natural" generator for this + * environment will be used. + * + * @param generator Chunk generator + * @return This object, for chaining + */ + @NotNull + public WorldCreator generator(@Nullable ChunkGenerator generator) { + this.generator = generator; + + return this; + } + + /** + * Sets the generator that will be used to create or load the world. + *

+ * This may be null, in which case the "natural" generator for this + * environment will be used. + *

+ * If the generator cannot be found for the given name, the natural + * environment generator will be used instead and a warning will be + * printed to the console. + * + * @param generator Name of the generator to use, in "plugin:id" notation + * @return This object, for chaining + */ + @NotNull + public WorldCreator generator(@Nullable String generator) { + this.generator = getGeneratorForName(name, generator, Bukkit.getConsoleSender()); + + return this; + } + + /** + * Sets the generator that will be used to create or load the world. + *

+ * This may be null, in which case the "natural" generator for this + * environment will be used. + *

+ * If the generator cannot be found for the given name, the natural + * environment generator will be used instead and a warning will be + * printed to the specified output + * + * @param generator Name of the generator to use, in "plugin:id" notation + * @param output {@link CommandSender} that will receive any error + * messages + * @return This object, for chaining + */ + @NotNull + public WorldCreator generator(@Nullable String generator, @Nullable CommandSender output) { + this.generator = getGeneratorForName(name, generator, output); + + return this; + } + + /** + * Sets the generator settings of the world that will be created or loaded + * + * @param generatorSettings The settings that should be used by the generator + * @return This object, for chaining + */ + @NotNull + public WorldCreator generatorSettings(@NotNull String generatorSettings) { + this.generatorSettings = generatorSettings; + + return this; + } + + /** + * Gets the generator settings of the world that will be created or loaded + * + * @return The settings that should be used by the generator + */ + @NotNull + public String generatorSettings() { + return generatorSettings; + } + + /** + * Sets whether or not worlds created or loaded with this creator will + * have structures. + * + * @param generate Whether to generate structures + * @return This object, for chaining + */ + @NotNull + public WorldCreator generateStructures(boolean generate) { + this.generateStructures = generate; + + return this; + } + + /** + * Gets whether or not structures will be generated in the world. + * + * @return True if structures will be generated + */ + public boolean generateStructures() { + return generateStructures; + } + + /** + * Creates a world with the specified options. + *

+ * If the world already exists, it will be loaded from disk and some + * options may be ignored. + * + * @return Newly created or loaded world + */ + @Nullable + public World createWorld() { + return Bukkit.createWorld(this); + } + + /** + * Creates a new {@link WorldCreator} for the given world name + * + * @param name Name of the world to load or create + * @return Resulting WorldCreator + */ + @NotNull + public static WorldCreator name(@NotNull String name) { + return new WorldCreator(name); + } + + /** + * Attempts to get the {@link ChunkGenerator} with the given name. + *

+ * If the generator is not found, null will be returned and a message will + * be printed to the specified {@link CommandSender} explaining why. + *

+ * The name must be in the "plugin:id" notation, or optionally just + * "plugin", where "plugin" is the safe-name of a plugin and "id" is an + * optional unique identifier for the generator you wish to request from + * the plugin. + * + * @param world Name of the world this will be used for + * @param name Name of the generator to retrieve + * @param output Where to output if errors are present + * @return Resulting generator, or null + */ + @Nullable + public static ChunkGenerator getGeneratorForName(@NotNull String world, @Nullable String name, @Nullable CommandSender output) { + ChunkGenerator result = null; + + if (world == null) { + throw new IllegalArgumentException("World name must be specified"); + } + + if (output == null) { + output = Bukkit.getConsoleSender(); + } + + if (name != null) { + String[] split = name.split(":", 2); + String id = (split.length > 1) ? split[1] : null; + Plugin plugin = Bukkit.getPluginManager().getPlugin(split[0]); + + if (plugin == null) { + output.sendMessage("Could not set generator for world '" + world + "': Plugin '" + split[0] + "' does not exist"); + } else if (!plugin.isEnabled()) { + output.sendMessage("Could not set generator for world '" + world + "': Plugin '" + plugin.getDescription().getFullName() + "' is not enabled"); + } else { + result = plugin.getDefaultWorldGenerator(world, id); + } + } + + return result; + } +} diff --git a/api/src/main/java/org/bukkit/WorldType.java b/api/src/main/java/org/bukkit/WorldType.java new file mode 100644 index 000000000..e82fe0767 --- /dev/null +++ b/api/src/main/java/org/bukkit/WorldType.java @@ -0,0 +1,54 @@ +package org.bukkit; + +import com.google.common.collect.Maps; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +import java.util.Map; + +/** + * Represents various types of worlds that may exist + */ +public enum WorldType { + NORMAL("DEFAULT"), + FLAT("FLAT"), + VERSION_1_1("DEFAULT_1_1"), + LARGE_BIOMES("LARGEBIOMES"), + AMPLIFIED("AMPLIFIED"), + CUSTOMIZED("CUSTOMIZED"), + BUFFET("BUFFET"); + + private final static Map BY_NAME = Maps.newHashMap(); + private final String name; + + private WorldType(/*@NotNull*/ String name) { + this.name = name; + } + + /** + * Gets the name of this WorldType + * + * @return Name of this type + */ + @NotNull + public String getName() { + return name; + } + + /** + * Gets a WorldType by its name + * + * @param name Name of the WorldType to get + * @return Requested WorldType, or null if not found + */ + @Nullable + public static WorldType getByName(@NotNull String name) { + return BY_NAME.get(name.toUpperCase(java.util.Locale.ENGLISH)); + } + + static { + for (WorldType type : values()) { + BY_NAME.put(type.name, type); + } + } +} diff --git a/api/src/main/java/org/bukkit/advancement/Advancement.java b/api/src/main/java/org/bukkit/advancement/Advancement.java new file mode 100644 index 000000000..7c5009974 --- /dev/null +++ b/api/src/main/java/org/bukkit/advancement/Advancement.java @@ -0,0 +1,20 @@ +package org.bukkit.advancement; + +import java.util.Collection; +import org.bukkit.Keyed; +import org.jetbrains.annotations.NotNull; + +/** + * Represents an advancement that may be awarded to a player. This class is not + * reference safe as the underlying advancement may be reloaded. + */ +public interface Advancement extends Keyed { + + /** + * Get all the criteria present in this advancement. + * + * @return a unmodifiable copy of all criteria + */ + @NotNull + Collection getCriteria(); +} diff --git a/api/src/main/java/org/bukkit/advancement/AdvancementProgress.java b/api/src/main/java/org/bukkit/advancement/AdvancementProgress.java new file mode 100644 index 000000000..00823dc9b --- /dev/null +++ b/api/src/main/java/org/bukkit/advancement/AdvancementProgress.java @@ -0,0 +1,71 @@ +package org.bukkit.advancement; + +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +import java.util.Collection; +import java.util.Date; + +/** + * The individual status of an advancement for a player. This class is not + * reference safe as the underlying advancement may be reloaded. + */ +public interface AdvancementProgress { + + /** + * The advancement this progress is concerning. + * + * @return the relevant advancement + */ + @NotNull + Advancement getAdvancement(); + + /** + * Check if all criteria for this advancement have been met. + * + * @return true if this advancement is done + */ + boolean isDone(); + + /** + * Mark the specified criteria as awarded at the current time. + * + * @param criteria the criteria to mark + * @return true if awarded, false if criteria does not exist or already + * awarded. + */ + boolean awardCriteria(@NotNull String criteria); + + /** + * Mark the specified criteria as uncompleted. + * + * @param criteria the criteria to mark + * @return true if removed, false if criteria does not exist or not awarded + */ + boolean revokeCriteria(@NotNull String criteria); + + /** + * Get the date the specified criteria was awarded. + * + * @param criteria the criteria to check + * @return date awarded or null if unawarded or criteria does not exist + */ + @Nullable + Date getDateAwarded(@NotNull String criteria); + + /** + * Get the criteria which have not been awarded. + * + * @return unmodifiable copy of criteria remaining + */ + @NotNull + Collection getRemainingCriteria(); + + /** + * Gets the criteria which have been awarded. + * + * @return unmodifiable copy of criteria awarded + */ + @NotNull + Collection getAwardedCriteria(); +} diff --git a/api/src/main/java/org/bukkit/attribute/Attributable.java b/api/src/main/java/org/bukkit/attribute/Attributable.java new file mode 100644 index 000000000..0ed96b5af --- /dev/null +++ b/api/src/main/java/org/bukkit/attribute/Attributable.java @@ -0,0 +1,20 @@ +package org.bukkit.attribute; + +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +/** + * Represents an object which may contain attributes. + */ +public interface Attributable { + + /** + * Gets the specified attribute instance from the object. This instance will + * be backed directly to the object and any changes will be visible at once. + * + * @param attribute the attribute to get + * @return the attribute instance or null if not applicable to this object + */ + @Nullable + AttributeInstance getAttribute(@NotNull Attribute attribute); +} diff --git a/api/src/main/java/org/bukkit/attribute/Attribute.java b/api/src/main/java/org/bukkit/attribute/Attribute.java new file mode 100644 index 000000000..b282dc863 --- /dev/null +++ b/api/src/main/java/org/bukkit/attribute/Attribute.java @@ -0,0 +1,56 @@ +package org.bukkit.attribute; + +/** + * Types of attributes which may be present on an {@link Attributable}. + */ +public enum Attribute { + + /** + * Maximum health of an Entity. + */ + GENERIC_MAX_HEALTH, + /** + * Range at which an Entity will follow others. + */ + GENERIC_FOLLOW_RANGE, + /** + * Resistance of an Entity to knockback. + */ + GENERIC_KNOCKBACK_RESISTANCE, + /** + * Movement speed of an Entity. + */ + GENERIC_MOVEMENT_SPEED, + /** + * Flying speed of an Entity. + */ + GENERIC_FLYING_SPEED, + /** + * Attack damage of an Entity. + */ + GENERIC_ATTACK_DAMAGE, + /** + * Attack speed of an Entity. + */ + GENERIC_ATTACK_SPEED, + /** + * Armor bonus of an Entity. + */ + GENERIC_ARMOR, + /** + * Armor durability bonus of an Entity. + */ + GENERIC_ARMOR_TOUGHNESS, + /** + * Luck bonus of an Entity. + */ + GENERIC_LUCK, + /** + * Strength with which a horse will jump. + */ + HORSE_JUMP_STRENGTH, + /** + * Chance of a zombie to spawn reinforcements. + */ + ZOMBIE_SPAWN_REINFORCEMENTS; +} diff --git a/api/src/main/java/org/bukkit/attribute/AttributeInstance.java b/api/src/main/java/org/bukkit/attribute/AttributeInstance.java new file mode 100644 index 000000000..18bafb04e --- /dev/null +++ b/api/src/main/java/org/bukkit/attribute/AttributeInstance.java @@ -0,0 +1,71 @@ +package org.bukkit.attribute; + +import org.jetbrains.annotations.NotNull; + +import java.util.Collection; + +/** + * Represents a mutable instance of an attribute and its associated modifiers + * and values. + */ +public interface AttributeInstance { + + /** + * The attribute pertaining to this instance. + * + * @return the attribute + */ + @NotNull + Attribute getAttribute(); + + /** + * Base value of this instance before modifiers are applied. + * + * @return base value + */ + double getBaseValue(); + + /** + * Set the base value of this instance. + * + * @param value new base value + */ + void setBaseValue(double value); + + /** + * Get all modifiers present on this instance. + * + * @return a copied collection of all modifiers + */ + @NotNull + Collection getModifiers(); + + /** + * Add a modifier to this instance. + * + * @param modifier to add + */ + void addModifier(@NotNull AttributeModifier modifier); + + /** + * Remove a modifier from this instance. + * + * @param modifier to remove + */ + void removeModifier(@NotNull AttributeModifier modifier); + + /** + * Get the value of this instance after all associated modifiers have been + * applied. + * + * @return the total attribute value + */ + double getValue(); + + /** + * Gets the default value of the Attribute attached to this instance. + * + * @return server default value + */ + double getDefaultValue(); +} diff --git a/api/src/main/java/org/bukkit/attribute/AttributeModifier.java b/api/src/main/java/org/bukkit/attribute/AttributeModifier.java new file mode 100644 index 000000000..f0dff145a --- /dev/null +++ b/api/src/main/java/org/bukkit/attribute/AttributeModifier.java @@ -0,0 +1,166 @@ +package org.bukkit.attribute; + +import java.util.HashMap; +import java.util.Map; +import java.util.Objects; +import java.util.UUID; +import org.apache.commons.lang.Validate; +import org.bukkit.configuration.serialization.ConfigurationSerializable; +import org.bukkit.inventory.EquipmentSlot; +import org.bukkit.util.NumberConversions; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +/** + * Concrete implementation of an attribute modifier. + */ +public class AttributeModifier implements ConfigurationSerializable { + + private final UUID uuid; + private final String name; + private final double amount; + private final Operation operation; + private final EquipmentSlot slot; + + public AttributeModifier(@NotNull String name, double amount, @NotNull Operation operation) { + this(UUID.randomUUID(), name, amount, operation); + } + + public AttributeModifier(@NotNull UUID uuid, @NotNull String name, double amount, @NotNull Operation operation) { + this(uuid, name, amount, operation, null); + } + + public AttributeModifier(@NotNull UUID uuid, @NotNull String name, double amount, @NotNull Operation operation, @Nullable EquipmentSlot slot) { + Validate.notNull(uuid, "UUID cannot be null"); + //Validate.notEmpty(name, "Name cannot be empty"); // Paper + Validate.notNull(operation, "Operation cannot be null"); + this.uuid = uuid; + this.name = name != null ? name : ""; // Paper + this.amount = amount; + this.operation = operation; + this.slot = slot; + } + + /** + * Get the unique ID for this modifier. + * + * @return unique id + */ + @NotNull + public UUID getUniqueId() { + return uuid; + } + + /** + * Get the name of this modifier. + * + * @return name + */ + @NotNull + public String getName() { + return name; + } + + /** + * Get the amount by which this modifier will apply its {@link Operation}. + * + * @return modification amount + */ + public double getAmount() { + return amount; + } + + /** + * Get the operation this modifier will apply. + * + * @return operation + */ + @NotNull + public Operation getOperation() { + return operation; + } + + /** + * Get the {@link EquipmentSlot} this AttributeModifier is active on, + * or null if this modifier is applicable for any slot. + * + * @return the slot + */ + @Nullable + public EquipmentSlot getSlot() { + return slot; + } + + @NotNull + @Override + public Map serialize() { + Map data = new HashMap(); + data.put("uuid", uuid.toString()); + data.put("name", name); + data.put("operation", operation.ordinal()); + data.put("amount", amount); + if (slot != null) { + data.put("slot", slot.name()); + } + return data; + } + + @Override + public boolean equals(Object other) { + if (!(other instanceof AttributeModifier)) { + return false; + } + AttributeModifier mod = (AttributeModifier) other; + boolean slots = (this.slot != null ? (this.slot == mod.slot) : mod.slot == null); + return this.uuid.equals(mod.uuid) && this.name.equals(mod.name) && this.amount == mod.amount && this.operation == mod.operation && slots; + } + + @Override + public int hashCode() { + int hash = 5; + hash = 17 * hash + Objects.hashCode(this.uuid); + hash = 17 * hash + Objects.hashCode(this.name); + hash = 17 * hash + (int) (Double.doubleToLongBits(this.amount) ^ (Double.doubleToLongBits(this.amount) >>> 32)); + hash = 17 * hash + Objects.hashCode(this.operation); + hash = 17 * hash + Objects.hashCode(this.slot); + return hash; + } + + @Override + public String toString() { + return "AttributeModifier{" + + "uuid=" + this.uuid.toString() + + ", name=" + this.name + + ", operation=" + this.operation.name() + + ", amount=" + this.amount + + ", slot=" + (this.slot != null ? this.slot.name() : "") + + "}"; + } + + @NotNull + public static AttributeModifier deserialize(@NotNull Map args) { + if (args.containsKey("slot")) { + return new AttributeModifier(UUID.fromString((String) args.get("uuid")), (String) args.get("name"), NumberConversions.toDouble(args.get("amount")), Operation.values()[NumberConversions.toInt(args.get("operation"))], EquipmentSlot.valueOf((args.get("slot").toString().toUpperCase()))); + } + return new AttributeModifier(UUID.fromString((String) args.get("uuid")), (String) args.get("name"), NumberConversions.toDouble(args.get("amount")), Operation.values()[NumberConversions.toInt(args.get("operation"))]); + } + + /** + * Enumerable operation to be applied. + */ + public enum Operation { + + /** + * Adds (or subtracts) the specified amount to the base value. + */ + ADD_NUMBER, + /** + * Adds this scalar of amount to the base value. + */ + ADD_SCALAR, + /** + * Multiply amount by this value, after adding 1 to it. + */ + MULTIPLY_SCALAR_1; + } +} diff --git a/api/src/main/java/org/bukkit/block/Banner.java b/api/src/main/java/org/bukkit/block/Banner.java new file mode 100644 index 000000000..befa9da6f --- /dev/null +++ b/api/src/main/java/org/bukkit/block/Banner.java @@ -0,0 +1,87 @@ +package org.bukkit.block; + +import org.bukkit.DyeColor; +import org.bukkit.block.banner.Pattern; +import org.jetbrains.annotations.NotNull; + +import java.util.List; + +/** + * Represents a captured state of a banner. + */ +public interface Banner extends BlockState { + + /** + * Returns the base color for this banner + * + * @return the base color + */ + @NotNull + DyeColor getBaseColor(); + + /** + * Sets the base color for this banner. + * Only valid for shield pseudo banners, otherwise base depends on block + * type + * + * @param color the base color + */ + void setBaseColor(@NotNull DyeColor color); + + /** + * Returns a list of patterns on this banner + * + * @return the patterns + */ + @NotNull + List getPatterns(); + + /** + * Sets the patterns used on this banner + * + * @param patterns the new list of patterns + */ + void setPatterns(@NotNull List patterns); + + /** + * Adds a new pattern on top of the existing + * patterns + * + * @param pattern the new pattern to add + */ + void addPattern(@NotNull Pattern pattern); + + /** + * Returns the pattern at the specified index + * + * @param i the index + * @return the pattern + */ + @NotNull + Pattern getPattern(int i); + + /** + * Removes the pattern at the specified index + * + * @param i the index + * @return the removed pattern + */ + @NotNull + Pattern removePattern(int i); + + /** + * Sets the pattern at the specified index + * + * @param i the index + * @param pattern the new pattern + */ + void setPattern(int i, @NotNull Pattern pattern); + + /** + * Returns the number of patterns on this + * banner + * + * @return the number of patterns + */ + int numberOfPatterns(); +} diff --git a/api/src/main/java/org/bukkit/block/Beacon.java b/api/src/main/java/org/bukkit/block/Beacon.java new file mode 100644 index 000000000..e5332c0a7 --- /dev/null +++ b/api/src/main/java/org/bukkit/block/Beacon.java @@ -0,0 +1,76 @@ +package org.bukkit.block; + +import java.util.Collection; +import org.bukkit.Nameable; +import org.bukkit.entity.LivingEntity; +import org.bukkit.inventory.BeaconInventory; +import org.bukkit.potion.PotionEffect; +import org.bukkit.potion.PotionEffectType; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +/** + * Represents a captured state of a beacon. + */ +public interface Beacon extends Container, Nameable { + + @NotNull + @Override + BeaconInventory getInventory(); + + @NotNull + @Override + BeaconInventory getSnapshotInventory(); + + /** + * Returns the list of players within the beacon's range of effect. + *

+ * This will return an empty list if the block represented by this state is + * no longer a beacon. + * + * @return the players in range + * @throws IllegalStateException if this block state is not placed + */ + @NotNull + Collection getEntitiesInRange(); + + /** + * Returns the tier of the beacon pyramid (0-4). The tier refers to the + * beacon's power level, based on how many layers of blocks are in the + * pyramid. Tier 1 refers to a beacon with one layer of 9 blocks under it. + * + * @return the beacon tier + */ + int getTier(); + + /** + * Returns the primary effect set on the beacon + * + * @return the primary effect or null if not set + */ + @Nullable + PotionEffect getPrimaryEffect(); + + /** + * Set the primary effect on this beacon, or null to clear. + * + * @param effect new primary effect + */ + void setPrimaryEffect(@Nullable PotionEffectType effect); + + /** + * Returns the secondary effect set on the beacon. + * + * @return the secondary effect or null if no secondary effect + */ + @Nullable + PotionEffect getSecondaryEffect(); + + /** + * Set the secondary effect on this beacon, or null to clear. Note that tier + * must be >= 4 for this effect to be active. + * + * @param effect desired secondary effect + */ + void setSecondaryEffect(@Nullable PotionEffectType effect); +} diff --git a/api/src/main/java/org/bukkit/block/Bed.java b/api/src/main/java/org/bukkit/block/Bed.java new file mode 100644 index 000000000..a2393852e --- /dev/null +++ b/api/src/main/java/org/bukkit/block/Bed.java @@ -0,0 +1,10 @@ +package org.bukkit.block; + +import org.bukkit.material.Colorable; + +/** + * Represents a captured state of a bed. + * @deprecated does not provide useful information beyond the material itself + */ +@Deprecated +public interface Bed extends BlockState, Colorable { } diff --git a/api/src/main/java/org/bukkit/block/Biome.java b/api/src/main/java/org/bukkit/block/Biome.java new file mode 100644 index 000000000..a3b310b73 --- /dev/null +++ b/api/src/main/java/org/bukkit/block/Biome.java @@ -0,0 +1,80 @@ +package org.bukkit.block; + +/** + * Holds all accepted Biomes in the default server + */ +public enum Biome { + OCEAN, + PLAINS, + DESERT, + MOUNTAINS, + FOREST, + TAIGA, + SWAMP, + RIVER, + NETHER, + THE_END, + FROZEN_OCEAN, + FROZEN_RIVER, + SNOWY_TUNDRA, + SNOWY_MOUNTAINS, + MUSHROOM_FIELDS, + MUSHROOM_FIELD_SHORE, + BEACH, + DESERT_HILLS, + WOODED_HILLS, + TAIGA_HILLS, + MOUNTAIN_EDGE, + JUNGLE, + JUNGLE_HILLS, + JUNGLE_EDGE, + DEEP_OCEAN, + STONE_SHORE, + SNOWY_BEACH, + BIRCH_FOREST, + BIRCH_FOREST_HILLS, + DARK_FOREST, + SNOWY_TAIGA, + SNOWY_TAIGA_HILLS, + GIANT_TREE_TAIGA, + GIANT_TREE_TAIGA_HILLS, + WOODED_MOUNTAINS, + SAVANNA, + SAVANNA_PLATEAU, + BADLANDS, + WOODED_BADLANDS_PLATEAU, + BADLANDS_PLATEAU, + SMALL_END_ISLANDS, + END_MIDLANDS, + END_HIGHLANDS, + END_BARRENS, + WARM_OCEAN, + LUKEWARM_OCEAN, + COLD_OCEAN, + DEEP_WARM_OCEAN, + DEEP_LUKEWARM_OCEAN, + DEEP_COLD_OCEAN, + DEEP_FROZEN_OCEAN, + THE_VOID, + SUNFLOWER_PLAINS, + DESERT_LAKES, + GRAVELLY_MOUNTAINS, + FLOWER_FOREST, + TAIGA_MOUNTAINS, + SWAMP_HILLS, + ICE_SPIKES, + MODIFIED_JUNGLE, + MODIFIED_JUNGLE_EDGE, + TALL_BIRCH_FOREST, + TALL_BIRCH_HILLS, + DARK_FOREST_HILLS, + SNOWY_TAIGA_MOUNTAINS, + GIANT_SPRUCE_TAIGA, + GIANT_SPRUCE_TAIGA_HILLS, + MODIFIED_GRAVELLY_MOUNTAINS, + SHATTERED_SAVANNA, + SHATTERED_SAVANNA_PLATEAU, + ERODED_BADLANDS, + MODIFIED_WOODED_BADLANDS_PLATEAU, + MODIFIED_BADLANDS_PLATEAU +} diff --git a/api/src/main/java/org/bukkit/block/Block.java b/api/src/main/java/org/bukkit/block/Block.java new file mode 100644 index 000000000..c20f903a9 --- /dev/null +++ b/api/src/main/java/org/bukkit/block/Block.java @@ -0,0 +1,535 @@ +package org.bukkit.block; + +import java.util.Collection; + +import org.bukkit.Chunk; +import org.bukkit.FluidCollisionMode; +import org.bukkit.Location; +import org.bukkit.Material; +import org.bukkit.World; +import org.bukkit.block.data.Bisected; +import org.bukkit.block.data.BlockData; +import org.bukkit.inventory.ItemStack; +import org.bukkit.metadata.Metadatable; +import org.bukkit.util.BoundingBox; +import org.bukkit.util.RayTraceResult; +import org.bukkit.util.Vector; +import org.jetbrains.annotations.Contract; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +/** + * Represents a block. This is a live object, and only one Block may exist for + * any given location in a world. The state of the block may change + * concurrently to your own handling of it; use block.getState() to get a + * snapshot state of a block which will not be modified. + * + *
+ * Note that parts of this class which require access to the world at large + * (i.e. lighting and power) may not be able to be safely accessed during world + * generation when used in cases like BlockPhysicsEvent!!!! + */ +public interface Block extends Metadatable { + + /** + * Gets the metadata for this block + * + * @return block specific metadata + * @deprecated Magic value + */ + @Deprecated + byte getData(); + + /** + * Gets the complete block data for this block + * + * @return block specific data + */ + @NotNull + BlockData getBlockData(); + + /** + * Gets the block at the given offsets + * + * @param modX X-coordinate offset + * @param modY Y-coordinate offset + * @param modZ Z-coordinate offset + * @return Block at the given offsets + */ + @NotNull + Block getRelative(int modX, int modY, int modZ); + + /** + * Gets the block at the given face + *

+ * This method is equal to getRelative(face, 1) + * + * @param face Face of this block to return + * @return Block at the given face + * @see #getRelative(BlockFace, int) + */ + @NotNull + Block getRelative(@NotNull BlockFace face); + + /** + * Gets the block at the given distance of the given face + *

+ * For example, the following method places water at 100,102,100; two + * blocks above 100,100,100. + * + *

+     * Block block = world.getBlockAt(100, 100, 100);
+     * Block shower = block.getRelative(BlockFace.UP, 2);
+     * shower.setType(Material.WATER);
+     * 
+ * + * @param face Face of this block to return + * @param distance Distance to get the block at + * @return Block at the given face + */ + @NotNull + Block getRelative(@NotNull BlockFace face, int distance); + + /** + * Gets the type of this block + * + * @return block type + */ + @NotNull + Material getType(); + + /** + * Gets the light level between 0-15 + * + * @return light level + */ + byte getLightLevel(); + + /** + * Get the amount of light at this block from the sky. + *

+ * Any light given from other sources (such as blocks like torches) will + * be ignored. + * + * @return Sky light level + */ + byte getLightFromSky(); + + /** + * Get the amount of light at this block from nearby blocks. + *

+ * Any light given from other sources (such as the sun) will be ignored. + * + * @return Block light level + */ + byte getLightFromBlocks(); + + /** + * Gets the world which contains this Block + * + * @return World containing this block + */ + @NotNull + World getWorld(); + + /** + * Gets the x-coordinate of this block + * + * @return x-coordinate + */ + int getX(); + + /** + * Gets the y-coordinate of this block + * + * @return y-coordinate + */ + int getY(); + + /** + * Gets the z-coordinate of this block + * + * @return z-coordinate + */ + int getZ(); + + // Paper Start + /** + * Returns this block's coordinates packed into a long value. + * Computed via: {@code Block.getBlockKey(this.getX(), this.getY(), this.getZ())} + * @see Block#getBlockKey(int, int, int) + * @return This block's x, y, and z coordinates packed into a long value + */ + public default long getBlockKey() { + return Block.getBlockKey(this.getX(), this.getY(), this.getZ()); + } + + /** + * Returns the specified block coordinates packed into a long value + *

+ * The return value can be computed as follows: + *
+ * {@code long value = ((long)x & 0x7FFFFFF) | (((long)z & 0x7FFFFFF) << 27) | ((long)y << 54);} + *
+ *

+ * + *

+ * And may be unpacked as follows: + *
+ * {@code int x = (int) ((packed << 37) >> 37);} + *
+ *
+ * {@code int y = (int) (packed >>> 54);} + *
+ *
+ * {@code int z = (int) ((packed << 10) >> 37);} + *
+ *

+ * + * @return This block's x, y, and z coordinates packed into a long value + */ + public static long getBlockKey(int x, int y, int z) { + return ((long)x & 0x7FFFFFF) | (((long)z & 0x7FFFFFF) << 27) | ((long)y << 54); + } + + /** + * Returns the x component from the packed value. + * @param packed The packed value, as computed by {@link Block#getBlockKey(int, int, int)} + * @see Block#getBlockKey(int, int, int) + * @return The x component from the packed value. + */ + public static int getBlockKeyX(long packed) { + return (int) ((packed << 37) >> 37); + } + + /** + * Returns the y component from the packed value. + * @param packed The packed value, as computed by {@link Block#getBlockKey(int, int, int)} + * @see Block#getBlockKey(int, int, int) + * @return The y component from the packed value. + */ + public static int getBlockKeyY(long packed) { + return (int) (packed >>> 54); + } + + /** + * Returns the z component from the packed value. + * @param packed The packed value, as computed by {@link Block#getBlockKey(int, int, int)} + * @see Block#getBlockKey(int, int, int) + * @return The z component from the packed value. + */ + public static int getBlockKeyZ(long packed) { + return (int) ((packed << 10) >> 37); + } + // Paper End + + /** + * Gets the Location of the block + * + * @return Location of block + */ + @NotNull + Location getLocation(); + + /** + * Stores the location of the block in the provided Location object. + *

+ * If the provided Location is null this method does nothing and returns + * null. + * + * @param loc the location to copy into + * @return The Location object provided or null + */ + @Contract("null -> null; !null -> !null") + @Nullable + Location getLocation(@Nullable Location loc); + + /** + * Gets the chunk which contains this block + * + * @return Containing Chunk + */ + @NotNull + Chunk getChunk(); + + /** + * Sets the complete data for this block + * + * @param data new block specific data + */ + void setBlockData(@NotNull BlockData data); + + /** + * Sets the complete data for this block + * + *
+ * Note that applyPhysics = false is not in general safe. It should only be + * used when you need to avoid triggering a physics update of neighboring + * blocks, for example when creating a {@link Bisected} block. If you are + * using a custom populator, then this parameter may also be required to + * prevent triggering infinite chunk loads on border blocks. This method + * should NOT be used to "hack" physics by placing blocks in impossible + * locations. Such blocks are liable to be removed on various events such as + * world upgrades. Furthermore setting large amounts of such blocks in close + * proximity may overload the server physics engine if an update is + * triggered at a later point. If this occurs, the resulting behavior is + * undefined. + * + * @param data new block specific data + * @param applyPhysics false to cancel physics from the changed block + */ + void setBlockData(@NotNull BlockData data, boolean applyPhysics); + + /** + * Sets the type of this block + * + * @param type Material to change this block to + */ + void setType(@NotNull Material type); + + /** + * Sets the type of this block + * + *
+ * Note that applyPhysics = false is not in general safe. It should only be + * used when you need to avoid triggering a physics update of neighboring + * blocks, for example when creating a {@link Bisected} block. If you are + * using a custom populator, then this parameter may also be required to + * prevent triggering infinite chunk loads on border blocks. This method + * should NOT be used to "hack" physics by placing blocks in impossible + * locations. Such blocks are liable to be removed on various events such as + * world upgrades. Furthermore setting large amounts of such blocks in close + * proximity may overload the server physics engine if an update is + * triggered at a later point. If this occurs, the resulting behavior is + * undefined. + * + * @param type Material to change this block to + * @param applyPhysics False to cancel physics on the changed block. + */ + void setType(@NotNull Material type, boolean applyPhysics); + + /** + * Gets the face relation of this block compared to the given block. + *

+ * For example: + *

{@code
+     * Block current = world.getBlockAt(100, 100, 100);
+     * Block target = world.getBlockAt(100, 101, 100);
+     *
+     * current.getFace(target) == BlockFace.Up;
+     * }
+ *
+ * If the given block is not connected to this block, null may be returned + * + * @param block Block to compare against this block + * @return BlockFace of this block which has the requested block, or null + */ + @Nullable + BlockFace getFace(@NotNull Block block); + + /** + * Captures the current state of this block. You may then cast that state + * into any accepted type, such as Furnace or Sign. + *

+ * The returned object will never be updated, and you are not guaranteed + * that (for example) a sign is still a sign after you capture its state. + * + * @return BlockState with the current state of this block. + */ + @NotNull + BlockState getState(); + + // Paper start + /** + * @see #getState() optionally disables use of snapshot, to operate on real block data + * @param useSnapshot if this block is a TE, should we create a fully copy of the TileEntity + * @return BlockState with the current state of this block + */ + @NotNull + BlockState getState(boolean useSnapshot); + // Paper end + + /** + * Returns the biome that this block resides in + * + * @return Biome type containing this block + */ + @NotNull + Biome getBiome(); + + /** + * Sets the biome that this block resides in + * + * @param bio new Biome type for this block + */ + void setBiome(@NotNull Biome bio); + + /** + * Returns true if the block is being powered by Redstone. + * + * @return True if the block is powered. + */ + boolean isBlockPowered(); + + /** + * Returns true if the block is being indirectly powered by Redstone. + * + * @return True if the block is indirectly powered. + */ + boolean isBlockIndirectlyPowered(); + + /** + * Returns true if the block face is being powered by Redstone. + * + * @param face The block face + * @return True if the block face is powered. + */ + boolean isBlockFacePowered(@NotNull BlockFace face); + + /** + * Returns true if the block face is being indirectly powered by Redstone. + * + * @param face The block face + * @return True if the block face is indirectly powered. + */ + boolean isBlockFaceIndirectlyPowered(@NotNull BlockFace face); + + /** + * Returns the redstone power being provided to this block face + * + * @param face the face of the block to query or BlockFace.SELF for the + * block itself + * @return The power level. + */ + int getBlockPower(@NotNull BlockFace face); + + /** + * Returns the redstone power being provided to this block + * + * @return The power level. + */ + int getBlockPower(); + + /** + * Checks if this block is empty. + *

+ * A block is considered empty when {@link #getType()} returns {@link + * Material#AIR}. + * + * @return true if this block is empty + */ + boolean isEmpty(); + + /** + * Checks if this block is liquid. + *

+ * A block is considered liquid when {@link #getType()} returns {@link + * Material#WATER} or {@link Material#LAVA}. + * + * @return true if this block is liquid + */ + boolean isLiquid(); + + /** + * Gets the temperature of this block. + *

+ * If the raw biome temperature without adjusting for height effects is + * required then please use {@link World#getTemperature(int, int)}. + * + * @return Temperature of this block + */ + double getTemperature(); + + /** + * Gets the humidity of the biome of this block + * + * @return Humidity of this block + */ + double getHumidity(); + + /** + * Returns the reaction of the block when moved by a piston + * + * @return reaction + */ + @NotNull + PistonMoveReaction getPistonMoveReaction(); + + /** + * Breaks the block and spawns items as if a player had digged it + * + * @return true if the block was destroyed + */ + boolean breakNaturally(); + + /** + * Breaks the block and spawns items as if a player had digged it with a + * specific tool + * + * @param tool The tool or item in hand used for digging + * @return true if the block was destroyed + */ + boolean breakNaturally(@NotNull ItemStack tool); + + /** + * Returns a list of items which would drop by destroying this block + * + * @return a list of dropped items for this type of block + */ + @NotNull + Collection getDrops(); + + /** + * Returns a list of items which would drop by destroying this block with + * a specific tool + * + * @param tool The tool or item in hand used for digging + * @return a list of dropped items for this type of block + */ + @NotNull + Collection getDrops(@NotNull ItemStack tool); + + /** + * Checks if this block is passable. + *

+ * A block is passable if it has no colliding parts that would prevent + * players from moving through it. + *

+ * Examples: Tall grass, flowers, signs, etc. are passable, but open doors, + * fence gates, trap doors, etc. are not because they still have parts that + * can be collided with. + * + * @return true if passable + */ + boolean isPassable(); + + /** + * Performs a ray trace that checks for collision with this specific block + * in its current state using its precise collision shape. + * + * @param start the start location + * @param direction the ray direction + * @param maxDistance the maximum distance + * @param fluidCollisionMode the fluid collision mode + * @return the ray trace hit result, or null if there is no hit + */ + @Nullable + RayTraceResult rayTrace(@NotNull Location start, @NotNull Vector direction, double maxDistance, @NotNull FluidCollisionMode fluidCollisionMode); + + /** + * Gets the approximate bounding box for this block. + *

+ * This isn't exact as some blocks {@link org.bukkit.block.data.type.Stairs} + * contain many bounding boxes to establish their complete form. + * + * Also, the box may not be exactly the same as the collision shape (such as + * cactus, which is 16/16 of a block with 15/16 collisional bounds). + * + * This method will return an empty bounding box if the geometric shape of + * the block is empty (such as air blocks). + * + * @return the approximate bounding box of the block + */ + @NotNull + BoundingBox getBoundingBox(); +} diff --git a/api/src/main/java/org/bukkit/block/BlockFace.java b/api/src/main/java/org/bukkit/block/BlockFace.java new file mode 100644 index 000000000..2fed469b6 --- /dev/null +++ b/api/src/main/java/org/bukkit/block/BlockFace.java @@ -0,0 +1,150 @@ +package org.bukkit.block; + +import org.bukkit.util.Vector; +import org.jetbrains.annotations.NotNull; + +/** + * Represents the face of a block + */ +public enum BlockFace { + NORTH(0, 0, -1), + EAST(1, 0, 0), + SOUTH(0, 0, 1), + WEST(-1, 0, 0), + UP(0, 1, 0), + DOWN(0, -1, 0), + NORTH_EAST(NORTH, EAST), + NORTH_WEST(NORTH, WEST), + SOUTH_EAST(SOUTH, EAST), + SOUTH_WEST(SOUTH, WEST), + WEST_NORTH_WEST(WEST, NORTH_WEST), + NORTH_NORTH_WEST(NORTH, NORTH_WEST), + NORTH_NORTH_EAST(NORTH, NORTH_EAST), + EAST_NORTH_EAST(EAST, NORTH_EAST), + EAST_SOUTH_EAST(EAST, SOUTH_EAST), + SOUTH_SOUTH_EAST(SOUTH, SOUTH_EAST), + SOUTH_SOUTH_WEST(SOUTH, SOUTH_WEST), + WEST_SOUTH_WEST(WEST, SOUTH_WEST), + SELF(0, 0, 0); + + private final int modX; + private final int modY; + private final int modZ; + + private BlockFace(final int modX, final int modY, final int modZ) { + this.modX = modX; + this.modY = modY; + this.modZ = modZ; + } + + private BlockFace(final BlockFace face1, final BlockFace face2) { + this.modX = face1.getModX() + face2.getModX(); + this.modY = face1.getModY() + face2.getModY(); + this.modZ = face1.getModZ() + face2.getModZ(); + } + + /** + * Get the amount of X-coordinates to modify to get the represented block + * + * @return Amount of X-coordinates to modify + */ + public int getModX() { + return modX; + } + + /** + * Get the amount of Y-coordinates to modify to get the represented block + * + * @return Amount of Y-coordinates to modify + */ + public int getModY() { + return modY; + } + + /** + * Get the amount of Z-coordinates to modify to get the represented block + * + * @return Amount of Z-coordinates to modify + */ + public int getModZ() { + return modZ; + } + + /** + * Gets the normal vector corresponding to this block face. + * + * @return the normal vector + */ + @NotNull + public Vector getDirection() { + Vector direction = new Vector(modX, modY, modZ); + if (modX != 0 || modY != 0 || modZ != 0) { + direction.normalize(); + } + return direction; + } + + @NotNull + public BlockFace getOppositeFace() { + switch (this) { + case NORTH: + return BlockFace.SOUTH; + + case SOUTH: + return BlockFace.NORTH; + + case EAST: + return BlockFace.WEST; + + case WEST: + return BlockFace.EAST; + + case UP: + return BlockFace.DOWN; + + case DOWN: + return BlockFace.UP; + + case NORTH_EAST: + return BlockFace.SOUTH_WEST; + + case NORTH_WEST: + return BlockFace.SOUTH_EAST; + + case SOUTH_EAST: + return BlockFace.NORTH_WEST; + + case SOUTH_WEST: + return BlockFace.NORTH_EAST; + + case WEST_NORTH_WEST: + return BlockFace.EAST_SOUTH_EAST; + + case NORTH_NORTH_WEST: + return BlockFace.SOUTH_SOUTH_EAST; + + case NORTH_NORTH_EAST: + return BlockFace.SOUTH_SOUTH_WEST; + + case EAST_NORTH_EAST: + return BlockFace.WEST_SOUTH_WEST; + + case EAST_SOUTH_EAST: + return BlockFace.WEST_NORTH_WEST; + + case SOUTH_SOUTH_EAST: + return BlockFace.NORTH_NORTH_WEST; + + case SOUTH_SOUTH_WEST: + return BlockFace.NORTH_NORTH_EAST; + + case WEST_SOUTH_WEST: + return BlockFace.EAST_NORTH_EAST; + + case SELF: + return BlockFace.SELF; + } + + return BlockFace.SELF; + } +} diff --git a/api/src/main/java/org/bukkit/block/BlockState.java b/api/src/main/java/org/bukkit/block/BlockState.java new file mode 100644 index 000000000..631cbf2be --- /dev/null +++ b/api/src/main/java/org/bukkit/block/BlockState.java @@ -0,0 +1,224 @@ +package org.bukkit.block; + +import org.bukkit.Chunk; +import org.bukkit.Location; +import org.bukkit.Material; +import org.bukkit.World; +import org.bukkit.block.data.BlockData; +import org.bukkit.material.MaterialData; +import org.bukkit.metadata.Metadatable; +import org.jetbrains.annotations.Contract; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +/** + * Represents a captured state of a block, which will not change + * automatically. + *

+ * Unlike Block, which only one object can exist per coordinate, BlockState + * can exist multiple times for any given Block. Note that another plugin may + * change the state of the block and you will not know, or they may change the + * block to another type entirely, causing your BlockState to become invalid. + */ +public interface BlockState extends Metadatable { + + /** + * Gets the block represented by this block state. + * + * @return the block represented by this block state + * @throws IllegalStateException if this block state is not placed + */ + @NotNull + Block getBlock(); + + /** + * Gets the metadata for this block state. + * + * @return block specific metadata + */ + @NotNull + MaterialData getData(); + + /** + * Gets the data for this block state. + * + * @return block specific data + */ + @NotNull + BlockData getBlockData(); + + /** + * Gets the type of this block state. + * + * @return block type + */ + @NotNull + Material getType(); + + /** + * Gets the current light level of the block represented by this block state. + * + * @return the light level between 0-15 + * @throws IllegalStateException if this block state is not placed + */ + byte getLightLevel(); + + /** + * Gets the world which contains the block represented by this block state. + * + * @return the world containing the block represented by this block state + * @throws IllegalStateException if this block state is not placed + */ + @NotNull + World getWorld(); + + /** + * Gets the x-coordinate of this block state. + * + * @return x-coordinate + */ + int getX(); + + /** + * Gets the y-coordinate of this block state. + * + * @return y-coordinate + */ + int getY(); + + /** + * Gets the z-coordinate of this block state. + * + * @return z-coordinate + */ + int getZ(); + + /** + * Gets the location of this block state. + *

+ * If this block state is not placed the location's world will be null! + * + * @return the location + */ + @NotNull + Location getLocation(); + + /** + * Stores the location of this block state in the provided Location object. + *

+ * If the provided Location is null this method does nothing and returns + * null. + *

+ * If this block state is not placed the location's world will be null! + * + * @param loc the location to copy into + * @return The Location object provided or null + */ + @Contract("null -> null; !null -> !null") + @Nullable + Location getLocation(@Nullable Location loc); + + /** + * Gets the chunk which contains the block represented by this block state. + * + * @return the containing Chunk + * @throws IllegalStateException if this block state is not placed + */ + @NotNull + Chunk getChunk(); + + /** + * Sets the metadata for this block state. + * + * @param data New block specific metadata + */ + void setData(@NotNull MaterialData data); + + /** + * Sets the data for this block state. + * + * @param data New block specific data + */ + void setBlockData(@NotNull BlockData data); + + /** + * Sets the type of this block state. + * + * @param type Material to change this block state to + */ + void setType(@NotNull Material type); + + /** + * Attempts to update the block represented by this state, setting it to + * the new values as defined by this state. + *

+ * This has the same effect as calling update(false). That is to say, + * this will not modify the state of a block if it is no longer the same + * type as it was when this state was taken. It will return false in this + * eventuality. + * + * @return true if the update was successful, otherwise false + * @see #update(boolean) + */ + boolean update(); + + /** + * Attempts to update the block represented by this state, setting it to + * the new values as defined by this state. + *

+ * This has the same effect as calling update(force, true). That is to + * say, this will trigger a physics update to surrounding blocks. + * + * @param force true to forcefully set the state + * @return true if the update was successful, otherwise false + */ + boolean update(boolean force); + + /** + * Attempts to update the block represented by this state, setting it to + * the new values as defined by this state. + *

+ * If this state is not placed, this will have no effect and return true. + *

+ * Unless force is true, this will not modify the state of a block if it + * is no longer the same type as it was when this state was taken. It will + * return false in this eventuality. + *

+ * If force is true, it will set the type of the block to match the new + * state, set the state data and then return true. + *

+ * If applyPhysics is true, it will trigger a physics update on + * surrounding blocks which could cause them to update or disappear. + * + * @param force true to forcefully set the state + * @param applyPhysics false to cancel updating physics on surrounding + * blocks + * @return true if the update was successful, otherwise false + */ + boolean update(boolean force, boolean applyPhysics); + + /** + * @return The data as a raw byte. + * @deprecated Magic value + */ + @Deprecated + public byte getRawData(); + + /** + * @param data The new data value for the block. + * @deprecated Magic value + */ + @Deprecated + public void setRawData(byte data); + + /** + * Returns whether this state is placed in the world. + *

+ * Some methods will not work if the block state isn't + * placed in the world. + * + * @return whether the state is placed in the world + * or 'virtual' (e.g. on an itemstack) + */ + boolean isPlaced(); +} diff --git a/api/src/main/java/org/bukkit/block/BrewingStand.java b/api/src/main/java/org/bukkit/block/BrewingStand.java new file mode 100644 index 000000000..7611a126c --- /dev/null +++ b/api/src/main/java/org/bukkit/block/BrewingStand.java @@ -0,0 +1,47 @@ +package org.bukkit.block; + +import org.bukkit.Nameable; +import org.bukkit.inventory.BrewerInventory; +import org.jetbrains.annotations.NotNull; + +/** + * Represents a captured state of a brewing stand. + */ +public interface BrewingStand extends Container, Nameable { + + /** + * How much time is left in the brewing cycle. + * + * @return Brew Time + */ + int getBrewingTime(); + + /** + * Set the time left before brewing completes. + * + * @param brewTime Brewing time + */ + void setBrewingTime(int brewTime); + + /** + * Get the level of current fuel for brewing. + * + * @return The fuel level + */ + int getFuelLevel(); + + /** + * Set the level of current fuel for brewing. + * + * @param level fuel level + */ + void setFuelLevel(int level); + + @NotNull + @Override + BrewerInventory getInventory(); + + @NotNull + @Override + BrewerInventory getSnapshotInventory(); +} diff --git a/api/src/main/java/org/bukkit/block/Chest.java b/api/src/main/java/org/bukkit/block/Chest.java new file mode 100644 index 000000000..eb475ec84 --- /dev/null +++ b/api/src/main/java/org/bukkit/block/Chest.java @@ -0,0 +1,30 @@ +package org.bukkit.block; + +import com.destroystokyo.paper.loottable.LootableBlockInventory; +import org.bukkit.Nameable; +import org.bukkit.inventory.Inventory; +import org.bukkit.loot.Lootable; +import org.jetbrains.annotations.NotNull; + +/** + * Represents a captured state of a chest. + */ +public interface Chest extends Container, Nameable, LootableBlockInventory { // Paper + + /** + * Gets the inventory of the chest block represented by this block state. + *

+ * If the chest is a double chest, it returns just the portion of the + * inventory linked to the half of the chest corresponding to this block state. + *

+ * If the block was changed to a different type in the meantime, the + * returned inventory might no longer be valid. + *

+ * If this block state is not placed this will return the captured + * inventory snapshot instead. + * + * @return the inventory + */ + @NotNull + Inventory getBlockInventory(); +} diff --git a/api/src/main/java/org/bukkit/block/CommandBlock.java b/api/src/main/java/org/bukkit/block/CommandBlock.java new file mode 100644 index 000000000..b7ee5bc94 --- /dev/null +++ b/api/src/main/java/org/bukkit/block/CommandBlock.java @@ -0,0 +1,48 @@ +package org.bukkit.block; + +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +/** + * Represents a captured state of a command block. + */ +public interface CommandBlock extends BlockState { + + /** + * Gets the command that this CommandBlock will run when powered. + * This will never return null. If the CommandBlock does not have a + * command, an empty String will be returned instead. + * + * @return Command that this CommandBlock will run when powered. + */ + @NotNull + public String getCommand(); + + /** + * Sets the command that this CommandBlock will run when powered. + * Setting the command to null is the same as setting it to an empty + * String. + * + * @param command Command that this CommandBlock will run when powered. + */ + public void setCommand(@Nullable String command); + + /** + * Gets the name of this CommandBlock. The name is used with commands + * that this CommandBlock executes. This name will never be null, and + * by default is "@". + * + * @return Name of this CommandBlock. + */ + @NotNull + public String getName(); + + /** + * Sets the name of this CommandBlock. The name is used with commands + * that this CommandBlock executes. Setting the name to null is the + * same as setting it to "@". + * + * @param name New name for this CommandBlock. + */ + public void setName(@Nullable String name); +} diff --git a/api/src/main/java/org/bukkit/block/Comparator.java b/api/src/main/java/org/bukkit/block/Comparator.java new file mode 100644 index 000000000..c9acc916d --- /dev/null +++ b/api/src/main/java/org/bukkit/block/Comparator.java @@ -0,0 +1,6 @@ +package org.bukkit.block; + +/** + * Represents a captured state of an on / off comparator. + */ +public interface Comparator extends BlockState { } diff --git a/api/src/main/java/org/bukkit/block/Conduit.java b/api/src/main/java/org/bukkit/block/Conduit.java new file mode 100644 index 000000000..d889f41b7 --- /dev/null +++ b/api/src/main/java/org/bukkit/block/Conduit.java @@ -0,0 +1,6 @@ +package org.bukkit.block; + +/** + * Represents a captured state of a conduit. + */ +public interface Conduit extends BlockState { } diff --git a/api/src/main/java/org/bukkit/block/Container.java b/api/src/main/java/org/bukkit/block/Container.java new file mode 100644 index 000000000..96888ba89 --- /dev/null +++ b/api/src/main/java/org/bukkit/block/Container.java @@ -0,0 +1,39 @@ +package org.bukkit.block; + +import org.bukkit.inventory.Inventory; +import org.bukkit.inventory.InventoryHolder; +import org.jetbrains.annotations.NotNull; + +/** + * Represents a captured state of a container block. + */ +public interface Container extends BlockState, InventoryHolder, Lockable { + + /** + * Gets the inventory of the block represented by this block state. + *

+ * If the block was changed to a different type in the meantime, the + * returned inventory might no longer be valid. + *

+ * If this block state is not placed this will return the captured inventory + * snapshot instead. + * + * @return the inventory + */ + @NotNull + @Override + Inventory getInventory(); + + /** + * Gets the captured inventory snapshot of this container. + *

+ * The returned inventory is not linked to any block. Any modifications to + * the returned inventory will not be applied to the block represented by + * this block state up until {@link #update(boolean, boolean)} has been + * called. + * + * @return the captured inventory snapshot + */ + @NotNull + Inventory getSnapshotInventory(); +} diff --git a/api/src/main/java/org/bukkit/block/CreatureSpawner.java b/api/src/main/java/org/bukkit/block/CreatureSpawner.java new file mode 100644 index 000000000..5773e99ee --- /dev/null +++ b/api/src/main/java/org/bukkit/block/CreatureSpawner.java @@ -0,0 +1,202 @@ +package org.bukkit.block; + +import org.bukkit.entity.EntityType; +import org.jetbrains.annotations.NotNull; + +/** + * Represents a captured state of a creature spawner. + */ +public interface CreatureSpawner extends BlockState { + + /** + * Get the spawner's creature type. + * + * @return The creature type. + */ + @NotNull + public EntityType getSpawnedType(); + + /** + * Set the spawner's creature type. + * + * @param creatureType The creature type. + */ + public void setSpawnedType(@NotNull EntityType creatureType); + + /** + * Set the spawner mob type. + * + * @param creatureType The creature type's name. + * @deprecated magic value, use + * {@link #setSpawnedType(org.bukkit.entity.EntityType)}. + */ + @Deprecated + public void setCreatureTypeByName(@NotNull String creatureType); + + /** + * Get the spawner's creature type. + * + * @return The creature type's name. + * @deprecated magic value, use {@link #getSpawnedType()}. + */ + @Deprecated + @NotNull + public String getCreatureTypeName(); + + /** + * Get the spawner's delay. + *
+ * This is the delay, in ticks, until the spawner will spawn its next mob. + * + * @return The delay. + */ + public int getDelay(); + + /** + * Set the spawner's delay. + *
+ * If set to -1, the spawn delay will be reset to a random value between + * {@link #getMinSpawnDelay} and {@link #getMaxSpawnDelay()}. + * + * @param delay The delay. + */ + public void setDelay(int delay); + + /** + * The minimum spawn delay amount (in ticks). + *
+ * This value is used when the spawner resets its delay (for any reason). + * It will choose a random number between {@link #getMinSpawnDelay()} + * and {@link #getMaxSpawnDelay()} for its next {@link #getDelay()}. + * + * Default value is 200 ticks. + * + * @return the minimum spawn delay amount + */ + public int getMinSpawnDelay(); + + /** + * Set the minimum spawn delay amount (in ticks). + * + * @see #getMinSpawnDelay() + * @param delay the minimum spawn delay amount + */ + public void setMinSpawnDelay(int delay); + + /** + * The maximum spawn delay amount (in ticks). + *
+ * This value is used when the spawner resets its delay (for any reason). + * It will choose a random number between {@link #getMinSpawnDelay()} + * and {@link #getMaxSpawnDelay()} for its next {@link #getDelay()}. + *
+ * This value must be greater than 0 and less than or equal to + * {@link #getMaxSpawnDelay()}. + * + * Default value is 800 ticks. + * + * @return the maximum spawn delay amount + */ + public int getMaxSpawnDelay(); + + /** + * Set the maximum spawn delay amount (in ticks). + *
+ * This value must be greater than 0, as well as greater than or + * equal to {@link #getMinSpawnDelay()} + * + * @see #getMaxSpawnDelay() + * @param delay the new maximum spawn delay amount + */ + public void setMaxSpawnDelay(int delay); + + /** + * Get how many mobs attempt to spawn. + *
+ * Default value is 4. + * + * @return the current spawn count + */ + public int getSpawnCount(); + + /** + * Set how many mobs attempt to spawn. + * + * @param spawnCount the new spawn count + */ + public void setSpawnCount(int spawnCount); + + /** + * Set the new maximum amount of similar entities that are allowed to be + * within spawning range of this spawner. + *
+ * If more than the maximum number of entities are within range, the spawner + * will not spawn and try again with a new {@link #getDelay()}. + *
+ * Default value is 16. + * + * @return the maximum number of nearby, similar, entities + */ + public int getMaxNearbyEntities(); + + /** + * Set the maximum number of similar entities that are allowed to be within + * spawning range of this spawner. + *
+ * Similar entities are entities that are of the same {@link EntityType} + * + * @param maxNearbyEntities the maximum number of nearby, similar, entities + */ + public void setMaxNearbyEntities(int maxNearbyEntities); + + /** + * Get the maximum distance(squared) a player can be in order for this + * spawner to be active. + *
+ * If this value is less than or equal to 0, this spawner is always active + * (given that there are players online). + *
+ * Default value is 16. + * + * @return the maximum distance(squared) a player can be in order for this + * spawner to be active. + */ + public int getRequiredPlayerRange(); + + /** + * Set the maximum distance (squared) a player can be in order for this + * spawner to be active. + *
+ * Setting this value to less than or equal to 0 will make this spawner + * always active (given that there are players online). + * + * @param requiredPlayerRange the maximum distance (squared) a player can be + * in order for this spawner to be active. + */ + public void setRequiredPlayerRange(int requiredPlayerRange); + + /** + * Get the radius around which the spawner will attempt to spawn mobs in. + *
+ * This area is square, includes the block the spawner is in, and is + * centered on the spawner's x,z coordinates - not the spawner itself. + *
+ * It is 2 blocks high, centered on the spawner's y-coordinate (its bottom); + * thus allowing mobs to spawn as high as its top surface and as low + * as 1 block below its bottom surface. + *
+ * Default value is 4. + * + * @return the spawn range + */ + public int getSpawnRange(); + + /** + * Set the new spawn range. + *
+ * + * @see #getSpawnRange() + * @param spawnRange the new spawn range + */ + public void setSpawnRange(int spawnRange); +} diff --git a/api/src/main/java/org/bukkit/block/DaylightDetector.java b/api/src/main/java/org/bukkit/block/DaylightDetector.java new file mode 100644 index 000000000..ea4117ea8 --- /dev/null +++ b/api/src/main/java/org/bukkit/block/DaylightDetector.java @@ -0,0 +1,6 @@ +package org.bukkit.block; + +/** + * Represents a captured state of a (possibly inverted) daylight detector. + */ +public interface DaylightDetector extends BlockState { } diff --git a/api/src/main/java/org/bukkit/block/Dispenser.java b/api/src/main/java/org/bukkit/block/Dispenser.java new file mode 100644 index 000000000..07af1a3f0 --- /dev/null +++ b/api/src/main/java/org/bukkit/block/Dispenser.java @@ -0,0 +1,36 @@ +package org.bukkit.block; + +import com.destroystokyo.paper.loottable.LootableBlockInventory; +import org.bukkit.Nameable; +import org.bukkit.loot.Lootable; +import org.bukkit.projectiles.BlockProjectileSource; +import org.jetbrains.annotations.Nullable; + +/** + * Represents a captured state of a dispenser. + */ +public interface Dispenser extends Container, Nameable, LootableBlockInventory { // Paper + + /** + * Gets the BlockProjectileSource object for the dispenser. + *

+ * If the block represented by this state is no longer a dispenser, this + * will return null. + * + * @return a BlockProjectileSource if valid, otherwise null + * @throws IllegalStateException if this block state is not placed + */ + @Nullable + public BlockProjectileSource getBlockProjectileSource(); + + /** + * Attempts to dispense the contents of the dispenser. + *

+ * If the block represented by this state is no longer a dispenser, this + * will return false. + * + * @return true if successful, otherwise false + * @throws IllegalStateException if this block state is not placed + */ + public boolean dispense(); +} diff --git a/api/src/main/java/org/bukkit/block/DoubleChest.java b/api/src/main/java/org/bukkit/block/DoubleChest.java new file mode 100644 index 000000000..97153adfa --- /dev/null +++ b/api/src/main/java/org/bukkit/block/DoubleChest.java @@ -0,0 +1,57 @@ +package org.bukkit.block; + +import org.bukkit.Location; +import org.bukkit.World; +import org.bukkit.inventory.DoubleChestInventory; +import org.bukkit.inventory.Inventory; +import org.bukkit.inventory.InventoryHolder; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +/** + * Represents a double chest. + */ +public class DoubleChest implements InventoryHolder { + private DoubleChestInventory inventory; + + public DoubleChest(@NotNull DoubleChestInventory chest) { + inventory = chest; + } + + @NotNull + public Inventory getInventory() { + return inventory; + } + + @Nullable + public InventoryHolder getLeftSide() { + return inventory.getLeftSide().getHolder(); + } + + @Nullable + public InventoryHolder getRightSide() { + return inventory.getRightSide().getHolder(); + } + + @NotNull + public Location getLocation() { + return getInventory().getLocation(); + } + + @Nullable + public World getWorld() { + return getLocation().getWorld(); + } + + public double getX() { + return getLocation().getX(); + } + + public double getY() { + return getLocation().getY(); + } + + public double getZ() { + return getLocation().getZ(); + } +} diff --git a/api/src/main/java/org/bukkit/block/Dropper.java b/api/src/main/java/org/bukkit/block/Dropper.java new file mode 100644 index 000000000..47737b590 --- /dev/null +++ b/api/src/main/java/org/bukkit/block/Dropper.java @@ -0,0 +1,33 @@ +package org.bukkit.block; + +import com.destroystokyo.paper.loottable.LootableBlockInventory; +import org.bukkit.Nameable; +import org.bukkit.loot.Lootable; + +/** + * Represents a captured state of a dropper. + */ +public interface Dropper extends Container, Nameable, LootableBlockInventory { // Paper + + /** + * Tries to drop a randomly selected item from the dropper's inventory, + * following the normal behavior of a dropper. + *

+ * Normal behavior of a dropper is as follows: + *

+ * If the block that the dropper is facing is an InventoryHolder, + * the randomly selected ItemStack is placed within that + * Inventory in the first slot that's available, starting with 0 and + * counting up. If the inventory is full, nothing happens. + *

+ * If the block that the dropper is facing is not an InventoryHolder, + * the randomly selected ItemStack is dropped on + * the ground in the form of an {@link org.bukkit.entity.Item Item}. + *

+ * If the block represented by this state is no longer a dropper, this will + * do nothing. + * + * @throws IllegalStateException if this block state is not placed + */ + public void drop(); +} diff --git a/api/src/main/java/org/bukkit/block/EnchantingTable.java b/api/src/main/java/org/bukkit/block/EnchantingTable.java new file mode 100644 index 000000000..9f5aa6c6c --- /dev/null +++ b/api/src/main/java/org/bukkit/block/EnchantingTable.java @@ -0,0 +1,8 @@ +package org.bukkit.block; + +import org.bukkit.Nameable; + +/** + * Represents a captured state of an enchanting table. + */ +public interface EnchantingTable extends BlockState, Nameable { } diff --git a/api/src/main/java/org/bukkit/block/EndGateway.java b/api/src/main/java/org/bukkit/block/EndGateway.java new file mode 100644 index 000000000..e737f2db2 --- /dev/null +++ b/api/src/main/java/org/bukkit/block/EndGateway.java @@ -0,0 +1,68 @@ +package org.bukkit.block; + +import org.bukkit.Location; +import org.jetbrains.annotations.Nullable; + +/** + * Represents a captured state of an end gateway. + */ +public interface EndGateway extends BlockState { + + /** + * Gets the location that entities are teleported to when + * entering the gateway portal. + *

+ * If this block state is not placed the location's world will be null. + * + * @return the gateway exit location + */ + @Nullable + Location getExitLocation(); + + /** + * Sets the exit location that entities are teleported to when + * they enter the gateway portal. + *

+ * If this block state is not placed the location's world has to be null. + * + * @param location the new exit location + * @throws IllegalArgumentException for differing worlds + */ + void setExitLocation(@Nullable Location location); + + /** + * Gets whether this gateway will teleport entities directly to + * the exit location instead of finding a nearby location. + * + * @return true if the gateway is teleporting to the exact location + */ + boolean isExactTeleport(); + + /** + * Sets whether this gateway will teleport entities directly to + * the exit location instead of finding a nearby location. + * + * @param exact whether to teleport to the exact location + */ + void setExactTeleport(boolean exact); + + /** + * Gets the age in ticks of the gateway. + *
+ * If the age is less than 200 ticks a magenta beam will be emitted, whilst + * if it is a multiple of 2400 ticks a purple beam will be emitted. + * + * @return age in ticks + */ + long getAge(); + + /** + * Sets the age in ticks of the gateway. + *
+ * If the age is less than 200 ticks a magenta beam will be emitted, whilst + * if it is a multiple of 2400 ticks a purple beam will be emitted. + * + * @param age new age in ticks + */ + void setAge(long age); +} diff --git a/api/src/main/java/org/bukkit/block/EnderChest.java b/api/src/main/java/org/bukkit/block/EnderChest.java new file mode 100644 index 000000000..e0eb2560f --- /dev/null +++ b/api/src/main/java/org/bukkit/block/EnderChest.java @@ -0,0 +1,6 @@ +package org.bukkit.block; + +/** + * Represents a captured state of an ender chest. + */ +public interface EnderChest extends BlockState { } diff --git a/api/src/main/java/org/bukkit/block/FlowerPot.java b/api/src/main/java/org/bukkit/block/FlowerPot.java new file mode 100644 index 000000000..1155edc3a --- /dev/null +++ b/api/src/main/java/org/bukkit/block/FlowerPot.java @@ -0,0 +1,30 @@ +package org.bukkit.block; + +import org.bukkit.material.MaterialData; +import org.jetbrains.annotations.Nullable; + +/** + * Represents a captured state of a flower pot. + * @deprecated not a tile entity in future versions of Minecraft + */ +@Deprecated +public interface FlowerPot extends BlockState { + + /** + * Gets the item present in this flower pot. + * + * @return item present, or null for empty. + */ + @Nullable + MaterialData getContents(); + + /** + * Sets the item present in this flower pot. + * + * NOTE: The Vanilla Minecraft client will currently not refresh this until + * a block update is triggered. + * + * @param item new item, or null for empty. + */ + void setContents(@Nullable MaterialData item); +} diff --git a/api/src/main/java/org/bukkit/block/Furnace.java b/api/src/main/java/org/bukkit/block/Furnace.java new file mode 100644 index 000000000..97b25dbd9 --- /dev/null +++ b/api/src/main/java/org/bukkit/block/Furnace.java @@ -0,0 +1,92 @@ +package org.bukkit.block; + +import org.bukkit.Nameable; +import org.bukkit.inventory.FurnaceInventory; +import org.jetbrains.annotations.NotNull; + +/** + * Represents a captured state of a furnace. + */ +public interface Furnace extends Container, Nameable { + + /** + * Get burn time. + * + * @return Burn time + */ + public short getBurnTime(); + + /** + * Set burn time. + * + * A burn time greater than 0 will cause this block to be lit, whilst a time + * less than 0 will extinguish it. + * + * @param burnTime Burn time + */ + public void setBurnTime(short burnTime); + + /** + * Get cook time. + * + * This is the amount of time the item has been cooking for. + * + * @return Cook time + */ + public short getCookTime(); + + /** + * Set cook time. + * + * This is the amount of time the item has been cooking for. + * + * @param cookTime Cook time + */ + public void setCookTime(short cookTime); + + /** + * Get cook time total. + * + * This is the amount of time the item is required to cook for. + * + * @return Cook time total + */ + public int getCookTimeTotal(); + + /** + * Set cook time. + * + * This is the amount of time the item is required to cook for. + * + * @param cookTimeTotal Cook time total + */ + public void setCookTimeTotal(int cookTimeTotal); + + // Paper start + /** + * Gets the cook speed multiplier that this {@link Furnace} will cook + * compared to vanilla. + * + * @return the multiplier, a value between 0 and 200 + */ + public double getCookSpeedMultiplier(); + + /** + * Sets the speed multiplier that this {@link Furnace} will cook + * compared to vanilla. + * + * @param multiplier the multiplier to set, a value between 0 and 200 + * @throws IllegalArgumentException if value is less than 0 + * @throws IllegalArgumentException if value is more than 200 + */ + public void setCookSpeedMultiplier(double multiplier); + // Paper end + + @NotNull + @Override + public FurnaceInventory getInventory(); + + @NotNull + @Override + public FurnaceInventory getSnapshotInventory(); +} diff --git a/api/src/main/java/org/bukkit/block/Hopper.java b/api/src/main/java/org/bukkit/block/Hopper.java new file mode 100644 index 000000000..221123e8c --- /dev/null +++ b/api/src/main/java/org/bukkit/block/Hopper.java @@ -0,0 +1,10 @@ +package org.bukkit.block; + +import com.destroystokyo.paper.loottable.LootableBlockInventory; +import org.bukkit.Nameable; +import org.bukkit.loot.Lootable; + +/** + * Represents a captured state of a hopper. + */ +public interface Hopper extends Container, Nameable, LootableBlockInventory { } // Paper diff --git a/api/src/main/java/org/bukkit/block/Jukebox.java b/api/src/main/java/org/bukkit/block/Jukebox.java new file mode 100644 index 000000000..1203182f3 --- /dev/null +++ b/api/src/main/java/org/bukkit/block/Jukebox.java @@ -0,0 +1,60 @@ +package org.bukkit.block; + +import org.bukkit.Material; +import org.bukkit.inventory.ItemStack; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +/** + * Represents a captured state of a jukebox. + */ +public interface Jukebox extends BlockState { + + /** + * Gets the record inserted into the jukebox. + * + * @return The record Material, or AIR if none is inserted + */ + @NotNull + public Material getPlaying(); + + /** + * Sets the record being played. + * + * @param record The record Material, or null/AIR to stop playing + */ + public void setPlaying(@Nullable Material record); + + /** + * Gets the record item inserted into the jukebox. + * + * @return a copy of the inserted record, or an air stack if none + */ + @NotNull + public ItemStack getRecord(); + + /** + * Sets the record being played. + * + * @param record the record to insert or null/AIR to empty + */ + public void setRecord(@Nullable ItemStack record); + + /** + * Checks if the jukebox is playing a record. + * + * @return True if there is a record playing + */ + public boolean isPlaying(); + + /** + * Stops the jukebox playing and ejects the current record. + *

+ * If the block represented by this state is no longer a jukebox, this will + * do nothing and return false. + * + * @return True if a record was ejected; false if there was none playing + * @throws IllegalStateException if this block state is not placed + */ + public boolean eject(); +} diff --git a/api/src/main/java/org/bukkit/block/Lockable.java b/api/src/main/java/org/bukkit/block/Lockable.java new file mode 100644 index 000000000..f307cb170 --- /dev/null +++ b/api/src/main/java/org/bukkit/block/Lockable.java @@ -0,0 +1,35 @@ +package org.bukkit.block; + +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +/** + * Represents a block (usually a container) that may be locked. When a lock is + * active an item with a name corresponding to the key will be required to open + * this block. + */ +public interface Lockable { + + /** + * Checks if the container has a valid (non empty) key. + * + * @return true if the key is valid. + */ + boolean isLocked(); + + /** + * Gets the key needed to access the container. + * + * @return the key needed. + */ + @NotNull + String getLock(); + + /** + * Sets the key required to access this container. Set to null (or empty + * string) to remove key. + * + * @param key the key required to access the container. + */ + void setLock(@Nullable String key); +} diff --git a/api/src/main/java/org/bukkit/block/NoteBlock.java b/api/src/main/java/org/bukkit/block/NoteBlock.java new file mode 100644 index 000000000..14bb68bae --- /dev/null +++ b/api/src/main/java/org/bukkit/block/NoteBlock.java @@ -0,0 +1,84 @@ +package org.bukkit.block; + +import org.bukkit.Instrument; +import org.bukkit.Note; + +/** + * Represents a captured state of a note block. + * @deprecated not a tile entity in future versions of Minecraft + */ +@Deprecated +public interface NoteBlock extends BlockState { + + /** + * Gets the note. + * + * @return The note. + */ + public Note getNote(); + + /** + * Gets the note. + * + * @return The note ID. + * @deprecated Magic value + */ + @Deprecated + public byte getRawNote(); + + /** + * Set the note. + * + * @param note The note. + */ + public void setNote(Note note); + + /** + * Set the note. + * + * @param note The note ID. + * @deprecated Magic value + */ + @Deprecated + public void setRawNote(byte note); + + /** + * Attempts to play the note at the block. + *

+ * If the block represented by this block state is no longer a note block, + * this will return false. + * + * @return true if successful, otherwise false + * @throws IllegalStateException if this block state is not placed + */ + public boolean play(); + + /** + * Plays an arbitrary note with an arbitrary instrument at the block. + *

+ * If the block represented by this block state is no longer a note block, + * this will return false. + * + * @param instrument Instrument ID + * @param note Note ID + * @return true if successful, otherwise false + * @throws IllegalStateException if this block state is not placed + * @deprecated Magic value + */ + @Deprecated + public boolean play(byte instrument, byte note); + + /** + * Plays an arbitrary note with an arbitrary instrument at the block. + *

+ * If the block represented by this block state is no longer a note block, + * this will return false. + * + * @param instrument The instrument + * @param note The note + * @return true if successful, otherwise false + * @throws IllegalStateException if this block state is not placed + * @see Instrument Note + */ + public boolean play(Instrument instrument, Note note); +} diff --git a/api/src/main/java/org/bukkit/block/PistonMoveReaction.java b/api/src/main/java/org/bukkit/block/PistonMoveReaction.java new file mode 100644 index 000000000..02df88682 --- /dev/null +++ b/api/src/main/java/org/bukkit/block/PistonMoveReaction.java @@ -0,0 +1,69 @@ +package org.bukkit.block; + +import org.jetbrains.annotations.Nullable; + +import java.util.HashMap; +import java.util.Map; + +/** + * Represents how a block or entity will react when interacting with a piston + * when it is extending or retracting. + */ +public enum PistonMoveReaction { + + /** + * Indicates that the block can be pushed or pulled. + */ + MOVE(0), + /** + * Indicates the block is fragile and will break if pushed on. + */ + BREAK(1), + /** + * Indicates that the block will resist being pushed or pulled. + */ + BLOCK(2), + /** + * Indicates that the entity will ignore any interaction(s) with + * pistons. + *
+ * Blocks should use {@link PistonMoveReaction#BLOCK}. + */ + IGNORE(3), + /** + * Indicates that the block can only be pushed by pistons, not pulled. + */ + PUSH_ONLY(4); + + private int id; + private static Map byId = new HashMap(); + static { + for (PistonMoveReaction reaction : PistonMoveReaction.values()) { + byId.put(reaction.id, reaction); + } + } + + private PistonMoveReaction(int id) { + this.id = id; + } + + /** + * @return The ID of the move reaction + * @deprecated Magic value + */ + @Deprecated + public int getId() { + return this.id; + } + + /** + * @param id An ID + * @return The move reaction with that ID + * @deprecated Magic value + */ + @Deprecated + @Nullable + public static PistonMoveReaction getById(int id) { + return byId.get(id); + } +} diff --git a/api/src/main/java/org/bukkit/block/ShulkerBox.java b/api/src/main/java/org/bukkit/block/ShulkerBox.java new file mode 100644 index 000000000..8c8811d4d --- /dev/null +++ b/api/src/main/java/org/bukkit/block/ShulkerBox.java @@ -0,0 +1,21 @@ +package org.bukkit.block; + +import com.destroystokyo.paper.loottable.LootableBlockInventory; +import org.bukkit.DyeColor; +import org.bukkit.Nameable; +import org.bukkit.loot.Lootable; +import org.jetbrains.annotations.NotNull; + +/** + * Represents a captured state of a ShulkerBox. + */ +public interface ShulkerBox extends Container, Nameable, LootableBlockInventory { // Paper + + /** + * Get the {@link DyeColor} corresponding to this ShulkerBox + * + * @return the {@link DyeColor} of this ShulkerBox + */ + @NotNull + public DyeColor getColor(); +} diff --git a/api/src/main/java/org/bukkit/block/Sign.java b/api/src/main/java/org/bukkit/block/Sign.java new file mode 100644 index 000000000..74db5efcb --- /dev/null +++ b/api/src/main/java/org/bukkit/block/Sign.java @@ -0,0 +1,63 @@ +package org.bukkit.block; + +import org.jetbrains.annotations.NotNull; + +/** + * Represents a captured state of either a SignPost or a WallSign. + */ +public interface Sign extends BlockState { + + /** + * Gets all the lines of text currently on this sign. + * + * @return Array of Strings containing each line of text + */ + @NotNull + public String[] getLines(); + + /** + * Gets the line of text at the specified index. + *

+ * For example, getLine(0) will return the first line of text. + * + * @param index Line number to get the text from, starting at 0 + * @throws IndexOutOfBoundsException Thrown when the line does not exist + * @return Text on the given line + */ + @NotNull + public String getLine(int index) throws IndexOutOfBoundsException; + + /** + * Sets the line of text at the specified index. + *

+ * For example, setLine(0, "Line One") will set the first line of text to + * "Line One". + * + * @param index Line number to set the text at, starting from 0 + * @param line New text to set at the specified index + * @throws IndexOutOfBoundsException If the index is out of the range 0..3 + */ + public void setLine(int index, @NotNull String line) throws IndexOutOfBoundsException; + + /** + * Marks whether this sign can be edited by players. + *
+ * This is a special value, which is not persisted. It should only be set if + * a placed sign is manipulated during the BlockPlaceEvent. Behaviour + * outside of this event is undefined. + * + * @return if this sign is currently editable + */ + public boolean isEditable(); + + /** + * Marks whether this sign can be edited by players. + *
+ * This is a special value, which is not persisted. It should only be set if + * a placed sign is manipulated during the BlockPlaceEvent. Behaviour + * outside of this event is undefined. + * + * @param editable if this sign is currently editable + */ + public void setEditable(boolean editable); +} diff --git a/api/src/main/java/org/bukkit/block/Skull.java b/api/src/main/java/org/bukkit/block/Skull.java new file mode 100644 index 000000000..27675ecd1 --- /dev/null +++ b/api/src/main/java/org/bukkit/block/Skull.java @@ -0,0 +1,119 @@ +package org.bukkit.block; + +import org.bukkit.Material; +import org.bukkit.OfflinePlayer; +import org.bukkit.SkullType; +import org.bukkit.block.data.BlockData; +import org.jetbrains.annotations.Contract; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; +import com.destroystokyo.paper.profile.PlayerProfile; // Paper + +/** + * Represents a captured state of a skull block. + */ +public interface Skull extends BlockState { + + /** + * Checks to see if the skull has an owner + * + * @return true if the skull has an owner + */ + public boolean hasOwner(); + + /** + * Gets the owner of the skull, if one exists + * + * @return the owner of the skull or null if the skull does not have an owner + * @deprecated See {@link #getOwningPlayer()}. + */ + @Deprecated + @Nullable + public String getOwner(); + + /** + * Sets the owner of the skull + *

+ * Involves a potentially blocking web request to acquire the profile data for + * the provided name. + * + * @param name the new owner of the skull + * @return true if the owner was successfully set + * @deprecated see {@link #setOwningPlayer(org.bukkit.OfflinePlayer)}. + */ + @Deprecated + @Contract("null -> false") + public boolean setOwner(@Nullable String name); + + /** + * Get the player which owns the skull. This player may appear as the + * texture depending on skull type. + * + * @return owning player + */ + @Nullable + public OfflinePlayer getOwningPlayer(); + + /** + * Set the player which owns the skull. This player may appear as the + * texture depending on skull type. + * + * @param player the owning player + */ + public void setOwningPlayer(@NotNull OfflinePlayer player); + + // Paper start + /** + * Sets this skull to use the supplied Player Profile, which can include textures already prefilled. + * @param profile The profile to set this Skull to use, may not be null + */ + void setPlayerProfile(@NotNull PlayerProfile profile); + + /** + * If the skull has an owner, per {@link #hasOwner()}, return the owners {@link PlayerProfile} + * @return The profile of the owner, if set + */ + @Nullable PlayerProfile getPlayerProfile(); + // Paper end + + /** + * Gets the rotation of the skull in the world (or facing direction if this + * is a wall mounted skull). + * + * @return the rotation of the skull + * @deprecated use {@link BlockData} + */ + @Deprecated + @NotNull + public BlockFace getRotation(); + + /** + * Sets the rotation of the skull in the world (or facing direction if this + * is a wall mounted skull). + * + * @param rotation the rotation of the skull + * @deprecated use {@link BlockData} + */ + @Deprecated + public void setRotation(@NotNull BlockFace rotation); + + /** + * Gets the type of skull + * + * @return the type of skull + * @deprecated check {@link Material} instead + */ + @Deprecated + @NotNull + public SkullType getSkullType(); + + /** + * Sets the type of skull + * + * @param skullType the type of skull + * @deprecated check {@link Material} instead + */ + @Deprecated + @Contract("_ -> fail") + public void setSkullType(SkullType skullType); +} diff --git a/api/src/main/java/org/bukkit/block/Structure.java b/api/src/main/java/org/bukkit/block/Structure.java new file mode 100644 index 000000000..d0f1d507e --- /dev/null +++ b/api/src/main/java/org/bukkit/block/Structure.java @@ -0,0 +1,243 @@ +package org.bukkit.block; + +import org.bukkit.block.structure.Mirror; +import org.bukkit.block.structure.StructureRotation; +import org.bukkit.block.structure.UsageMode; +import org.bukkit.entity.LivingEntity; +import org.bukkit.util.BlockVector; +import org.jetbrains.annotations.NotNull; + +/** + * Represents a structure block that can save and load blocks from a file. They + * can only be used by OPs, and are not obtainable in survival. + */ +public interface Structure extends BlockState { + + /** + * The name of this structure. + * + * @return structure name + */ + @NotNull + String getStructureName(); + + /** + * Set the name of this structure. This is case-sensitive. The name of the + * structure in the {@link UsageMode#SAVE} structure block MUST match the + * name within the {@link UsageMode#CORNER} block or the size calculation + * will fail. + * + * @param name the case-sensitive name of this structure + */ + void setStructureName(@NotNull String name); + + /** + * Get the name of who created this structure. + * + * @return the name of whoever created this structure. + */ + @NotNull + String getAuthor(); + + /** + * Set the name of whoever created this structure. + * + * @param author whoever created this structure (not empty) + */ + void setAuthor(@NotNull String author); + + /** + * Set the name of whoever created this structure using a + * {@link LivingEntity}. + * + * @param livingEntity the entity who created this structure + */ + void setAuthor(@NotNull LivingEntity livingEntity); + + /** + * The relative position of the structure outline based on the position of + * the structure block. Maximum allowed distance is 32 blocks in any + * direction. + * + * @return a Location which contains the relative distance this structure is + * from the structure block. + */ + @NotNull + BlockVector getRelativePosition(); + + /** + * Set the relative position from the structure block. Maximum allowed + * distance is 32 blocks in any direction. + * + * @param vector the {@link BlockVector} containing the relative origin + * coordinates of this structure. + */ + void setRelativePosition(@NotNull BlockVector vector); + + /** + * The distance to the opposite corner of this structure. The maximum + * structure size is 32x32x32. When a structure has successfully been + * calculated (i.e. it is within the maximum allowed distance) a white + * border surrounds the structure. + * + * @return a {@link BlockVector} which contains the total size of the + * structure. + */ + @NotNull + BlockVector getStructureSize(); + + /** + * Set the maximum size of this structure from the origin point. Maximum + * allowed size is 32x32x32. + * + * @param vector the {@link BlockVector} containing the size of this + * structure, based off of the origin coordinates. + */ + void setStructureSize(@NotNull BlockVector vector); + + /** + * Sets the mirroring of the structure. + * + * @param mirror the new mirroring method + */ + void setMirror(@NotNull Mirror mirror); + + /** + * How this structure is mirrored. + * + * @return the current mirroring method + */ + @NotNull + Mirror getMirror(); + + /** + * Set how this structure is rotated. + * + * @param rotation the new rotation + */ + void setRotation(@NotNull StructureRotation rotation); + + /** + * Get how this structure is rotated. + * + * @return the new rotation + */ + @NotNull + StructureRotation getRotation(); + + /** + * Set the {@link UsageMode} of this structure block. + * + * @param mode the new mode to set. + */ + void setUsageMode(@NotNull UsageMode mode); + + /** + * Get the {@link UsageMode} of this structure block. + * + * @return the mode this block is currently in. + */ + @NotNull + UsageMode getUsageMode(); + + /** + * While in {@link UsageMode#SAVE} mode, this will ignore any entities when + * saving the structure. + *
+ * While in {@link UsageMode#LOAD} mode this will ignore any entities that + * were saved to file. + * + * @param ignoreEntities the flag to set + */ + void setIgnoreEntities(boolean ignoreEntities); + + /** + * Get if this structure block should ignore entities. + * + * @return true if the appropriate {@link UsageMode} should ignore entities. + */ + boolean isIgnoreEntities(); + + /** + * Set if the structure outline should show air blocks. + * + * @param showAir if the structure block should show air blocks + */ + void setShowAir(boolean showAir); + + /** + * Check if this structure block is currently showing all air blocks + * + * @return true if the structure block is showing all air blocks + */ + boolean isShowAir(); + + /** + * Set if this structure box should show the bounding box. + * + * @param showBoundingBox if the structure box should be shown + */ + void setBoundingBoxVisible(boolean showBoundingBox); + + /** + * Get if this structure block is currently showing the bounding box. + * + * @return true if the bounding box is shown + */ + boolean isBoundingBoxVisible(); + + /** + * Set the integrity of the structure. Integrity must be between 0.0 and 1.0 + * Lower integrity values will result in more blocks being removed when + * loading a structure. Integrity and {@link #getSeed()} are used together + * to determine which blocks are randomly removed to mimic "decay." + * + * @param integrity the integrity of this structure + */ + void setIntegrity(float integrity); + + /** + * Get the integrity of this structure. + * + * @return the integrity of this structure + */ + float getIntegrity(); + + /** + * The seed used to determine which blocks will be removed upon loading. + * {@link #getIntegrity()} and seed are used together to determine which + * blocks are randomly removed to mimic "decay." + * + * @param seed the seed used to determine how many blocks will be removed + */ + void setSeed(long seed); + + /** + * The seed used to determine how many blocks are removed upon loading of + * this structure. + * + * @return the seed used + */ + long getSeed(); + + /** + * Only applicable while in {@link UsageMode#DATA}. Metadata are specific + * functions that can be applied to the structure location. Consult the + * Minecraft + * wiki for more information. + * + * @param metadata the function to perform on the selected location + */ + void setMetadata(@NotNull String metadata); + + /** + * Get the metadata function this structure block will perform when + * activated. Consult the + * Minecraft + * Wiki for more information. + * + * @return the function that will be performed when this block is activated + */ + @NotNull + String getMetadata(); +} diff --git a/api/src/main/java/org/bukkit/block/banner/Pattern.java b/api/src/main/java/org/bukkit/block/banner/Pattern.java new file mode 100644 index 000000000..5c293ab0b --- /dev/null +++ b/api/src/main/java/org/bukkit/block/banner/Pattern.java @@ -0,0 +1,98 @@ +package org.bukkit.block.banner; + +import com.google.common.collect.ImmutableMap; +import java.util.Map; +import java.util.NoSuchElementException; +import org.bukkit.DyeColor; +import org.bukkit.configuration.serialization.ConfigurationSerializable; +import org.bukkit.configuration.serialization.SerializableAs; +import org.jetbrains.annotations.NotNull; + +@SerializableAs("Pattern") +public class Pattern implements ConfigurationSerializable { + + private static final String COLOR = "color"; + private static final String PATTERN = "pattern"; + + private final DyeColor color; + private final PatternType pattern; + + /** + * Creates a new pattern from the specified color and + * pattern type + * + * @param color the pattern color + * @param pattern the pattern type + */ + public Pattern(@NotNull DyeColor color, @NotNull PatternType pattern) { + this.color = color; + this.pattern = pattern; + } + + /** + * Constructor for deserialization. + * + * @param map the map to deserialize from + */ + public Pattern(@NotNull Map map) { + color = DyeColor.legacyValueOf(getString(map, COLOR)); + pattern = PatternType.getByIdentifier(getString(map, PATTERN)); + } + + private static String getString(@NotNull Map map, @NotNull Object key) { + Object str = map.get(key); + if (str instanceof String) { + return (String) str; + } + throw new NoSuchElementException(map + " does not contain " + key); + } + + @NotNull + @Override + public Map serialize() { + return ImmutableMap.of( + COLOR, color.toString(), + PATTERN, pattern.getIdentifier() + ); + } + + /** + * Returns the color of the pattern + * + * @return the color of the pattern + */ + @NotNull + public DyeColor getColor() { + return color; + } + + /** + * Returns the type of pattern + * + * @return the pattern type + */ + @NotNull + public PatternType getPattern() { + return pattern; + } + + @Override + public int hashCode() { + int hash = 3; + hash = 97 * hash + (this.color != null ? this.color.hashCode() : 0); + hash = 97 * hash + (this.pattern != null ? this.pattern.hashCode() : 0); + return hash; + } + + @Override + public boolean equals(Object obj) { + if (obj == null) { + return false; + } + if (getClass() != obj.getClass()) { + return false; + } + final Pattern other = (Pattern) obj; + return this.color == other.color && this.pattern == other.pattern; + } +} diff --git a/api/src/main/java/org/bukkit/block/banner/PatternType.java b/api/src/main/java/org/bukkit/block/banner/PatternType.java new file mode 100644 index 000000000..95f55a446 --- /dev/null +++ b/api/src/main/java/org/bukkit/block/banner/PatternType.java @@ -0,0 +1,87 @@ +package org.bukkit.block.banner; + +import org.jetbrains.annotations.Contract; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +import java.util.HashMap; +import java.util.Map; + +public enum PatternType { + BASE("b"), + SQUARE_BOTTOM_LEFT("bl"), + SQUARE_BOTTOM_RIGHT("br"), + SQUARE_TOP_LEFT("tl"), + SQUARE_TOP_RIGHT("tr"), + STRIPE_BOTTOM("bs"), + STRIPE_TOP("ts"), + STRIPE_LEFT("ls"), + STRIPE_RIGHT("rs"), + STRIPE_CENTER("cs"), + STRIPE_MIDDLE("ms"), + STRIPE_DOWNRIGHT("drs"), + STRIPE_DOWNLEFT("dls"), + STRIPE_SMALL("ss"), + CROSS("cr"), + STRAIGHT_CROSS("sc"), + TRIANGLE_BOTTOM("bt"), + TRIANGLE_TOP("tt"), + TRIANGLES_BOTTOM("bts"), + TRIANGLES_TOP("tts"), + DIAGONAL_LEFT("ld"), + DIAGONAL_RIGHT("rd"), + DIAGONAL_LEFT_MIRROR("lud"), + DIAGONAL_RIGHT_MIRROR("rud"), + CIRCLE_MIDDLE("mc"), + RHOMBUS_MIDDLE("mr"), + HALF_VERTICAL("vh"), + HALF_HORIZONTAL("hh"), + HALF_VERTICAL_MIRROR("vhr"), + HALF_HORIZONTAL_MIRROR("hhb"), + BORDER("bo"), + CURLY_BORDER("cbo"), + CREEPER("cre"), + GRADIENT("gra"), + GRADIENT_UP("gru"), + BRICKS("bri"), + SKULL("sku"), + FLOWER("flo"), + MOJANG("moj"); + + private final String identifier; + private static final Map byString = new HashMap(); + + static { + for (PatternType p : values()) { + byString.put(p.identifier, p); + } + } + + private PatternType(/*@NotNull*/ String key) { + this.identifier = key; + } + + /** + * Returns the identifier used to represent + * this pattern type + * + * @return the pattern's identifier + */ + @NotNull + public String getIdentifier() { + return identifier; + } + + /** + * Returns the pattern type which matches the passed + * identifier or null if no matches are found + * + * @param identifier the identifier + * @return the matched pattern type or null + */ + @Contract("null -> null") + @Nullable + public static PatternType getByIdentifier(@Nullable String identifier) { + return byString.get(identifier); + } +} diff --git a/api/src/main/java/org/bukkit/block/data/Ageable.java b/api/src/main/java/org/bukkit/block/data/Ageable.java new file mode 100644 index 000000000..2f7b84a3f --- /dev/null +++ b/api/src/main/java/org/bukkit/block/data/Ageable.java @@ -0,0 +1,33 @@ +package org.bukkit.block.data; + +/** + * 'age' represents the different growth stages that a crop-like block can go + * through. + *
+ * A value of 0 indicates that the crop was freshly planted, whilst a value + * equal to {@link #getMaximumAge()} indicates that the crop is ripe and ready + * to be harvested. + */ +public interface Ageable extends BlockData { + + /** + * Gets the value of the 'age' property. + * + * @return the 'age' value + */ + int getAge(); + + /** + * Sets the value of the 'age' property. + * + * @param age the new 'age' value + */ + void setAge(int age); + + /** + * Gets the maximum allowed value of the 'age' property. + * + * @return the maximum 'age' value + */ + int getMaximumAge(); +} diff --git a/api/src/main/java/org/bukkit/block/data/AnaloguePowerable.java b/api/src/main/java/org/bukkit/block/data/AnaloguePowerable.java new file mode 100644 index 000000000..68ec26a10 --- /dev/null +++ b/api/src/main/java/org/bukkit/block/data/AnaloguePowerable.java @@ -0,0 +1,31 @@ +package org.bukkit.block.data; + +/** + * 'power' represents the redstone power level currently being emitted or + * transmitted via this block. + *
+ * May not be over 9000 or {@link #getMaximumPower()} (usually 15). + */ +public interface AnaloguePowerable extends BlockData { + + /** + * Gets the value of the 'power' property. + * + * @return the 'power' value + */ + int getPower(); + + /** + * Sets the value of the 'power' property. + * + * @param power the new 'power' value + */ + void setPower(int power); + + /** + * Gets the maximum allowed value of the 'power' property. + * + * @return the maximum 'power' value + */ + int getMaximumPower(); +} diff --git a/api/src/main/java/org/bukkit/block/data/Attachable.java b/api/src/main/java/org/bukkit/block/data/Attachable.java new file mode 100644 index 000000000..97c7fa728 --- /dev/null +++ b/api/src/main/java/org/bukkit/block/data/Attachable.java @@ -0,0 +1,27 @@ +package org.bukkit.block.data; + +/** + * 'attached' denotes whether a tripwire hook or string forms a complete + * tripwire circuit and is ready to trigger. + *
+ * Updating the property on a tripwire hook will change the texture to indicate + * a connected string, but will not have any effect when used on the tripwire + * string itself. It may however still be used to check whether the string forms + * a circuit. + */ +public interface Attachable extends BlockData { + + /** + * Gets the value of the 'attached' property. + * + * @return the 'attached' value + */ + boolean isAttached(); + + /** + * Sets the value of the 'attached' property. + * + * @param attached the new 'attached' value + */ + void setAttached(boolean attached); +} diff --git a/api/src/main/java/org/bukkit/block/data/Bisected.java b/api/src/main/java/org/bukkit/block/data/Bisected.java new file mode 100644 index 000000000..7b7c0d2be --- /dev/null +++ b/api/src/main/java/org/bukkit/block/data/Bisected.java @@ -0,0 +1,40 @@ +package org.bukkit.block.data; + +import org.jetbrains.annotations.NotNull; + +/** + * 'half' denotes which half of a two block tall material this block is. + *
+ * In game it may be referred to as either (top, bottom) or (upper, lower). + */ +public interface Bisected extends BlockData { + + /** + * Gets the value of the 'half' property. + * + * @return the 'half' value + */ + @NotNull + Half getHalf(); + + /** + * Sets the value of the 'half' property. + * + * @param half the new 'half' value + */ + void setHalf(@NotNull Half half); + + /** + * The half of a vertically bisected block. + */ + public enum Half { + /** + * The top half of the block, normally with the higher y coordinate. + */ + TOP, + /** + * The bottom half of the block, normally with the lower y coordinate. + */ + BOTTOM; + } +} diff --git a/api/src/main/java/org/bukkit/block/data/BlockData.java b/api/src/main/java/org/bukkit/block/data/BlockData.java new file mode 100644 index 000000000..22c5e84eb --- /dev/null +++ b/api/src/main/java/org/bukkit/block/data/BlockData.java @@ -0,0 +1,96 @@ +package org.bukkit.block.data; + +import org.bukkit.Material; +import org.bukkit.Server; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +public interface BlockData extends Cloneable { + + /** + * Get the Material represented by this block data. + * + * @return the material + */ + @NotNull + Material getMaterial(); + + /** + * Gets a string, which when passed into a method such as + * {@link Server#createBlockData(java.lang.String)} will unambiguously + * recreate this instance. + * + * @return serialized data string for this block + */ + @NotNull + String getAsString(); + + /** + * Gets a string, which when passed into a method such as + * {@link Server#createBlockData(java.lang.String)} will recreate this or a + * similar instance where unspecified states (if any) may be optionally + * omitted. If this instance was parsed and states are omitted, this exact + * instance will be creatable when parsed again, else their equality cannot + * be guaranteed. + *

+ * This method will only take effect for BlockData instances created by + * methods such as {@link Server#createBlockData(String)} or any similar + * method whereby states are optionally defined. If otherwise, the result of + * {@link #getAsString()} will be returned. The following behaviour would be + * expected: + *

{@code
+     * String dataString = "minecraft:chest[waterlogged=true]"
+     * BlockData data = Bukkit.createBlockData(dataString);
+     * dataString.equals(data.getAsString(true)); // This would return true
+     * dataString.equals(data.getAsString(false)); // This would return false as all states are present
+     * dataString.equals(data.getAsString()); // This is equivalent to the above, "getAsString(false)"
+     * }
+ * + * @param hideUnspecified true if unspecified states should be omitted, + * false if they are to be shown as performed by {@link #getAsString()}. + * + * @return serialized data string for this block + */ + @NotNull + String getAsString(boolean hideUnspecified); + + /** + * Merges all explicitly set states from the given data with this BlockData. + *
+ * Note that the given data MUST have been created from one of the String + * parse methods, e.g. {@link Server#createBlockData(java.lang.String)} and + * not have been subsequently modified. + *
+ * Note also that the block types must match identically. + * + * @param data the data to merge from + * @return a new instance of this blockdata with the merged data + */ + @NotNull + BlockData merge(@NotNull BlockData data); + + /** + * Checks if the specified BlockData matches this block data. + *
+ * The semantics of this method are such that for manually created or + * modified BlockData it has the same effect as + * {@link Object#equals(java.lang.Object)}, whilst for parsed data (that to + * which {@link #merge(org.bukkit.block.data.BlockData)} applies, it will + * return true when the type and all explicitly set states match. + *
+ * Note that these semantics mean that a.matches(b) may not be the same + * as b.matches(a) + * + * @param data the data to match against (normally a parsed constant) + * @return if there is a match + */ + boolean matches(@Nullable BlockData data); + + /** + * Returns a copy of this BlockData. + * + * @return a copy of the block data + */ + @NotNull + BlockData clone(); +} diff --git a/api/src/main/java/org/bukkit/block/data/Directional.java b/api/src/main/java/org/bukkit/block/data/Directional.java new file mode 100644 index 000000000..825ff08dd --- /dev/null +++ b/api/src/main/java/org/bukkit/block/data/Directional.java @@ -0,0 +1,37 @@ +package org.bukkit.block.data; + +import java.util.Set; +import org.bukkit.block.BlockFace; +import org.jetbrains.annotations.NotNull; + +/** + * 'facing' represents the face towards which the block is pointing. + *
+ * Some blocks may not be able to face in all directions, use + * {@link #getFaces()} to get all possible directions for this block. + */ +public interface Directional extends BlockData { + + /** + * Gets the value of the 'facing' property. + * + * @return the 'facing' value + */ + @NotNull + BlockFace getFacing(); + + /** + * Sets the value of the 'facing' property. + * + * @param facing the new 'facing' value + */ + void setFacing(@NotNull BlockFace facing); + + /** + * Gets the faces which are applicable to this block. + * + * @return the allowed 'facing' values + */ + @NotNull + Set getFaces(); +} diff --git a/api/src/main/java/org/bukkit/block/data/Levelled.java b/api/src/main/java/org/bukkit/block/data/Levelled.java new file mode 100644 index 000000000..5255538fe --- /dev/null +++ b/api/src/main/java/org/bukkit/block/data/Levelled.java @@ -0,0 +1,39 @@ +package org.bukkit.block.data; + +/** + * 'level' represents the amount of fluid contained within this block, either by + * itself or inside a cauldron. + *
+ * In the case of water and lava blocks the levels have special meanings: a + * level of 0 corresponds to a source block, 1-7 regular fluid heights, and 8-15 + * to "falling" fluids. All falling fluids have the same behaviour, but the + * level corresponds to that of the block above them, equal to + * this.level - 8 + * Note that counterintuitively, an adjusted level of 1 is the highest level, + * whilst 7 is the lowest. + *
+ * May not be higher than {@link #getMaximumLevel()}. + */ +public interface Levelled extends BlockData { + + /** + * Gets the value of the 'level' property. + * + * @return the 'level' value + */ + int getLevel(); + + /** + * Sets the value of the 'level' property. + * + * @param level the new 'level' value + */ + void setLevel(int level); + + /** + * Gets the maximum allowed value of the 'level' property. + * + * @return the maximum 'level' value + */ + int getMaximumLevel(); +} diff --git a/api/src/main/java/org/bukkit/block/data/Lightable.java b/api/src/main/java/org/bukkit/block/data/Lightable.java new file mode 100644 index 000000000..59a725e3a --- /dev/null +++ b/api/src/main/java/org/bukkit/block/data/Lightable.java @@ -0,0 +1,22 @@ +package org.bukkit.block.data; + +/** + * 'lit' denotes whether this block (either a redstone torch or furnace) is + * currently lit - that is not burned out. + */ +public interface Lightable extends BlockData { + + /** + * Gets the value of the 'lit' property. + * + * @return the 'lit' value + */ + boolean isLit(); + + /** + * Sets the value of the 'lit' property. + * + * @param lit the new 'lit' value + */ + void setLit(boolean lit); +} diff --git a/api/src/main/java/org/bukkit/block/data/MultipleFacing.java b/api/src/main/java/org/bukkit/block/data/MultipleFacing.java new file mode 100644 index 000000000..e790b7fdb --- /dev/null +++ b/api/src/main/java/org/bukkit/block/data/MultipleFacing.java @@ -0,0 +1,49 @@ +package org.bukkit.block.data; + +import java.util.Set; +import org.bukkit.block.BlockFace; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +/** + * This class encompasses the 'north', 'east', 'south', 'west', 'up', 'down' + * boolean flags which are used to set which faces of the block textures are + * displayed on. + *
+ * Some blocks may not be able to have faces on all directions, use + * {@link #getAllowedFaces()} to get all possible faces for this block. + */ +public interface MultipleFacing extends BlockData { + + /** + * Checks if this block has the specified face enabled. + * + * @param face to check + * @return if face is enabled + */ + boolean hasFace(@NotNull BlockFace face); + + /** + * Set whether this block has the specified face enabled. + * + * @param face to set + * @param has the face + */ + void setFace(@Nullable BlockFace face, boolean has); + + /** + * Get all of the faces which are enabled on this block. + * + * @return all faces enabled + */ + @NotNull + Set getFaces(); + + /** + * Gets all of this faces which may be set on this block. + * + * @return all allowed faces + */ + @NotNull + Set getAllowedFaces(); +} diff --git a/api/src/main/java/org/bukkit/block/data/Openable.java b/api/src/main/java/org/bukkit/block/data/Openable.java new file mode 100644 index 000000000..c04944ed7 --- /dev/null +++ b/api/src/main/java/org/bukkit/block/data/Openable.java @@ -0,0 +1,21 @@ +package org.bukkit.block.data; + +/** + * 'open' denotes whether this door-like block is currently opened. + */ +public interface Openable extends BlockData { + + /** + * Gets the value of the 'open' property. + * + * @return the 'open' value + */ + boolean isOpen(); + + /** + * Sets the value of the 'open' property. + * + * @param open the new 'open' value + */ + void setOpen(boolean open); +} diff --git a/api/src/main/java/org/bukkit/block/data/Orientable.java b/api/src/main/java/org/bukkit/block/data/Orientable.java new file mode 100644 index 000000000..5b4561a16 --- /dev/null +++ b/api/src/main/java/org/bukkit/block/data/Orientable.java @@ -0,0 +1,38 @@ +package org.bukkit.block.data; + +import java.util.Set; +import org.bukkit.Axis; +import org.jetbrains.annotations.NotNull; + +/** + * 'axis' represents the axis along whilst this block is oriented. + *
+ * Some blocks such as the portal block may not be able to be placed in all + * orientations, use {@link #getAxes()} to retrieve all possible such + * orientations. + */ +public interface Orientable extends BlockData { + + /** + * Gets the value of the 'axis' property. + * + * @return the 'axis' value + */ + @NotNull + Axis getAxis(); + + /** + * Sets the value of the 'axis' property. + * + * @param axis the new 'axis' value + */ + void setAxis(@NotNull Axis axis); + + /** + * Gets the axes which are applicable to this block. + * + * @return the allowed 'axis' values + */ + @NotNull + Set getAxes(); +} diff --git a/api/src/main/java/org/bukkit/block/data/Powerable.java b/api/src/main/java/org/bukkit/block/data/Powerable.java new file mode 100644 index 000000000..994f72f53 --- /dev/null +++ b/api/src/main/java/org/bukkit/block/data/Powerable.java @@ -0,0 +1,22 @@ +package org.bukkit.block.data; + +/** + * 'powered' indicates whether this block is in the powered state or not, i.e. + * receiving a redstone current of power > 0. + */ +public interface Powerable extends BlockData { + + /** + * Gets the value of the 'powered' property. + * + * @return the 'powered' value + */ + boolean isPowered(); + + /** + * Sets the value of the 'powered' property. + * + * @param powered the new 'powered' value + */ + void setPowered(boolean powered); +} diff --git a/api/src/main/java/org/bukkit/block/data/Rail.java b/api/src/main/java/org/bukkit/block/data/Rail.java new file mode 100644 index 000000000..e8300a741 --- /dev/null +++ b/api/src/main/java/org/bukkit/block/data/Rail.java @@ -0,0 +1,88 @@ +package org.bukkit.block.data; + +import org.jetbrains.annotations.NotNull; + +import java.util.Set; + +/** + * 'shape' represents the current layout of a minecart rail. + *
+ * Some types of rail may not be able to be laid out in all shapes, use + * {@link #getShapes()} to get those applicable to this block. + */ +public interface Rail extends BlockData { + + /** + * Gets the value of the 'shape' property. + * + * @return the 'shape' value + */ + @NotNull + Shape getShape(); + + /** + * Sets the value of the 'shape' property. + * + * @param shape the new 'shape' value + */ + void setShape(@NotNull Shape shape); + + /** + * Gets the shapes which are applicable to this block. + * + * @return the allowed 'shape' values + */ + @NotNull + Set getShapes(); + + /** + * The different types of shapes a rail block can occupy. + */ + public enum Shape { + + /** + * The rail runs flat along the north/south (Z) axis. + */ + NORTH_SOUTH, + /** + * The rail runs flat along the east/west (X) axis. + */ + EAST_WEST, + /** + * The rail ascends in the east (positive X) direction. + */ + ASCENDING_EAST, + /** + * The rail ascends in the west (negative X) direction. + */ + ASCENDING_WEST, + /** + * The rail ascends in the north (negative Z) direction. + */ + ASCENDING_NORTH, + /** + * The rail ascends in the south (positive Z) direction. + */ + ASCENDING_SOUTH, + /** + * The rail forms a curve connecting the south and east faces of the + * block. + */ + SOUTH_EAST, + /** + * The rail forms a curve connecting the south and west faces of the + * block. + */ + SOUTH_WEST, + /** + * The rail forms a curve connecting the north and west faces of the + * block. + */ + NORTH_WEST, + /** + * The rail forms a curve connecting the north and east faces of the + * block. + */ + NORTH_EAST; + } +} diff --git a/api/src/main/java/org/bukkit/block/data/Rotatable.java b/api/src/main/java/org/bukkit/block/data/Rotatable.java new file mode 100644 index 000000000..3f60fbce4 --- /dev/null +++ b/api/src/main/java/org/bukkit/block/data/Rotatable.java @@ -0,0 +1,25 @@ +package org.bukkit.block.data; + +import org.bukkit.block.BlockFace; +import org.jetbrains.annotations.NotNull; + +/** + * 'rotation' represents the current rotation of this block. + */ +public interface Rotatable extends BlockData { + + /** + * Gets the value of the 'rotation' property. + * + * @return the 'rotation' value + */ + @NotNull + BlockFace getRotation(); + + /** + * Sets the value of the 'rotation' property. + * + * @param rotation the new 'rotation' value + */ + void setRotation(@NotNull BlockFace rotation); +} diff --git a/api/src/main/java/org/bukkit/block/data/Snowable.java b/api/src/main/java/org/bukkit/block/data/Snowable.java new file mode 100644 index 000000000..dbb034ed8 --- /dev/null +++ b/api/src/main/java/org/bukkit/block/data/Snowable.java @@ -0,0 +1,22 @@ +package org.bukkit.block.data; + +/** + * 'snowy' denotes whether this block has a snow covered side and top texture + * (normally because the block above is snow). + */ +public interface Snowable extends BlockData { + + /** + * Gets the value of the 'snowy' property. + * + * @return the 'snowy' value + */ + boolean isSnowy(); + + /** + * Sets the value of the 'snowy' property. + * + * @param snowy the new 'snowy' value + */ + void setSnowy(boolean snowy); +} diff --git a/api/src/main/java/org/bukkit/block/data/Waterlogged.java b/api/src/main/java/org/bukkit/block/data/Waterlogged.java new file mode 100644 index 000000000..400cc9971 --- /dev/null +++ b/api/src/main/java/org/bukkit/block/data/Waterlogged.java @@ -0,0 +1,21 @@ +package org.bukkit.block.data; + +/** + * 'waterlogged' denotes whether this block has fluid in it. + */ +public interface Waterlogged extends BlockData { + + /** + * Gets the value of the 'waterlogged' property. + * + * @return the 'waterlogged' value + */ + boolean isWaterlogged(); + + /** + * Sets the value of the 'waterlogged' property. + * + * @param waterlogged the new 'waterlogged' value + */ + void setWaterlogged(boolean waterlogged); +} diff --git a/api/src/main/java/org/bukkit/block/data/type/Bed.java b/api/src/main/java/org/bukkit/block/data/type/Bed.java new file mode 100644 index 000000000..ed519bfeb --- /dev/null +++ b/api/src/main/java/org/bukkit/block/data/type/Bed.java @@ -0,0 +1,52 @@ +package org.bukkit.block.data.type; + +import org.bukkit.block.data.Bisected; +import org.bukkit.block.data.Directional; +import org.jetbrains.annotations.NotNull; + +/** + * Similar to {@link Bisected}, 'part' denotes which half of the bed this block + * corresponds to. + *
+ * 'occupied' property is a quick flag to check if a player is currently + * sleeping in this bed block. + */ +public interface Bed extends Directional { + + /** + * Gets the value of the 'part' property. + * + * @return the 'part' value + */ + @NotNull + Part getPart(); + + /** + * Sets the value of the 'part' property. + * + * @param part the new 'part' value + */ + void setPart(@NotNull Part part); + + /** + * Gets the value of the 'occupied' property. + * + * @return the 'occupied' value + */ + boolean isOccupied(); + + /** + * Horizontal half of a bed. + */ + public enum Part { + + /** + * The head is the upper part of the bed containing the pillow. + */ + HEAD, + /** + * The foot is the lower half of the bed. + */ + FOOT; + } +} diff --git a/api/src/main/java/org/bukkit/block/data/type/BrewingStand.java b/api/src/main/java/org/bukkit/block/data/type/BrewingStand.java new file mode 100644 index 000000000..6a7687d52 --- /dev/null +++ b/api/src/main/java/org/bukkit/block/data/type/BrewingStand.java @@ -0,0 +1,45 @@ +package org.bukkit.block.data.type; + +import java.util.Set; +import org.bukkit.block.data.BlockData; +import org.jetbrains.annotations.NotNull; + +/** + * Interface to the 'has_bottle_0', 'has_bottle_1', 'has_bottle_2' flags on a + * brewing stand which indicate which bottles are rendered on the outside. + *
+ * Stand may have 0, 1... {@link #getMaximumBottles()}-1 bottles. + */ +public interface BrewingStand extends BlockData { + + /** + * Checks if the stand has the following bottle + * + * @param bottle to check + * @return if bottle is present + */ + boolean hasBottle(int bottle); + + /** + * Set whether the stand has this bottle present. + * + * @param bottle to set + * @param has bottle + */ + void setBottle(int bottle, boolean has); + + /** + * Get the indexes of all the bottles present on this block. + * + * @return set of all bottles + */ + @NotNull + Set getBottles(); + + /** + * Get the maximum amount of bottles present on this stand. + * + * @return maximum bottle count + */ + int getMaximumBottles(); +} diff --git a/api/src/main/java/org/bukkit/block/data/type/BubbleColumn.java b/api/src/main/java/org/bukkit/block/data/type/BubbleColumn.java new file mode 100644 index 000000000..5ab82d209 --- /dev/null +++ b/api/src/main/java/org/bukkit/block/data/type/BubbleColumn.java @@ -0,0 +1,25 @@ +package org.bukkit.block.data.type; + +import org.bukkit.block.data.BlockData; + +/** + * 'drag' indicates whether a force will be applied on entities moving through + * this block. + */ +public interface BubbleColumn extends BlockData { + + /** + * Gets the value of the 'drag' property. + * + * @return the 'part' value + */ + boolean isDrag(); + + /** + * Sets the value of the 'drag' property. + * + * @param drag the new 'drag' value + */ + void setDrag(boolean drag); + +} diff --git a/api/src/main/java/org/bukkit/block/data/type/Cake.java b/api/src/main/java/org/bukkit/block/data/type/Cake.java new file mode 100644 index 000000000..65c9ea1e8 --- /dev/null +++ b/api/src/main/java/org/bukkit/block/data/type/Cake.java @@ -0,0 +1,34 @@ +package org.bukkit.block.data.type; + +import org.bukkit.block.data.BlockData; + +/** + * 'bites' represents the amount of bites which have been taken from this slice + * of cake. + *
+ * A value of 0 indicates that the cake has not been eaten, whilst a value of + * {@link #getMaximumBites()} indicates that it is all gone :( + */ +public interface Cake extends BlockData { + + /** + * Gets the value of the 'bites' property. + * + * @return the 'bites' value + */ + int getBites(); + + /** + * Sets the value of the 'bites' property. + * + * @param bites the new 'bites' value + */ + void setBites(int bites); + + /** + * Gets the maximum allowed value of the 'bites' property. + * + * @return the maximum 'bites' value + */ + int getMaximumBites(); +} diff --git a/api/src/main/java/org/bukkit/block/data/type/Chest.java b/api/src/main/java/org/bukkit/block/data/type/Chest.java new file mode 100644 index 000000000..d79c3d000 --- /dev/null +++ b/api/src/main/java/org/bukkit/block/data/type/Chest.java @@ -0,0 +1,51 @@ +package org.bukkit.block.data.type; + +import org.bukkit.block.data.Directional; +import org.bukkit.block.data.Waterlogged; +import org.jetbrains.annotations.NotNull; + +/** + * 'type' represents which part of a double chest this block is, or if it is a + * single chest. + */ +public interface Chest extends Directional, Waterlogged { + + /** + * Gets the value of the 'type' property. + * + * @return the 'type' value + */ + @NotNull + Type getType(); + + /** + * Sets the value of the 'type' property. + * + * @param type the new 'type' value + */ + void setType(@NotNull Type type); + + /** + * Type of this chest block. + *
+ * NB: Left and right are relative to the chest itself, i.e opposite to what + * a player placing the appropriate block would see. + */ + public enum Type { + /** + * The chest is not linked to any others and contains only one 27 slot + * inventory. + */ + SINGLE, + /** + * The chest is the left hand side of a double chest and shares a 54 + * block inventory with the chest to its right. + */ + LEFT, + /** + * The chest is the right hand side of a double chest and shares a 54 + * block inventory with the chest to its left. + */ + RIGHT; + } +} diff --git a/api/src/main/java/org/bukkit/block/data/type/Cocoa.java b/api/src/main/java/org/bukkit/block/data/type/Cocoa.java new file mode 100644 index 000000000..092e46c72 --- /dev/null +++ b/api/src/main/java/org/bukkit/block/data/type/Cocoa.java @@ -0,0 +1,7 @@ +package org.bukkit.block.data.type; + +import org.bukkit.block.data.Ageable; +import org.bukkit.block.data.Directional; + +public interface Cocoa extends Ageable, Directional { +} diff --git a/api/src/main/java/org/bukkit/block/data/type/CommandBlock.java b/api/src/main/java/org/bukkit/block/data/type/CommandBlock.java new file mode 100644 index 000000000..9a7122c90 --- /dev/null +++ b/api/src/main/java/org/bukkit/block/data/type/CommandBlock.java @@ -0,0 +1,24 @@ +package org.bukkit.block.data.type; + +import org.bukkit.block.data.Directional; + +/** + * 'conditional' denotes whether this command block is conditional or not, i.e. + * will only execute if the preceeding command block also executed successfully. + */ +public interface CommandBlock extends Directional { + + /** + * Gets the value of the 'conditional' property. + * + * @return the 'conditional' value + */ + boolean isConditional(); + + /** + * Sets the value of the 'conditional' property. + * + * @param conditional the new 'conditional' value + */ + void setConditional(boolean conditional); +} diff --git a/api/src/main/java/org/bukkit/block/data/type/Comparator.java b/api/src/main/java/org/bukkit/block/data/type/Comparator.java new file mode 100644 index 000000000..6cfd29660 --- /dev/null +++ b/api/src/main/java/org/bukkit/block/data/type/Comparator.java @@ -0,0 +1,43 @@ +package org.bukkit.block.data.type; + +import org.bukkit.block.data.Directional; +import org.bukkit.block.data.Powerable; +import org.jetbrains.annotations.NotNull; + +/** + * 'mode' indicates what mode this comparator will operate in. + */ +public interface Comparator extends Directional, Powerable { + + /** + * Gets the value of the 'mode' property. + * + * @return the 'mode' value + */ + @NotNull + Mode getMode(); + + /** + * Sets the value of the 'mode' property. + * + * @param mode the new 'mode' value + */ + void setMode(@NotNull Mode mode); + + /** + * The mode in which a comparator will operate in. + */ + public enum Mode { + + /** + * The default mode, similar to a transistor. The comparator will turn + * off if either side input is greater than the rear input. + */ + COMPARE, + /** + * Alternate subtraction mode. The output signal strength will be equal + * to max(rear-max(left,right),0). + */ + SUBTRACT; + } +} diff --git a/api/src/main/java/org/bukkit/block/data/type/CoralWallFan.java b/api/src/main/java/org/bukkit/block/data/type/CoralWallFan.java new file mode 100644 index 000000000..3b92d4f99 --- /dev/null +++ b/api/src/main/java/org/bukkit/block/data/type/CoralWallFan.java @@ -0,0 +1,7 @@ +package org.bukkit.block.data.type; + +import org.bukkit.block.data.Directional; +import org.bukkit.block.data.Waterlogged; + +public interface CoralWallFan extends Directional, Waterlogged { +} diff --git a/api/src/main/java/org/bukkit/block/data/type/DaylightDetector.java b/api/src/main/java/org/bukkit/block/data/type/DaylightDetector.java new file mode 100644 index 000000000..deefb6c62 --- /dev/null +++ b/api/src/main/java/org/bukkit/block/data/type/DaylightDetector.java @@ -0,0 +1,24 @@ +package org.bukkit.block.data.type; + +import org.bukkit.block.data.AnaloguePowerable; + +/** + * 'inverted' denotes whether this daylight detector is in the inverted mode, + * i.e. activates in the absence of light rather than presence." + */ +public interface DaylightDetector extends AnaloguePowerable { + + /** + * Gets the value of the 'inverted' property. + * + * @return the 'inverted' value + */ + boolean isInverted(); + + /** + * Sets the value of the 'inverted' property. + * + * @param inverted the new 'inverted' value + */ + void setInverted(boolean inverted); +} diff --git a/api/src/main/java/org/bukkit/block/data/type/Dispenser.java b/api/src/main/java/org/bukkit/block/data/type/Dispenser.java new file mode 100644 index 000000000..fea25e1c2 --- /dev/null +++ b/api/src/main/java/org/bukkit/block/data/type/Dispenser.java @@ -0,0 +1,25 @@ +package org.bukkit.block.data.type; + +import org.bukkit.block.data.Directional; +import org.bukkit.block.data.Powerable; + +/** + * Similar to {@link Powerable}, 'triggered' indicates whether or not the + * dispenser is currently activated. + */ +public interface Dispenser extends Directional { + + /** + * Gets the value of the 'triggered' property. + * + * @return the 'triggered' value + */ + boolean isTriggered(); + + /** + * Sets the value of the 'triggered' property. + * + * @param triggered the new 'triggered' value + */ + void setTriggered(boolean triggered); +} diff --git a/api/src/main/java/org/bukkit/block/data/type/Door.java b/api/src/main/java/org/bukkit/block/data/type/Door.java new file mode 100644 index 000000000..5b0bba5ef --- /dev/null +++ b/api/src/main/java/org/bukkit/block/data/type/Door.java @@ -0,0 +1,43 @@ +package org.bukkit.block.data.type; + +import org.bukkit.block.data.Bisected; +import org.bukkit.block.data.Directional; +import org.bukkit.block.data.Openable; +import org.bukkit.block.data.Powerable; +import org.jetbrains.annotations.NotNull; + +/** + * 'hinge' indicates which hinge this door is attached to and will rotate around + * when opened. + */ +public interface Door extends Bisected, Directional, Openable, Powerable { + + /** + * Gets the value of the 'hinge' property. + * + * @return the 'hinge' value + */ + @NotNull + Hinge getHinge(); + + /** + * Sets the value of the 'hinge' property. + * + * @param hinge the new 'hinge' value + */ + void setHinge(@NotNull Hinge hinge); + + /** + * The hinge of a door. + */ + public enum Hinge { + /** + * Door is attached to the left side. + */ + LEFT, + /** + * Door is attached to the right side. + */ + RIGHT; + } +} diff --git a/api/src/main/java/org/bukkit/block/data/type/EndPortalFrame.java b/api/src/main/java/org/bukkit/block/data/type/EndPortalFrame.java new file mode 100644 index 000000000..6c181d633 --- /dev/null +++ b/api/src/main/java/org/bukkit/block/data/type/EndPortalFrame.java @@ -0,0 +1,24 @@ +package org.bukkit.block.data.type; + +import org.bukkit.block.data.Directional; + +/** + * 'eye' denotes whether this end portal frame has been activated by having an + * eye of ender placed in it. + */ +public interface EndPortalFrame extends Directional { + + /** + * Gets the value of the 'eye' property. + * + * @return the 'eye' value + */ + boolean hasEye(); + + /** + * Sets the value of the 'eye' property. + * + * @param eye the new 'eye' value + */ + void setEye(boolean eye); +} diff --git a/api/src/main/java/org/bukkit/block/data/type/EnderChest.java b/api/src/main/java/org/bukkit/block/data/type/EnderChest.java new file mode 100644 index 000000000..5a9e04c4a --- /dev/null +++ b/api/src/main/java/org/bukkit/block/data/type/EnderChest.java @@ -0,0 +1,7 @@ +package org.bukkit.block.data.type; + +import org.bukkit.block.data.Directional; +import org.bukkit.block.data.Waterlogged; + +public interface EnderChest extends Directional, Waterlogged { +} diff --git a/api/src/main/java/org/bukkit/block/data/type/Farmland.java b/api/src/main/java/org/bukkit/block/data/type/Farmland.java new file mode 100644 index 000000000..a9fff6f61 --- /dev/null +++ b/api/src/main/java/org/bukkit/block/data/type/Farmland.java @@ -0,0 +1,34 @@ +package org.bukkit.block.data.type; + +import org.bukkit.block.data.BlockData; + +/** + * The 'moisture' level of farmland indicates how close it is to a water source + * (if any). + *
+ * A higher moisture level leads, to faster growth of crops on this block, but + * cannot be higher than {@link #getMaximumMoisture()}. + */ +public interface Farmland extends BlockData { + + /** + * Gets the value of the 'moisture' property. + * + * @return the 'moisture' value + */ + int getMoisture(); + + /** + * Sets the value of the 'moisture' property. + * + * @param moisture the new 'moisture' value + */ + void setMoisture(int moisture); + + /** + * Gets the maximum allowed value of the 'moisture' property. + * + * @return the maximum 'moisture' value + */ + int getMaximumMoisture(); +} diff --git a/api/src/main/java/org/bukkit/block/data/type/Fence.java b/api/src/main/java/org/bukkit/block/data/type/Fence.java new file mode 100644 index 000000000..f2c627421 --- /dev/null +++ b/api/src/main/java/org/bukkit/block/data/type/Fence.java @@ -0,0 +1,7 @@ +package org.bukkit.block.data.type; + +import org.bukkit.block.data.MultipleFacing; +import org.bukkit.block.data.Waterlogged; + +public interface Fence extends MultipleFacing, Waterlogged { +} diff --git a/api/src/main/java/org/bukkit/block/data/type/Fire.java b/api/src/main/java/org/bukkit/block/data/type/Fire.java new file mode 100644 index 000000000..6282d021e --- /dev/null +++ b/api/src/main/java/org/bukkit/block/data/type/Fire.java @@ -0,0 +1,10 @@ +package org.bukkit.block.data.type; + +import org.bukkit.block.data.Ageable; +import org.bukkit.block.data.MultipleFacing; + +/** + * md_5's mixtape. + */ +public interface Fire extends Ageable, MultipleFacing { +} diff --git a/api/src/main/java/org/bukkit/block/data/type/Furnace.java b/api/src/main/java/org/bukkit/block/data/type/Furnace.java new file mode 100644 index 000000000..830366ce0 --- /dev/null +++ b/api/src/main/java/org/bukkit/block/data/type/Furnace.java @@ -0,0 +1,7 @@ +package org.bukkit.block.data.type; + +import org.bukkit.block.data.Directional; +import org.bukkit.block.data.Lightable; + +public interface Furnace extends Directional, Lightable { +} diff --git a/api/src/main/java/org/bukkit/block/data/type/Gate.java b/api/src/main/java/org/bukkit/block/data/type/Gate.java new file mode 100644 index 000000000..494f97d47 --- /dev/null +++ b/api/src/main/java/org/bukkit/block/data/type/Gate.java @@ -0,0 +1,26 @@ +package org.bukkit.block.data.type; + +import org.bukkit.block.data.Directional; +import org.bukkit.block.data.Openable; +import org.bukkit.block.data.Powerable; + +/** + * 'in_wall" indicates if the fence gate is attached to a wall, and if true the + * texture is lowered by a small amount to blend in better. + */ +public interface Gate extends Directional, Openable, Powerable { + + /** + * Gets the value of the 'in_wall' property. + * + * @return the 'in_wall' value + */ + boolean isInWall(); + + /** + * Sets the value of the 'in_wall' property. + * + * @param inWall the new 'in_wall' value + */ + void setInWall(boolean inWall); +} diff --git a/api/src/main/java/org/bukkit/block/data/type/GlassPane.java b/api/src/main/java/org/bukkit/block/data/type/GlassPane.java new file mode 100644 index 000000000..44eaa553c --- /dev/null +++ b/api/src/main/java/org/bukkit/block/data/type/GlassPane.java @@ -0,0 +1,7 @@ +package org.bukkit.block.data.type; + +import org.bukkit.block.data.MultipleFacing; +import org.bukkit.block.data.Waterlogged; + +public interface GlassPane extends MultipleFacing, Waterlogged { +} diff --git a/api/src/main/java/org/bukkit/block/data/type/Hopper.java b/api/src/main/java/org/bukkit/block/data/type/Hopper.java new file mode 100644 index 000000000..b176fc8b6 --- /dev/null +++ b/api/src/main/java/org/bukkit/block/data/type/Hopper.java @@ -0,0 +1,28 @@ +package org.bukkit.block.data.type; + +import org.bukkit.block.data.Directional; +import org.bukkit.block.data.Powerable; + +/** + * Similar to {@link Powerable}, 'enabled' indicates whether or not the hopper + * is currently activated. + *
+ * Unlike most other blocks, a hopper is only enabled when it is not + * receiving any power. + */ +public interface Hopper extends Directional { + + /** + * Gets the value of the 'enabled' property. + * + * @return the 'enabled' value + */ + boolean isEnabled(); + + /** + * Sets the value of the 'enabled' property. + * + * @param enabled the new 'enabled' value + */ + void setEnabled(boolean enabled); +} diff --git a/api/src/main/java/org/bukkit/block/data/type/Jukebox.java b/api/src/main/java/org/bukkit/block/data/type/Jukebox.java new file mode 100644 index 000000000..5e3713620 --- /dev/null +++ b/api/src/main/java/org/bukkit/block/data/type/Jukebox.java @@ -0,0 +1,17 @@ +package org.bukkit.block.data.type; + +import org.bukkit.block.data.BlockData; + +/** + * 'has_record' is a quick flag to check whether this jukebox has a record + * inside it. + */ +public interface Jukebox extends BlockData { + + /** + * Gets the value of the 'has_record' property. + * + * @return the 'has_record' value + */ + boolean hasRecord(); +} diff --git a/api/src/main/java/org/bukkit/block/data/type/Ladder.java b/api/src/main/java/org/bukkit/block/data/type/Ladder.java new file mode 100644 index 000000000..ee1b340f2 --- /dev/null +++ b/api/src/main/java/org/bukkit/block/data/type/Ladder.java @@ -0,0 +1,7 @@ +package org.bukkit.block.data.type; + +import org.bukkit.block.data.Directional; +import org.bukkit.block.data.Waterlogged; + +public interface Ladder extends Directional, Waterlogged { +} diff --git a/api/src/main/java/org/bukkit/block/data/type/Leaves.java b/api/src/main/java/org/bukkit/block/data/type/Leaves.java new file mode 100644 index 000000000..3874d5d96 --- /dev/null +++ b/api/src/main/java/org/bukkit/block/data/type/Leaves.java @@ -0,0 +1,42 @@ +package org.bukkit.block.data.type; + +import org.bukkit.block.data.BlockData; + +/** + * 'persistent' indicates whether or not leaves will be checked by the server to + * see if they are subject to decay or not. + *
+ * 'distance' denotes how far the block is from a tree and is used in + * conjunction with 'persistent' flag to determine if the leaves will decay or + * not. + */ +public interface Leaves extends BlockData { + + /** + * Gets the value of the 'persistent' property. + * + * @return the persistent value + */ + boolean isPersistent(); + + /** + * Sets the value of the 'persistent' property. + * + * @param persistent the new 'persistent' value + */ + void setPersistent(boolean persistent); + + /** + * Gets the value of the 'distance' property. + * + * @return the 'distance' value + */ + int getDistance(); + + /** + * Sets the value of the 'distance' property. + * + * @param distance the new 'distance' value + */ + void setDistance(int distance); +} diff --git a/api/src/main/java/org/bukkit/block/data/type/NoteBlock.java b/api/src/main/java/org/bukkit/block/data/type/NoteBlock.java new file mode 100644 index 000000000..ec48ca8ca --- /dev/null +++ b/api/src/main/java/org/bukkit/block/data/type/NoteBlock.java @@ -0,0 +1,44 @@ +package org.bukkit.block.data.type; + +import org.bukkit.Instrument; +import org.bukkit.Note; +import org.bukkit.block.data.Powerable; +import org.jetbrains.annotations.NotNull; + +/** + * 'instrument' is the type of sound made when this note block is activated. + *
+ * 'note' is the specified tuned pitch that the instrument will be played in. + */ +public interface NoteBlock extends Powerable { + + /** + * Gets the value of the 'instrument' property. + * + * @return the 'instrument' value + */ + @NotNull + Instrument getInstrument(); + + /** + * Sets the value of the 'instrument' property. + * + * @param instrument the new 'instrument' value + */ + void setInstrument(@NotNull Instrument instrument); + + /** + * Gets the value of the 'note' property. + * + * @return the 'note' value + */ + @NotNull + Note getNote(); + + /** + * Sets the value of the 'note' property. + * + * @param note the new 'note' value + */ + void setNote(@NotNull Note note); +} diff --git a/api/src/main/java/org/bukkit/block/data/type/Observer.java b/api/src/main/java/org/bukkit/block/data/type/Observer.java new file mode 100644 index 000000000..f40c73500 --- /dev/null +++ b/api/src/main/java/org/bukkit/block/data/type/Observer.java @@ -0,0 +1,7 @@ +package org.bukkit.block.data.type; + +import org.bukkit.block.data.Directional; +import org.bukkit.block.data.Powerable; + +public interface Observer extends Directional, Powerable { +} diff --git a/api/src/main/java/org/bukkit/block/data/type/Piston.java b/api/src/main/java/org/bukkit/block/data/type/Piston.java new file mode 100644 index 000000000..9eec7317b --- /dev/null +++ b/api/src/main/java/org/bukkit/block/data/type/Piston.java @@ -0,0 +1,23 @@ +package org.bukkit.block.data.type; + +import org.bukkit.block.data.Directional; + +/** + * 'extended' denotes whether the piston head is currently extended or not. + */ +public interface Piston extends Directional { + + /** + * Gets the value of the 'extended' property. + * + * @return the 'extended' value + */ + boolean isExtended(); + + /** + * Sets the value of the 'extended' property. + * + * @param extended the new 'extended' value + */ + void setExtended(boolean extended); +} diff --git a/api/src/main/java/org/bukkit/block/data/type/PistonHead.java b/api/src/main/java/org/bukkit/block/data/type/PistonHead.java new file mode 100644 index 000000000..d1eaa20a0 --- /dev/null +++ b/api/src/main/java/org/bukkit/block/data/type/PistonHead.java @@ -0,0 +1,22 @@ +package org.bukkit.block.data.type; + +/** + * 'short' denotes this piston head is shorter than the usual amount because it + * is currently retracting. + */ +public interface PistonHead extends TechnicalPiston { + + /** + * Gets the value of the 'short' property. + * + * @return the 'short' value + */ + boolean isShort(); + + /** + * Sets the value of the 'short' property. + * + * @param _short the new 'short' value + */ + void setShort(boolean _short); +} diff --git a/api/src/main/java/org/bukkit/block/data/type/RedstoneRail.java b/api/src/main/java/org/bukkit/block/data/type/RedstoneRail.java new file mode 100644 index 000000000..1cf7630c3 --- /dev/null +++ b/api/src/main/java/org/bukkit/block/data/type/RedstoneRail.java @@ -0,0 +1,10 @@ +package org.bukkit.block.data.type; + +import org.bukkit.block.data.Powerable; +import org.bukkit.block.data.Rail; + +/** + * A type of minecart rail which interacts with redstone in one way or another. + */ +public interface RedstoneRail extends Powerable, Rail { +} diff --git a/api/src/main/java/org/bukkit/block/data/type/RedstoneWallTorch.java b/api/src/main/java/org/bukkit/block/data/type/RedstoneWallTorch.java new file mode 100644 index 000000000..7e1e09859 --- /dev/null +++ b/api/src/main/java/org/bukkit/block/data/type/RedstoneWallTorch.java @@ -0,0 +1,7 @@ +package org.bukkit.block.data.type; + +import org.bukkit.block.data.Directional; +import org.bukkit.block.data.Lightable; + +public interface RedstoneWallTorch extends Directional, Lightable { +} diff --git a/api/src/main/java/org/bukkit/block/data/type/RedstoneWire.java b/api/src/main/java/org/bukkit/block/data/type/RedstoneWire.java new file mode 100644 index 000000000..ba8a80ee3 --- /dev/null +++ b/api/src/main/java/org/bukkit/block/data/type/RedstoneWire.java @@ -0,0 +1,56 @@ +package org.bukkit.block.data.type; + +import java.util.Set; +import org.bukkit.block.BlockFace; +import org.bukkit.block.data.AnaloguePowerable; +import org.jetbrains.annotations.NotNull; + +/** + * 'north', 'east', 'south', 'west' represent the types of connections this + * redstone wire has to adjacent blocks. + */ +public interface RedstoneWire extends AnaloguePowerable { + + /** + * Checks the type of connection on the specified face. + * + * @param face to check + * @return connection type + */ + @NotNull + Connection getFace(@NotNull BlockFace face); + + /** + * Sets the type of connection on the specified face. + * + * @param face to set + * @param connection the connection type + */ + void setFace(@NotNull BlockFace face, @NotNull Connection connection); + + /** + * Gets all of this faces which may be set on this block. + * + * @return all allowed faces + */ + @NotNull + Set getAllowedFaces(); + + /** + * The way in which a redstone wire can connect to an adjacent block face. + */ + public enum Connection { + /** + * The wire travels up the side of the block adjacent to this face. + */ + UP, + /** + * The wire travels flat from this face and into the adjacent block. + */ + SIDE, + /** + * The wire does not connect in this direction. + */ + NONE; + } +} diff --git a/api/src/main/java/org/bukkit/block/data/type/Repeater.java b/api/src/main/java/org/bukkit/block/data/type/Repeater.java new file mode 100644 index 000000000..a78563ed8 --- /dev/null +++ b/api/src/main/java/org/bukkit/block/data/type/Repeater.java @@ -0,0 +1,62 @@ +package org.bukkit.block.data.type; + +import org.bukkit.block.data.Directional; +import org.bukkit.block.data.Powerable; + +/** + * 'delay' is the propagation delay of a repeater, i.e. how many ticks before it + * will be activated from a current change and propagate it to the next block. + *
+ * Delay may not be lower than {@link #getMinimumDelay()} or higher than + * {@link #getMaximumDelay()}. + *
+ * 'locked' denotes whether the repeater is in the locked state or not. + *
+ * A locked repeater will not change its output until it is unlocked. In game, a + * locked repeater is created by having a constant current perpendicularly + * entering the block. + */ +public interface Repeater extends Directional, Powerable { + + /** + * Gets the value of the 'delay' property. + * + * @return the 'delay' value + */ + int getDelay(); + + /** + * Sets the value of the 'delay' property. + * + * @param delay the new 'delay' value + */ + void setDelay(int delay); + + /** + * Gets the minimum allowed value of the 'delay' property. + * + * @return the minimum 'delay' value + */ + int getMinimumDelay(); + + /** + * Gets the maximum allowed value of the 'delay' property. + * + * @return the maximum 'delay' value + */ + int getMaximumDelay(); + + /** + * Gets the value of the 'locked' property. + * + * @return the 'locked' value + */ + boolean isLocked(); + + /** + * Sets the value of the 'locked' property. + * + * @param locked the new 'locked' value + */ + void setLocked(boolean locked); +} diff --git a/api/src/main/java/org/bukkit/block/data/type/Sapling.java b/api/src/main/java/org/bukkit/block/data/type/Sapling.java new file mode 100644 index 000000000..a4e1c2f4c --- /dev/null +++ b/api/src/main/java/org/bukkit/block/data/type/Sapling.java @@ -0,0 +1,33 @@ +package org.bukkit.block.data.type; + +import org.bukkit.block.data.BlockData; + +/** + * 'stage' represents the growth stage of a sapling. + *
+ * When the sapling reaches {@link #getMaximumStage()} it will attempt to grow + * into a tree as the next stage. + */ +public interface Sapling extends BlockData { + + /** + * Gets the value of the 'stage' property. + * + * @return the 'stage' value + */ + int getStage(); + + /** + * Sets the value of the 'stage' property. + * + * @param stage the new 'stage' value + */ + void setStage(int stage); + + /** + * Gets the maximum allowed value of the 'stage' property. + * + * @return the maximum 'stage' value + */ + int getMaximumStage(); +} diff --git a/api/src/main/java/org/bukkit/block/data/type/SeaPickle.java b/api/src/main/java/org/bukkit/block/data/type/SeaPickle.java new file mode 100644 index 000000000..62013c6af --- /dev/null +++ b/api/src/main/java/org/bukkit/block/data/type/SeaPickle.java @@ -0,0 +1,37 @@ +package org.bukkit.block.data.type; + +import org.bukkit.block.data.Waterlogged; + +/** + * 'pickles' indicates the number of pickles in this block. + */ +public interface SeaPickle extends Waterlogged { + + /** + * Gets the value of the 'pickles' property. + * + * @return the 'pickles' value + */ + int getPickles(); + + /** + * Sets the value of the 'pickles' property. + * + * @param pickles the new 'pickles' value + */ + void setPickles(int pickles); + + /** + * Gets the minimum allowed value of the 'pickles' property. + * + * @return the minimum 'pickles' value + */ + int getMinimumPickles(); + + /** + * Gets the maximum allowed value of the 'pickles' property. + * + * @return the maximum 'pickles' value + */ + int getMaximumPickles(); +} diff --git a/api/src/main/java/org/bukkit/block/data/type/Sign.java b/api/src/main/java/org/bukkit/block/data/type/Sign.java new file mode 100644 index 000000000..29ef2c5bf --- /dev/null +++ b/api/src/main/java/org/bukkit/block/data/type/Sign.java @@ -0,0 +1,7 @@ +package org.bukkit.block.data.type; + +import org.bukkit.block.data.Rotatable; +import org.bukkit.block.data.Waterlogged; + +public interface Sign extends Rotatable, Waterlogged { +} diff --git a/api/src/main/java/org/bukkit/block/data/type/Slab.java b/api/src/main/java/org/bukkit/block/data/type/Slab.java new file mode 100644 index 000000000..cb09c3268 --- /dev/null +++ b/api/src/main/java/org/bukkit/block/data/type/Slab.java @@ -0,0 +1,44 @@ +package org.bukkit.block.data.type; + +import org.bukkit.block.data.Waterlogged; +import org.jetbrains.annotations.NotNull; + +/** + * 'type' represents what state the slab is in - either top, bottom, or a double + * slab occupying the full block. + */ +public interface Slab extends Waterlogged { + + /** + * Gets the value of the 'type' property. + * + * @return the 'type' value + */ + @NotNull + Type getType(); + + /** + * Sets the value of the 'type' property. + * + * @param type the new 'type' value + */ + void setType(@NotNull Type type); + + /** + * The type of the slab. + */ + public enum Type { + /** + * The slab occupies the upper y half of the block. + */ + TOP, + /** + * The slab occupies the lower y half of the block. + */ + BOTTOM, + /** + * The slab occupies the entire block. + */ + DOUBLE; + } +} diff --git a/api/src/main/java/org/bukkit/block/data/type/Snow.java b/api/src/main/java/org/bukkit/block/data/type/Snow.java new file mode 100644 index 000000000..9c089cff2 --- /dev/null +++ b/api/src/main/java/org/bukkit/block/data/type/Snow.java @@ -0,0 +1,41 @@ +package org.bukkit.block.data.type; + +import org.bukkit.block.data.BlockData; + +/** + * 'layers' represents the amount of layers of snow which are present in this + * block. + *
+ * May not be lower than {@link #getMinimumLayers()} or higher than + * {@link #getMaximumLayers()}. + */ +public interface Snow extends BlockData { + + /** + * Gets the value of the 'layers' property. + * + * @return the 'layers' value + */ + int getLayers(); + + /** + * Sets the value of the 'layers' property. + * + * @param layers the new 'layers' value + */ + void setLayers(int layers); + + /** + * Gets the minimum allowed value of the 'layers' property. + * + * @return the minimum 'layers' value + */ + int getMinimumLayers(); + + /** + * Gets the maximum allowed value of the 'layers' property. + * + * @return the maximum 'layers' value + */ + int getMaximumLayers(); +} diff --git a/api/src/main/java/org/bukkit/block/data/type/Stairs.java b/api/src/main/java/org/bukkit/block/data/type/Stairs.java new file mode 100644 index 000000000..8bc553956 --- /dev/null +++ b/api/src/main/java/org/bukkit/block/data/type/Stairs.java @@ -0,0 +1,53 @@ +package org.bukkit.block.data.type; + +import org.bukkit.block.data.Bisected; +import org.bukkit.block.data.Directional; +import org.bukkit.block.data.Waterlogged; +import org.jetbrains.annotations.NotNull; + +/** + * 'shape' represents the texture and bounding box shape of these stairs. + */ +public interface Stairs extends Bisected, Directional, Waterlogged { + + /** + * Gets the value of the 'shape' property. + * + * @return the 'shape' value + */ + @NotNull + Shape getShape(); + + /** + * Sets the value of the 'shape' property. + * + * @param shape the new 'shape' value + */ + void setShape(@NotNull Shape shape); + + /** + * The shape of a stair block - used for constructing corners. + */ + public enum Shape { + /** + * Regular stair block. + */ + STRAIGHT, + /** + * Inner corner stair block with higher left side. + */ + INNER_LEFT, + /** + * Inner corner stair block with higher right side. + */ + INNER_RIGHT, + /** + * Outer corner stair block with higher left side. + */ + OUTER_LEFT, + /** + * Outer corner stair block with higher right side. + */ + OUTER_RIGHT; + } +} diff --git a/api/src/main/java/org/bukkit/block/data/type/StructureBlock.java b/api/src/main/java/org/bukkit/block/data/type/StructureBlock.java new file mode 100644 index 000000000..3c13a9a56 --- /dev/null +++ b/api/src/main/java/org/bukkit/block/data/type/StructureBlock.java @@ -0,0 +1,49 @@ +package org.bukkit.block.data.type; + +import org.bukkit.block.data.BlockData; +import org.jetbrains.annotations.NotNull; + +/** + * 'mode' represents the different modes in which this structure block may + * operate. + */ +public interface StructureBlock extends BlockData { + + /** + * Gets the value of the 'mode' property. + * + * @return the 'mode' value + */ + @NotNull + Mode getMode(); + + /** + * Sets the value of the 'mode' property. + * + * @param mode the new 'mode' value + */ + void setMode(@NotNull Mode mode); + + /** + * Operating mode of a structure block. + */ + public enum Mode { + /** + * Allows selection and saving of a structure. + */ + SAVE, + /** + * Allows loading of a structure. + */ + LOAD, + /** + * Used for detection of two opposite corners of a structure. + */ + CORNER, + /** + * Dummy block used to run a custom function during world generation + * before being removed. + */ + DATA; + } +} diff --git a/api/src/main/java/org/bukkit/block/data/type/Switch.java b/api/src/main/java/org/bukkit/block/data/type/Switch.java new file mode 100644 index 000000000..1060cb55b --- /dev/null +++ b/api/src/main/java/org/bukkit/block/data/type/Switch.java @@ -0,0 +1,47 @@ +package org.bukkit.block.data.type; + +import org.bukkit.block.data.Directional; +import org.bukkit.block.data.Powerable; +import org.jetbrains.annotations.NotNull; + +/** + * 'face' represents the face to which a lever or button is stuck. + *
+ * This is used in conjunction with {@link Directional} to compute the + * orientation of these blocks. + */ +public interface Switch extends Directional, Powerable { + + /** + * Gets the value of the 'face' property. + * + * @return the 'face' value + */ + @NotNull + Face getFace(); + + /** + * Sets the value of the 'face' property. + * + * @param face the new 'face' value + */ + void setFace(@NotNull Face face); + + /** + * The face to which a switch type block is stuck. + */ + public enum Face { + /** + * The switch is mounted to the floor and pointing upwards. + */ + FLOOR, + /** + * The switch is mounted to the wall. + */ + WALL, + /** + * The switch is mounted to the ceiling and pointing dowanrds. + */ + CEILING; + } +} diff --git a/api/src/main/java/org/bukkit/block/data/type/TNT.java b/api/src/main/java/org/bukkit/block/data/type/TNT.java new file mode 100644 index 000000000..7008c20fd --- /dev/null +++ b/api/src/main/java/org/bukkit/block/data/type/TNT.java @@ -0,0 +1,23 @@ +package org.bukkit.block.data.type; + +import org.bukkit.block.data.BlockData; + +/** + * 'unstable' indicates whether this TNT will explode on punching. + */ +public interface TNT extends BlockData { + + /** + * Gets the value of the 'unstable' property. + * + * @return the 'unstable' value + */ + boolean isUnstable(); + + /** + * Sets the value of the 'unstable' property. + * + * @param unstable the new 'unstable' value + */ + void setUnstable(boolean unstable); +} diff --git a/api/src/main/java/org/bukkit/block/data/type/TechnicalPiston.java b/api/src/main/java/org/bukkit/block/data/type/TechnicalPiston.java new file mode 100644 index 000000000..4c8b6e9be --- /dev/null +++ b/api/src/main/java/org/bukkit/block/data/type/TechnicalPiston.java @@ -0,0 +1,41 @@ +package org.bukkit.block.data.type; + +import org.bukkit.block.data.Directional; +import org.jetbrains.annotations.NotNull; + +/** + * 'type' represents the type of piston which this (technical) block corresponds + * to. + */ +public interface TechnicalPiston extends Directional { + + /** + * Gets the value of the 'type' property. + * + * @return the 'type' value + */ + @NotNull + Type getType(); + + /** + * Sets the value of the 'type' property. + * + * @param type the new 'type' value + */ + void setType(@NotNull Type type); + + /** + * Different piston variants. + */ + public enum Type { + /** + * A normal piston which does not pull connected blocks backwards on + * retraction. + */ + NORMAL, + /** + * A sticky piston which will also retract connected blocks. + */ + STICKY; + } +} diff --git a/api/src/main/java/org/bukkit/block/data/type/TrapDoor.java b/api/src/main/java/org/bukkit/block/data/type/TrapDoor.java new file mode 100644 index 000000000..13876b379 --- /dev/null +++ b/api/src/main/java/org/bukkit/block/data/type/TrapDoor.java @@ -0,0 +1,10 @@ +package org.bukkit.block.data.type; + +import org.bukkit.block.data.Bisected; +import org.bukkit.block.data.Directional; +import org.bukkit.block.data.Openable; +import org.bukkit.block.data.Powerable; +import org.bukkit.block.data.Waterlogged; + +public interface TrapDoor extends Bisected, Directional, Openable, Powerable, Waterlogged { +} diff --git a/api/src/main/java/org/bukkit/block/data/type/Tripwire.java b/api/src/main/java/org/bukkit/block/data/type/Tripwire.java new file mode 100644 index 000000000..567f6f00f --- /dev/null +++ b/api/src/main/java/org/bukkit/block/data/type/Tripwire.java @@ -0,0 +1,26 @@ +package org.bukkit.block.data.type; + +import org.bukkit.block.data.Attachable; +import org.bukkit.block.data.MultipleFacing; +import org.bukkit.block.data.Powerable; + +/** + * 'disarmed' denotes that the tripwire was broken with shears and will not + * subsequently produce a current when destroyed. + */ +public interface Tripwire extends Attachable, MultipleFacing, Powerable { + + /** + * Gets the value of the 'disarmed' property. + * + * @return the 'disarmed' value + */ + boolean isDisarmed(); + + /** + * Sets the value of the 'disarmed' property. + * + * @param disarmed the new 'disarmed' value + */ + void setDisarmed(boolean disarmed); +} diff --git a/api/src/main/java/org/bukkit/block/data/type/TripwireHook.java b/api/src/main/java/org/bukkit/block/data/type/TripwireHook.java new file mode 100644 index 000000000..1e450761f --- /dev/null +++ b/api/src/main/java/org/bukkit/block/data/type/TripwireHook.java @@ -0,0 +1,8 @@ +package org.bukkit.block.data.type; + +import org.bukkit.block.data.Attachable; +import org.bukkit.block.data.Directional; +import org.bukkit.block.data.Powerable; + +public interface TripwireHook extends Attachable, Directional, Powerable { +} diff --git a/api/src/main/java/org/bukkit/block/data/type/TurtleEgg.java b/api/src/main/java/org/bukkit/block/data/type/TurtleEgg.java new file mode 100644 index 000000000..a88fad4af --- /dev/null +++ b/api/src/main/java/org/bukkit/block/data/type/TurtleEgg.java @@ -0,0 +1,60 @@ +package org.bukkit.block.data.type; + +import org.bukkit.block.data.BlockData; + +/** + * 'hatch' is the number of turtles which may hatch from these eggs. + *
+ * 'eggs' is the number of eggs which appear in this block. + */ +public interface TurtleEgg extends BlockData { + + /** + * Gets the value of the 'eggs' property. + * + * @return the 'eggs' value + */ + int getEggs(); + + /** + * Sets the value of the 'eggs' property. + * + * @param eggs the new 'eggs' value + */ + void setEggs(int eggs); + + /** + * Gets the minimum allowed value of the 'eggs' property. + * + * @return the minimum 'eggs' value + */ + int getMinimumEggs(); + + /** + * Gets the maximum allowed value of the 'eggs' property. + * + * @return the maximum 'eggs' value + */ + int getMaximumEggs(); + + /** + * Gets the value of the 'hatch' property. + * + * @return the 'hatch' value + */ + int getHatch(); + + /** + * Sets the value of the 'hatch' property. + * + * @param hatch the new 'hatch' value + */ + void setHatch(int hatch); + + /** + * Gets the maximum allowed value of the 'hatch' property. + * + * @return the maximum 'hatch' value + */ + int getMaximumHatch(); +} diff --git a/api/src/main/java/org/bukkit/block/data/type/WallSign.java b/api/src/main/java/org/bukkit/block/data/type/WallSign.java new file mode 100644 index 000000000..66238859d --- /dev/null +++ b/api/src/main/java/org/bukkit/block/data/type/WallSign.java @@ -0,0 +1,7 @@ +package org.bukkit.block.data.type; + +import org.bukkit.block.data.Directional; +import org.bukkit.block.data.Waterlogged; + +public interface WallSign extends Directional, Waterlogged { +} diff --git a/api/src/main/java/org/bukkit/block/structure/Mirror.java b/api/src/main/java/org/bukkit/block/structure/Mirror.java new file mode 100644 index 000000000..86e812a1b --- /dev/null +++ b/api/src/main/java/org/bukkit/block/structure/Mirror.java @@ -0,0 +1,27 @@ +package org.bukkit.block.structure; + +/** + * Represents how a {@link org.bukkit.block.Structure} can be mirrored upon + * being loaded. + */ +public enum Mirror { + + /** + * No mirroring. + *
+ * Positive X to Positive Z + */ + NONE, + /** + * Structure is mirrored left to right. + *
+ * Similar to looking in a mirror. Positive X to Negative Z + */ + LEFT_RIGHT, + /** + * Structure is mirrored front to back. + *
+ * Positive Z to Negative X + */ + FRONT_BACK; +} diff --git a/api/src/main/java/org/bukkit/block/structure/StructureRotation.java b/api/src/main/java/org/bukkit/block/structure/StructureRotation.java new file mode 100644 index 000000000..0d0bfca43 --- /dev/null +++ b/api/src/main/java/org/bukkit/block/structure/StructureRotation.java @@ -0,0 +1,26 @@ +package org.bukkit.block.structure; + +/** + * Represents how a {@link org.bukkit.block.Structure} can be rotated. + */ +public enum StructureRotation { + + /** + * No rotation. + */ + NONE, + /** + * Rotated clockwise 90 degrees. + */ + CLOCKWISE_90, + /** + * Rotated clockwise 180 degrees. + */ + CLOCKWISE_180, + /** + * Rotated counter clockwise 90 degrees. + *
+ * Equivalent to rotating clockwise 270 degrees. + */ + COUNTERCLOCKWISE_90; +} diff --git a/api/src/main/java/org/bukkit/block/structure/UsageMode.java b/api/src/main/java/org/bukkit/block/structure/UsageMode.java new file mode 100644 index 000000000..cbea3f386 --- /dev/null +++ b/api/src/main/java/org/bukkit/block/structure/UsageMode.java @@ -0,0 +1,29 @@ +package org.bukkit.block.structure; + +/** + * Represents how a {@link org.bukkit.block.Structure} can be used. + */ +public enum UsageMode { + + /** + * The mode used when saving a structure. + */ + SAVE, + /** + * The mode used when loading a structure. + */ + LOAD, + /** + * Used when saving a structure for easy size calculation. When using this + * mode, the Structure name MUST match the name in the second Structure + * block that is in {@link UsageMode#SAVE}. + */ + CORNER, + /** + * Used to run specific custom functions, which can only be used for certain + * Structures. The structure block is removed after this function completes. + * The data tags (functions) can be found on the + * wiki. + */ + DATA; +} diff --git a/api/src/main/java/org/bukkit/boss/BarColor.java b/api/src/main/java/org/bukkit/boss/BarColor.java new file mode 100644 index 000000000..e191d9ffe --- /dev/null +++ b/api/src/main/java/org/bukkit/boss/BarColor.java @@ -0,0 +1,11 @@ +package org.bukkit.boss; + +public enum BarColor { + PINK, + BLUE, + RED, + GREEN, + YELLOW, + PURPLE, + WHITE +} diff --git a/api/src/main/java/org/bukkit/boss/BarFlag.java b/api/src/main/java/org/bukkit/boss/BarFlag.java new file mode 100644 index 000000000..69e02998d --- /dev/null +++ b/api/src/main/java/org/bukkit/boss/BarFlag.java @@ -0,0 +1,17 @@ +package org.bukkit.boss; + +public enum BarFlag { + + /** + * Darkens the sky like during fighting a wither. + */ + DARKEN_SKY, + /** + * Tells the client to play the Ender Dragon boss music. + */ + PLAY_BOSS_MUSIC, + /** + * Creates fog around the world. + */ + CREATE_FOG, +} diff --git a/api/src/main/java/org/bukkit/boss/BarStyle.java b/api/src/main/java/org/bukkit/boss/BarStyle.java new file mode 100644 index 000000000..3e499eb77 --- /dev/null +++ b/api/src/main/java/org/bukkit/boss/BarStyle.java @@ -0,0 +1,24 @@ +package org.bukkit.boss; + +public enum BarStyle { + /** + * Makes the boss bar solid (no segments) + */ + SOLID, + /** + * Splits the boss bar into 6 segments + */ + SEGMENTED_6, + /** + * Splits the boss bar into 10 segments + */ + SEGMENTED_10, + /** + * Splits the boss bar into 12 segments + */ + SEGMENTED_12, + /** + * Splits the boss bar into 20 segments + */ + SEGMENTED_20, +} diff --git a/api/src/main/java/org/bukkit/boss/BossBar.java b/api/src/main/java/org/bukkit/boss/BossBar.java new file mode 100644 index 000000000..acaf8ff0b --- /dev/null +++ b/api/src/main/java/org/bukkit/boss/BossBar.java @@ -0,0 +1,151 @@ +package org.bukkit.boss; + +import org.bukkit.entity.Player; +import org.jetbrains.annotations.Contract; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +import java.util.List; + +public interface BossBar { + + /** + * Returns the title of this boss bar + * + * @return the title of the bar + */ + @NotNull + String getTitle(); + + /** + * Sets the title of this boss bar + * + * @param title the title of the bar + */ + void setTitle(@Nullable String title); + + /** + * Returns the color of this boss bar + * + * @return the color of the bar + */ + @NotNull + BarColor getColor(); + + /** + * Sets the color of this boss bar. + * + * @param color the color of the bar + */ + void setColor(@NotNull BarColor color); + + /** + * Returns the style of this boss bar + * + * @return the style of the bar + */ + @NotNull + BarStyle getStyle(); + + /** + * Sets the bar style of this boss bar + * + * @param style the style of the bar + */ + void setStyle(@NotNull BarStyle style); + + /** + * Remove an existing flag on this boss bar + * + * @param flag the existing flag to remove + */ + void removeFlag(@NotNull BarFlag flag); + + /** + * Add an optional flag to this boss bar + * + * @param flag an optional flag to set on the boss bar + */ + void addFlag(@NotNull BarFlag flag); + + /** + * Returns whether this boss bar as the passed flag set + * + * @param flag the flag to check + * @return whether it has the flag + */ + boolean hasFlag(@NotNull BarFlag flag); + + /** + * Sets the progress of the bar. Values should be between 0.0 (empty) and + * 1.0 (full) + * + * @param progress the progress of the bar + */ + void setProgress(double progress); + + /** + * Returns the progress of the bar between 0.0 and 1.0 + * + * @return the progress of the bar + */ + double getProgress(); + + /** + * Adds the player to this boss bar causing it to display on their screen. + * + * @param player the player to add + */ + void addPlayer(@NotNull Player player); + + /** + * Removes the player from this boss bar causing it to be removed from their + * screen. + * + * @param player the player to remove + */ + void removePlayer(@NotNull Player player); + + /** + * Removes all players from this boss bar + * + * @see #removePlayer(Player) + */ + void removeAll(); + + /** + * Returns all players viewing this boss bar + * + * @return a immutable list of players + */ + @NotNull + List getPlayers(); + + /** + * Set if the boss bar is displayed to attached players. + * + * @param visible visible status + */ + void setVisible(boolean visible); + + /** + * Return if the boss bar is displayed to attached players. + * + * @return visible status + */ + boolean isVisible(); + + /** + * Shows the previously hidden boss bar to all attached players + * @deprecated {@link #setVisible(boolean)} + */ + @Deprecated + void show(); + + /** + * Hides this boss bar from all attached players + * @deprecated {@link #setVisible(boolean)} + */ + @Deprecated + void hide(); +} diff --git a/api/src/main/java/org/bukkit/boss/KeyedBossBar.java b/api/src/main/java/org/bukkit/boss/KeyedBossBar.java new file mode 100644 index 000000000..6a1fe5f91 --- /dev/null +++ b/api/src/main/java/org/bukkit/boss/KeyedBossBar.java @@ -0,0 +1,9 @@ +package org.bukkit.boss; + +import org.bukkit.Keyed; + +/** + * Represents a custom {@link BossBar} that has a + * {@link org.bukkit.NamespacedKey} + */ +public interface KeyedBossBar extends BossBar, Keyed { } diff --git a/api/src/main/java/org/bukkit/command/BlockCommandSender.java b/api/src/main/java/org/bukkit/command/BlockCommandSender.java new file mode 100644 index 000000000..b8f2f7ebe --- /dev/null +++ b/api/src/main/java/org/bukkit/command/BlockCommandSender.java @@ -0,0 +1,15 @@ +package org.bukkit.command; + +import org.bukkit.block.Block; +import org.jetbrains.annotations.NotNull; + +public interface BlockCommandSender extends CommandSender { + + /** + * Returns the block this command sender belongs to + * + * @return Block for the command sender + */ + @NotNull + public Block getBlock(); +} diff --git a/api/src/main/java/org/bukkit/command/BufferedCommandSender.java b/api/src/main/java/org/bukkit/command/BufferedCommandSender.java new file mode 100644 index 000000000..f9a00aecc --- /dev/null +++ b/api/src/main/java/org/bukkit/command/BufferedCommandSender.java @@ -0,0 +1,21 @@ +package org.bukkit.command; + +import org.jetbrains.annotations.NotNull; + +public class BufferedCommandSender implements MessageCommandSender { + private final StringBuffer buffer = new StringBuffer(); + @Override + public void sendMessage(@NotNull String message) { + buffer.append(message); + buffer.append("\n"); + } + + @NotNull + public String getBuffer() { + return buffer.toString(); + } + + public void reset() { + this.buffer.setLength(0); + } +} diff --git a/api/src/main/java/org/bukkit/command/Command.java b/api/src/main/java/org/bukkit/command/Command.java new file mode 100644 index 000000000..f0222fc27 --- /dev/null +++ b/api/src/main/java/org/bukkit/command/Command.java @@ -0,0 +1,451 @@ +package org.bukkit.command; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; +import java.util.Set; + +import org.apache.commons.lang.Validate; +import org.bukkit.Bukkit; +import org.bukkit.ChatColor; +import org.bukkit.GameRule; +import org.bukkit.Location; +import org.bukkit.Server; +import org.bukkit.entity.Player; +import org.bukkit.entity.minecart.CommandMinecart; +import org.bukkit.permissions.Permissible; +import org.bukkit.plugin.PluginDescriptionFile; +import org.bukkit.util.StringUtil; + +import com.google.common.collect.ImmutableList; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +/** + * Represents a Command, which executes various tasks upon user input + */ +public abstract class Command { + private String name; + private String nextLabel; + private String label; + private List aliases; + private List activeAliases; + private CommandMap commandMap; + protected String description; + protected String usageMessage; + private String permission; + private String permissionMessage; + public co.aikar.timings.Timing timings; // Paper + @NotNull public String getTimingName() {return getName();} // Paper + + protected Command(@NotNull String name) { + this(name, "", "/" + name, new ArrayList()); + } + + protected Command(@NotNull String name, @NotNull String description, @NotNull String usageMessage, @NotNull List aliases) { + this.name = name; + this.nextLabel = name; + this.label = name; + this.description = (description == null) ? "" : description; + this.usageMessage = (usageMessage == null) ? "/" + name : usageMessage; + this.aliases = aliases; + this.activeAliases = new ArrayList(aliases); + } + + /** + * Executes the command, returning its success + * + * @param sender Source object which is executing this command + * @param commandLabel The alias of the command used + * @param args All arguments passed to the command, split via ' ' + * @return true if the command was successful, otherwise false + */ + public abstract boolean execute(@NotNull CommandSender sender, @NotNull String commandLabel, @NotNull String[] args); + + /** + * Executed on tab completion for this command, returning a list of + * options the player can tab through. + * + * @param sender Source object which is executing this command + * @param alias the alias being used + * @param args All arguments passed to the command, split via ' ' + * @return a list of tab-completions for the specified arguments. This + * will never be null. List may be immutable. + * @throws IllegalArgumentException if sender, alias, or args is null + */ + @NotNull + public List tabComplete(@NotNull CommandSender sender, @NotNull String alias, @NotNull String[] args) throws IllegalArgumentException { + return tabComplete0(sender, alias, args, null); + } + + /** + * Executed on tab completion for this command, returning a list of + * options the player can tab through. + * + * @param sender Source object which is executing this command + * @param alias the alias being used + * @param args All arguments passed to the command, split via ' ' + * @param location The position looked at by the sender, or null if none + * @return a list of tab-completions for the specified arguments. This + * will never be null. List may be immutable. + * @throws IllegalArgumentException if sender, alias, or args is null + */ + @NotNull + public List tabComplete(@NotNull CommandSender sender, @NotNull String alias, @NotNull String[] args, @Nullable Location location) throws IllegalArgumentException { + return tabComplete(sender, alias, args); + } + + @NotNull + private List tabComplete0(@NotNull CommandSender sender, @NotNull String alias, @NotNull String[] args, @Nullable Location location) throws IllegalArgumentException { + Validate.notNull(sender, "Sender cannot be null"); + Validate.notNull(args, "Arguments cannot be null"); + Validate.notNull(alias, "Alias cannot be null"); + + if (args.length == 0) { + return ImmutableList.of(); + } + + String lastWord = args[args.length - 1]; + + Player senderPlayer = sender instanceof Player ? (Player) sender : null; + + ArrayList matchedPlayers = new ArrayList(); + for (Player player : sender.getServer().getOnlinePlayers()) { + String name = player.getName(); + if ((senderPlayer == null || senderPlayer.canSee(player)) && StringUtil.startsWithIgnoreCase(name, lastWord)) { + matchedPlayers.add(name); + } + } + + Collections.sort(matchedPlayers, String.CASE_INSENSITIVE_ORDER); + return matchedPlayers; + } + + /** + * Returns the name of this command + * + * @return Name of this command + */ + @NotNull + public String getName() { + return name; + } + + /** + * Sets the name of this command. + *

+ * May only be used before registering the command. + * Will return true if the new name is set, and false + * if the command has already been registered. + * + * @param name New command name + * @return returns true if the name change happened instantly or false if + * the command was already registered + */ + public boolean setName(@NotNull String name) { + if (!isRegistered()) { + this.name = (name == null) ? "" : name; + return true; + } + return false; + } + + /** + * Gets the permission required by users to be able to perform this + * command + * + * @return Permission name, or null if none + */ + @Nullable + public String getPermission() { + return permission; + } + + /** + * Sets the permission required by users to be able to perform this + * command + * + * @param permission Permission name or null + */ + public void setPermission(@Nullable String permission) { + this.permission = permission; + } + + /** + * Tests the given {@link CommandSender} to see if they can perform this + * command. + *

+ * If they do not have permission, they will be informed that they cannot + * do this. + * + * @param target User to test + * @return true if they can use it, otherwise false + */ + public boolean testPermission(@NotNull CommandSender target) { + if (testPermissionSilent(target)) { + return true; + } + + if (permissionMessage == null) { + target.sendMessage(Bukkit.getPermissionMessage()); // Paper + } else if (permissionMessage.length() != 0) { + for (String line : permissionMessage.replace("", permission).split("\n")) { + target.sendMessage(line); + } + } + + return false; + } + + /** + * Tests the given {@link CommandSender} to see if they can perform this + * command. + *

+ * No error is sent to the sender. + * + * @param target User to test + * @return true if they can use it, otherwise false + */ + public boolean testPermissionSilent(@NotNull CommandSender target) { + if ((permission == null) || (permission.length() == 0)) { + return true; + } + + for (String p : permission.split(";")) { + if (target.hasPermission(p)) { + return true; + } + } + + return false; + } + + /** + * Returns the label for this command + * + * @return Label of this command + */ + @NotNull + public String getLabel() { + return label; + } + + /** + * Sets the label of this command. + *

+ * May only be used before registering the command. + * Will return true if the new name is set, and false + * if the command has already been registered. + * + * @param name The command's name + * @return returns true if the name change happened instantly or false if + * the command was already registered + */ + public boolean setLabel(@NotNull String name) { + if (name == null) { + name = ""; + } + this.nextLabel = name; + if (!isRegistered()) { + this.label = name; + return true; + } + return false; + } + + /** + * Registers this command to a CommandMap. + * Once called it only allows changes the registered CommandMap + * + * @param commandMap the CommandMap to register this command to + * @return true if the registration was successful (the current registered + * CommandMap was the passed CommandMap or null) false otherwise + */ + public boolean register(@NotNull CommandMap commandMap) { + if (allowChangesFrom(commandMap)) { + this.commandMap = commandMap; + return true; + } + + return false; + } + + /** + * Unregisters this command from the passed CommandMap applying any + * outstanding changes + * + * @param commandMap the CommandMap to unregister + * @return true if the unregistration was successful (the current + * registered CommandMap was the passed CommandMap or null) false + * otherwise + */ + public boolean unregister(@NotNull CommandMap commandMap) { + if (allowChangesFrom(commandMap)) { + this.commandMap = null; + this.activeAliases = new ArrayList(this.aliases); + this.label = this.nextLabel; + return true; + } + + return false; + } + + private boolean allowChangesFrom(@NotNull CommandMap commandMap) { + return (null == this.commandMap || this.commandMap == commandMap); + } + + /** + * Returns the current registered state of this command + * + * @return true if this command is currently registered false otherwise + */ + public boolean isRegistered() { + return (null != this.commandMap); + } + + /** + * Returns a list of active aliases of this command + * + * @return List of aliases + */ + @NotNull + public List getAliases() { + return activeAliases; + } + + /** + * Returns a message to be displayed on a failed permission check for this + * command + * + * @return Permission check failed message + */ + @Nullable + public String getPermissionMessage() { + return permissionMessage; + } + + /** + * Gets a brief description of this command + * + * @return Description of this command + */ + @NotNull + public String getDescription() { + return description; + } + + /** + * Gets an example usage of this command + * + * @return One or more example usages + */ + @NotNull + public String getUsage() { + return usageMessage; + } + + /** + * Sets the list of aliases to request on registration for this command. + * This is not effective outside of defining aliases in the {@link + * PluginDescriptionFile#getCommands()} (under the + * `aliases' node) is equivalent to this method. + * + * @param aliases aliases to register to this command + * @return this command object, for chaining + */ + @NotNull + public Command setAliases(@NotNull List aliases) { + this.aliases = aliases; + if (!isRegistered()) { + this.activeAliases = new ArrayList(aliases); + } + return this; + } + + /** + * Sets a brief description of this command. Defining a description in the + * {@link PluginDescriptionFile#getCommands()} (under the + * `description' node) is equivalent to this method. + * + * @param description new command description + * @return this command object, for chaining + */ + @NotNull + public Command setDescription(@NotNull String description) { + this.description = (description == null) ? "" : description; + return this; + } + + /** + * Sets the message sent when a permission check fails + * + * @param permissionMessage new permission message, null to indicate + * default message, or an empty string to indicate no message + * @return this command object, for chaining + */ + @NotNull + public Command setPermissionMessage(@Nullable String permissionMessage) { + this.permissionMessage = permissionMessage; + return this; + } + + /** + * Sets the example usage of this command + * + * @param usage new example usage + * @return this command object, for chaining + */ + @NotNull + public Command setUsage(@NotNull String usage) { + this.usageMessage = (usage == null) ? "" : usage; + return this; + } + + public static void broadcastCommandMessage(@NotNull CommandSender source, @NotNull String message) { + broadcastCommandMessage(source, message, true); + } + + public static void broadcastCommandMessage(@NotNull CommandSender source, @NotNull String message, boolean sendToSource) { + String result = source.getName() + ": " + message; + + if (source instanceof BlockCommandSender) { + BlockCommandSender blockCommandSender = (BlockCommandSender) source; + + if (!blockCommandSender.getBlock().getWorld().getGameRuleValue(GameRule.COMMAND_BLOCK_OUTPUT)) { + Bukkit.getConsoleSender().sendMessage(result); + return; + } + } else if (source instanceof CommandMinecart) { + CommandMinecart commandMinecart = (CommandMinecart) source; + + if (!commandMinecart.getWorld().getGameRuleValue(GameRule.COMMAND_BLOCK_OUTPUT)) { + Bukkit.getConsoleSender().sendMessage(result); + return; + } + } + + Set users = Bukkit.getPluginManager().getPermissionSubscriptions(Server.BROADCAST_CHANNEL_ADMINISTRATIVE); + String colored = ChatColor.GRAY + "" + ChatColor.ITALIC + "[" + result + ChatColor.GRAY + ChatColor.ITALIC + "]"; + + if (sendToSource && !(source instanceof ConsoleCommandSender)) { + source.sendMessage(message); + } + + for (Permissible user : users) { + if (user instanceof CommandSender && user.hasPermission(Server.BROADCAST_CHANNEL_ADMINISTRATIVE)) { + CommandSender target = (CommandSender) user; + + if (target instanceof ConsoleCommandSender) { + target.sendMessage(result); + } else if (target != source) { + target.sendMessage(colored); + } + } + } + } + + @Override + public String toString() { + return getClass().getName() + '(' + name + ')'; + } +} diff --git a/api/src/main/java/org/bukkit/command/CommandException.java b/api/src/main/java/org/bukkit/command/CommandException.java new file mode 100644 index 000000000..b63015f40 --- /dev/null +++ b/api/src/main/java/org/bukkit/command/CommandException.java @@ -0,0 +1,28 @@ +package org.bukkit.command; + +/** + * Thrown when an unhandled exception occurs during the execution of a Command + */ +@SuppressWarnings("serial") +public class CommandException extends RuntimeException { + + /** + * Creates a new instance of CommandException without detail + * message. + */ + public CommandException() {} + + /** + * Constructs an instance of CommandException with the + * specified detail message. + * + * @param msg the detail message. + */ + public CommandException(String msg) { + super(msg); + } + + public CommandException(String msg, Throwable cause) { + super(msg, cause); + } +} diff --git a/api/src/main/java/org/bukkit/command/CommandExecutor.java b/api/src/main/java/org/bukkit/command/CommandExecutor.java new file mode 100644 index 000000000..45cb8da12 --- /dev/null +++ b/api/src/main/java/org/bukkit/command/CommandExecutor.java @@ -0,0 +1,23 @@ +package org.bukkit.command; + +import org.jetbrains.annotations.NotNull; + +/** + * Represents a class which contains a single method for executing commands + */ +public interface CommandExecutor { + + /** + * Executes the given command, returning its success. + *
+ * If false is returned, then the "usage" plugin.yml entry for this command + * (if defined) will be sent to the player. + * + * @param sender Source of the command + * @param command Command which was executed + * @param label Alias of the command which was used + * @param args Passed command arguments + * @return true if a valid command, otherwise false + */ + public boolean onCommand(@NotNull CommandSender sender, @NotNull Command command, @NotNull String label, @NotNull String[] args); +} diff --git a/api/src/main/java/org/bukkit/command/CommandMap.java b/api/src/main/java/org/bukkit/command/CommandMap.java new file mode 100644 index 000000000..864c263bb --- /dev/null +++ b/api/src/main/java/org/bukkit/command/CommandMap.java @@ -0,0 +1,141 @@ +package org.bukkit.command; + +import java.util.List; +import org.bukkit.Location; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +public interface CommandMap { + + /** + * Registers all the commands belonging to a certain plugin. + *

+ * Caller can use:- + *

    + *
  • command.getName() to determine the label registered for this + * command + *
  • command.getAliases() to determine the aliases which where + * registered + *
+ * + * @param fallbackPrefix a prefix which is prepended to each command with + * a ':' one or more times to make the command unique + * @param commands a list of commands to register + */ + public void registerAll(@NotNull String fallbackPrefix, @NotNull List commands); + + /** + * Registers a command. Returns true on success; false if name is already + * taken and fallback had to be used. + *

+ * Caller can use:- + *

    + *
  • command.getName() to determine the label registered for this + * command + *
  • command.getAliases() to determine the aliases which where + * registered + *
+ * + * @param label the label of the command, without the '/'-prefix. + * @param fallbackPrefix a prefix which is prepended to the command with a + * ':' one or more times to make the command unique + * @param command the command to register + * @return true if command was registered with the passed in label, false + * otherwise, which indicates the fallbackPrefix was used one or more + * times + */ + public boolean register(@NotNull String label, @NotNull String fallbackPrefix, @NotNull Command command); + + /** + * Registers a command. Returns true on success; false if name is already + * taken and fallback had to be used. + *

+ * Caller can use:- + *

    + *
  • command.getName() to determine the label registered for this + * command + *
  • command.getAliases() to determine the aliases which where + * registered + *
+ * + * @param fallbackPrefix a prefix which is prepended to the command with a + * ':' one or more times to make the command unique + * @param command the command to register, from which label is determined + * from the command name + * @return true if command was registered with the passed in label, false + * otherwise, which indicates the fallbackPrefix was used one or more + * times + */ + public boolean register(@NotNull String fallbackPrefix, @NotNull Command command); + + /** + * Looks for the requested command and executes it if found. + * + * @param sender The command's sender + * @param cmdLine command + arguments. Example: "/test abc 123" + * @return returns false if no target is found, true otherwise. + * @throws CommandException Thrown when the executor for the given command + * fails with an unhandled exception + */ + public boolean dispatch(@NotNull CommandSender sender, @NotNull String cmdLine) throws CommandException; + + /** + * Clears all registered commands. + */ + public void clearCommands(); + + /** + * Gets the command registered to the specified name + * + * @param name Name of the command to retrieve + * @return Command with the specified name or null if a command with that + * label doesn't exist + */ + @Nullable + public Command getCommand(@NotNull String name); + + /** + * Looks for the requested command and executes an appropriate + * tab-completer if found. This method will also tab-complete partial + * commands. + * + * @param sender The command's sender. + * @param cmdLine The entire command string to tab-complete, excluding + * initial slash. + * @return a list of possible tab-completions. This list may be immutable. + * Will be null if no matching command of which sender has permission. + * @throws CommandException Thrown when the tab-completer for the given + * command fails with an unhandled exception + * @throws IllegalArgumentException if either sender or cmdLine are null + */ + @Nullable + public List tabComplete(@NotNull CommandSender sender, @NotNull String cmdLine) throws IllegalArgumentException; + + /** + * Looks for the requested command and executes an appropriate + * tab-completer if found. This method will also tab-complete partial + * commands. + * + * @param sender The command's sender. + * @param cmdLine The entire command string to tab-complete, excluding + * initial slash. + * @param location The position looked at by the sender, or null if none + * @return a list of possible tab-completions. This list may be immutable. + * Will be null if no matching command of which sender has permission. + * @throws CommandException Thrown when the tab-completer for the given + * command fails with an unhandled exception + * @throws IllegalArgumentException if either sender or cmdLine are null + */ + @Nullable + public List tabComplete(@NotNull CommandSender sender, @NotNull String cmdLine, @Nullable Location location) throws IllegalArgumentException; + + // Paper start - Expose Known Commands + /** + * Return a Map of known commands + * + * @return known commands + */ + @NotNull + public java.util.Map getKnownCommands(); + // Paper end +} diff --git a/api/src/main/java/org/bukkit/command/CommandSender.java b/api/src/main/java/org/bukkit/command/CommandSender.java new file mode 100644 index 000000000..be11d52a0 --- /dev/null +++ b/api/src/main/java/org/bukkit/command/CommandSender.java @@ -0,0 +1,91 @@ +package org.bukkit.command; + +import org.bukkit.Server; +import org.bukkit.permissions.Permissible; +import org.jetbrains.annotations.NotNull; + +public interface CommandSender extends Permissible { + + /** + * Sends this sender a message + * + * @param message Message to be displayed + */ + public void sendMessage(@NotNull String message); + + /** + * Sends this sender multiple messages + * + * @param messages An array of messages to be displayed + */ + public void sendMessage(@NotNull String[] messages); + + /** + * Returns the server instance that this command is running on + * + * @return Server instance + */ + @NotNull + public Server getServer(); + + /** + * Gets the name of this command sender + * + * @return Name of the sender + */ + @NotNull + public String getName(); + + // Spigot start + public class Spigot + { + + /** + * Sends this sender a chat component. + * + * @param component the components to send + */ + public void sendMessage(@NotNull net.md_5.bungee.api.chat.BaseComponent component) { + throw new UnsupportedOperationException("Not supported yet."); + } + + /** + * Sends an array of components as a single message to the sender. + * + * @param components the components to send + */ + public void sendMessage(@NotNull net.md_5.bungee.api.chat.BaseComponent... components) { + throw new UnsupportedOperationException("Not supported yet."); + } + } + + @NotNull + Spigot spigot(); + // Spigot end + + // Paper start + /** + * Sends the component to the sender + * + *

If this sender does not support sending full components then + * the component will be sent as legacy text.

+ * + * @param component the component to send + */ + default void sendMessage(@NotNull net.md_5.bungee.api.chat.BaseComponent component) { + this.sendMessage(component.toLegacyText()); + } + + /** + * Sends an array of components as a single message to the sender + * + *

If this sender does not support sending full components then + * the components will be sent as legacy text.

+ * + * @param components the components to send + */ + default void sendMessage(@NotNull net.md_5.bungee.api.chat.BaseComponent... components) { + this.sendMessage(new net.md_5.bungee.api.chat.TextComponent(components).toLegacyText()); + } + // Paper end +} diff --git a/api/src/main/java/org/bukkit/command/ConsoleCommandSender.java b/api/src/main/java/org/bukkit/command/ConsoleCommandSender.java new file mode 100644 index 000000000..f309c2ed3 --- /dev/null +++ b/api/src/main/java/org/bukkit/command/ConsoleCommandSender.java @@ -0,0 +1,6 @@ +package org.bukkit.command; + +import org.bukkit.conversations.Conversable; + +public interface ConsoleCommandSender extends CommandSender, Conversable { +} diff --git a/api/src/main/java/org/bukkit/command/FormattedCommandAlias.java b/api/src/main/java/org/bukkit/command/FormattedCommandAlias.java new file mode 100644 index 000000000..9d4f553c0 --- /dev/null +++ b/api/src/main/java/org/bukkit/command/FormattedCommandAlias.java @@ -0,0 +1,130 @@ +package org.bukkit.command; + +import java.util.ArrayList; +import java.util.regex.Matcher; // Paper +import java.util.regex.Pattern; // Paper + +import org.bukkit.Bukkit; +import org.jetbrains.annotations.NotNull; + +public class FormattedCommandAlias extends Command { + private final String[] formatStrings; + + public FormattedCommandAlias(@NotNull String alias, @NotNull String[] formatStrings) { + super(alias); + timings = co.aikar.timings.TimingsManager.getCommandTiming("minecraft", this); // Spigot + this.formatStrings = formatStrings; + } + + @Override + public boolean execute(@NotNull CommandSender sender, @NotNull String commandLabel, @NotNull String[] args) { + boolean result = false; + ArrayList commands = new ArrayList(); + for (String formatString : formatStrings) { + try { + commands.add(buildCommand(sender, formatString, args)); // Paper + } catch (Throwable throwable) { + if (throwable instanceof IllegalArgumentException) { + sender.sendMessage(throwable.getMessage()); + } else { + sender.sendMessage(org.bukkit.ChatColor.RED + "An internal error occurred while attempting to perform this command"); + } + return false; + } + } + + for (String command : commands) { + result |= Bukkit.dispatchCommand(sender, command); + } + + return result; + } + + private String buildCommand(@NotNull CommandSender sender, @NotNull String formatString, @NotNull String[] args) { // Paper + if (formatString.contains("$sender")) { // Paper + formatString = formatString.replaceAll(Pattern.quote("$sender"), Matcher.quoteReplacement(sender.getName())); // Paper + } // Paper + int index = formatString.indexOf('$'); + while (index != -1) { + int start = index; + + if (index > 0 && formatString.charAt(start - 1) == '\\') { + formatString = formatString.substring(0, start - 1) + formatString.substring(start); + index = formatString.indexOf('$', index); + continue; + } + + boolean required = false; + if (formatString.charAt(index + 1) == '$') { + required = true; + // Move index past the second $ + index++; + } + + // Move index past the $ + index++; + int argStart = index; + while (index < formatString.length() && inRange(((int) formatString.charAt(index)) - 48, 0, 9)) { + // Move index past current digit + index++; + } + + // No numbers found + if (argStart == index) { + throw new IllegalArgumentException("Invalid replacement token"); + } + + int position = Integer.parseInt(formatString.substring(argStart, index)); + + // Arguments are not 0 indexed + if (position == 0) { + throw new IllegalArgumentException("Invalid replacement token"); + } + + // Convert position to 0 index + position--; + + boolean rest = false; + if (index < formatString.length() && formatString.charAt(index) == '-') { + rest = true; + // Move index past the - + index++; + } + + int end = index; + + if (required && position >= args.length) { + throw new IllegalArgumentException("Missing required argument " + (position + 1)); + } + + StringBuilder replacement = new StringBuilder(); + if (rest && position < args.length) { + for (int i = position; i < args.length; i++) { + if (i != position) { + replacement.append(' '); + } + replacement.append(args[i]); + } + } else if (position < args.length) { + replacement.append(args[position]); + } + + formatString = formatString.substring(0, start) + replacement.toString() + formatString.substring(end); + // Move index past the replaced data so we don't process it again + index = start + replacement.length(); + + // Move to the next replacement token + index = formatString.indexOf('$', index); + } + + return formatString; + } + + @NotNull + @Override // Paper + public String getTimingName() {return "Command Forwarder - " + super.getTimingName();} // Paper + + private static boolean inRange(int i, int j, int k) { + return i >= j && i <= k; + } +} diff --git a/api/src/main/java/org/bukkit/command/MessageCommandSender.java b/api/src/main/java/org/bukkit/command/MessageCommandSender.java new file mode 100644 index 000000000..ca1893e9f --- /dev/null +++ b/api/src/main/java/org/bukkit/command/MessageCommandSender.java @@ -0,0 +1,114 @@ +package org.bukkit.command; + +import org.apache.commons.lang.NotImplementedException; +import org.bukkit.Bukkit; +import org.bukkit.Server; +import org.bukkit.permissions.Permission; +import org.bukkit.permissions.PermissionAttachment; +import org.bukkit.permissions.PermissionAttachmentInfo; +import org.bukkit.plugin.Plugin; + +import java.util.Set; +import org.jetbrains.annotations.NotNull; + +/** + * For when all you care about is just messaging + */ +public interface MessageCommandSender extends CommandSender { + + @Override + default void sendMessage(@NotNull String[] messages) { + for (String message : messages) { + sendMessage(message); + } + } + + @NotNull + @Override + default Server getServer() { + return Bukkit.getServer(); + } + + @NotNull + @Override + default String getName() { + throw new NotImplementedException(); + } + + @Override + default boolean isOp() { + throw new NotImplementedException(); + } + + @Override + default void setOp(boolean value) { + throw new NotImplementedException(); + } + + @Override + default boolean isPermissionSet(@NotNull String name) { + throw new NotImplementedException(); + } + + @Override + default boolean isPermissionSet(@NotNull Permission perm) { + throw new NotImplementedException(); + } + + @Override + default boolean hasPermission(@NotNull String name) { + throw new NotImplementedException(); + } + + @Override + default boolean hasPermission(@NotNull Permission perm) { + throw new NotImplementedException(); + } + + @NotNull + @Override + default PermissionAttachment addAttachment(@NotNull Plugin plugin, @NotNull String name, boolean value) { + throw new NotImplementedException(); + } + + @NotNull + @Override + default PermissionAttachment addAttachment(@NotNull Plugin plugin) { + throw new NotImplementedException(); + } + + @NotNull + @Override + default PermissionAttachment addAttachment(@NotNull Plugin plugin, @NotNull String name, boolean value, int ticks) { + throw new NotImplementedException(); + } + + @NotNull + @Override + default PermissionAttachment addAttachment(@NotNull Plugin plugin, int ticks) { + throw new NotImplementedException(); + } + + @Override + default void removeAttachment(@NotNull PermissionAttachment attachment) { + throw new NotImplementedException(); + } + + @Override + default void recalculatePermissions() { + throw new NotImplementedException(); + } + + @NotNull + @Override + default Set getEffectivePermissions() { + throw new NotImplementedException(); + } + + @NotNull + @Override + default Spigot spigot() { + throw new NotImplementedException(); + } + +} diff --git a/api/src/main/java/org/bukkit/command/MultipleCommandAlias.java b/api/src/main/java/org/bukkit/command/MultipleCommandAlias.java new file mode 100644 index 000000000..8487bfe33 --- /dev/null +++ b/api/src/main/java/org/bukkit/command/MultipleCommandAlias.java @@ -0,0 +1,36 @@ +package org.bukkit.command; + +import org.jetbrains.annotations.NotNull; + +/** + * Represents a command that delegates to one or more other commands + */ +public class MultipleCommandAlias extends Command { + private Command[] commands; + + public MultipleCommandAlias(@NotNull String name, @NotNull Command[] commands) { + super(name); + this.commands = commands; + } + + /** + * Gets the commands associated with the multi-command alias. + * + * @return commands associated with alias + */ + @NotNull + public Command[] getCommands() { + return commands; + } + + @Override + public boolean execute(@NotNull CommandSender sender, @NotNull String commandLabel, @NotNull String[] args) { + boolean result = false; + + for (Command command : commands) { + result |= command.execute(sender, commandLabel, args); + } + + return result; + } +} diff --git a/api/src/main/java/org/bukkit/command/PluginCommand.java b/api/src/main/java/org/bukkit/command/PluginCommand.java new file mode 100644 index 000000000..e420b7902 --- /dev/null +++ b/api/src/main/java/org/bukkit/command/PluginCommand.java @@ -0,0 +1,167 @@ +package org.bukkit.command; + +import java.util.List; + +import org.apache.commons.lang.Validate; +import org.bukkit.plugin.Plugin; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +/** + * Represents a {@link Command} belonging to a plugin + */ +public final class PluginCommand extends Command implements PluginIdentifiableCommand { + private final Plugin owningPlugin; + private CommandExecutor executor; + private TabCompleter completer; + + protected PluginCommand(@NotNull String name, @NotNull Plugin owner) { + super(name); + this.executor = owner; + this.owningPlugin = owner; + this.usageMessage = ""; + } + + /** + * Executes the command, returning its success + * + * @param sender Source object which is executing this command + * @param commandLabel The alias of the command used + * @param args All arguments passed to the command, split via ' ' + * @return true if the command was successful, otherwise false + */ + @Override + public boolean execute(@NotNull CommandSender sender, @NotNull String commandLabel, @NotNull String[] args) { + boolean success = false; + + if (!owningPlugin.isEnabled()) { + throw new CommandException("Cannot execute command '" + commandLabel + "' in plugin " + owningPlugin.getDescription().getFullName() + " - plugin is disabled."); + } + + if (!testPermission(sender)) { + return true; + } + + try { + success = executor.onCommand(sender, this, commandLabel, args); + } catch (Throwable ex) { + throw new CommandException("Unhandled exception executing command '" + commandLabel + "' in plugin " + owningPlugin.getDescription().getFullName(), ex); + } + + if (!success && usageMessage.length() > 0) { + for (String line : usageMessage.replace("", commandLabel).split("\n")) { + sender.sendMessage(line); + } + } + + return success; + } + + /** + * Sets the {@link CommandExecutor} to run when parsing this command + * + * @param executor New executor to run + */ + public void setExecutor(@Nullable CommandExecutor executor) { + this.executor = executor == null ? owningPlugin : executor; + } + + /** + * Gets the {@link CommandExecutor} associated with this command + * + * @return CommandExecutor object linked to this command + */ + @NotNull + public CommandExecutor getExecutor() { + return executor; + } + + /** + * Sets the {@link TabCompleter} to run when tab-completing this command. + *

+ * If no TabCompleter is specified, and the command's executor implements + * TabCompleter, then the executor will be used for tab completion. + * + * @param completer New tab completer + */ + public void setTabCompleter(@Nullable TabCompleter completer) { + this.completer = completer; + } + + /** + * Gets the {@link TabCompleter} associated with this command. + * + * @return TabCompleter object linked to this command + */ + @Nullable + public TabCompleter getTabCompleter() { + return completer; + } + + /** + * Gets the owner of this PluginCommand + * + * @return Plugin that owns this command + */ + @NotNull + public Plugin getPlugin() { + return owningPlugin; + } + + /** + * {@inheritDoc} + *

+ * Delegates to the tab completer if present. + *

+ * If it is not present or returns null, will delegate to the current + * command executor if it implements {@link TabCompleter}. If a non-null + * list has not been found, will default to standard player name + * completion in {@link + * Command#tabComplete(CommandSender, String, String[])}. + *

+ * This method does not consider permissions. + * + * @throws CommandException if the completer or executor throw an + * exception during the process of tab-completing. + * @throws IllegalArgumentException if sender, alias, or args is null + */ + @NotNull + @Override + public java.util.List tabComplete(@NotNull CommandSender sender, @NotNull String alias, @NotNull String[] args) throws CommandException, IllegalArgumentException { + Validate.notNull(sender, "Sender cannot be null"); + Validate.notNull(args, "Arguments cannot be null"); + Validate.notNull(alias, "Alias cannot be null"); + + List completions = null; + try { + if (completer != null) { + completions = completer.onTabComplete(sender, this, alias, args); + } + if (completions == null && executor instanceof TabCompleter) { + completions = ((TabCompleter) executor).onTabComplete(sender, this, alias, args); + } + } catch (Throwable ex) { + StringBuilder message = new StringBuilder(); + message.append("Unhandled exception during tab completion for command '/").append(alias).append(' '); + for (String arg : args) { + message.append(arg).append(' '); + } + message.deleteCharAt(message.length() - 1).append("' in plugin ").append(owningPlugin.getDescription().getFullName()); + throw new CommandException(message.toString(), ex); + } + + if (completions == null) { + if (!sender.getServer().suggestPlayerNamesWhenNullTabCompletions()) return com.google.common.collect.ImmutableList.of(); // Paper - allow preventing player name suggestions by default + return super.tabComplete(sender, alias, args); + } + return completions; + } + + @Override + public String toString() { + StringBuilder stringBuilder = new StringBuilder(super.toString()); + stringBuilder.deleteCharAt(stringBuilder.length() - 1); + stringBuilder.append(", ").append(owningPlugin.getDescription().getFullName()).append(')'); + return stringBuilder.toString(); + } +} diff --git a/api/src/main/java/org/bukkit/command/PluginCommandYamlParser.java b/api/src/main/java/org/bukkit/command/PluginCommandYamlParser.java new file mode 100644 index 000000000..92b3f7997 --- /dev/null +++ b/api/src/main/java/org/bukkit/command/PluginCommandYamlParser.java @@ -0,0 +1,78 @@ +package org.bukkit.command; + +import java.util.ArrayList; +import java.util.List; +import java.util.Map; +import java.util.Map.Entry; + +import org.bukkit.Bukkit; +import org.bukkit.plugin.Plugin; +import org.jetbrains.annotations.NotNull; + +public class PluginCommandYamlParser { + + @NotNull + public static List parse(@NotNull Plugin plugin) { + List pluginCmds = new ArrayList(); + + Map> map = plugin.getDescription().getCommands(); + + if (map == null) { + return pluginCmds; + } + + for (Entry> entry : map.entrySet()) { + if (entry.getKey().contains(":")) { + Bukkit.getServer().getLogger().severe("Could not load command " + entry.getKey() + " for plugin " + plugin.getName() + ": Illegal Characters"); + continue; + } + Command newCmd = new PluginCommand(entry.getKey(), plugin); + Object description = entry.getValue().get("description"); + Object usage = entry.getValue().get("usage"); + Object aliases = entry.getValue().get("aliases"); + Object permission = entry.getValue().get("permission"); + Object permissionMessage = entry.getValue().get("permission-message"); + + if (description != null) { + newCmd.setDescription(description.toString()); + } + + if (usage != null) { + newCmd.setUsage(usage.toString()); + } + + if (aliases != null) { + List aliasList = new ArrayList(); + + if (aliases instanceof List) { + for (Object o : (List) aliases) { + if (o.toString().contains(":")) { + Bukkit.getServer().getLogger().severe("Could not load alias " + o.toString() + " for plugin " + plugin.getName() + ": Illegal Characters"); + continue; + } + aliasList.add(o.toString()); + } + } else { + if (aliases.toString().contains(":")) { + Bukkit.getServer().getLogger().severe("Could not load alias " + aliases.toString() + " for plugin " + plugin.getName() + ": Illegal Characters"); + } else { + aliasList.add(aliases.toString()); + } + } + + newCmd.setAliases(aliasList); + } + + if (permission != null) { + newCmd.setPermission(permission.toString()); + } + + if (permissionMessage != null) { + newCmd.setPermissionMessage(permissionMessage.toString()); + } + + pluginCmds.add(newCmd); + } + return pluginCmds; + } +} diff --git a/api/src/main/java/org/bukkit/command/PluginIdentifiableCommand.java b/api/src/main/java/org/bukkit/command/PluginIdentifiableCommand.java new file mode 100644 index 000000000..d51e0fd60 --- /dev/null +++ b/api/src/main/java/org/bukkit/command/PluginIdentifiableCommand.java @@ -0,0 +1,21 @@ +package org.bukkit.command; + +import org.bukkit.plugin.Plugin; +import org.jetbrains.annotations.NotNull; + +/** + * This interface is used by the help system to group commands into + * sub-indexes based on the {@link Plugin} they are a part of. Custom command + * implementations will need to implement this interface to have a sub-index + * automatically generated on the plugin's behalf. + */ +public interface PluginIdentifiableCommand { + + /** + * Gets the owner of this PluginIdentifiableCommand. + * + * @return Plugin that owns this PluginIdentifiableCommand. + */ + @NotNull + public Plugin getPlugin(); +} diff --git a/api/src/main/java/org/bukkit/command/ProxiedCommandSender.java b/api/src/main/java/org/bukkit/command/ProxiedCommandSender.java new file mode 100644 index 000000000..fcc34b640 --- /dev/null +++ b/api/src/main/java/org/bukkit/command/ProxiedCommandSender.java @@ -0,0 +1,24 @@ + +package org.bukkit.command; + +import org.jetbrains.annotations.NotNull; + +public interface ProxiedCommandSender extends CommandSender { + + /** + * Returns the CommandSender which triggered this proxied command + * + * @return the caller which triggered the command + */ + @NotNull + CommandSender getCaller(); + + /** + * Returns the CommandSender which is being used to call the command + * + * @return the caller which the command is being run as + */ + @NotNull + CommandSender getCallee(); + +} diff --git a/api/src/main/java/org/bukkit/command/RemoteConsoleCommandSender.java b/api/src/main/java/org/bukkit/command/RemoteConsoleCommandSender.java new file mode 100644 index 000000000..dc3bc1d1a --- /dev/null +++ b/api/src/main/java/org/bukkit/command/RemoteConsoleCommandSender.java @@ -0,0 +1,4 @@ +package org.bukkit.command; + +public interface RemoteConsoleCommandSender extends CommandSender { +} diff --git a/api/src/main/java/org/bukkit/command/SimpleCommandMap.java b/api/src/main/java/org/bukkit/command/SimpleCommandMap.java new file mode 100644 index 000000000..b78f3d69d --- /dev/null +++ b/api/src/main/java/org/bukkit/command/SimpleCommandMap.java @@ -0,0 +1,295 @@ +package org.bukkit.command; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collection; +import java.util.Collections; +import java.util.HashMap; +import java.util.Iterator; +import java.util.List; +import java.util.Map; + +import com.destroystokyo.paper.event.server.ServerExceptionEvent; +import com.destroystokyo.paper.exception.ServerCommandException; +import com.destroystokyo.paper.exception.ServerTabCompleteException; +import org.apache.commons.lang.Validate; +import org.bukkit.Location; +import org.bukkit.Server; +import org.bukkit.command.defaults.*; +import org.bukkit.entity.Player; +import org.bukkit.util.StringUtil; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +public class SimpleCommandMap implements CommandMap { + protected final Map knownCommands = new HashMap(); + private final Server server; + + public SimpleCommandMap(@NotNull final Server server) { + this.server = server; + setDefaultCommands(); + } + + private void setDefaultCommands() { + register("bukkit", new VersionCommand("version")); + register("bukkit", new ReloadCommand("reload")); + register("bukkit", new PluginsCommand("plugins")); + register("bukkit", new co.aikar.timings.TimingsCommand("timings")); // Paper + } + + public void setFallbackCommands() { + register("bukkit", new HelpCommand()); + } + + /** + * {@inheritDoc} + */ + public void registerAll(@NotNull String fallbackPrefix, @NotNull List commands) { + if (commands != null) { + for (Command c : commands) { + register(fallbackPrefix, c); + } + } + } + + /** + * {@inheritDoc} + */ + public boolean register(@NotNull String fallbackPrefix, @NotNull Command command) { + return register(command.getName(), fallbackPrefix, command); + } + + /** + * {@inheritDoc} + */ + public boolean register(@NotNull String label, @NotNull String fallbackPrefix, @NotNull Command command) { + command.timings = co.aikar.timings.TimingsManager.getCommandTiming(fallbackPrefix, command); // Paper + label = label.toLowerCase(java.util.Locale.ENGLISH).trim(); + fallbackPrefix = fallbackPrefix.toLowerCase(java.util.Locale.ENGLISH).trim(); + boolean registered = register(label, command, false, fallbackPrefix); + + Iterator iterator = command.getAliases().iterator(); + while (iterator.hasNext()) { + if (!register(iterator.next(), command, true, fallbackPrefix)) { + iterator.remove(); + } + } + + // If we failed to register under the real name, we need to set the command label to the direct address + if (!registered) { + command.setLabel(fallbackPrefix + ":" + label); + } + + // Register to us so further updates of the commands label and aliases are postponed until its reregistered + command.register(this); + + return registered; + } + + /** + * Registers a command with the given name is possible. Also uses + * fallbackPrefix to create a unique name. + * + * @param label the name of the command, without the '/'-prefix. + * @param command the command to register + * @param isAlias whether the command is an alias + * @param fallbackPrefix a prefix which is prepended to the command for a + * unique address + * @return true if command was registered, false otherwise. + */ + private synchronized boolean register(@NotNull String label, @NotNull Command command, boolean isAlias, @NotNull String fallbackPrefix) { + knownCommands.put(fallbackPrefix + ":" + label, command); + if ((command instanceof BukkitCommand || isAlias) && knownCommands.containsKey(label)) { + // Request is for an alias/fallback command and it conflicts with + // a existing command or previous alias ignore it + // Note: This will mean it gets removed from the commands list of active aliases + return false; + } + + boolean registered = true; + + // If the command exists but is an alias we overwrite it, otherwise we return + Command conflict = knownCommands.get(label); + if (conflict != null && conflict.getLabel().equals(label)) { + return false; + } + + if (!isAlias) { + command.setLabel(label); + } + knownCommands.put(label, command); + + return registered; + } + + /** + * {@inheritDoc} + */ + public boolean dispatch(@NotNull CommandSender sender, @NotNull String commandLine) throws CommandException { + String[] args = commandLine.split(" "); + + if (args.length == 0) { + return false; + } + + String sentCommandLabel = args[0].toLowerCase(java.util.Locale.ENGLISH); + Command target = getCommand(sentCommandLabel); + + if (target == null) { + return false; + } + + // Paper start - Plugins do weird things to workaround normal registration + if (target.timings == null) { + target.timings = co.aikar.timings.TimingsManager.getCommandTiming(null, target); + } + // Paper end + + try { + try (co.aikar.timings.Timing ignored = target.timings.startTiming()) { // Paper - use try with resources + // Note: we don't return the result of target.execute as thats success / failure, we return handled (true) or not handled (false) + target.execute(sender, sentCommandLabel, Arrays.copyOfRange(args, 1, args.length)); + } // target.timings.stopTiming(); // Spigot // Paper + } catch (CommandException ex) { + server.getPluginManager().callEvent(new ServerExceptionEvent(new ServerCommandException(ex, target, sender, args))); // Paper + //target.timings.stopTiming(); // Spigot // Paper + throw ex; + } catch (Throwable ex) { + //target.timings.stopTiming(); // Spigot // Paper + String msg = "Unhandled exception executing '" + commandLine + "' in " + target; + server.getPluginManager().callEvent(new ServerExceptionEvent(new ServerCommandException(ex, target, sender, args))); // Paper + throw new CommandException(msg, ex); + } + + // return true as command was handled + return true; + } + + public synchronized void clearCommands() { + for (Map.Entry entry : knownCommands.entrySet()) { + entry.getValue().unregister(this); + } + knownCommands.clear(); + setDefaultCommands(); + } + + @Nullable + public Command getCommand(@NotNull String name) { + Command target = knownCommands.get(name.toLowerCase(java.util.Locale.ENGLISH)); + return target; + } + + @Nullable + public List tabComplete(@NotNull CommandSender sender, @NotNull String cmdLine) { + return tabComplete(sender, cmdLine, null); + } + + @Nullable + public List tabComplete(@NotNull CommandSender sender, @NotNull String cmdLine, @Nullable Location location) { + Validate.notNull(sender, "Sender cannot be null"); + Validate.notNull(cmdLine, "Command line cannot null"); + + int spaceIndex = cmdLine.indexOf(' '); + + if (spaceIndex == -1) { + ArrayList completions = new ArrayList(); + Map knownCommands = this.knownCommands; + + final String prefix = (sender instanceof Player ? "/" : ""); + + for (Map.Entry commandEntry : knownCommands.entrySet()) { + Command command = commandEntry.getValue(); + + if (!command.testPermissionSilent(sender)) { + continue; + } + + String name = commandEntry.getKey(); // Use the alias, not command name + + if (StringUtil.startsWithIgnoreCase(name, cmdLine)) { + completions.add(prefix + name); + } + } + + Collections.sort(completions, String.CASE_INSENSITIVE_ORDER); + return completions; + } + + String commandName = cmdLine.substring(0, spaceIndex); + Command target = getCommand(commandName); + + if (target == null) { + return null; + } + + if (!target.testPermissionSilent(sender)) { + return null; + } + + String[] args = cmdLine.substring(spaceIndex + 1, cmdLine.length()).split(" ", -1); + + try { + return target.tabComplete(sender, commandName, args, location); + } catch (CommandException ex) { + throw ex; + } catch (Throwable ex) { + String msg = "Unhandled exception executing tab-completer for '" + cmdLine + "' in " + target; + server.getPluginManager().callEvent(new ServerExceptionEvent(new ServerTabCompleteException(msg, ex, target, sender, args))); // Paper + throw new CommandException(msg, ex); + } + } + + @NotNull + public Collection getCommands() { + return Collections.unmodifiableCollection(knownCommands.values()); + } + + public void registerServerAliases() { + Map values = server.getCommandAliases(); + + for (Map.Entry entry : values.entrySet()) { + String alias = entry.getKey(); + if (alias.contains(" ")) { + server.getLogger().warning("Could not register alias " + alias + " because it contains illegal characters"); + continue; + } + + String[] commandStrings = entry.getValue(); + List targets = new ArrayList(); + StringBuilder bad = new StringBuilder(); + + for (String commandString : commandStrings) { + String[] commandArgs = commandString.split(" "); + Command command = getCommand(commandArgs[0]); + + if (command == null) { + if (bad.length() > 0) { + bad.append(", "); + } + bad.append(commandString); + } else { + targets.add(commandString); + } + } + + if (bad.length() > 0) { + server.getLogger().warning("Could not register alias " + alias + " because it contains commands that do not exist: " + bad); + continue; + } + + // We register these as commands so they have absolute priority. + if (targets.size() > 0) { + knownCommands.put(alias.toLowerCase(java.util.Locale.ENGLISH), new FormattedCommandAlias(alias.toLowerCase(java.util.Locale.ENGLISH), targets.toArray(new String[targets.size()]))); + } else { + knownCommands.remove(alias.toLowerCase(java.util.Locale.ENGLISH)); + } + } + } + + // Paper start - Expose Known Commands + @NotNull + public Map getKnownCommands() { + return knownCommands; + } + // Paper end +} diff --git a/api/src/main/java/org/bukkit/command/TabCompleter.java b/api/src/main/java/org/bukkit/command/TabCompleter.java new file mode 100644 index 000000000..04c0e8749 --- /dev/null +++ b/api/src/main/java/org/bukkit/command/TabCompleter.java @@ -0,0 +1,28 @@ +package org.bukkit.command; + +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +import java.util.List; + +/** + * Represents a class which can suggest tab completions for commands. + */ +public interface TabCompleter { + + /** + * Requests a list of possible completions for a command argument. + * + * @param sender Source of the command. For players tab-completing a + * command inside of a command block, this will be the player, not + * the command block. + * @param command Command which was executed + * @param alias The alias used + * @param args The arguments passed to the command, including final + * partial argument to be completed and command label + * @return A List of possible completions for the final argument, or null + * to default to the command executor + */ + @Nullable + public List onTabComplete(@NotNull CommandSender sender, @NotNull Command command, @NotNull String alias, @NotNull String[] args); +} diff --git a/api/src/main/java/org/bukkit/command/TabExecutor.java b/api/src/main/java/org/bukkit/command/TabExecutor.java new file mode 100644 index 000000000..6b8e3fb2a --- /dev/null +++ b/api/src/main/java/org/bukkit/command/TabExecutor.java @@ -0,0 +1,8 @@ +package org.bukkit.command; + +/** + * This class is provided as a convenience to implement both TabCompleter and + * CommandExecutor. + */ +public interface TabExecutor extends TabCompleter, CommandExecutor { +} diff --git a/api/src/main/java/org/bukkit/command/defaults/BukkitCommand.java b/api/src/main/java/org/bukkit/command/defaults/BukkitCommand.java new file mode 100644 index 000000000..1d8249da2 --- /dev/null +++ b/api/src/main/java/org/bukkit/command/defaults/BukkitCommand.java @@ -0,0 +1,16 @@ +package org.bukkit.command.defaults; + +import java.util.List; + +import org.bukkit.command.Command; +import org.jetbrains.annotations.NotNull; + +public abstract class BukkitCommand extends Command { + protected BukkitCommand(@NotNull String name) { + super(name); + } + + protected BukkitCommand(@NotNull String name, @NotNull String description, @NotNull String usageMessage, @NotNull List aliases) { + super(name, description, usageMessage, aliases); + } +} diff --git a/api/src/main/java/org/bukkit/command/defaults/HelpCommand.java b/api/src/main/java/org/bukkit/command/defaults/HelpCommand.java new file mode 100644 index 000000000..391283bd4 --- /dev/null +++ b/api/src/main/java/org/bukkit/command/defaults/HelpCommand.java @@ -0,0 +1,232 @@ +package org.bukkit.command.defaults; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.TreeSet; + +import org.apache.commons.lang.ArrayUtils; +import org.apache.commons.lang.StringUtils; +import org.apache.commons.lang.Validate; +import org.apache.commons.lang.math.NumberUtils; +import org.bukkit.Bukkit; +import org.bukkit.ChatColor; +import org.bukkit.command.CommandSender; +import org.bukkit.command.ConsoleCommandSender; +import org.bukkit.help.HelpMap; +import org.bukkit.help.HelpTopic; +import org.bukkit.help.HelpTopicComparator; +import org.bukkit.help.IndexHelpTopic; +import org.bukkit.util.ChatPaginator; + +import com.google.common.collect.ImmutableList; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +public class HelpCommand extends BukkitCommand { + public HelpCommand() { + super("help"); + this.description = "Shows the help menu"; + this.usageMessage = "/help \n/help \n/help "; + this.setAliases(Arrays.asList(new String[] { "?" })); + this.setPermission("bukkit.command.help"); + } + + @Override + public boolean execute(@NotNull CommandSender sender, @NotNull String currentAlias, @NotNull String[] args) { + if (!testPermission(sender)) return true; + + String command; + int pageNumber; + int pageHeight; + int pageWidth; + + if (args.length == 0) { + command = ""; + pageNumber = 1; + } else if (NumberUtils.isDigits(args[args.length - 1])) { + command = StringUtils.join(ArrayUtils.subarray(args, 0, args.length - 1), " "); + try { + pageNumber = NumberUtils.createInteger(args[args.length - 1]); + } catch (NumberFormatException exception) { + pageNumber = 1; + } + if (pageNumber <= 0) { + pageNumber = 1; + } + } else { + command = StringUtils.join(args, " "); + pageNumber = 1; + } + + if (sender instanceof ConsoleCommandSender) { + pageHeight = ChatPaginator.UNBOUNDED_PAGE_HEIGHT; + pageWidth = ChatPaginator.UNBOUNDED_PAGE_WIDTH; + } else { + pageHeight = ChatPaginator.CLOSED_CHAT_PAGE_HEIGHT - 1; + pageWidth = ChatPaginator.GUARANTEED_NO_WRAP_CHAT_PAGE_WIDTH; + } + + HelpMap helpMap = Bukkit.getServer().getHelpMap(); + HelpTopic topic = helpMap.getHelpTopic(command); + + if (topic == null) { + topic = helpMap.getHelpTopic("/" + command); + } + + if (topic == null) { + topic = findPossibleMatches(command); + } + + if (topic == null || !topic.canSee(sender)) { + sender.sendMessage(ChatColor.RED + "No help for " + command); + return true; + } + + ChatPaginator.ChatPage page = ChatPaginator.paginate(topic.getFullText(sender), pageNumber, pageWidth, pageHeight); + + StringBuilder header = new StringBuilder(); + header.append(ChatColor.YELLOW); + header.append("--------- "); + header.append(ChatColor.WHITE); + header.append("Help: "); + header.append(topic.getName()); + header.append(" "); + if (page.getTotalPages() > 1) { + header.append("("); + header.append(page.getPageNumber()); + header.append("/"); + header.append(page.getTotalPages()); + header.append(") "); + } + header.append(ChatColor.YELLOW); + for (int i = header.length(); i < ChatPaginator.GUARANTEED_NO_WRAP_CHAT_PAGE_WIDTH; i++) { + header.append("-"); + } + sender.sendMessage(header.toString()); + + sender.sendMessage(page.getLines()); + + return true; + } + + @NotNull + @Override + public List tabComplete(@NotNull CommandSender sender, @NotNull String alias, @NotNull String[] args) { + Validate.notNull(sender, "Sender cannot be null"); + Validate.notNull(args, "Arguments cannot be null"); + Validate.notNull(alias, "Alias cannot be null"); + + if (args.length == 1) { + List matchedTopics = new ArrayList(); + String searchString = args[0]; + for (HelpTopic topic : Bukkit.getServer().getHelpMap().getHelpTopics()) { + String trimmedTopic = topic.getName().startsWith("/") ? topic.getName().substring(1) : topic.getName(); + + if (trimmedTopic.startsWith(searchString)) { + matchedTopics.add(trimmedTopic); + } + } + return matchedTopics; + } + return ImmutableList.of(); + } + + @Nullable + protected HelpTopic findPossibleMatches(@NotNull String searchString) { + int maxDistance = (searchString.length() / 5) + 3; + Set possibleMatches = new TreeSet(HelpTopicComparator.helpTopicComparatorInstance()); + + if (searchString.startsWith("/")) { + searchString = searchString.substring(1); + } + + for (HelpTopic topic : Bukkit.getServer().getHelpMap().getHelpTopics()) { + String trimmedTopic = topic.getName().startsWith("/") ? topic.getName().substring(1) : topic.getName(); + + if (trimmedTopic.length() < searchString.length()) { + continue; + } + + if (Character.toLowerCase(trimmedTopic.charAt(0)) != Character.toLowerCase(searchString.charAt(0))) { + continue; + } + + if (damerauLevenshteinDistance(searchString, trimmedTopic.substring(0, searchString.length())) < maxDistance) { + possibleMatches.add(topic); + } + } + + if (possibleMatches.size() > 0) { + return new IndexHelpTopic("Search", null, null, possibleMatches, "Search for: " + searchString); + } else { + return null; + } + } + + /** + * Computes the Dameraur-Levenshtein Distance between two strings. Adapted + * from the algorithm at Wikipedia: Damerau–Levenshtein distance + * + * @param s1 The first string being compared. + * @param s2 The second string being compared. + * @return The number of substitutions, deletions, insertions, and + * transpositions required to get from s1 to s2. + */ + protected static int damerauLevenshteinDistance(@Nullable String s1, @Nullable String s2) { + if (s1 == null && s2 == null) { + return 0; + } + if (s1 != null && s2 == null) { + return s1.length(); + } + if (s1 == null && s2 != null) { + return s2.length(); + } + + int s1Len = s1.length(); + int s2Len = s2.length(); + int[][] H = new int[s1Len + 2][s2Len + 2]; + + int INF = s1Len + s2Len; + H[0][0] = INF; + for (int i = 0; i <= s1Len; i++) { + H[i + 1][1] = i; + H[i + 1][0] = INF; + } + for (int j = 0; j <= s2Len; j++) { + H[1][j + 1] = j; + H[0][j + 1] = INF; + } + + Map sd = new HashMap(); + for (char Letter : (s1 + s2).toCharArray()) { + if (!sd.containsKey(Letter)) { + sd.put(Letter, 0); + } + } + + for (int i = 1; i <= s1Len; i++) { + int DB = 0; + for (int j = 1; j <= s2Len; j++) { + int i1 = sd.get(s2.charAt(j - 1)); + int j1 = DB; + + if (s1.charAt(i - 1) == s2.charAt(j - 1)) { + H[i + 1][j + 1] = H[i][j]; + DB = j; + } else { + H[i + 1][j + 1] = Math.min(H[i][j], Math.min(H[i + 1][j], H[i][j + 1])) + 1; + } + + H[i + 1][j + 1] = Math.min(H[i + 1][j + 1], H[i1][j1] + (i - i1 - 1) + 1 + (j - j1 - 1)); + } + sd.put(s1.charAt(i - 1), i); + } + + return H[s1Len + 1][s2Len + 1]; + } +} diff --git a/api/src/main/java/org/bukkit/command/defaults/PluginsCommand.java b/api/src/main/java/org/bukkit/command/defaults/PluginsCommand.java new file mode 100644 index 000000000..6cfd9f3c6 --- /dev/null +++ b/api/src/main/java/org/bukkit/command/defaults/PluginsCommand.java @@ -0,0 +1,67 @@ +package org.bukkit.command.defaults; + +import java.util.Arrays; +import java.util.Collections; +import java.util.List; +import java.util.Map; +import java.util.TreeMap; + +import org.bukkit.Bukkit; +import org.bukkit.ChatColor; +import org.bukkit.command.CommandSender; +import org.bukkit.plugin.Plugin; +import org.jetbrains.annotations.NotNull; + +public class PluginsCommand extends BukkitCommand { + public PluginsCommand(@NotNull String name) { + super(name); + this.description = "Gets a list of plugins running on the server"; + this.usageMessage = "/plugins"; + this.setPermission("bukkit.command.plugins"); + this.setAliases(Arrays.asList("pl")); + } + + @Override + public boolean execute(@NotNull CommandSender sender, @NotNull String currentAlias, @NotNull String[] args) { + if (!testPermission(sender)) return true; + + sender.sendMessage("Plugins " + getPluginList()); + return true; + } + + @NotNull + @Override + public List tabComplete(@NotNull CommandSender sender, @NotNull String alias, @NotNull String[] args) throws IllegalArgumentException { + return Collections.emptyList(); + } + + @NotNull + private String getPluginList() { + // Paper start + TreeMap plugins = new TreeMap<>(String.CASE_INSENSITIVE_ORDER); + + for (Plugin plugin : Bukkit.getPluginManager().getPlugins()) { + // Paper start - Add an asterisk to legacy plugins (so admins are aware) + String pluginName = plugin.getDescription().getName(); + if (org.bukkit.UnsafeValues.isLegacyPlugin(plugin)) { + pluginName += "*"; + } + + plugins.put(pluginName, plugin.isEnabled() ? ChatColor.GREEN : ChatColor.RED); + // Paper end + } + + StringBuilder pluginList = new StringBuilder(); + for (Map.Entry entry : plugins.entrySet()) { + if (pluginList.length() > 0) { + pluginList.append(ChatColor.WHITE); + pluginList.append(", "); + } + pluginList.append(entry.getValue()); + pluginList.append(entry.getKey()); + } + + return "(" + plugins.size() + "): " + pluginList.toString(); + // Paper end + } +} diff --git a/api/src/main/java/org/bukkit/command/defaults/ReloadCommand.java b/api/src/main/java/org/bukkit/command/defaults/ReloadCommand.java new file mode 100644 index 000000000..607323a83 --- /dev/null +++ b/api/src/main/java/org/bukkit/command/defaults/ReloadCommand.java @@ -0,0 +1,66 @@ +package org.bukkit.command.defaults; + +import java.util.Arrays; +import java.util.Collections; +import java.util.List; + +import org.bukkit.Bukkit; +import org.bukkit.ChatColor; +import org.bukkit.command.Command; +import org.bukkit.command.CommandSender; +import org.jetbrains.annotations.NotNull; + +public class ReloadCommand extends BukkitCommand { + public ReloadCommand(@NotNull String name) { + super(name); + this.description = "Reloads the server configuration and plugins"; + this.usageMessage = "/reload [permissions|commands|confirm]"; // Paper + this.setPermission("bukkit.command.reload"); + this.setAliases(Arrays.asList("rl")); + } + + @Override + public boolean execute(@NotNull CommandSender sender, @NotNull String currentAlias, @NotNull String[] args) { // Paper + if (!testPermission(sender)) return true; + + // Paper start - Reload permissions.yml & require confirm + boolean confirmed = System.getProperty("LetMeReload") != null; + if (args.length == 1) { + if (args[0].equalsIgnoreCase("permissions")) { + Bukkit.getServer().reloadPermissions(); + Command.broadcastCommandMessage(sender, ChatColor.GREEN + "Permissions successfully reloaded."); + return true; + } else if ("commands".equalsIgnoreCase(args[0])) { + if (Bukkit.getServer().reloadCommandAliases()) { + Command.broadcastCommandMessage(sender, ChatColor.GREEN + "Command aliases successfully reloaded."); + } else { + Command.broadcastCommandMessage(sender, ChatColor.RED + "An error occurred while trying to reload command aliases."); + } + return true; + } else if ("confirm".equalsIgnoreCase(args[0])) { + confirmed = true; + } else { + Command.broadcastCommandMessage(sender, ChatColor.RED + "Usage: " + usageMessage); + return true; + } + } + if (!confirmed) { + Command.broadcastCommandMessage(sender, ChatColor.RED + "Are you sure you wish to reload your server? Doing so may cause bugs and memory leaks. It is recommended to restart instead of using /reload. To confirm, please type " + ChatColor.YELLOW + "/reload confirm"); + return true; + } + // Paper end + + Command.broadcastCommandMessage(sender, ChatColor.RED + "Please note that this command is not supported and may cause issues when using some plugins."); + Command.broadcastCommandMessage(sender, ChatColor.RED + "If you encounter any issues please use the /stop command to restart your server."); + Bukkit.reload(); + Command.broadcastCommandMessage(sender, ChatColor.GREEN + "Reload complete."); + + return true; + } + + @NotNull + @Override + public List tabComplete(@NotNull CommandSender sender, @NotNull String alias, @NotNull String[] args) throws IllegalArgumentException { + return java.util.Collections.singletonList("permissions"); // Paper + } +} diff --git a/src/api/main/java/org/bukkit/command/defaults/VersionCommand.java b/api/src/main/java/org/bukkit/command/defaults/VersionCommand.java similarity index 100% rename from src/api/main/java/org/bukkit/command/defaults/VersionCommand.java rename to api/src/main/java/org/bukkit/command/defaults/VersionCommand.java diff --git a/api/src/main/java/org/bukkit/configuration/Configuration.java b/api/src/main/java/org/bukkit/configuration/Configuration.java new file mode 100644 index 000000000..7f7e3ff71 --- /dev/null +++ b/api/src/main/java/org/bukkit/configuration/Configuration.java @@ -0,0 +1,89 @@ +package org.bukkit.configuration; + +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +import java.util.Map; + +/** + * Represents a source of configurable options and settings + */ +public interface Configuration extends ConfigurationSection { + /** + * Sets the default value of the given path as provided. + *

+ * If no source {@link Configuration} was provided as a default + * collection, then a new {@link MemoryConfiguration} will be created to + * hold the new default value. + *

+ * If value is null, the value will be removed from the default + * Configuration source. + * + * @param path Path of the value to set. + * @param value Value to set the default to. + * @throws IllegalArgumentException Thrown if path is null. + */ + public void addDefault(@NotNull String path, @Nullable Object value); + + /** + * Sets the default values of the given paths as provided. + *

+ * If no source {@link Configuration} was provided as a default + * collection, then a new {@link MemoryConfiguration} will be created to + * hold the new default values. + * + * @param defaults A map of Path{@literal ->}Values to add to defaults. + * @throws IllegalArgumentException Thrown if defaults is null. + */ + public void addDefaults(@NotNull Map defaults); + + /** + * Sets the default values of the given paths as provided. + *

+ * If no source {@link Configuration} was provided as a default + * collection, then a new {@link MemoryConfiguration} will be created to + * hold the new default value. + *

+ * This method will not hold a reference to the specified Configuration, + * nor will it automatically update if that Configuration ever changes. If + * you require this, you should set the default source with {@link + * #setDefaults(org.bukkit.configuration.Configuration)}. + * + * @param defaults A configuration holding a list of defaults to copy. + * @throws IllegalArgumentException Thrown if defaults is null or this. + */ + public void addDefaults(@NotNull Configuration defaults); + + /** + * Sets the source of all default values for this {@link Configuration}. + *

+ * If a previous source was set, or previous default values were defined, + * then they will not be copied to the new source. + * + * @param defaults New source of default values for this configuration. + * @throws IllegalArgumentException Thrown if defaults is null or this. + */ + public void setDefaults(@NotNull Configuration defaults); + + /** + * Gets the source {@link Configuration} for this configuration. + *

+ * If no configuration source was set, but default values were added, then + * a {@link MemoryConfiguration} will be returned. If no source was set + * and no defaults were set, then this method will return null. + * + * @return Configuration source for default values, or null if none exist. + */ + @Nullable + public Configuration getDefaults(); + + /** + * Gets the {@link ConfigurationOptions} for this {@link Configuration}. + *

+ * All setters through this method are chainable. + * + * @return Options for this configuration + */ + @NotNull + public ConfigurationOptions options(); +} diff --git a/api/src/main/java/org/bukkit/configuration/ConfigurationOptions.java b/api/src/main/java/org/bukkit/configuration/ConfigurationOptions.java new file mode 100644 index 000000000..356bad6bc --- /dev/null +++ b/api/src/main/java/org/bukkit/configuration/ConfigurationOptions.java @@ -0,0 +1,95 @@ +package org.bukkit.configuration; + +import org.jetbrains.annotations.NotNull; + +/** + * Various settings for controlling the input and output of a {@link + * Configuration} + */ +public class ConfigurationOptions { + private char pathSeparator = '.'; + private boolean copyDefaults = false; + private final Configuration configuration; + + protected ConfigurationOptions(@NotNull Configuration configuration) { + this.configuration = configuration; + } + + /** + * Returns the {@link Configuration} that this object is responsible for. + * + * @return Parent configuration + */ + @NotNull + public Configuration configuration() { + return configuration; + } + + /** + * Gets the char that will be used to separate {@link + * ConfigurationSection}s + *

+ * This value does not affect how the {@link Configuration} is stored, + * only in how you access the data. The default value is '.'. + * + * @return Path separator + */ + public char pathSeparator() { + return pathSeparator; + } + + /** + * Sets the char that will be used to separate {@link + * ConfigurationSection}s + *

+ * This value does not affect how the {@link Configuration} is stored, + * only in how you access the data. The default value is '.'. + * + * @param value Path separator + * @return This object, for chaining + */ + @NotNull + public ConfigurationOptions pathSeparator(char value) { + this.pathSeparator = value; + return this; + } + + /** + * Checks if the {@link Configuration} should copy values from its default + * {@link Configuration} directly. + *

+ * If this is true, all values in the default Configuration will be + * directly copied, making it impossible to distinguish between values + * that were set and values that are provided by default. As a result, + * {@link ConfigurationSection#contains(java.lang.String)} will always + * return the same value as {@link + * ConfigurationSection#isSet(java.lang.String)}. The default value is + * false. + * + * @return Whether or not defaults are directly copied + */ + public boolean copyDefaults() { + return copyDefaults; + } + + /** + * Sets if the {@link Configuration} should copy values from its default + * {@link Configuration} directly. + *

+ * If this is true, all values in the default Configuration will be + * directly copied, making it impossible to distinguish between values + * that were set and values that are provided by default. As a result, + * {@link ConfigurationSection#contains(java.lang.String)} will always + * return the same value as {@link + * ConfigurationSection#isSet(java.lang.String)}. The default value is + * false. + * + * @param value Whether or not defaults are directly copied + * @return This object, for chaining + */ + @NotNull + public ConfigurationOptions copyDefaults(boolean value) { + this.copyDefaults = value; + return this; + } +} diff --git a/api/src/main/java/org/bukkit/configuration/ConfigurationSection.java b/api/src/main/java/org/bukkit/configuration/ConfigurationSection.java new file mode 100644 index 000000000..e83de68ac --- /dev/null +++ b/api/src/main/java/org/bukkit/configuration/ConfigurationSection.java @@ -0,0 +1,946 @@ +package org.bukkit.configuration; + +import java.util.Map; +import java.util.Set; +import java.util.List; + +import org.bukkit.Color; +import org.bukkit.OfflinePlayer; +import org.bukkit.configuration.serialization.ConfigurationSerializable; +import org.bukkit.util.Vector; +import org.bukkit.inventory.ItemStack; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +/** + * Represents a section of a {@link Configuration} + */ +public interface ConfigurationSection { + /** + * Gets a set containing all keys in this section. + *

+ * If deep is set to true, then this will contain all the keys within any + * child {@link ConfigurationSection}s (and their children, etc). These + * will be in a valid path notation for you to use. + *

+ * If deep is set to false, then this will contain only the keys of any + * direct children, and not their own children. + * + * @param deep Whether or not to get a deep list, as opposed to a shallow + * list. + * @return Set of keys contained within this ConfigurationSection. + */ + @NotNull + public Set getKeys(boolean deep); + + /** + * Gets a Map containing all keys and their values for this section. + *

+ * If deep is set to true, then this will contain all the keys and values + * within any child {@link ConfigurationSection}s (and their children, + * etc). These keys will be in a valid path notation for you to use. + *

+ * If deep is set to false, then this will contain only the keys and + * values of any direct children, and not their own children. + * + * @param deep Whether or not to get a deep list, as opposed to a shallow + * list. + * @return Map of keys and values of this section. + */ + @NotNull + public Map getValues(boolean deep); + + /** + * Checks if this {@link ConfigurationSection} contains the given path. + *

+ * If the value for the requested path does not exist but a default value + * has been specified, this will return true. + * + * @param path Path to check for existence. + * @return True if this section contains the requested path, either via + * default or being set. + * @throws IllegalArgumentException Thrown when path is null. + */ + public boolean contains(@NotNull String path); + + /** + * Checks if this {@link ConfigurationSection} contains the given path. + *

+ * If the value for the requested path does not exist, the boolean parameter + * of true has been specified, a default value for the path exists, this + * will return true. + *

+ * If a boolean parameter of false has been specified, true will only be + * returned if there is a set value for the specified path. + * + * @param path Path to check for existence. + * @param ignoreDefault Whether or not to ignore if a default value for the + * specified path exists. + * @return True if this section contains the requested path, or if a default + * value exist and the boolean parameter for this method is true. + * @throws IllegalArgumentException Thrown when path is null. + */ + public boolean contains(@NotNull String path, boolean ignoreDefault); + + /** + * Checks if this {@link ConfigurationSection} has a value set for the + * given path. + *

+ * If the value for the requested path does not exist but a default value + * has been specified, this will still return false. + * + * @param path Path to check for existence. + * @return True if this section contains the requested path, regardless of + * having a default. + * @throws IllegalArgumentException Thrown when path is null. + */ + public boolean isSet(@NotNull String path); + + /** + * Gets the path of this {@link ConfigurationSection} from its root {@link + * Configuration} + *

+ * For any {@link Configuration} themselves, this will return an empty + * string. + *

+ * If the section is no longer contained within its root for any reason, + * such as being replaced with a different value, this may return null. + *

+ * To retrieve the single name of this section, that is, the final part of + * the path returned by this method, you may use {@link #getName()}. + * + * @return Path of this section relative to its root + */ + @Nullable + public String getCurrentPath(); + + /** + * Gets the name of this individual {@link ConfigurationSection}, in the + * path. + *

+ * This will always be the final part of {@link #getCurrentPath()}, unless + * the section is orphaned. + * + * @return Name of this section + */ + @NotNull + public String getName(); + + /** + * Gets the root {@link Configuration} that contains this {@link + * ConfigurationSection} + *

+ * For any {@link Configuration} themselves, this will return its own + * object. + *

+ * If the section is no longer contained within its root for any reason, + * such as being replaced with a different value, this may return null. + * + * @return Root configuration containing this section. + */ + @Nullable + public Configuration getRoot(); + + /** + * Gets the parent {@link ConfigurationSection} that directly contains + * this {@link ConfigurationSection}. + *

+ * For any {@link Configuration} themselves, this will return null. + *

+ * If the section is no longer contained within its parent for any reason, + * such as being replaced with a different value, this may return null. + * + * @return Parent section containing this section. + */ + @Nullable + public ConfigurationSection getParent(); + + /** + * Gets the requested Object by path. + *

+ * If the Object does not exist but a default value has been specified, + * this will return the default value. If the Object does not exist and no + * default value was specified, this will return null. + * + * @param path Path of the Object to get. + * @return Requested Object. + */ + @Nullable + public Object get(@NotNull String path); + + /** + * Gets the requested Object by path, returning a default value if not + * found. + *

+ * If the Object does not exist then the specified default value will + * returned regardless of if a default has been identified in the root + * {@link Configuration}. + * + * @param path Path of the Object to get. + * @param def The default value to return if the path is not found. + * @return Requested Object. + */ + @Nullable + public Object get(@NotNull String path, @Nullable Object def); + + /** + * Sets the specified path to the given value. + *

+ * If value is null, the entry will be removed. Any existing entry will be + * replaced, regardless of what the new value is. + *

+ * Some implementations may have limitations on what you may store. See + * their individual javadocs for details. No implementations should allow + * you to store {@link Configuration}s or {@link ConfigurationSection}s, + * please use {@link #createSection(java.lang.String)} for that. + * + * @param path Path of the object to set. + * @param value New value to set the path to. + */ + public void set(@NotNull String path, @Nullable Object value); + + /** + * Creates an empty {@link ConfigurationSection} at the specified path. + *

+ * Any value that was previously set at this path will be overwritten. If + * the previous value was itself a {@link ConfigurationSection}, it will + * be orphaned. + * + * @param path Path to create the section at. + * @return Newly created section + */ + @NotNull + public ConfigurationSection createSection(@NotNull String path); + + /** + * Creates a {@link ConfigurationSection} at the specified path, with + * specified values. + *

+ * Any value that was previously set at this path will be overwritten. If + * the previous value was itself a {@link ConfigurationSection}, it will + * be orphaned. + * + * @param path Path to create the section at. + * @param map The values to used. + * @return Newly created section + */ + @NotNull + public ConfigurationSection createSection(@NotNull String path, @NotNull Map map); + + // Primitives + /** + * Gets the requested String by path. + *

+ * If the String does not exist but a default value has been specified, + * this will return the default value. If the String does not exist and no + * default value was specified, this will return null. + * + * @param path Path of the String to get. + * @return Requested String. + */ + @Nullable + public String getString(@NotNull String path); + + /** + * Gets the requested String by path, returning a default value if not + * found. + *

+ * If the String does not exist then the specified default value will + * returned regardless of if a default has been identified in the root + * {@link Configuration}. + * + * @param path Path of the String to get. + * @param def The default value to return if the path is not found or is + * not a String. + * @return Requested String. + */ + @Nullable + public String getString(@NotNull String path, @Nullable String def); + + /** + * Checks if the specified path is a String. + *

+ * If the path exists but is not a String, this will return false. If the + * path does not exist, this will return false. If the path does not exist + * but a default value has been specified, this will check if that default + * value is a String and return appropriately. + * + * @param path Path of the String to check. + * @return Whether or not the specified path is a String. + */ + public boolean isString(@NotNull String path); + + /** + * Gets the requested int by path. + *

+ * If the int does not exist but a default value has been specified, this + * will return the default value. If the int does not exist and no default + * value was specified, this will return 0. + * + * @param path Path of the int to get. + * @return Requested int. + */ + public int getInt(@NotNull String path); + + /** + * Gets the requested int by path, returning a default value if not found. + *

+ * If the int does not exist then the specified default value will + * returned regardless of if a default has been identified in the root + * {@link Configuration}. + * + * @param path Path of the int to get. + * @param def The default value to return if the path is not found or is + * not an int. + * @return Requested int. + */ + public int getInt(@NotNull String path, int def); + + /** + * Checks if the specified path is an int. + *

+ * If the path exists but is not a int, this will return false. If the + * path does not exist, this will return false. If the path does not exist + * but a default value has been specified, this will check if that default + * value is a int and return appropriately. + * + * @param path Path of the int to check. + * @return Whether or not the specified path is an int. + */ + public boolean isInt(@NotNull String path); + + /** + * Gets the requested boolean by path. + *

+ * If the boolean does not exist but a default value has been specified, + * this will return the default value. If the boolean does not exist and + * no default value was specified, this will return false. + * + * @param path Path of the boolean to get. + * @return Requested boolean. + */ + public boolean getBoolean(@NotNull String path); + + /** + * Gets the requested boolean by path, returning a default value if not + * found. + *

+ * If the boolean does not exist then the specified default value will + * returned regardless of if a default has been identified in the root + * {@link Configuration}. + * + * @param path Path of the boolean to get. + * @param def The default value to return if the path is not found or is + * not a boolean. + * @return Requested boolean. + */ + public boolean getBoolean(@NotNull String path, boolean def); + + /** + * Checks if the specified path is a boolean. + *

+ * If the path exists but is not a boolean, this will return false. If the + * path does not exist, this will return false. If the path does not exist + * but a default value has been specified, this will check if that default + * value is a boolean and return appropriately. + * + * @param path Path of the boolean to check. + * @return Whether or not the specified path is a boolean. + */ + public boolean isBoolean(@NotNull String path); + + /** + * Gets the requested double by path. + *

+ * If the double does not exist but a default value has been specified, + * this will return the default value. If the double does not exist and no + * default value was specified, this will return 0. + * + * @param path Path of the double to get. + * @return Requested double. + */ + public double getDouble(@NotNull String path); + + /** + * Gets the requested double by path, returning a default value if not + * found. + *

+ * If the double does not exist then the specified default value will + * returned regardless of if a default has been identified in the root + * {@link Configuration}. + * + * @param path Path of the double to get. + * @param def The default value to return if the path is not found or is + * not a double. + * @return Requested double. + */ + public double getDouble(@NotNull String path, double def); + + /** + * Checks if the specified path is a double. + *

+ * If the path exists but is not a double, this will return false. If the + * path does not exist, this will return false. If the path does not exist + * but a default value has been specified, this will check if that default + * value is a double and return appropriately. + * + * @param path Path of the double to check. + * @return Whether or not the specified path is a double. + */ + public boolean isDouble(@NotNull String path); + + /** + * Gets the requested long by path. + *

+ * If the long does not exist but a default value has been specified, this + * will return the default value. If the long does not exist and no + * default value was specified, this will return 0. + * + * @param path Path of the long to get. + * @return Requested long. + */ + public long getLong(@NotNull String path); + + /** + * Gets the requested long by path, returning a default value if not + * found. + *

+ * If the long does not exist then the specified default value will + * returned regardless of if a default has been identified in the root + * {@link Configuration}. + * + * @param path Path of the long to get. + * @param def The default value to return if the path is not found or is + * not a long. + * @return Requested long. + */ + public long getLong(@NotNull String path, long def); + + /** + * Checks if the specified path is a long. + *

+ * If the path exists but is not a long, this will return false. If the + * path does not exist, this will return false. If the path does not exist + * but a default value has been specified, this will check if that default + * value is a long and return appropriately. + * + * @param path Path of the long to check. + * @return Whether or not the specified path is a long. + */ + public boolean isLong(@NotNull String path); + + // Java + /** + * Gets the requested List by path. + *

+ * If the List does not exist but a default value has been specified, this + * will return the default value. If the List does not exist and no + * default value was specified, this will return null. + * + * @param path Path of the List to get. + * @return Requested List. + */ + @Nullable + public List getList(@NotNull String path); + + /** + * Gets the requested List by path, returning a default value if not + * found. + *

+ * If the List does not exist then the specified default value will + * returned regardless of if a default has been identified in the root + * {@link Configuration}. + * + * @param path Path of the List to get. + * @param def The default value to return if the path is not found or is + * not a List. + * @return Requested List. + */ + @Nullable + public List getList(@NotNull String path, @Nullable List def); + + /** + * Checks if the specified path is a List. + *

+ * If the path exists but is not a List, this will return false. If the + * path does not exist, this will return false. If the path does not exist + * but a default value has been specified, this will check if that default + * value is a List and return appropriately. + * + * @param path Path of the List to check. + * @return Whether or not the specified path is a List. + */ + public boolean isList(@NotNull String path); + + /** + * Gets the requested List of String by path. + *

+ * If the List does not exist but a default value has been specified, this + * will return the default value. If the List does not exist and no + * default value was specified, this will return an empty List. + *

+ * This method will attempt to cast any values into a String if possible, + * but may miss any values out if they are not compatible. + * + * @param path Path of the List to get. + * @return Requested List of String. + */ + @NotNull + public List getStringList(@NotNull String path); + + /** + * Gets the requested List of Integer by path. + *

+ * If the List does not exist but a default value has been specified, this + * will return the default value. If the List does not exist and no + * default value was specified, this will return an empty List. + *

+ * This method will attempt to cast any values into a Integer if possible, + * but may miss any values out if they are not compatible. + * + * @param path Path of the List to get. + * @return Requested List of Integer. + */ + @NotNull + public List getIntegerList(@NotNull String path); + + /** + * Gets the requested List of Boolean by path. + *

+ * If the List does not exist but a default value has been specified, this + * will return the default value. If the List does not exist and no + * default value was specified, this will return an empty List. + *

+ * This method will attempt to cast any values into a Boolean if possible, + * but may miss any values out if they are not compatible. + * + * @param path Path of the List to get. + * @return Requested List of Boolean. + */ + @NotNull + public List getBooleanList(@NotNull String path); + + /** + * Gets the requested List of Double by path. + *

+ * If the List does not exist but a default value has been specified, this + * will return the default value. If the List does not exist and no + * default value was specified, this will return an empty List. + *

+ * This method will attempt to cast any values into a Double if possible, + * but may miss any values out if they are not compatible. + * + * @param path Path of the List to get. + * @return Requested List of Double. + */ + @NotNull + public List getDoubleList(@NotNull String path); + + /** + * Gets the requested List of Float by path. + *

+ * If the List does not exist but a default value has been specified, this + * will return the default value. If the List does not exist and no + * default value was specified, this will return an empty List. + *

+ * This method will attempt to cast any values into a Float if possible, + * but may miss any values out if they are not compatible. + * + * @param path Path of the List to get. + * @return Requested List of Float. + */ + @NotNull + public List getFloatList(@NotNull String path); + + /** + * Gets the requested List of Long by path. + *

+ * If the List does not exist but a default value has been specified, this + * will return the default value. If the List does not exist and no + * default value was specified, this will return an empty List. + *

+ * This method will attempt to cast any values into a Long if possible, + * but may miss any values out if they are not compatible. + * + * @param path Path of the List to get. + * @return Requested List of Long. + */ + @NotNull + public List getLongList(@NotNull String path); + + /** + * Gets the requested List of Byte by path. + *

+ * If the List does not exist but a default value has been specified, this + * will return the default value. If the List does not exist and no + * default value was specified, this will return an empty List. + *

+ * This method will attempt to cast any values into a Byte if possible, + * but may miss any values out if they are not compatible. + * + * @param path Path of the List to get. + * @return Requested List of Byte. + */ + @NotNull + public List getByteList(@NotNull String path); + + /** + * Gets the requested List of Character by path. + *

+ * If the List does not exist but a default value has been specified, this + * will return the default value. If the List does not exist and no + * default value was specified, this will return an empty List. + *

+ * This method will attempt to cast any values into a Character if + * possible, but may miss any values out if they are not compatible. + * + * @param path Path of the List to get. + * @return Requested List of Character. + */ + @NotNull + public List getCharacterList(@NotNull String path); + + /** + * Gets the requested List of Short by path. + *

+ * If the List does not exist but a default value has been specified, this + * will return the default value. If the List does not exist and no + * default value was specified, this will return an empty List. + *

+ * This method will attempt to cast any values into a Short if possible, + * but may miss any values out if they are not compatible. + * + * @param path Path of the List to get. + * @return Requested List of Short. + */ + @NotNull + public List getShortList(@NotNull String path); + + /** + * Gets the requested List of Maps by path. + *

+ * If the List does not exist but a default value has been specified, this + * will return the default value. If the List does not exist and no + * default value was specified, this will return an empty List. + *

+ * This method will attempt to cast any values into a Map if possible, but + * may miss any values out if they are not compatible. + * + * @param path Path of the List to get. + * @return Requested List of Maps. + */ + @NotNull + public List> getMapList(@NotNull String path); + + // Bukkit + /** + * Gets the requested object at the given path. + * + * If the Object does not exist but a default value has been specified, this + * will return the default value. If the Object does not exist and no + * default value was specified, this will return null. + * + * Note: For example #getObject(path, String.class) is not + * equivalent to {@link #getString(String) #getString(path)} because + * {@link #getString(String) #getString(path)} converts internally all + * Objects to Strings. However, #getObject(path, Boolean.class) is + * equivalent to {@link #getBoolean(String) #getBoolean(path)} for example. + * + * @param the type of the requested object + * @param path the path to the object. + * @param clazz the type of the requested object + * @return Requested object + */ + @Nullable + public T getObject(@NotNull String path, @NotNull Class clazz); + + /** + * Gets the requested object at the given path, returning a default value if + * not found + * + * If the Object does not exist then the specified default value will + * returned regardless of if a default has been identified in the root + * {@link Configuration}. + * + * Note: For example #getObject(path, String.class, def) is + * not equivalent to + * {@link #getString(String, String) #getString(path, def)} because + * {@link #getString(String, String) #getString(path, def)} converts + * internally all Objects to Strings. However, #getObject(path, + * Boolean.class, def) is equivalent to {@link #getBoolean(String, boolean) #getBoolean(path, + * def)} for example. + * + * @param the type of the requested object + * @param path the path to the object. + * @param clazz the type of the requested object + * @param def the default object to return if the object is not present at + * the path + * @return Requested object + */ + @Nullable + public T getObject(@NotNull String path, @NotNull Class clazz, @Nullable T def); + + /** + * Gets the requested {@link ConfigurationSerializable} object at the given + * path. + * + * If the Object does not exist but a default value has been specified, this + * will return the default value. If the Object does not exist and no + * default value was specified, this will return null. + * + * @param the type of {@link ConfigurationSerializable} + * @param path the path to the object. + * @param clazz the type of {@link ConfigurationSerializable} + * @return Requested {@link ConfigurationSerializable} object + */ + @Nullable + public T getSerializable(@NotNull String path, @NotNull Class clazz); + + /** + * Gets the requested {@link ConfigurationSerializable} object at the given + * path, returning a default value if not found + * + * If the Object does not exist then the specified default value will + * returned regardless of if a default has been identified in the root + * {@link Configuration}. + * + * @param the type of {@link ConfigurationSerializable} + * @param path the path to the object. + * @param clazz the type of {@link ConfigurationSerializable} + * @param def the default object to return if the object is not present at + * the path + * @return Requested {@link ConfigurationSerializable} object + */ + @Nullable + public T getSerializable(@NotNull String path, @NotNull Class clazz, @Nullable T def); + + /** + * Gets the requested Vector by path. + *

+ * If the Vector does not exist but a default value has been specified, + * this will return the default value. If the Vector does not exist and no + * default value was specified, this will return null. + * + * @param path Path of the Vector to get. + * @return Requested Vector. + */ + @Nullable + public Vector getVector(@NotNull String path); + + /** + * Gets the requested {@link Vector} by path, returning a default value if + * not found. + *

+ * If the Vector does not exist then the specified default value will + * returned regardless of if a default has been identified in the root + * {@link Configuration}. + * + * @param path Path of the Vector to get. + * @param def The default value to return if the path is not found or is + * not a Vector. + * @return Requested Vector. + */ + @Nullable + public Vector getVector(@NotNull String path, @Nullable Vector def); + + /** + * Checks if the specified path is a Vector. + *

+ * If the path exists but is not a Vector, this will return false. If the + * path does not exist, this will return false. If the path does not exist + * but a default value has been specified, this will check if that default + * value is a Vector and return appropriately. + * + * @param path Path of the Vector to check. + * @return Whether or not the specified path is a Vector. + */ + public boolean isVector(@NotNull String path); + + /** + * Gets the requested OfflinePlayer by path. + *

+ * If the OfflinePlayer does not exist but a default value has been + * specified, this will return the default value. If the OfflinePlayer + * does not exist and no default value was specified, this will return + * null. + * + * @param path Path of the OfflinePlayer to get. + * @return Requested OfflinePlayer. + */ + @Nullable + public OfflinePlayer getOfflinePlayer(@NotNull String path); + + /** + * Gets the requested {@link OfflinePlayer} by path, returning a default + * value if not found. + *

+ * If the OfflinePlayer does not exist then the specified default value + * will returned regardless of if a default has been identified in the + * root {@link Configuration}. + * + * @param path Path of the OfflinePlayer to get. + * @param def The default value to return if the path is not found or is + * not an OfflinePlayer. + * @return Requested OfflinePlayer. + */ + @Nullable + public OfflinePlayer getOfflinePlayer(@NotNull String path, @Nullable OfflinePlayer def); + + /** + * Checks if the specified path is an OfflinePlayer. + *

+ * If the path exists but is not a OfflinePlayer, this will return false. + * If the path does not exist, this will return false. If the path does + * not exist but a default value has been specified, this will check if + * that default value is a OfflinePlayer and return appropriately. + * + * @param path Path of the OfflinePlayer to check. + * @return Whether or not the specified path is an OfflinePlayer. + */ + public boolean isOfflinePlayer(@NotNull String path); + + /** + * Gets the requested ItemStack by path. + *

+ * If the ItemStack does not exist but a default value has been specified, + * this will return the default value. If the ItemStack does not exist and + * no default value was specified, this will return null. + * + * @param path Path of the ItemStack to get. + * @return Requested ItemStack. + */ + @Nullable + public ItemStack getItemStack(@NotNull String path); + + /** + * Gets the requested {@link ItemStack} by path, returning a default value + * if not found. + *

+ * If the ItemStack does not exist then the specified default value will + * returned regardless of if a default has been identified in the root + * {@link Configuration}. + * + * @param path Path of the ItemStack to get. + * @param def The default value to return if the path is not found or is + * not an ItemStack. + * @return Requested ItemStack. + */ + @Nullable + public ItemStack getItemStack(@NotNull String path, @Nullable ItemStack def); + + /** + * Checks if the specified path is an ItemStack. + *

+ * If the path exists but is not a ItemStack, this will return false. If + * the path does not exist, this will return false. If the path does not + * exist but a default value has been specified, this will check if that + * default value is a ItemStack and return appropriately. + * + * @param path Path of the ItemStack to check. + * @return Whether or not the specified path is an ItemStack. + */ + public boolean isItemStack(@NotNull String path); + + /** + * Gets the requested Color by path. + *

+ * If the Color does not exist but a default value has been specified, + * this will return the default value. If the Color does not exist and no + * default value was specified, this will return null. + * + * @param path Path of the Color to get. + * @return Requested Color. + */ + @Nullable + public Color getColor(@NotNull String path); + + /** + * Gets the requested {@link Color} by path, returning a default value if + * not found. + *

+ * If the Color does not exist then the specified default value will + * returned regardless of if a default has been identified in the root + * {@link Configuration}. + * + * @param path Path of the Color to get. + * @param def The default value to return if the path is not found or is + * not a Color. + * @return Requested Color. + */ + @Nullable + public Color getColor(@NotNull String path, @Nullable Color def); + + /** + * Checks if the specified path is a Color. + *

+ * If the path exists but is not a Color, this will return false. If the + * path does not exist, this will return false. If the path does not exist + * but a default value has been specified, this will check if that default + * value is a Color and return appropriately. + * + * @param path Path of the Color to check. + * @return Whether or not the specified path is a Color. + */ + public boolean isColor(@NotNull String path); + + /** + * Gets the requested ConfigurationSection by path. + *

+ * If the ConfigurationSection does not exist but a default value has been + * specified, this will return the default value. If the + * ConfigurationSection does not exist and no default value was specified, + * this will return null. + * + * @param path Path of the ConfigurationSection to get. + * @return Requested ConfigurationSection. + */ + @Nullable + public ConfigurationSection getConfigurationSection(@NotNull String path); + + /** + * Checks if the specified path is a ConfigurationSection. + *

+ * If the path exists but is not a ConfigurationSection, this will return + * false. If the path does not exist, this will return false. If the path + * does not exist but a default value has been specified, this will check + * if that default value is a ConfigurationSection and return + * appropriately. + * + * @param path Path of the ConfigurationSection to check. + * @return Whether or not the specified path is a ConfigurationSection. + */ + public boolean isConfigurationSection(@NotNull String path); + + /** + * Gets the equivalent {@link ConfigurationSection} from the default + * {@link Configuration} defined in {@link #getRoot()}. + *

+ * If the root contains no defaults, or the defaults doesn't contain a + * value for this path, or the value at this path is not a {@link + * ConfigurationSection} then this will return null. + * + * @return Equivalent section in root configuration + */ + @Nullable + public ConfigurationSection getDefaultSection(); + + /** + * Sets the default value in the root at the given path as provided. + *

+ * If no source {@link Configuration} was provided as a default + * collection, then a new {@link MemoryConfiguration} will be created to + * hold the new default value. + *

+ * If value is null, the value will be removed from the default + * Configuration source. + *

+ * If the value as returned by {@link #getDefaultSection()} is null, then + * this will create a new section at the path, replacing anything that may + * have existed there previously. + * + * @param path Path of the value to set. + * @param value Value to set the default to. + * @throws IllegalArgumentException Thrown if path is null. + */ + public void addDefault(@NotNull String path, @Nullable Object value); +} diff --git a/api/src/main/java/org/bukkit/configuration/InvalidConfigurationException.java b/api/src/main/java/org/bukkit/configuration/InvalidConfigurationException.java new file mode 100644 index 000000000..d23480e59 --- /dev/null +++ b/api/src/main/java/org/bukkit/configuration/InvalidConfigurationException.java @@ -0,0 +1,45 @@ +package org.bukkit.configuration; + +/** + * Exception thrown when attempting to load an invalid {@link Configuration} + */ +@SuppressWarnings("serial") +public class InvalidConfigurationException extends Exception { + + /** + * Creates a new instance of InvalidConfigurationException without a + * message or cause. + */ + public InvalidConfigurationException() {} + + /** + * Constructs an instance of InvalidConfigurationException with the + * specified message. + * + * @param msg The details of the exception. + */ + public InvalidConfigurationException(String msg) { + super(msg); + } + + /** + * Constructs an instance of InvalidConfigurationException with the + * specified cause. + * + * @param cause The cause of the exception. + */ + public InvalidConfigurationException(Throwable cause) { + super(cause); + } + + /** + * Constructs an instance of InvalidConfigurationException with the + * specified message and cause. + * + * @param cause The cause of the exception. + * @param msg The details of the exception. + */ + public InvalidConfigurationException(String msg, Throwable cause) { + super(msg, cause); + } +} diff --git a/api/src/main/java/org/bukkit/configuration/MemoryConfiguration.java b/api/src/main/java/org/bukkit/configuration/MemoryConfiguration.java new file mode 100644 index 000000000..e03108b12 --- /dev/null +++ b/api/src/main/java/org/bukkit/configuration/MemoryConfiguration.java @@ -0,0 +1,84 @@ +package org.bukkit.configuration; + +import java.util.Map; + +import org.apache.commons.lang.Validate; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +/** + * This is a {@link Configuration} implementation that does not save or load + * from any source, and stores all values in memory only. + * This is useful for temporary Configurations for providing defaults. + */ +public class MemoryConfiguration extends MemorySection implements Configuration { + protected Configuration defaults; + protected MemoryConfigurationOptions options; + + /** + * Creates an empty {@link MemoryConfiguration} with no default values. + */ + public MemoryConfiguration() {} + + /** + * Creates an empty {@link MemoryConfiguration} using the specified {@link + * Configuration} as a source for all default values. + * + * @param defaults Default value provider + * @throws IllegalArgumentException Thrown if defaults is null + */ + public MemoryConfiguration(@Nullable Configuration defaults) { + this.defaults = defaults; + } + + @Override + public void addDefault(@NotNull String path, @Nullable Object value) { + Validate.notNull(path, "Path may not be null"); + + if (defaults == null) { + defaults = new MemoryConfiguration(); + } + + defaults.set(path, value); + } + + public void addDefaults(@NotNull Map defaults) { + Validate.notNull(defaults, "Defaults may not be null"); + + for (Map.Entry entry : defaults.entrySet()) { + addDefault(entry.getKey(), entry.getValue()); + } + } + + public void addDefaults(@NotNull Configuration defaults) { + Validate.notNull(defaults, "Defaults may not be null"); + + addDefaults(defaults.getValues(true)); + } + + public void setDefaults(@NotNull Configuration defaults) { + Validate.notNull(defaults, "Defaults may not be null"); + + this.defaults = defaults; + } + + @Nullable + public Configuration getDefaults() { + return defaults; + } + + @Nullable + @Override + public ConfigurationSection getParent() { + return null; + } + + @NotNull + public MemoryConfigurationOptions options() { + if (options == null) { + options = new MemoryConfigurationOptions(this); + } + + return options; + } +} diff --git a/api/src/main/java/org/bukkit/configuration/MemoryConfigurationOptions.java b/api/src/main/java/org/bukkit/configuration/MemoryConfigurationOptions.java new file mode 100644 index 000000000..ead85f6fa --- /dev/null +++ b/api/src/main/java/org/bukkit/configuration/MemoryConfigurationOptions.java @@ -0,0 +1,33 @@ +package org.bukkit.configuration; + +import org.jetbrains.annotations.NotNull; + +/** + * Various settings for controlling the input and output of a {@link + * MemoryConfiguration} + */ +public class MemoryConfigurationOptions extends ConfigurationOptions { + protected MemoryConfigurationOptions(@NotNull MemoryConfiguration configuration) { + super(configuration); + } + + @NotNull + @Override + public MemoryConfiguration configuration() { + return (MemoryConfiguration) super.configuration(); + } + + @NotNull + @Override + public MemoryConfigurationOptions copyDefaults(boolean value) { + super.copyDefaults(value); + return this; + } + + @NotNull + @Override + public MemoryConfigurationOptions pathSeparator(char value) { + super.pathSeparator(value); + return this; + } +} diff --git a/api/src/main/java/org/bukkit/configuration/MemorySection.java b/api/src/main/java/org/bukkit/configuration/MemorySection.java new file mode 100644 index 000000000..37bb1c67a --- /dev/null +++ b/api/src/main/java/org/bukkit/configuration/MemorySection.java @@ -0,0 +1,889 @@ +package org.bukkit.configuration; + +import static org.bukkit.util.NumberConversions.*; + +import java.util.ArrayList; +import java.util.LinkedHashMap; +import java.util.LinkedHashSet; +import java.util.List; +import java.util.Map; +import java.util.Set; + +import org.apache.commons.lang.Validate; +import org.bukkit.Color; +import org.bukkit.OfflinePlayer; +import org.bukkit.configuration.serialization.ConfigurationSerializable; +import org.bukkit.inventory.ItemStack; +import org.bukkit.util.Vector; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +/** + * A type of {@link ConfigurationSection} that is stored in memory. + */ +public class MemorySection implements ConfigurationSection { + protected final Map map = new LinkedHashMap(); + private final Configuration root; + private final ConfigurationSection parent; + private final String path; + private final String fullPath; + + /** + * Creates an empty MemorySection for use as a root {@link Configuration} + * section. + *

+ * Note that calling this without being yourself a {@link Configuration} + * will throw an exception! + * + * @throws IllegalStateException Thrown if this is not a {@link + * Configuration} root. + */ + protected MemorySection() { + if (!(this instanceof Configuration)) { + throw new IllegalStateException("Cannot construct a root MemorySection when not a Configuration"); + } + + this.path = ""; + this.fullPath = ""; + this.parent = null; + this.root = (Configuration) this; + } + + /** + * Creates an empty MemorySection with the specified parent and path. + * + * @param parent Parent section that contains this own section. + * @param path Path that you may access this section from via the root + * {@link Configuration}. + * @throws IllegalArgumentException Thrown is parent or path is null, or + * if parent contains no root Configuration. + */ + protected MemorySection(@NotNull ConfigurationSection parent, @NotNull String path) { + Validate.notNull(parent, "Parent cannot be null"); + Validate.notNull(path, "Path cannot be null"); + + this.path = path; + this.parent = parent; + this.root = parent.getRoot(); + + Validate.notNull(root, "Path cannot be orphaned"); + + this.fullPath = createPath(parent, path); + } + + @NotNull + public Set getKeys(boolean deep) { + Set result = new LinkedHashSet(); + + Configuration root = getRoot(); + if (root != null && root.options().copyDefaults()) { + ConfigurationSection defaults = getDefaultSection(); + + if (defaults != null) { + result.addAll(defaults.getKeys(deep)); + } + } + + mapChildrenKeys(result, this, deep); + + return result; + } + + @NotNull + public Map getValues(boolean deep) { + Map result = new LinkedHashMap(); + + Configuration root = getRoot(); + if (root != null && root.options().copyDefaults()) { + ConfigurationSection defaults = getDefaultSection(); + + if (defaults != null) { + result.putAll(defaults.getValues(deep)); + } + } + + mapChildrenValues(result, this, deep); + + return result; + } + + public boolean contains(@NotNull String path) { + return contains(path, false); + } + + public boolean contains(@NotNull String path, boolean ignoreDefault) { + return ((ignoreDefault) ? get(path, null) : get(path)) != null; + } + + public boolean isSet(@NotNull String path) { + Configuration root = getRoot(); + if (root == null) { + return false; + } + if (root.options().copyDefaults()) { + return contains(path); + } + return get(path, null) != null; + } + + @NotNull + public String getCurrentPath() { + return fullPath; + } + + @NotNull + public String getName() { + return path; + } + + @Nullable + public Configuration getRoot() { + return root; + } + + @Nullable + public ConfigurationSection getParent() { + return parent; + } + + public void addDefault(@NotNull String path, @Nullable Object value) { + Validate.notNull(path, "Path cannot be null"); + + Configuration root = getRoot(); + if (root == null) { + throw new IllegalStateException("Cannot add default without root"); + } + if (root == this) { + throw new UnsupportedOperationException("Unsupported addDefault(String, Object) implementation"); + } + root.addDefault(createPath(this, path), value); + } + + @Nullable + public ConfigurationSection getDefaultSection() { + Configuration root = getRoot(); + Configuration defaults = root == null ? null : root.getDefaults(); + + if (defaults != null) { + if (defaults.isConfigurationSection(getCurrentPath())) { + return defaults.getConfigurationSection(getCurrentPath()); + } + } + + return null; + } + + public void set(@NotNull String path, @Nullable Object value) { + Validate.notEmpty(path, "Cannot set to an empty path"); + + Configuration root = getRoot(); + if (root == null) { + throw new IllegalStateException("Cannot use section without a root"); + } + + final char separator = root.options().pathSeparator(); + // i1 is the leading (higher) index + // i2 is the trailing (lower) index + int i1 = -1, i2; + ConfigurationSection section = this; + while ((i1 = path.indexOf(separator, i2 = i1 + 1)) != -1) { + String node = path.substring(i2, i1); + ConfigurationSection subSection = section.getConfigurationSection(node); + if (subSection == null) { + if (value == null) { + // no need to create missing sub-sections if we want to remove the value: + return; + } + section = section.createSection(node); + } else { + section = subSection; + } + } + + String key = path.substring(i2); + if (section == this) { + if (value == null) { + map.remove(key); + } else { + map.put(key, value); + } + } else { + section.set(key, value); + } + } + + @Nullable + public Object get(@NotNull String path) { + return get(path, getDefault(path)); + } + + @Nullable + public Object get(@NotNull String path, @Nullable Object def) { + Validate.notNull(path, "Path cannot be null"); + + if (path.length() == 0) { + return this; + } + + Configuration root = getRoot(); + if (root == null) { + throw new IllegalStateException("Cannot access section without a root"); + } + + final char separator = root.options().pathSeparator(); + // i1 is the leading (higher) index + // i2 is the trailing (lower) index + int i1 = -1, i2; + ConfigurationSection section = this; + while ((i1 = path.indexOf(separator, i2 = i1 + 1)) != -1) { + section = section.getConfigurationSection(path.substring(i2, i1)); + if (section == null) { + return def; + } + } + + String key = path.substring(i2); + if (section == this) { + Object result = map.get(key); + return (result == null) ? def : result; + } + return section.get(key, def); + } + + @NotNull + public ConfigurationSection createSection(@NotNull String path) { + Validate.notEmpty(path, "Cannot create section at empty path"); + Configuration root = getRoot(); + if (root == null) { + throw new IllegalStateException("Cannot create section without a root"); + } + + final char separator = root.options().pathSeparator(); + // i1 is the leading (higher) index + // i2 is the trailing (lower) index + int i1 = -1, i2; + ConfigurationSection section = this; + while ((i1 = path.indexOf(separator, i2 = i1 + 1)) != -1) { + String node = path.substring(i2, i1); + ConfigurationSection subSection = section.getConfigurationSection(node); + if (subSection == null) { + section = section.createSection(node); + } else { + section = subSection; + } + } + + String key = path.substring(i2); + if (section == this) { + ConfigurationSection result = new MemorySection(this, key); + map.put(key, result); + return result; + } + return section.createSection(key); + } + + @NotNull + public ConfigurationSection createSection(@NotNull String path, @NotNull Map map) { + ConfigurationSection section = createSection(path); + + for (Map.Entry entry : map.entrySet()) { + if (entry.getValue() instanceof Map) { + section.createSection(entry.getKey().toString(), (Map) entry.getValue()); + } else { + section.set(entry.getKey().toString(), entry.getValue()); + } + } + + return section; + } + + // Primitives + @Nullable + public String getString(@NotNull String path) { + Object def = getDefault(path); + return getString(path, def != null ? def.toString() : null); + } + + @Nullable + public String getString(@NotNull String path, @Nullable String def) { + Object val = get(path, def); + return (val != null) ? val.toString() : def; + } + + public boolean isString(@NotNull String path) { + Object val = get(path); + return val instanceof String; + } + + public int getInt(@NotNull String path) { + Object def = getDefault(path); + return getInt(path, (def instanceof Number) ? toInt(def) : 0); + } + + public int getInt(@NotNull String path, int def) { + Object val = get(path, def); + return (val instanceof Number) ? toInt(val) : def; + } + + public boolean isInt(@NotNull String path) { + Object val = get(path); + return val instanceof Integer; + } + + public boolean getBoolean(@NotNull String path) { + Object def = getDefault(path); + return getBoolean(path, (def instanceof Boolean) ? (Boolean) def : false); + } + + public boolean getBoolean(@NotNull String path, boolean def) { + Object val = get(path, def); + return (val instanceof Boolean) ? (Boolean) val : def; + } + + public boolean isBoolean(@NotNull String path) { + Object val = get(path); + return val instanceof Boolean; + } + + public double getDouble(@NotNull String path) { + Object def = getDefault(path); + return getDouble(path, (def instanceof Number) ? toDouble(def) : 0); + } + + public double getDouble(@NotNull String path, double def) { + Object val = get(path, def); + return (val instanceof Number) ? toDouble(val) : def; + } + + public boolean isDouble(@NotNull String path) { + Object val = get(path); + return val instanceof Double; + } + + public long getLong(@NotNull String path) { + Object def = getDefault(path); + return getLong(path, (def instanceof Number) ? toLong(def) : 0); + } + + public long getLong(@NotNull String path, long def) { + Object val = get(path, def); + return (val instanceof Number) ? toLong(val) : def; + } + + public boolean isLong(@NotNull String path) { + Object val = get(path); + return val instanceof Long; + } + + // Java + @Nullable + public List getList(@NotNull String path) { + Object def = getDefault(path); + return getList(path, (def instanceof List) ? (List) def : null); + } + + @Nullable + public List getList(@NotNull String path, @Nullable List def) { + Object val = get(path, def); + return (List) ((val instanceof List) ? val : def); + } + + public boolean isList(@NotNull String path) { + Object val = get(path); + return val instanceof List; + } + + @NotNull + public List getStringList(@NotNull String path) { + List list = getList(path); + + if (list == null) { + return new ArrayList(0); + } + + List result = new ArrayList(); + + for (Object object : list) { + if ((object instanceof String) || (isPrimitiveWrapper(object))) { + result.add(String.valueOf(object)); + } + } + + return result; + } + + @NotNull + public List getIntegerList(@NotNull String path) { + List list = getList(path); + + if (list == null) { + return new ArrayList(0); + } + + List result = new ArrayList(); + + for (Object object : list) { + if (object instanceof Integer) { + result.add((Integer) object); + } else if (object instanceof String) { + try { + result.add(Integer.valueOf((String) object)); + } catch (Exception ex) { + } + } else if (object instanceof Character) { + result.add((int) ((Character) object).charValue()); + } else if (object instanceof Number) { + result.add(((Number) object).intValue()); + } + } + + return result; + } + + @NotNull + public List getBooleanList(@NotNull String path) { + List list = getList(path); + + if (list == null) { + return new ArrayList(0); + } + + List result = new ArrayList(); + + for (Object object : list) { + if (object instanceof Boolean) { + result.add((Boolean) object); + } else if (object instanceof String) { + if (Boolean.TRUE.toString().equals(object)) { + result.add(true); + } else if (Boolean.FALSE.toString().equals(object)) { + result.add(false); + } + } + } + + return result; + } + + @NotNull + public List getDoubleList(@NotNull String path) { + List list = getList(path); + + if (list == null) { + return new ArrayList(0); + } + + List result = new ArrayList(); + + for (Object object : list) { + if (object instanceof Double) { + result.add((Double) object); + } else if (object instanceof String) { + try { + result.add(Double.valueOf((String) object)); + } catch (Exception ex) { + } + } else if (object instanceof Character) { + result.add((double) ((Character) object).charValue()); + } else if (object instanceof Number) { + result.add(((Number) object).doubleValue()); + } + } + + return result; + } + + @NotNull + public List getFloatList(@NotNull String path) { + List list = getList(path); + + if (list == null) { + return new ArrayList(0); + } + + List result = new ArrayList(); + + for (Object object : list) { + if (object instanceof Float) { + result.add((Float) object); + } else if (object instanceof String) { + try { + result.add(Float.valueOf((String) object)); + } catch (Exception ex) { + } + } else if (object instanceof Character) { + result.add((float) ((Character) object).charValue()); + } else if (object instanceof Number) { + result.add(((Number) object).floatValue()); + } + } + + return result; + } + + @NotNull + public List getLongList(@NotNull String path) { + List list = getList(path); + + if (list == null) { + return new ArrayList(0); + } + + List result = new ArrayList(); + + for (Object object : list) { + if (object instanceof Long) { + result.add((Long) object); + } else if (object instanceof String) { + try { + result.add(Long.valueOf((String) object)); + } catch (Exception ex) { + } + } else if (object instanceof Character) { + result.add((long) ((Character) object).charValue()); + } else if (object instanceof Number) { + result.add(((Number) object).longValue()); + } + } + + return result; + } + + @NotNull + public List getByteList(@NotNull String path) { + List list = getList(path); + + if (list == null) { + return new ArrayList(0); + } + + List result = new ArrayList(); + + for (Object object : list) { + if (object instanceof Byte) { + result.add((Byte) object); + } else if (object instanceof String) { + try { + result.add(Byte.valueOf((String) object)); + } catch (Exception ex) { + } + } else if (object instanceof Character) { + result.add((byte) ((Character) object).charValue()); + } else if (object instanceof Number) { + result.add(((Number) object).byteValue()); + } + } + + return result; + } + + @NotNull + public List getCharacterList(@NotNull String path) { + List list = getList(path); + + if (list == null) { + return new ArrayList(0); + } + + List result = new ArrayList(); + + for (Object object : list) { + if (object instanceof Character) { + result.add((Character) object); + } else if (object instanceof String) { + String str = (String) object; + + if (str.length() == 1) { + result.add(str.charAt(0)); + } + } else if (object instanceof Number) { + result.add((char) ((Number) object).intValue()); + } + } + + return result; + } + + @NotNull + public List getShortList(@NotNull String path) { + List list = getList(path); + + if (list == null) { + return new ArrayList(0); + } + + List result = new ArrayList(); + + for (Object object : list) { + if (object instanceof Short) { + result.add((Short) object); + } else if (object instanceof String) { + try { + result.add(Short.valueOf((String) object)); + } catch (Exception ex) { + } + } else if (object instanceof Character) { + result.add((short) ((Character) object).charValue()); + } else if (object instanceof Number) { + result.add(((Number) object).shortValue()); + } + } + + return result; + } + + @NotNull + public List> getMapList(@NotNull String path) { + List list = getList(path); + List> result = new ArrayList>(); + + if (list == null) { + return result; + } + + for (Object object : list) { + if (object instanceof Map) { + result.add((Map) object); + } + } + + return result; + } + + // Bukkit + @Nullable + @Override + public T getObject(@NotNull String path, @NotNull Class clazz) { + Validate.notNull(clazz, "Class cannot be null"); + Object def = getDefault(path); + return getObject(path, clazz, (def != null && clazz.isInstance(def)) ? clazz.cast(def) : null); + } + + @Nullable + @Override + public T getObject(@NotNull String path, @NotNull Class clazz, @Nullable T def) { + Validate.notNull(clazz, "Class cannot be null"); + Object val = get(path, def); + return (val != null && clazz.isInstance(val)) ? clazz.cast(val) : def; + } + + @Nullable + @Override + public T getSerializable(@NotNull String path, @NotNull Class clazz) { + return getObject(path, clazz); + } + + @Nullable + @Override + public T getSerializable(@NotNull String path, @NotNull Class clazz, @Nullable T def) { + return getObject(path, clazz, def); + } + + @Nullable + public Vector getVector(@NotNull String path) { + return getSerializable(path, Vector.class); + } + + @Nullable + public Vector getVector(@NotNull String path, @Nullable Vector def) { + return getSerializable(path, Vector.class, def); + } + + public boolean isVector(@NotNull String path) { + return getSerializable(path, Vector.class) != null; + } + + @Nullable + public OfflinePlayer getOfflinePlayer(@NotNull String path) { + return getSerializable(path, OfflinePlayer.class); + } + + @Nullable + public OfflinePlayer getOfflinePlayer(@NotNull String path, @Nullable OfflinePlayer def) { + return getSerializable(path, OfflinePlayer.class, def); + } + + public boolean isOfflinePlayer(@NotNull String path) { + return getSerializable(path, OfflinePlayer.class) != null; + } + + @Nullable + public ItemStack getItemStack(@NotNull String path) { + return getSerializable(path, ItemStack.class); + } + + @Nullable + public ItemStack getItemStack(@NotNull String path, @Nullable ItemStack def) { + return getSerializable(path, ItemStack.class, def); + } + + public boolean isItemStack(@NotNull String path) { + return getSerializable(path, ItemStack.class) != null; + } + + @Nullable + public Color getColor(@NotNull String path) { + return getSerializable(path, Color.class); + } + + @Nullable + public Color getColor(@NotNull String path, @Nullable Color def) { + return getSerializable(path, Color.class, def); + } + + public boolean isColor(@NotNull String path) { + return getSerializable(path, Color.class) != null; + } + + @Nullable + public ConfigurationSection getConfigurationSection(@NotNull String path) { + Object val = get(path, null); + if (val != null) { + return (val instanceof ConfigurationSection) ? (ConfigurationSection) val : null; + } + + val = get(path, getDefault(path)); + return (val instanceof ConfigurationSection) ? createSection(path) : null; + } + + public boolean isConfigurationSection(@NotNull String path) { + Object val = get(path); + return val instanceof ConfigurationSection; + } + + protected boolean isPrimitiveWrapper(@Nullable Object input) { + return input instanceof Integer || input instanceof Boolean || + input instanceof Character || input instanceof Byte || + input instanceof Short || input instanceof Double || + input instanceof Long || input instanceof Float; + } + + @Nullable + protected Object getDefault(@NotNull String path) { + Validate.notNull(path, "Path cannot be null"); + + Configuration root = getRoot(); + Configuration defaults = root == null ? null : root.getDefaults(); + return (defaults == null) ? null : defaults.get(createPath(this, path)); + } + + protected void mapChildrenKeys(@NotNull Set output, @NotNull ConfigurationSection section, boolean deep) { + if (section instanceof MemorySection) { + MemorySection sec = (MemorySection) section; + + for (Map.Entry entry : sec.map.entrySet()) { + output.add(createPath(section, entry.getKey(), this)); + + if ((deep) && (entry.getValue() instanceof ConfigurationSection)) { + ConfigurationSection subsection = (ConfigurationSection) entry.getValue(); + mapChildrenKeys(output, subsection, deep); + } + } + } else { + Set keys = section.getKeys(deep); + + for (String key : keys) { + output.add(createPath(section, key, this)); + } + } + } + + protected void mapChildrenValues(@NotNull Map output, @NotNull ConfigurationSection section, boolean deep) { + if (section instanceof MemorySection) { + MemorySection sec = (MemorySection) section; + + for (Map.Entry entry : sec.map.entrySet()) { + // Because of the copyDefaults call potentially copying out of order, we must remove and then add in our saved order + // This means that default values we haven't set end up getting placed first + // See SPIGOT-4558 for an example using spigot.yml - watch subsections move around to default order + String childPath = createPath(section, entry.getKey(), this); + output.remove(childPath); + output.put(childPath, entry.getValue()); + + if (entry.getValue() instanceof ConfigurationSection) { + if (deep) { + mapChildrenValues(output, (ConfigurationSection) entry.getValue(), deep); + } + } + } + } else { + Map values = section.getValues(deep); + + for (Map.Entry entry : values.entrySet()) { + output.put(createPath(section, entry.getKey(), this), entry.getValue()); + } + } + } + + /** + * Creates a full path to the given {@link ConfigurationSection} from its + * root {@link Configuration}. + *

+ * You may use this method for any given {@link ConfigurationSection}, not + * only {@link MemorySection}. + * + * @param section Section to create a path for. + * @param key Name of the specified section. + * @return Full path of the section from its root. + */ + @NotNull + public static String createPath(@NotNull ConfigurationSection section, @Nullable String key) { + return createPath(section, key, (section == null) ? null : section.getRoot()); + } + + /** + * Creates a relative path to the given {@link ConfigurationSection} from + * the given relative section. + *

+ * You may use this method for any given {@link ConfigurationSection}, not + * only {@link MemorySection}. + * + * @param section Section to create a path for. + * @param key Name of the specified section. + * @param relativeTo Section to create the path relative to. + * @return Full path of the section from its root. + */ + @NotNull + public static String createPath(@NotNull ConfigurationSection section, @Nullable String key, @Nullable ConfigurationSection relativeTo) { + Validate.notNull(section, "Cannot create path without a section"); + Configuration root = section.getRoot(); + if (root == null) { + throw new IllegalStateException("Cannot create path without a root"); + } + char separator = root.options().pathSeparator(); + + StringBuilder builder = new StringBuilder(); + if (section != null) { + for (ConfigurationSection parent = section; (parent != null) && (parent != relativeTo); parent = parent.getParent()) { + if (builder.length() > 0) { + builder.insert(0, separator); + } + + builder.insert(0, parent.getName()); + } + } + + if ((key != null) && (key.length() > 0)) { + if (builder.length() > 0) { + builder.append(separator); + } + + builder.append(key); + } + + return builder.toString(); + } + + @Override + public String toString() { + Configuration root = getRoot(); + return new StringBuilder() + .append(getClass().getSimpleName()) + .append("[path='") + .append(getCurrentPath()) + .append("', root='") + .append(root == null ? null : root.getClass().getSimpleName()) + .append("']") + .toString(); + } +} diff --git a/api/src/main/java/org/bukkit/configuration/file/FileConfiguration.java b/api/src/main/java/org/bukkit/configuration/file/FileConfiguration.java new file mode 100644 index 000000000..ba3c93fe4 --- /dev/null +++ b/api/src/main/java/org/bukkit/configuration/file/FileConfiguration.java @@ -0,0 +1,229 @@ +package org.bukkit.configuration.file; + +import com.google.common.base.Charsets; +import com.google.common.io.Files; + +import org.apache.commons.lang.Validate; +import org.bukkit.configuration.InvalidConfigurationException; + +import java.io.BufferedReader; +import java.io.File; +import java.io.FileInputStream; +import java.io.FileNotFoundException; +import java.io.FileOutputStream; +import java.io.IOException; +import java.io.InputStreamReader; +import java.io.OutputStreamWriter; +import java.io.Reader; +import java.io.Writer; + +import org.bukkit.configuration.Configuration; +import org.bukkit.configuration.MemoryConfiguration; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +/** + * This is a base class for all File based implementations of {@link + * Configuration} + */ +public abstract class FileConfiguration extends MemoryConfiguration { + + /** + * Creates an empty {@link FileConfiguration} with no default values. + */ + public FileConfiguration() { + super(); + } + + /** + * Creates an empty {@link FileConfiguration} using the specified {@link + * Configuration} as a source for all default values. + * + * @param defaults Default value provider + */ + public FileConfiguration(@Nullable Configuration defaults) { + super(defaults); + } + + /** + * Saves this {@link FileConfiguration} to the specified location. + *

+ * If the file does not exist, it will be created. If already exists, it + * will be overwritten. If it cannot be overwritten or created, an + * exception will be thrown. + *

+ * This method will save using the system default encoding, or possibly + * using UTF8. + * + * @param file File to save to. + * @throws IOException Thrown when the given file cannot be written to for + * any reason. + * @throws IllegalArgumentException Thrown when file is null. + */ + public void save(@NotNull File file) throws IOException { + Validate.notNull(file, "File cannot be null"); + + Files.createParentDirs(file); + + String data = saveToString(); + + Writer writer = new OutputStreamWriter(new FileOutputStream(file), Charsets.UTF_8); + + try { + writer.write(data); + } finally { + writer.close(); + } + } + + /** + * Saves this {@link FileConfiguration} to the specified location. + *

+ * If the file does not exist, it will be created. If already exists, it + * will be overwritten. If it cannot be overwritten or created, an + * exception will be thrown. + *

+ * This method will save using the system default encoding, or possibly + * using UTF8. + * + * @param file File to save to. + * @throws IOException Thrown when the given file cannot be written to for + * any reason. + * @throws IllegalArgumentException Thrown when file is null. + */ + public void save(@NotNull String file) throws IOException { + Validate.notNull(file, "File cannot be null"); + + save(new File(file)); + } + + /** + * Saves this {@link FileConfiguration} to a string, and returns it. + * + * @return String containing this configuration. + */ + @NotNull + public abstract String saveToString(); + + /** + * Loads this {@link FileConfiguration} from the specified location. + *

+ * All the values contained within this configuration will be removed, + * leaving only settings and defaults, and the new values will be loaded + * from the given file. + *

+ * If the file cannot be loaded for any reason, an exception will be + * thrown. + * + * @param file File to load from. + * @throws FileNotFoundException Thrown when the given file cannot be + * opened. + * @throws IOException Thrown when the given file cannot be read. + * @throws InvalidConfigurationException Thrown when the given file is not + * a valid Configuration. + * @throws IllegalArgumentException Thrown when file is null. + */ + public void load(@NotNull File file) throws FileNotFoundException, IOException, InvalidConfigurationException { + Validate.notNull(file, "File cannot be null"); + + final FileInputStream stream = new FileInputStream(file); + + load(new InputStreamReader(stream, Charsets.UTF_8)); + } + + /** + * Loads this {@link FileConfiguration} from the specified reader. + *

+ * All the values contained within this configuration will be removed, + * leaving only settings and defaults, and the new values will be loaded + * from the given stream. + * + * @param reader the reader to load from + * @throws IOException thrown when underlying reader throws an IOException + * @throws InvalidConfigurationException thrown when the reader does not + * represent a valid Configuration + * @throws IllegalArgumentException thrown when reader is null + */ + public void load(@NotNull Reader reader) throws IOException, InvalidConfigurationException { + BufferedReader input = reader instanceof BufferedReader ? (BufferedReader) reader : new BufferedReader(reader); + + StringBuilder builder = new StringBuilder(); + + try { + String line; + + while ((line = input.readLine()) != null) { + builder.append(line); + builder.append('\n'); + } + } finally { + input.close(); + } + + loadFromString(builder.toString()); + } + + /** + * Loads this {@link FileConfiguration} from the specified location. + *

+ * All the values contained within this configuration will be removed, + * leaving only settings and defaults, and the new values will be loaded + * from the given file. + *

+ * If the file cannot be loaded for any reason, an exception will be + * thrown. + * + * @param file File to load from. + * @throws FileNotFoundException Thrown when the given file cannot be + * opened. + * @throws IOException Thrown when the given file cannot be read. + * @throws InvalidConfigurationException Thrown when the given file is not + * a valid Configuration. + * @throws IllegalArgumentException Thrown when file is null. + */ + public void load(@NotNull String file) throws FileNotFoundException, IOException, InvalidConfigurationException { + Validate.notNull(file, "File cannot be null"); + + load(new File(file)); + } + + /** + * Loads this {@link FileConfiguration} from the specified string, as + * opposed to from file. + *

+ * All the values contained within this configuration will be removed, + * leaving only settings and defaults, and the new values will be loaded + * from the given string. + *

+ * If the string is invalid in any way, an exception will be thrown. + * + * @param contents Contents of a Configuration to load. + * @throws InvalidConfigurationException Thrown if the specified string is + * invalid. + * @throws IllegalArgumentException Thrown if contents is null. + */ + public abstract void loadFromString(@NotNull String contents) throws InvalidConfigurationException; + + /** + * Compiles the header for this {@link FileConfiguration} and returns the + * result. + *

+ * This will use the header from {@link #options()} -> {@link + * FileConfigurationOptions#header()}, respecting the rules of {@link + * FileConfigurationOptions#copyHeader()} if set. + * + * @return Compiled header + */ + @NotNull + protected abstract String buildHeader(); + + @NotNull + @Override + public FileConfigurationOptions options() { + if (options == null) { + options = new FileConfigurationOptions(this); + } + + return (FileConfigurationOptions) options; + } +} \ No newline at end of file diff --git a/api/src/main/java/org/bukkit/configuration/file/FileConfigurationOptions.java b/api/src/main/java/org/bukkit/configuration/file/FileConfigurationOptions.java new file mode 100644 index 000000000..4919f6c93 --- /dev/null +++ b/api/src/main/java/org/bukkit/configuration/file/FileConfigurationOptions.java @@ -0,0 +1,126 @@ +package org.bukkit.configuration.file; + +import org.bukkit.configuration.*; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +/** + * Various settings for controlling the input and output of a {@link + * FileConfiguration} + */ +public class FileConfigurationOptions extends MemoryConfigurationOptions { + private String header = null; + private boolean copyHeader = true; + + protected FileConfigurationOptions(@NotNull MemoryConfiguration configuration) { + super(configuration); + } + + @NotNull + @Override + public FileConfiguration configuration() { + return (FileConfiguration) super.configuration(); + } + + @NotNull + @Override + public FileConfigurationOptions copyDefaults(boolean value) { + super.copyDefaults(value); + return this; + } + + @NotNull + @Override + public FileConfigurationOptions pathSeparator(char value) { + super.pathSeparator(value); + return this; + } + + /** + * Gets the header that will be applied to the top of the saved output. + *

+ * This header will be commented out and applied directly at the top of + * the generated output of the {@link FileConfiguration}. It is not + * required to include a newline at the end of the header as it will + * automatically be applied, but you may include one if you wish for extra + * spacing. + *

+ * Null is a valid value which will indicate that no header is to be + * applied. The default value is null. + * + * @return Header + */ + @Nullable + public String header() { + return header; + } + + /** + * Sets the header that will be applied to the top of the saved output. + *

+ * This header will be commented out and applied directly at the top of + * the generated output of the {@link FileConfiguration}. It is not + * required to include a newline at the end of the header as it will + * automatically be applied, but you may include one if you wish for extra + * spacing. + *

+ * Null is a valid value which will indicate that no header is to be + * applied. + * + * @param value New header + * @return This object, for chaining + */ + @NotNull + public FileConfigurationOptions header(@Nullable String value) { + this.header = value; + return this; + } + + /** + * Gets whether or not the header should be copied from a default source. + *

+ * If this is true, if a default {@link FileConfiguration} is passed to + * {@link + * FileConfiguration#setDefaults(org.bukkit.configuration.Configuration)} + * then upon saving it will use the header from that config, instead of + * the one provided here. + *

+ * If no default is set on the configuration, or the default is not of + * type FileConfiguration, or that config has no header ({@link #header()} + * returns null) then the header specified in this configuration will be + * used. + *

+ * Defaults to true. + * + * @return Whether or not to copy the header + */ + public boolean copyHeader() { + return copyHeader; + } + + /** + * Sets whether or not the header should be copied from a default source. + *

+ * If this is true, if a default {@link FileConfiguration} is passed to + * {@link + * FileConfiguration#setDefaults(org.bukkit.configuration.Configuration)} + * then upon saving it will use the header from that config, instead of + * the one provided here. + *

+ * If no default is set on the configuration, or the default is not of + * type FileConfiguration, or that config has no header ({@link #header()} + * returns null) then the header specified in this configuration will be + * used. + *

+ * Defaults to true. + * + * @param value Whether or not to copy the header + * @return This object, for chaining + */ + @NotNull + public FileConfigurationOptions copyHeader(boolean value) { + copyHeader = value; + + return this; + } +} diff --git a/api/src/main/java/org/bukkit/configuration/file/YamlConfiguration.java b/api/src/main/java/org/bukkit/configuration/file/YamlConfiguration.java new file mode 100644 index 000000000..441e7ff98 --- /dev/null +++ b/api/src/main/java/org/bukkit/configuration/file/YamlConfiguration.java @@ -0,0 +1,222 @@ +package org.bukkit.configuration.file; + +import java.io.File; +import java.io.FileNotFoundException; +import java.io.IOException; +import java.io.Reader; +import java.util.Map; +import java.util.logging.Level; + +import org.apache.commons.lang.Validate; +import org.bukkit.Bukkit; +import org.bukkit.configuration.Configuration; +import org.bukkit.configuration.ConfigurationSection; +import org.bukkit.configuration.InvalidConfigurationException; +import org.jetbrains.annotations.NotNull; +import org.yaml.snakeyaml.DumperOptions; +import org.yaml.snakeyaml.Yaml; +import org.yaml.snakeyaml.error.YAMLException; +import org.yaml.snakeyaml.representer.Representer; + +/** + * An implementation of {@link Configuration} which saves all files in Yaml. + * Note that this implementation is not synchronized. + */ +public class YamlConfiguration extends FileConfiguration { + protected static final String COMMENT_PREFIX = "# "; + protected static final String BLANK_CONFIG = "{}\n"; + private final DumperOptions yamlOptions = new DumperOptions(); + private final Representer yamlRepresenter = new YamlRepresenter(); + private final Yaml yaml = new Yaml(new YamlConstructor(), yamlRepresenter, yamlOptions); + + @NotNull + @Override + public String saveToString() { + yamlOptions.setIndent(options().indent()); + yamlOptions.setDefaultFlowStyle(DumperOptions.FlowStyle.BLOCK); + yamlRepresenter.setDefaultFlowStyle(DumperOptions.FlowStyle.BLOCK); + + String header = buildHeader(); + String dump = yaml.dump(getValues(false)); + + if (dump.equals(BLANK_CONFIG)) { + dump = ""; + } + + return header + dump; + } + + @Override + public void loadFromString(@NotNull String contents) throws InvalidConfigurationException { + Validate.notNull(contents, "Contents cannot be null"); + + Map input; + try { + input = (Map) yaml.load(contents); + } catch (YAMLException e) { + throw new InvalidConfigurationException(e); + } catch (ClassCastException e) { + throw new InvalidConfigurationException("Top level is not a Map."); + } + + String header = parseHeader(contents); + if (header.length() > 0) { + options().header(header); + } + + if (input != null) { + convertMapsToSections(input, this); + } + } + + protected void convertMapsToSections(@NotNull Map input, @NotNull ConfigurationSection section) { + for (Map.Entry entry : input.entrySet()) { + String key = entry.getKey().toString(); + Object value = entry.getValue(); + + if (value instanceof Map) { + convertMapsToSections((Map) value, section.createSection(key)); + } else { + section.set(key, value); + } + } + } + + @NotNull + protected String parseHeader(@NotNull String input) { + String[] lines = input.split("\r?\n", -1); + StringBuilder result = new StringBuilder(); + boolean readingHeader = true; + boolean foundHeader = false; + + for (int i = 0; (i < lines.length) && (readingHeader); i++) { + String line = lines[i]; + + if (line.startsWith(COMMENT_PREFIX)) { + if (i > 0) { + result.append("\n"); + } + + if (line.length() > COMMENT_PREFIX.length()) { + result.append(line.substring(COMMENT_PREFIX.length())); + } + + foundHeader = true; + } else if ((foundHeader) && (line.length() == 0)) { + result.append("\n"); + } else if (foundHeader) { + readingHeader = false; + } + } + + return result.toString(); + } + + @NotNull + @Override + protected String buildHeader() { + String header = options().header(); + + if (options().copyHeader()) { + Configuration def = getDefaults(); + + if ((def != null) && (def instanceof FileConfiguration)) { + FileConfiguration filedefaults = (FileConfiguration) def; + String defaultsHeader = filedefaults.buildHeader(); + + if ((defaultsHeader != null) && (defaultsHeader.length() > 0)) { + return defaultsHeader; + } + } + } + + if (header == null) { + return ""; + } + + StringBuilder builder = new StringBuilder(); + String[] lines = header.split("\r?\n", -1); + boolean startedHeader = false; + + for (int i = lines.length - 1; i >= 0; i--) { + builder.insert(0, "\n"); + + if ((startedHeader) || (lines[i].length() != 0)) { + builder.insert(0, lines[i]); + builder.insert(0, COMMENT_PREFIX); + startedHeader = true; + } + } + + return builder.toString(); + } + + @NotNull + @Override + public YamlConfigurationOptions options() { + if (options == null) { + options = new YamlConfigurationOptions(this); + } + + return (YamlConfigurationOptions) options; + } + + /** + * Creates a new {@link YamlConfiguration}, loading from the given file. + *

+ * Any errors loading the Configuration will be logged and then ignored. + * If the specified input is not a valid config, a blank config will be + * returned. + *

+ * The encoding used may follow the system dependent default. + * + * @param file Input file + * @return Resulting configuration + * @throws IllegalArgumentException Thrown if file is null + */ + @NotNull + public static YamlConfiguration loadConfiguration(@NotNull File file) { + Validate.notNull(file, "File cannot be null"); + + YamlConfiguration config = new YamlConfiguration(); + + try { + config.load(file); + } catch (FileNotFoundException ex) { + } catch (IOException ex) { + Bukkit.getLogger().log(Level.SEVERE, "Cannot load " + file, ex); + } catch (InvalidConfigurationException ex) { + Bukkit.getLogger().log(Level.SEVERE, "Cannot load " + file, ex); + } + + return config; + } + + /** + * Creates a new {@link YamlConfiguration}, loading from the given reader. + *

+ * Any errors loading the Configuration will be logged and then ignored. + * If the specified input is not a valid config, a blank config will be + * returned. + * + * @param reader input + * @return resulting configuration + * @throws IllegalArgumentException Thrown if stream is null + */ + @NotNull + public static YamlConfiguration loadConfiguration(@NotNull Reader reader) { + Validate.notNull(reader, "Stream cannot be null"); + + YamlConfiguration config = new YamlConfiguration(); + + try { + config.load(reader); + } catch (IOException ex) { + Bukkit.getLogger().log(Level.SEVERE, "Cannot load configuration from stream", ex); + } catch (InvalidConfigurationException ex) { + Bukkit.getLogger().log(Level.SEVERE, "Cannot load configuration from stream", ex); + } + + return config; + } +} diff --git a/api/src/main/java/org/bukkit/configuration/file/YamlConfigurationOptions.java b/api/src/main/java/org/bukkit/configuration/file/YamlConfigurationOptions.java new file mode 100644 index 000000000..b2bf9785a --- /dev/null +++ b/api/src/main/java/org/bukkit/configuration/file/YamlConfigurationOptions.java @@ -0,0 +1,79 @@ +package org.bukkit.configuration.file; + +import org.apache.commons.lang.Validate; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +/** + * Various settings for controlling the input and output of a {@link + * YamlConfiguration} + */ +public class YamlConfigurationOptions extends FileConfigurationOptions { + private int indent = 2; + + protected YamlConfigurationOptions(@NotNull YamlConfiguration configuration) { + super(configuration); + } + + @NotNull + @Override + public YamlConfiguration configuration() { + return (YamlConfiguration) super.configuration(); + } + + @NotNull + @Override + public YamlConfigurationOptions copyDefaults(boolean value) { + super.copyDefaults(value); + return this; + } + + @NotNull + @Override + public YamlConfigurationOptions pathSeparator(char value) { + super.pathSeparator(value); + return this; + } + + @NotNull + @Override + public YamlConfigurationOptions header(@Nullable String value) { + super.header(value); + return this; + } + + @NotNull + @Override + public YamlConfigurationOptions copyHeader(boolean value) { + super.copyHeader(value); + return this; + } + + /** + * Gets how much spaces should be used to indent each line. + *

+ * The minimum value this may be is 2, and the maximum is 9. + * + * @return How much to indent by + */ + public int indent() { + return indent; + } + + /** + * Sets how much spaces should be used to indent each line. + *

+ * The minimum value this may be is 2, and the maximum is 9. + * + * @param value New indent + * @return This object, for chaining + */ + @NotNull + public YamlConfigurationOptions indent(int value) { + Validate.isTrue(value >= 2, "Indent must be at least 2 characters"); + Validate.isTrue(value <= 9, "Indent cannot be greater than 9 characters"); + + this.indent = value; + return this; + } +} diff --git a/api/src/main/java/org/bukkit/configuration/file/YamlConstructor.java b/api/src/main/java/org/bukkit/configuration/file/YamlConstructor.java new file mode 100644 index 000000000..397b58c53 --- /dev/null +++ b/api/src/main/java/org/bukkit/configuration/file/YamlConstructor.java @@ -0,0 +1,53 @@ +package org.bukkit.configuration.file; + +import java.util.LinkedHashMap; +import java.util.Map; + +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; +import org.yaml.snakeyaml.nodes.Node; +import org.yaml.snakeyaml.constructor.SafeConstructor; +import org.yaml.snakeyaml.error.YAMLException; +import org.yaml.snakeyaml.nodes.Tag; + +import org.bukkit.configuration.serialization.ConfigurationSerialization; + +public class YamlConstructor extends SafeConstructor { + + public YamlConstructor() { + this.yamlConstructors.put(Tag.MAP, new ConstructCustomObject()); + } + + private class ConstructCustomObject extends ConstructYamlMap { + + @Nullable + @Override + public Object construct(@NotNull Node node) { + if (node.isTwoStepsConstruction()) { + throw new YAMLException("Unexpected referential mapping structure. Node: " + node); + } + + Map raw = (Map) super.construct(node); + + if (raw.containsKey(ConfigurationSerialization.SERIALIZED_TYPE_KEY)) { + Map typed = new LinkedHashMap(raw.size()); + for (Map.Entry entry : raw.entrySet()) { + typed.put(entry.getKey().toString(), entry.getValue()); + } + + try { + return ConfigurationSerialization.deserializeObject(typed); + } catch (IllegalArgumentException ex) { + throw new YAMLException("Could not deserialize object", ex); + } + } + + return raw; + } + + @Override + public void construct2ndStep(@NotNull Node node, @NotNull Object object) { + throw new YAMLException("Unexpected referential mapping structure. Node: " + node); + } + } +} diff --git a/api/src/main/java/org/bukkit/configuration/file/YamlRepresenter.java b/api/src/main/java/org/bukkit/configuration/file/YamlRepresenter.java new file mode 100644 index 000000000..63cb35555 --- /dev/null +++ b/api/src/main/java/org/bukkit/configuration/file/YamlRepresenter.java @@ -0,0 +1,43 @@ +package org.bukkit.configuration.file; + +import java.util.LinkedHashMap; +import java.util.Map; + +import org.bukkit.configuration.ConfigurationSection; +import org.bukkit.configuration.serialization.ConfigurationSerializable; +import org.bukkit.configuration.serialization.ConfigurationSerialization; + +import org.jetbrains.annotations.NotNull; +import org.yaml.snakeyaml.nodes.Node; +import org.yaml.snakeyaml.representer.Representer; + +public class YamlRepresenter extends Representer { + + public YamlRepresenter() { + this.multiRepresenters.put(ConfigurationSection.class, new RepresentConfigurationSection()); + this.multiRepresenters.put(ConfigurationSerializable.class, new RepresentConfigurationSerializable()); + } + + private class RepresentConfigurationSection extends RepresentMap { + + @NotNull + @Override + public Node representData(@NotNull Object data) { + return super.representData(((ConfigurationSection) data).getValues(false)); + } + } + + private class RepresentConfigurationSerializable extends RepresentMap { + + @NotNull + @Override + public Node representData(@NotNull Object data) { + ConfigurationSerializable serializable = (ConfigurationSerializable) data; + Map values = new LinkedHashMap(); + values.put(ConfigurationSerialization.SERIALIZED_TYPE_KEY, ConfigurationSerialization.getAlias(serializable.getClass())); + values.putAll(serializable.serialize()); + + return super.representData(values); + } + } +} diff --git a/api/src/main/java/org/bukkit/configuration/serialization/ConfigurationSerializable.java b/api/src/main/java/org/bukkit/configuration/serialization/ConfigurationSerializable.java new file mode 100644 index 000000000..a666db15e --- /dev/null +++ b/api/src/main/java/org/bukkit/configuration/serialization/ConfigurationSerializable.java @@ -0,0 +1,38 @@ +package org.bukkit.configuration.serialization; + +import org.jetbrains.annotations.NotNull; + +import java.util.Map; + +/** + * Represents an object that may be serialized. + *

+ * These objects MUST implement one of the following, in addition to the + * methods as defined by this interface: + *

    + *
  • A static method "deserialize" that accepts a single {@link Map}< + * {@link String}, {@link Object}> and returns the class.
  • + *
  • A static method "valueOf" that accepts a single {@link Map}<{@link + * String}, {@link Object}> and returns the class.
  • + *
  • A constructor that accepts a single {@link Map}<{@link String}, + * {@link Object}>.
  • + *
+ * In addition to implementing this interface, you must register the class + * with {@link ConfigurationSerialization#registerClass(Class)}. + * + * @see DelegateDeserialization + * @see SerializableAs + */ +public interface ConfigurationSerializable { + + /** + * Creates a Map representation of this class. + *

+ * This class must provide a method to restore this class, as defined in + * the {@link ConfigurationSerializable} interface javadocs. + * + * @return Map containing the current state of this class + */ + @NotNull + public Map serialize(); +} diff --git a/api/src/main/java/org/bukkit/configuration/serialization/ConfigurationSerialization.java b/api/src/main/java/org/bukkit/configuration/serialization/ConfigurationSerialization.java new file mode 100644 index 000000000..76d9b9662 --- /dev/null +++ b/api/src/main/java/org/bukkit/configuration/serialization/ConfigurationSerialization.java @@ -0,0 +1,300 @@ +package org.bukkit.configuration.serialization; + +import java.lang.reflect.Constructor; +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; +import java.lang.reflect.Modifier; +import java.util.HashMap; +import java.util.Map; +import java.util.logging.Level; +import java.util.logging.Logger; + +import org.apache.commons.lang.Validate; +import org.bukkit.Color; +import org.bukkit.FireworkEffect; +import org.bukkit.Location; +import org.bukkit.attribute.AttributeModifier; +import org.bukkit.block.banner.Pattern; +import org.bukkit.configuration.Configuration; +import org.bukkit.inventory.ItemStack; +import org.bukkit.potion.PotionEffect; +import org.bukkit.util.BoundingBox; +import org.bukkit.util.BlockVector; +import org.bukkit.util.Vector; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +/** + * Utility class for storing and retrieving classes for {@link Configuration}. + */ +public class ConfigurationSerialization { + public static final String SERIALIZED_TYPE_KEY = "=="; + private final Class clazz; + private static Map> aliases = new HashMap>(); + + static { + registerClass(Vector.class); + registerClass(BlockVector.class); + registerClass(ItemStack.class); + registerClass(Color.class); + registerClass(PotionEffect.class); + registerClass(FireworkEffect.class); + registerClass(Pattern.class); + registerClass(Location.class); + registerClass(AttributeModifier.class); + registerClass(BoundingBox.class); + } + + protected ConfigurationSerialization(@NotNull Class clazz) { + this.clazz = clazz; + } + + @Nullable + protected Method getMethod(@NotNull String name, boolean isStatic) { + try { + Method method = clazz.getDeclaredMethod(name, Map.class); + + if (!ConfigurationSerializable.class.isAssignableFrom(method.getReturnType())) { + return null; + } + if (Modifier.isStatic(method.getModifiers()) != isStatic) { + return null; + } + + return method; + } catch (NoSuchMethodException ex) { + return null; + } catch (SecurityException ex) { + return null; + } + } + + @Nullable + protected Constructor getConstructor() { + try { + return clazz.getConstructor(Map.class); + } catch (NoSuchMethodException ex) { + return null; + } catch (SecurityException ex) { + return null; + } + } + + @Nullable + protected ConfigurationSerializable deserializeViaMethod(@NotNull Method method, @NotNull Map args) { + try { + ConfigurationSerializable result = (ConfigurationSerializable) method.invoke(null, args); + + if (result == null) { + Logger.getLogger(ConfigurationSerialization.class.getName()).log(Level.SEVERE, "Could not call method '" + method.toString() + "' of " + clazz + " for deserialization: method returned null"); + } else { + return result; + } + } catch (Throwable ex) { + Logger.getLogger(ConfigurationSerialization.class.getName()).log( + Level.SEVERE, + "Could not call method '" + method.toString() + "' of " + clazz + " for deserialization", + ex instanceof InvocationTargetException ? ex.getCause() : ex); + } + + return null; + } + + @Nullable + protected ConfigurationSerializable deserializeViaCtor(@NotNull Constructor ctor, @NotNull Map args) { + try { + return ctor.newInstance(args); + } catch (Throwable ex) { + Logger.getLogger(ConfigurationSerialization.class.getName()).log( + Level.SEVERE, + "Could not call constructor '" + ctor.toString() + "' of " + clazz + " for deserialization", + ex instanceof InvocationTargetException ? ex.getCause() : ex); + } + + return null; + } + + @Nullable + public ConfigurationSerializable deserialize(@NotNull Map args) { + Validate.notNull(args, "Args must not be null"); + + ConfigurationSerializable result = null; + Method method = null; + + if (result == null) { + method = getMethod("deserialize", true); + + if (method != null) { + result = deserializeViaMethod(method, args); + } + } + + if (result == null) { + method = getMethod("valueOf", true); + + if (method != null) { + result = deserializeViaMethod(method, args); + } + } + + if (result == null) { + Constructor constructor = getConstructor(); + + if (constructor != null) { + result = deserializeViaCtor(constructor, args); + } + } + + return result; + } + + /** + * Attempts to deserialize the given arguments into a new instance of the + * given class. + *

+ * The class must implement {@link ConfigurationSerializable}, including + * the extra methods as specified in the javadoc of + * ConfigurationSerializable. + *

+ * If a new instance could not be made, an example being the class not + * fully implementing the interface, null will be returned. + * + * @param args Arguments for deserialization + * @param clazz Class to deserialize into + * @return New instance of the specified class + */ + @Nullable + public static ConfigurationSerializable deserializeObject(@NotNull Map args, @NotNull Class clazz) { + return new ConfigurationSerialization(clazz).deserialize(args); + } + + /** + * Attempts to deserialize the given arguments into a new instance of the + * given class. + *

+ * The class must implement {@link ConfigurationSerializable}, including + * the extra methods as specified in the javadoc of + * ConfigurationSerializable. + *

+ * If a new instance could not be made, an example being the class not + * fully implementing the interface, null will be returned. + * + * @param args Arguments for deserialization + * @return New instance of the specified class + */ + @Nullable + public static ConfigurationSerializable deserializeObject(@NotNull Map args) { + Class clazz = null; + + if (args.containsKey(SERIALIZED_TYPE_KEY)) { + try { + String alias = (String) args.get(SERIALIZED_TYPE_KEY); + + if (alias == null) { + throw new IllegalArgumentException("Cannot have null alias"); + } + clazz = getClassByAlias(alias); + if (clazz == null) { + throw new IllegalArgumentException("Specified class does not exist ('" + alias + "')"); + } + } catch (ClassCastException ex) { + ex.fillInStackTrace(); + throw ex; + } + } else { + throw new IllegalArgumentException("Args doesn't contain type key ('" + SERIALIZED_TYPE_KEY + "')"); + } + + return new ConfigurationSerialization(clazz).deserialize(args); + } + + /** + * Registers the given {@link ConfigurationSerializable} class by its + * alias + * + * @param clazz Class to register + */ + public static void registerClass(@NotNull Class clazz) { + DelegateDeserialization delegate = clazz.getAnnotation(DelegateDeserialization.class); + + if (delegate == null) { + registerClass(clazz, getAlias(clazz)); + registerClass(clazz, clazz.getName()); + } + } + + /** + * Registers the given alias to the specified {@link + * ConfigurationSerializable} class + * + * @param clazz Class to register + * @param alias Alias to register as + * @see SerializableAs + */ + public static void registerClass(@NotNull Class clazz, @NotNull String alias) { + aliases.put(alias, clazz); + } + + /** + * Unregisters the specified alias to a {@link ConfigurationSerializable} + * + * @param alias Alias to unregister + */ + public static void unregisterClass(@NotNull String alias) { + aliases.remove(alias); + } + + /** + * Unregisters any aliases for the specified {@link + * ConfigurationSerializable} class + * + * @param clazz Class to unregister + */ + public static void unregisterClass(@NotNull Class clazz) { + while (aliases.values().remove(clazz)) { + ; + } + } + + /** + * Attempts to get a registered {@link ConfigurationSerializable} class by + * its alias + * + * @param alias Alias of the serializable + * @return Registered class, or null if not found + */ + @Nullable + public static Class getClassByAlias(@NotNull String alias) { + return aliases.get(alias); + } + + /** + * Gets the correct alias for the given {@link ConfigurationSerializable} + * class + * + * @param clazz Class to get alias for + * @return Alias to use for the class + */ + @NotNull + public static String getAlias(@NotNull Class clazz) { + DelegateDeserialization delegate = clazz.getAnnotation(DelegateDeserialization.class); + + if (delegate != null) { + if ((delegate.value() == null) || (delegate.value() == clazz)) { + delegate = null; + } else { + return getAlias(delegate.value()); + } + } + + if (delegate == null) { + SerializableAs alias = clazz.getAnnotation(SerializableAs.class); + + if ((alias != null) && (alias.value() != null)) { + return alias.value(); + } + } + + return clazz.getName(); + } +} diff --git a/api/src/main/java/org/bukkit/configuration/serialization/DelegateDeserialization.java b/api/src/main/java/org/bukkit/configuration/serialization/DelegateDeserialization.java new file mode 100644 index 000000000..2fa5d2dfe --- /dev/null +++ b/api/src/main/java/org/bukkit/configuration/serialization/DelegateDeserialization.java @@ -0,0 +1,25 @@ +package org.bukkit.configuration.serialization; + +import org.jetbrains.annotations.NotNull; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +/** + * Applies to a {@link ConfigurationSerializable} that will delegate all + * deserialization to another {@link ConfigurationSerializable}. + */ +@Retention(RetentionPolicy.RUNTIME) +@Target(ElementType.TYPE) +public @interface DelegateDeserialization { + /** + * Which class should be used as a delegate for this classes + * deserialization + * + * @return Delegate class + */ + @NotNull + public Class value(); +} diff --git a/api/src/main/java/org/bukkit/configuration/serialization/SerializableAs.java b/api/src/main/java/org/bukkit/configuration/serialization/SerializableAs.java new file mode 100644 index 000000000..9f4e79504 --- /dev/null +++ b/api/src/main/java/org/bukkit/configuration/serialization/SerializableAs.java @@ -0,0 +1,37 @@ +package org.bukkit.configuration.serialization; + +import org.jetbrains.annotations.NotNull; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +/** + * Represents an "alias" that a {@link ConfigurationSerializable} may be + * stored as. + * If this is not present on a {@link ConfigurationSerializable} class, it + * will use the fully qualified name of the class. + *

+ * This value will be stored in the configuration so that the configuration + * deserialization can determine what type it is. + *

+ * Using this annotation on any other class than a {@link + * ConfigurationSerializable} will have no effect. + * + * @see ConfigurationSerialization#registerClass(Class, String) + */ +@Retention(RetentionPolicy.RUNTIME) +@Target(ElementType.TYPE) +public @interface SerializableAs { + /** + * This is the name your class will be stored and retrieved as. + *

+ * This name MUST be unique. We recommend using names such as + * "MyPluginThing" instead of "Thing". + * + * @return Name to serialize the class as. + */ + @NotNull + public String value(); +} diff --git a/api/src/main/java/org/bukkit/conversations/BooleanPrompt.java b/api/src/main/java/org/bukkit/conversations/BooleanPrompt.java new file mode 100644 index 000000000..95d054436 --- /dev/null +++ b/api/src/main/java/org/bukkit/conversations/BooleanPrompt.java @@ -0,0 +1,41 @@ +package org.bukkit.conversations; + +import org.apache.commons.lang.ArrayUtils; +import org.apache.commons.lang.BooleanUtils; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +/** + * BooleanPrompt is the base class for any prompt that requires a boolean + * response from the user. + */ +public abstract class BooleanPrompt extends ValidatingPrompt { + + public BooleanPrompt() { + super(); + } + + @Override + protected boolean isInputValid(@NotNull ConversationContext context, @NotNull String input) { + String[] accepted = {/* Apache values: */"true", "false", "on", "off", "yes", "no",/* Additional values: */ "y", "n", "1", "0", "right", "wrong", "correct", "incorrect", "valid", "invalid"}; + return ArrayUtils.contains(accepted, input.toLowerCase()); + } + + @Nullable + @Override + protected Prompt acceptValidatedInput(@NotNull ConversationContext context, @NotNull String input) { + if (input.equalsIgnoreCase("y") || input.equals("1") || input.equalsIgnoreCase("right") || input.equalsIgnoreCase("correct") || input.equalsIgnoreCase("valid")) input = "true"; + return acceptValidatedInput(context, BooleanUtils.toBoolean(input)); + } + + /** + * Override this method to perform some action with the user's boolean + * response. + * + * @param context Context information about the conversation. + * @param input The user's boolean response. + * @return The next {@link Prompt} in the prompt graph. + */ + @Nullable + protected abstract Prompt acceptValidatedInput(@NotNull ConversationContext context, boolean input); +} diff --git a/api/src/main/java/org/bukkit/conversations/Conversable.java b/api/src/main/java/org/bukkit/conversations/Conversable.java new file mode 100644 index 000000000..914c6388e --- /dev/null +++ b/api/src/main/java/org/bukkit/conversations/Conversable.java @@ -0,0 +1,57 @@ +package org.bukkit.conversations; + +import org.jetbrains.annotations.NotNull; + +/** + * The Conversable interface is used to indicate objects that can have + * conversations. + */ +public interface Conversable { + + /** + * Tests to see of a Conversable object is actively engaged in a + * conversation. + * + * @return True if a conversation is in progress + */ + public boolean isConversing(); + + /** + * Accepts input into the active conversation. If no conversation is in + * progress, this method does nothing. + * + * @param input The input message into the conversation + */ + public void acceptConversationInput(@NotNull String input); + + /** + * Enters into a dialog with a Conversation object. + * + * @param conversation The conversation to begin + * @return True if the conversation should proceed, false if it has been + * enqueued + */ + public boolean beginConversation(@NotNull Conversation conversation); + + /** + * Abandons an active conversation. + * + * @param conversation The conversation to abandon + */ + public void abandonConversation(@NotNull Conversation conversation); + + /** + * Abandons an active conversation. + * + * @param conversation The conversation to abandon + * @param details Details about why the conversation was abandoned + */ + public void abandonConversation(@NotNull Conversation conversation, @NotNull ConversationAbandonedEvent details); + + /** + * Sends this sender a message raw + * + * @param message Message to be displayed + */ + public void sendRawMessage(@NotNull String message); +} diff --git a/api/src/main/java/org/bukkit/conversations/Conversation.java b/api/src/main/java/org/bukkit/conversations/Conversation.java new file mode 100644 index 000000000..cb77dbd3c --- /dev/null +++ b/api/src/main/java/org/bukkit/conversations/Conversation.java @@ -0,0 +1,304 @@ +package org.bukkit.conversations; + +import org.bukkit.plugin.Plugin; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +/** + * The Conversation class is responsible for tracking the current state of a + * conversation, displaying prompts to the user, and dispatching the user's + * response to the appropriate place. Conversation objects are not typically + * instantiated directly. Instead a {@link ConversationFactory} is used to + * construct identical conversations on demand. + *

+ * Conversation flow consists of a directed graph of {@link Prompt} objects. + * Each time a prompt gets input from the user, it must return the next prompt + * in the graph. Since each Prompt chooses the next Prompt, complex + * conversation trees can be implemented where the nature of the player's + * response directs the flow of the conversation. + *

+ * Each conversation has a {@link ConversationPrefix} that prepends all output + * from the conversation to the player. The ConversationPrefix can be used to + * display the plugin name or conversation status as the conversation evolves. + *

+ * Each conversation has a timeout measured in the number of inactive seconds + * to wait before abandoning the conversation. If the inactivity timeout is + * reached, the conversation is abandoned and the user's incoming and outgoing + * chat is returned to normal. + *

+ * You should not construct a conversation manually. Instead, use the {@link + * ConversationFactory} for access to all available options. + */ +public class Conversation { + + private Prompt firstPrompt; + private boolean abandoned; + protected Prompt currentPrompt; + protected ConversationContext context; + protected boolean modal; + protected boolean localEchoEnabled; + protected ConversationPrefix prefix; + protected List cancellers; + protected List abandonedListeners; + + /** + * Initializes a new Conversation. + * + * @param plugin The plugin that owns this conversation. + * @param forWhom The entity for whom this conversation is mediating. + * @param firstPrompt The first prompt in the conversation graph. + */ + public Conversation(@Nullable Plugin plugin, @NotNull Conversable forWhom, @Nullable Prompt firstPrompt) { + this(plugin, forWhom, firstPrompt, new HashMap()); + } + + /** + * Initializes a new Conversation. + * + * @param plugin The plugin that owns this conversation. + * @param forWhom The entity for whom this conversation is mediating. + * @param firstPrompt The first prompt in the conversation graph. + * @param initialSessionData Any initial values to put in the conversation + * context sessionData map. + */ + public Conversation(@Nullable Plugin plugin, @NotNull Conversable forWhom, @Nullable Prompt firstPrompt, @NotNull Map initialSessionData) { + this.firstPrompt = firstPrompt; + this.context = new ConversationContext(plugin, forWhom, initialSessionData); + this.modal = true; + this.localEchoEnabled = true; + this.prefix = new NullConversationPrefix(); + this.cancellers = new ArrayList(); + this.abandonedListeners = new ArrayList(); + } + + /** + * Gets the entity for whom this conversation is mediating. + * + * @return The entity. + */ + @NotNull + public Conversable getForWhom() { + return context.getForWhom(); + } + + /** + * Gets the modality of this conversation. If a conversation is modal, all + * messages directed to the player are suppressed for the duration of the + * conversation. + * + * @return The conversation modality. + */ + public boolean isModal() { + return modal; + } + + /** + * Sets the modality of this conversation. If a conversation is modal, + * all messages directed to the player are suppressed for the duration of + * the conversation. + * + * @param modal The new conversation modality. + */ + void setModal(boolean modal) { + this.modal = modal; + } + + /** + * Gets the status of local echo for this conversation. If local echo is + * enabled, any text submitted to a conversation gets echoed back into the + * submitter's chat window. + * + * @return The status of local echo. + */ + public boolean isLocalEchoEnabled() { + return localEchoEnabled; + } + + /** + * Sets the status of local echo for this conversation. If local echo is + * enabled, any text submitted to a conversation gets echoed back into the + * submitter's chat window. + * + * @param localEchoEnabled The status of local echo. + */ + public void setLocalEchoEnabled(boolean localEchoEnabled) { + this.localEchoEnabled = localEchoEnabled; + } + + /** + * Gets the {@link ConversationPrefix} that prepends all output from this + * conversation. + * + * @return The ConversationPrefix in use. + */ + @NotNull + public ConversationPrefix getPrefix() { + return prefix; + } + + /** + * Sets the {@link ConversationPrefix} that prepends all output from this + * conversation. + * + * @param prefix The ConversationPrefix to use. + */ + void setPrefix(@NotNull ConversationPrefix prefix) { + this.prefix = prefix; + } + + /** + * Adds a {@link ConversationCanceller} to the cancellers collection. + * + * @param canceller The {@link ConversationCanceller} to add. + */ + void addConversationCanceller(@NotNull ConversationCanceller canceller) { + canceller.setConversation(this); + this.cancellers.add(canceller); + } + + /** + * Gets the list of {@link ConversationCanceller}s + * + * @return The list. + */ + @NotNull + public List getCancellers() { + return cancellers; + } + + /** + * Returns the Conversation's {@link ConversationContext}. + * + * @return The ConversationContext. + */ + @NotNull + public ConversationContext getContext() { + return context; + } + + /** + * Displays the first prompt of this conversation and begins redirecting + * the user's chat responses. + */ + public void begin() { + if (currentPrompt == null) { + abandoned = false; + currentPrompt = firstPrompt; + context.getForWhom().beginConversation(this); + } + } + + /** + * Returns Returns the current state of the conversation. + * + * @return The current state of the conversation. + */ + @NotNull + public ConversationState getState() { + if (currentPrompt != null) { + return ConversationState.STARTED; + } else if (abandoned) { + return ConversationState.ABANDONED; + } else { + return ConversationState.UNSTARTED; + } + } + + /** + * Passes player input into the current prompt. The next prompt (as + * determined by the current prompt) is then displayed to the user. + * + * @param input The user's chat text. + */ + public void acceptInput(@NotNull String input) { + if (currentPrompt != null) { + + // Echo the user's input + if (localEchoEnabled) { + context.getForWhom().sendRawMessage(prefix.getPrefix(context) + input); + } + + // Test for conversation abandonment based on input + for (ConversationCanceller canceller : cancellers) { + if (canceller.cancelBasedOnInput(context, input)) { + abandon(new ConversationAbandonedEvent(this, canceller)); + return; + } + } + + // Not abandoned, output the next prompt + currentPrompt = currentPrompt.acceptInput(context, input); + outputNextPrompt(); + } + } + + /** + * Adds a {@link ConversationAbandonedListener}. + * + * @param listener The listener to add. + */ + public synchronized void addConversationAbandonedListener(@NotNull ConversationAbandonedListener listener) { + abandonedListeners.add(listener); + } + + /** + * Removes a {@link ConversationAbandonedListener}. + * + * @param listener The listener to remove. + */ + public synchronized void removeConversationAbandonedListener(@NotNull ConversationAbandonedListener listener) { + abandonedListeners.remove(listener); + } + + /** + * Abandons and resets the current conversation. Restores the user's + * normal chat behavior. + */ + public void abandon() { + abandon(new ConversationAbandonedEvent(this, new ManuallyAbandonedConversationCanceller())); + } + + /** + * Abandons and resets the current conversation. Restores the user's + * normal chat behavior. + * + * @param details Details about why the conversation was abandoned + */ + public synchronized void abandon(@NotNull ConversationAbandonedEvent details) { + if (!abandoned) { + abandoned = true; + currentPrompt = null; + context.getForWhom().abandonConversation(this); + for (ConversationAbandonedListener listener : abandonedListeners) { + listener.conversationAbandoned(details); + } + } + } + + /** + * Displays the next user prompt and abandons the conversation if the next + * prompt is null. + */ + public void outputNextPrompt() { + if (currentPrompt == null) { + abandon(new ConversationAbandonedEvent(this)); + } else { + context.getForWhom().sendRawMessage(prefix.getPrefix(context) + currentPrompt.getPromptText(context)); + if (!currentPrompt.blocksForInput(context)) { + currentPrompt = currentPrompt.acceptInput(context, null); + outputNextPrompt(); + } + } + } + + public enum ConversationState { + UNSTARTED, + STARTED, + ABANDONED + } +} diff --git a/api/src/main/java/org/bukkit/conversations/ConversationAbandonedEvent.java b/api/src/main/java/org/bukkit/conversations/ConversationAbandonedEvent.java new file mode 100644 index 000000000..5c634789d --- /dev/null +++ b/api/src/main/java/org/bukkit/conversations/ConversationAbandonedEvent.java @@ -0,0 +1,58 @@ +package org.bukkit.conversations; + +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +import java.util.EventObject; + +/** + * ConversationAbandonedEvent contains information about an abandoned + * conversation. + */ +public class ConversationAbandonedEvent extends EventObject { + + private ConversationContext context; + private ConversationCanceller canceller; + + public ConversationAbandonedEvent(@NotNull Conversation conversation) { + this(conversation, null); + } + + public ConversationAbandonedEvent(@NotNull Conversation conversation, @Nullable ConversationCanceller canceller) { + super(conversation); + this.context = conversation.getContext(); + this.canceller = canceller; + } + + /** + * Gets the object that caused the conversation to be abandoned. + * + * @return The object that abandoned the conversation. + */ + @Nullable + public ConversationCanceller getCanceller() { + return canceller; + } + + /** + * Gets the abandoned conversation's conversation context. + * + * @return The abandoned conversation's conversation context. + */ + @NotNull + public ConversationContext getContext() { + return context; + } + + /** + * Indicates how the conversation was abandoned - naturally as part of the + * prompt chain or prematurely via a {@link ConversationCanceller}. + * + * @return True if the conversation is abandoned gracefully by a {@link + * Prompt} returning null or the next prompt. False of the + * conversations is abandoned prematurely by a ConversationCanceller. + */ + public boolean gracefulExit() { + return canceller == null; + } +} diff --git a/api/src/main/java/org/bukkit/conversations/ConversationAbandonedListener.java b/api/src/main/java/org/bukkit/conversations/ConversationAbandonedListener.java new file mode 100644 index 000000000..48585ce53 --- /dev/null +++ b/api/src/main/java/org/bukkit/conversations/ConversationAbandonedListener.java @@ -0,0 +1,17 @@ +package org.bukkit.conversations; + +import org.jetbrains.annotations.NotNull; + +import java.util.EventListener; + +/** + */ +public interface ConversationAbandonedListener extends EventListener { + /** + * Called whenever a {@link Conversation} is abandoned. + * + * @param abandonedEvent Contains details about the abandoned + * conversation. + */ + public void conversationAbandoned(@NotNull ConversationAbandonedEvent abandonedEvent); +} diff --git a/api/src/main/java/org/bukkit/conversations/ConversationCanceller.java b/api/src/main/java/org/bukkit/conversations/ConversationCanceller.java new file mode 100644 index 000000000..bacac1409 --- /dev/null +++ b/api/src/main/java/org/bukkit/conversations/ConversationCanceller.java @@ -0,0 +1,37 @@ +package org.bukkit.conversations; + +import org.jetbrains.annotations.NotNull; + +/** + * A ConversationCanceller is a class that cancels an active {@link + * Conversation}. A Conversation can have more than one ConversationCanceller. + */ +public interface ConversationCanceller extends Cloneable { + + /** + * Sets the conversation this ConversationCanceller can optionally cancel. + * + * @param conversation A conversation. + */ + public void setConversation(@NotNull Conversation conversation); + + /** + * Cancels a conversation based on user input. + * + * @param context Context information about the conversation. + * @param input The input text from the user. + * @return True to cancel the conversation, False otherwise. + */ + public boolean cancelBasedOnInput(@NotNull ConversationContext context, @NotNull String input); + + /** + * Allows the {@link ConversationFactory} to duplicate this + * ConversationCanceller when creating a new {@link Conversation}. + *

+ * Implementing this method should reset any internal object state. + * + * @return A clone. + */ + @NotNull + public ConversationCanceller clone(); +} diff --git a/api/src/main/java/org/bukkit/conversations/ConversationContext.java b/api/src/main/java/org/bukkit/conversations/ConversationContext.java new file mode 100644 index 000000000..b192b03be --- /dev/null +++ b/api/src/main/java/org/bukkit/conversations/ConversationContext.java @@ -0,0 +1,88 @@ +package org.bukkit.conversations; + +import org.bukkit.plugin.Plugin; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +import java.util.Map; + +/** + * A ConversationContext provides continuity between nodes in the prompt graph + * by giving the developer access to the subject of the conversation and a + * generic map for storing values that are shared between all {@link Prompt} + * invocations. + */ +public class ConversationContext { + private final Conversable forWhom; + private final Map sessionData; + private final Plugin plugin; + + /** + * @param plugin The owning plugin. + * @param forWhom The subject of the conversation. + * @param initialSessionData Any initial values to put in the sessionData + * map. + */ + public ConversationContext(@Nullable Plugin plugin, @NotNull Conversable forWhom, @NotNull Map initialSessionData) { + this.plugin = plugin; + this.forWhom = forWhom; + this.sessionData = initialSessionData; + } + + /** + * Gets the plugin that owns this conversation. + * + * @return The owning plugin. + */ + @Nullable + public Plugin getPlugin() { + return plugin; + } + + /** + * Gets the subject of the conversation. + * + * @return The subject of the conversation. + */ + @NotNull + public Conversable getForWhom() { + return forWhom; + } + + /** + * Gets the underlying sessionData map. + * + * May be directly modified to manipulate session data. + * + * @return The full sessionData map. + */ + @NotNull + public Map getAllSessionData() { + return sessionData; + } + + /** + * Gets session data shared between all {@link Prompt} invocations. Use + * this as a way to pass data through each Prompt as the conversation + * develops. + * + * @param key The session data key. + * @return The requested session data. + */ + @Nullable + public Object getSessionData(@NotNull Object key) { + return sessionData.get(key); + } + + /** + * Sets session data shared between all {@link Prompt} invocations. Use + * this as a way to pass data through each prompt as the conversation + * develops. + * + * @param key The session data key. + * @param value The session data value. + */ + public void setSessionData(@NotNull Object key, @Nullable Object value) { + sessionData.put(key, value); + } +} diff --git a/api/src/main/java/org/bukkit/conversations/ConversationFactory.java b/api/src/main/java/org/bukkit/conversations/ConversationFactory.java new file mode 100644 index 000000000..efaaa7d94 --- /dev/null +++ b/api/src/main/java/org/bukkit/conversations/ConversationFactory.java @@ -0,0 +1,240 @@ +package org.bukkit.conversations; + +import org.bukkit.entity.Player; +import org.bukkit.plugin.Plugin; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +/** + * A ConversationFactory is responsible for creating a {@link Conversation} + * from a predefined template. A ConversationFactory is typically created when + * a plugin is instantiated and builds a Conversation each time a user + * initiates a conversation with the plugin. Each Conversation maintains its + * own state and calls back as needed into the plugin. + *

+ * The ConversationFactory implements a fluid API, allowing parameters to be + * set as an extension to the constructor. + */ +public class ConversationFactory { + + protected Plugin plugin; + protected boolean isModal; + protected boolean localEchoEnabled; + protected ConversationPrefix prefix; + protected Prompt firstPrompt; + protected Map initialSessionData; + protected String playerOnlyMessage; + protected List cancellers; + protected List abandonedListeners; + + /** + * Constructs a ConversationFactory. + * + * @param plugin The plugin that owns the factory. + */ + public ConversationFactory(@NotNull Plugin plugin) { + this.plugin = plugin; + isModal = true; + localEchoEnabled = true; + prefix = new NullConversationPrefix(); + firstPrompt = Prompt.END_OF_CONVERSATION; + initialSessionData = new HashMap(); + playerOnlyMessage = null; + cancellers = new ArrayList(); + abandonedListeners = new ArrayList(); + } + + /** + * Sets the modality of all {@link Conversation}s created by this factory. + * If a conversation is modal, all messages directed to the player are + * suppressed for the duration of the conversation. + *

+ * The default is True. + * + * @param modal The modality of all conversations to be created. + * @return This object. + */ + @NotNull + public ConversationFactory withModality(boolean modal) { + isModal = modal; + return this; + } + + /** + * Sets the local echo status for all {@link Conversation}s created by + * this factory. If local echo is enabled, any text submitted to a + * conversation gets echoed back into the submitter's chat window. + * + * @param localEchoEnabled The status of local echo. + * @return This object. + */ + @NotNull + public ConversationFactory withLocalEcho(boolean localEchoEnabled) { + this.localEchoEnabled = localEchoEnabled; + return this; + } + + /** + * Sets the {@link ConversationPrefix} that prepends all output from all + * generated conversations. + *

+ * The default is a {@link NullConversationPrefix}; + * + * @param prefix The ConversationPrefix to use. + * @return This object. + */ + @NotNull + public ConversationFactory withPrefix(@NotNull ConversationPrefix prefix) { + this.prefix = prefix; + return this; + } + + /** + * Sets the number of inactive seconds to wait before automatically + * abandoning all generated conversations. + *

+ * The default is 600 seconds (5 minutes). + * + * @param timeoutSeconds The number of seconds to wait. + * @return This object. + */ + @NotNull + public ConversationFactory withTimeout(int timeoutSeconds) { + return withConversationCanceller(new InactivityConversationCanceller(plugin, timeoutSeconds)); + } + + /** + * Sets the first prompt to use in all generated conversations. + *

+ * The default is Prompt.END_OF_CONVERSATION. + * + * @param firstPrompt The first prompt. + * @return This object. + */ + @NotNull + public ConversationFactory withFirstPrompt(@Nullable Prompt firstPrompt) { + this.firstPrompt = firstPrompt; + return this; + } + + /** + * Sets any initial data with which to populate the conversation context + * sessionData map. + * + * @param initialSessionData The conversation context's initial + * sessionData. + * @return This object. + */ + @NotNull + public ConversationFactory withInitialSessionData(@NotNull Map initialSessionData) { + this.initialSessionData = initialSessionData; + return this; + } + + /** + * Sets the player input that, when received, will immediately terminate + * the conversation. + * + * @param escapeSequence Input to terminate the conversation. + * @return This object. + */ + @NotNull + public ConversationFactory withEscapeSequence(@NotNull String escapeSequence) { + return withConversationCanceller(new ExactMatchConversationCanceller(escapeSequence)); + } + + /** + * Adds a {@link ConversationCanceller} to constructed conversations. + * + * @param canceller The {@link ConversationCanceller} to add. + * @return This object. + */ + @NotNull + public ConversationFactory withConversationCanceller(@NotNull ConversationCanceller canceller) { + cancellers.add(canceller); + return this; + } + + /** + * Prevents this factory from creating a conversation for non-player + * {@link Conversable} objects. + * + * @param playerOnlyMessage The message to return to a non-play in lieu of + * starting a conversation. + * @return This object. + */ + @NotNull + public ConversationFactory thatExcludesNonPlayersWithMessage(@Nullable String playerOnlyMessage) { + this.playerOnlyMessage = playerOnlyMessage; + return this; + } + + /** + * Adds a {@link ConversationAbandonedListener} to all conversations + * constructed by this factory. + * + * @param listener The listener to add. + * @return This object. + */ + @NotNull + public ConversationFactory addConversationAbandonedListener(@NotNull ConversationAbandonedListener listener) { + abandonedListeners.add(listener); + return this; + } + + /** + * Constructs a {@link Conversation} in accordance with the defaults set + * for this factory. + * + * @param forWhom The entity for whom the new conversation is mediating. + * @return A new conversation. + */ + @NotNull + public Conversation buildConversation(@NotNull Conversable forWhom) { + //Abort conversation construction if we aren't supposed to talk to non-players + if (playerOnlyMessage != null && !(forWhom instanceof Player)) { + return new Conversation(plugin, forWhom, new NotPlayerMessagePrompt()); + } + + //Clone any initial session data + Map copiedInitialSessionData = new HashMap(); + copiedInitialSessionData.putAll(initialSessionData); + + //Build and return a conversation + Conversation conversation = new Conversation(plugin, forWhom, firstPrompt, copiedInitialSessionData); + conversation.setModal(isModal); + conversation.setLocalEchoEnabled(localEchoEnabled); + conversation.setPrefix(prefix); + + //Clone the conversation cancellers + for (ConversationCanceller canceller : cancellers) { + conversation.addConversationCanceller(canceller.clone()); + } + + //Add the ConversationAbandonedListeners + for (ConversationAbandonedListener listener : abandonedListeners) { + conversation.addConversationAbandonedListener(listener); + } + + return conversation; + } + + private class NotPlayerMessagePrompt extends MessagePrompt { + + @NotNull + public String getPromptText(@NotNull ConversationContext context) { + return playerOnlyMessage; + } + + @Nullable + @Override + protected Prompt getNextPrompt(@NotNull ConversationContext context) { + return Prompt.END_OF_CONVERSATION; + } + } +} diff --git a/api/src/main/java/org/bukkit/conversations/ConversationPrefix.java b/api/src/main/java/org/bukkit/conversations/ConversationPrefix.java new file mode 100644 index 000000000..0c65f3365 --- /dev/null +++ b/api/src/main/java/org/bukkit/conversations/ConversationPrefix.java @@ -0,0 +1,20 @@ +package org.bukkit.conversations; + +import org.jetbrains.annotations.NotNull; + +/** + * A ConversationPrefix implementation prepends all output from the + * conversation to the player. The ConversationPrefix can be used to display + * the plugin name or conversation status as the conversation evolves. + */ +public interface ConversationPrefix { + + /** + * Gets the prefix to use before each message to the player. + * + * @param context Context information about the conversation. + * @return The prefix text. + */ + @NotNull + String getPrefix(@NotNull ConversationContext context); +} diff --git a/api/src/main/java/org/bukkit/conversations/ExactMatchConversationCanceller.java b/api/src/main/java/org/bukkit/conversations/ExactMatchConversationCanceller.java new file mode 100644 index 000000000..9a30914b6 --- /dev/null +++ b/api/src/main/java/org/bukkit/conversations/ExactMatchConversationCanceller.java @@ -0,0 +1,32 @@ +package org.bukkit.conversations; + +import org.jetbrains.annotations.NotNull; + +/** + * An ExactMatchConversationCanceller cancels a conversation if the user + * enters an exact input string + */ +public class ExactMatchConversationCanceller implements ConversationCanceller { + private String escapeSequence; + + /** + * Builds an ExactMatchConversationCanceller. + * + * @param escapeSequence The string that, if entered by the user, will + * cancel the conversation. + */ + public ExactMatchConversationCanceller(@NotNull String escapeSequence) { + this.escapeSequence = escapeSequence; + } + + public void setConversation(@NotNull Conversation conversation) {} + + public boolean cancelBasedOnInput(@NotNull ConversationContext context, @NotNull String input) { + return input.equals(escapeSequence); + } + + @NotNull + public ConversationCanceller clone() { + return new ExactMatchConversationCanceller(escapeSequence); + } +} diff --git a/api/src/main/java/org/bukkit/conversations/FixedSetPrompt.java b/api/src/main/java/org/bukkit/conversations/FixedSetPrompt.java new file mode 100644 index 000000000..a1dd10285 --- /dev/null +++ b/api/src/main/java/org/bukkit/conversations/FixedSetPrompt.java @@ -0,0 +1,49 @@ +package org.bukkit.conversations; + +import org.apache.commons.lang.StringUtils; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +import java.util.Arrays; +import java.util.List; + +/** + * FixedSetPrompt is the base class for any prompt that requires a fixed set + * response from the user. + */ +public abstract class FixedSetPrompt extends ValidatingPrompt { + + protected List fixedSet; + + /** + * Creates a FixedSetPrompt from a set of strings. + *

+ * foo = new FixedSetPrompt("bar", "cheese", "panda"); + * + * @param fixedSet A fixed set of strings, one of which the user must + * type. + */ + public FixedSetPrompt(@NotNull String... fixedSet) { + super(); + this.fixedSet = Arrays.asList(fixedSet); + } + + private FixedSetPrompt() {} + + @Override + protected boolean isInputValid(@NotNull ConversationContext context, @NotNull String input) { + return fixedSet.contains(input); + } + + /** + * Utility function to create a formatted string containing all the + * options declared in the constructor. + * + * @return the options formatted like "[bar, cheese, panda]" if bar, + * cheese, and panda were the options used + */ + @NotNull + protected String formatFixedSet() { + return "[" + StringUtils.join(fixedSet, ", ") + "]"; + } +} diff --git a/api/src/main/java/org/bukkit/conversations/InactivityConversationCanceller.java b/api/src/main/java/org/bukkit/conversations/InactivityConversationCanceller.java new file mode 100644 index 000000000..cfe2b69f6 --- /dev/null +++ b/api/src/main/java/org/bukkit/conversations/InactivityConversationCanceller.java @@ -0,0 +1,80 @@ +package org.bukkit.conversations; + +import org.bukkit.plugin.Plugin; +import org.jetbrains.annotations.NotNull; + +/** + * An InactivityConversationCanceller will cancel a {@link Conversation} after + * a period of inactivity by the user. + */ +public class InactivityConversationCanceller implements ConversationCanceller { + protected Plugin plugin; + protected int timeoutSeconds; + protected Conversation conversation; + private int taskId = -1; + + /** + * Creates an InactivityConversationCanceller. + * + * @param plugin The owning plugin. + * @param timeoutSeconds The number of seconds of inactivity to wait. + */ + public InactivityConversationCanceller(@NotNull Plugin plugin, int timeoutSeconds) { + this.plugin = plugin; + this.timeoutSeconds = timeoutSeconds; + } + + public void setConversation(@NotNull Conversation conversation) { + this.conversation = conversation; + startTimer(); + } + + public boolean cancelBasedOnInput(@NotNull ConversationContext context, @NotNull String input) { + // Reset the inactivity timer + stopTimer(); + startTimer(); + return false; + } + + @NotNull + public ConversationCanceller clone() { + return new InactivityConversationCanceller(plugin, timeoutSeconds); + } + + /** + * Starts an inactivity timer. + */ + private void startTimer() { + taskId = plugin.getServer().getScheduler().scheduleSyncDelayedTask(plugin, new Runnable() { + public void run() { + if (conversation.getState() == Conversation.ConversationState.UNSTARTED) { + startTimer(); + } else if (conversation.getState() == Conversation.ConversationState.STARTED) { + cancelling(conversation); + conversation.abandon(new ConversationAbandonedEvent(conversation, InactivityConversationCanceller.this)); + } + } + }, timeoutSeconds * 20); + } + + /** + * Stops the active inactivity timer. + */ + private void stopTimer() { + if (taskId != -1) { + plugin.getServer().getScheduler().cancelTask(taskId); + taskId = -1; + } + } + + /** + * Subclasses of InactivityConversationCanceller can override this method + * to take additional actions when the inactivity timer abandons the + * conversation. + * + * @param conversation The conversation being abandoned. + */ + protected void cancelling(@NotNull Conversation conversation) { + + } +} diff --git a/api/src/main/java/org/bukkit/conversations/ManuallyAbandonedConversationCanceller.java b/api/src/main/java/org/bukkit/conversations/ManuallyAbandonedConversationCanceller.java new file mode 100644 index 000000000..2c351a065 --- /dev/null +++ b/api/src/main/java/org/bukkit/conversations/ManuallyAbandonedConversationCanceller.java @@ -0,0 +1,23 @@ +package org.bukkit.conversations; + +import org.jetbrains.annotations.NotNull; + +/** + * The ManuallyAbandonedConversationCanceller is only used as part of a {@link + * ConversationAbandonedEvent} to indicate that the conversation was manually + * abandoned by programmatically calling the abandon() method on it. + */ +public class ManuallyAbandonedConversationCanceller implements ConversationCanceller { + public void setConversation(@NotNull Conversation conversation) { + throw new UnsupportedOperationException(); + } + + public boolean cancelBasedOnInput(@NotNull ConversationContext context, @NotNull String input) { + throw new UnsupportedOperationException(); + } + + @NotNull + public ConversationCanceller clone() { + throw new UnsupportedOperationException(); + } +} diff --git a/api/src/main/java/org/bukkit/conversations/MessagePrompt.java b/api/src/main/java/org/bukkit/conversations/MessagePrompt.java new file mode 100644 index 000000000..69b19b920 --- /dev/null +++ b/api/src/main/java/org/bukkit/conversations/MessagePrompt.java @@ -0,0 +1,47 @@ +package org.bukkit.conversations; + +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +/** + * MessagePrompt is the base class for any prompt that only displays a message + * to the user and requires no input. + */ +public abstract class MessagePrompt implements Prompt { + + public MessagePrompt() { + super(); + } + + /** + * Message prompts never wait for user input before continuing. + * + * @param context Context information about the conversation. + * @return Always false. + */ + public boolean blocksForInput(@NotNull ConversationContext context) { + return false; + } + + /** + * Accepts and ignores any user input, returning the next prompt in the + * prompt graph instead. + * + * @param context Context information about the conversation. + * @param input Ignored. + * @return The next prompt in the prompt graph. + */ + @Nullable + public Prompt acceptInput(@NotNull ConversationContext context, @Nullable String input) { + return getNextPrompt(context); + } + + /** + * Override this method to return the next prompt in the prompt graph. + * + * @param context Context information about the conversation. + * @return The next prompt in the prompt graph. + */ + @Nullable + protected abstract Prompt getNextPrompt(@NotNull ConversationContext context); +} diff --git a/api/src/main/java/org/bukkit/conversations/NullConversationPrefix.java b/api/src/main/java/org/bukkit/conversations/NullConversationPrefix.java new file mode 100644 index 000000000..ce1b20cde --- /dev/null +++ b/api/src/main/java/org/bukkit/conversations/NullConversationPrefix.java @@ -0,0 +1,21 @@ +package org.bukkit.conversations; + +import org.jetbrains.annotations.NotNull; + +/** + * NullConversationPrefix is a {@link ConversationPrefix} implementation that + * displays nothing in front of conversation output. + */ +public class NullConversationPrefix implements ConversationPrefix { + + /** + * Prepends each conversation message with an empty string. + * + * @param context Context information about the conversation. + * @return An empty string. + */ + @NotNull + public String getPrefix(@NotNull ConversationContext context) { + return ""; + } +} diff --git a/api/src/main/java/org/bukkit/conversations/NumericPrompt.java b/api/src/main/java/org/bukkit/conversations/NumericPrompt.java new file mode 100644 index 000000000..5da28a3b4 --- /dev/null +++ b/api/src/main/java/org/bukkit/conversations/NumericPrompt.java @@ -0,0 +1,89 @@ +package org.bukkit.conversations; + +import org.apache.commons.lang.math.NumberUtils; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +/** + * NumericPrompt is the base class for any prompt that requires a {@link + * Number} response from the user. + */ +public abstract class NumericPrompt extends ValidatingPrompt { + public NumericPrompt() { + super(); + } + + @Override + protected boolean isInputValid(@NotNull ConversationContext context, @NotNull String input) { + return NumberUtils.isNumber(input) && isNumberValid(context, NumberUtils.createNumber(input)); + } + + /** + * Override this method to do further validation on the numeric player + * input after the input has been determined to actually be a number. + * + * @param context Context information about the conversation. + * @param input The number the player provided. + * @return The validity of the player's input. + */ + protected boolean isNumberValid(@NotNull ConversationContext context, @NotNull Number input) { + return true; + } + + @Nullable + @Override + protected Prompt acceptValidatedInput(@NotNull ConversationContext context, @NotNull String input) { + try { + return acceptValidatedInput(context, NumberUtils.createNumber(input)); + } catch (NumberFormatException e) { + return acceptValidatedInput(context, NumberUtils.INTEGER_ZERO); + } + } + + /** + * Override this method to perform some action with the user's integer + * response. + * + * @param context Context information about the conversation. + * @param input The user's response as a {@link Number}. + * @return The next {@link Prompt} in the prompt graph. + */ + @Nullable + protected abstract Prompt acceptValidatedInput(@NotNull ConversationContext context, @NotNull Number input); + + @Nullable + @Override + protected String getFailedValidationText(@NotNull ConversationContext context, @NotNull String invalidInput) { + if (NumberUtils.isNumber(invalidInput)) { + return getFailedValidationText(context, NumberUtils.createNumber(invalidInput)); + } else { + return getInputNotNumericText(context, invalidInput); + } + } + + /** + * Optionally override this method to display an additional message if the + * user enters an invalid number. + * + * @param context Context information about the conversation. + * @param invalidInput The invalid input provided by the user. + * @return A message explaining how to correct the input. + */ + @Nullable + protected String getInputNotNumericText(@NotNull ConversationContext context, @NotNull String invalidInput) { + return null; + } + + /** + * Optionally override this method to display an additional message if the + * user enters an invalid numeric input. + * + * @param context Context information about the conversation. + * @param invalidInput The invalid input provided by the user. + * @return A message explaining how to correct the input. + */ + @Nullable + protected String getFailedValidationText(@NotNull ConversationContext context, @NotNull Number invalidInput) { + return null; + } +} diff --git a/api/src/main/java/org/bukkit/conversations/PlayerNamePrompt.java b/api/src/main/java/org/bukkit/conversations/PlayerNamePrompt.java new file mode 100644 index 000000000..86d51c488 --- /dev/null +++ b/api/src/main/java/org/bukkit/conversations/PlayerNamePrompt.java @@ -0,0 +1,41 @@ +package org.bukkit.conversations; + +import org.bukkit.entity.Player; +import org.bukkit.plugin.Plugin; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +/** + * PlayerNamePrompt is the base class for any prompt that requires the player + * to enter another player's name. + */ +public abstract class PlayerNamePrompt extends ValidatingPrompt { + private Plugin plugin; + + public PlayerNamePrompt(@NotNull Plugin plugin) { + super(); + this.plugin = plugin; + } + + @Override + protected boolean isInputValid(@NotNull ConversationContext context, @NotNull String input) { + return plugin.getServer().getPlayer(input) != null; + } + + @Nullable + @Override + protected Prompt acceptValidatedInput(@NotNull ConversationContext context, @NotNull String input) { + return acceptValidatedInput(context, plugin.getServer().getPlayer(input)); + } + + /** + * Override this method to perform some action with the user's player name + * response. + * + * @param context Context information about the conversation. + * @param input The user's player name response. + * @return The next {@link Prompt} in the prompt graph. + */ + @Nullable + protected abstract Prompt acceptValidatedInput(@NotNull ConversationContext context, @NotNull Player input); +} diff --git a/api/src/main/java/org/bukkit/conversations/PluginNameConversationPrefix.java b/api/src/main/java/org/bukkit/conversations/PluginNameConversationPrefix.java new file mode 100644 index 000000000..ed1f3b686 --- /dev/null +++ b/api/src/main/java/org/bukkit/conversations/PluginNameConversationPrefix.java @@ -0,0 +1,41 @@ +package org.bukkit.conversations; + +import org.bukkit.ChatColor; +import org.bukkit.plugin.Plugin; +import org.jetbrains.annotations.NotNull; + +/** + * PluginNameConversationPrefix is a {@link ConversationPrefix} implementation + * that displays the plugin name in front of conversation output. + */ +public class PluginNameConversationPrefix implements ConversationPrefix { + + protected String separator; + protected ChatColor prefixColor; + protected Plugin plugin; + + private String cachedPrefix; + + public PluginNameConversationPrefix(@NotNull Plugin plugin) { + this(plugin, " > ", ChatColor.LIGHT_PURPLE); + } + + public PluginNameConversationPrefix(@NotNull Plugin plugin, @NotNull String separator, @NotNull ChatColor prefixColor) { + this.separator = separator; + this.prefixColor = prefixColor; + this.plugin = plugin; + + cachedPrefix = prefixColor + plugin.getDescription().getName() + separator + ChatColor.WHITE; + } + + /** + * Prepends each conversation message with the plugin name. + * + * @param context Context information about the conversation. + * @return An empty string. + */ + @NotNull + public String getPrefix(@NotNull ConversationContext context) { + return cachedPrefix; + } +} diff --git a/api/src/main/java/org/bukkit/conversations/Prompt.java b/api/src/main/java/org/bukkit/conversations/Prompt.java new file mode 100644 index 000000000..fcca208c0 --- /dev/null +++ b/api/src/main/java/org/bukkit/conversations/Prompt.java @@ -0,0 +1,50 @@ +package org.bukkit.conversations; + +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +/** + * A Prompt is the main constituent of a {@link Conversation}. Each prompt + * displays text to the user and optionally waits for a user's response. + * Prompts are chained together into a directed graph that represents the + * conversation flow. To halt a conversation, END_OF_CONVERSATION is returned + * in liu of another Prompt object. + */ +public interface Prompt extends Cloneable { + + /** + * A convenience constant for indicating the end of a conversation. + */ + static final Prompt END_OF_CONVERSATION = null; + + /** + * Gets the text to display to the user when this prompt is first + * presented. + * + * @param context Context information about the conversation. + * @return The text to display. + */ + @NotNull + String getPromptText(@NotNull ConversationContext context); + + /** + * Checks to see if this prompt implementation should wait for user input + * or immediately display the next prompt. + * + * @param context Context information about the conversation. + * @return If true, the {@link Conversation} will wait for input before + * continuing. If false, {@link #acceptInput(ConversationContext, String)} will be called immediately with {@code null} input. + */ + boolean blocksForInput(@NotNull ConversationContext context); + + /** + * Accepts and processes input from the user. Using the input, the next + * Prompt in the prompt graph is returned. + * + * @param context Context information about the conversation. + * @param input The input text from the user. + * @return The next Prompt in the prompt graph. + */ + @Nullable + Prompt acceptInput(@NotNull ConversationContext context, @Nullable String input); +} diff --git a/api/src/main/java/org/bukkit/conversations/RegexPrompt.java b/api/src/main/java/org/bukkit/conversations/RegexPrompt.java new file mode 100644 index 000000000..a081c19ae --- /dev/null +++ b/api/src/main/java/org/bukkit/conversations/RegexPrompt.java @@ -0,0 +1,31 @@ +package org.bukkit.conversations; + +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +import java.util.regex.Pattern; + +/** + * RegexPrompt is the base class for any prompt that requires an input + * validated by a regular expression. + */ +public abstract class RegexPrompt extends ValidatingPrompt { + + private Pattern pattern; + + public RegexPrompt(@NotNull String regex) { + this(Pattern.compile(regex)); + } + + public RegexPrompt(@NotNull Pattern pattern) { + super(); + this.pattern = pattern; + } + + private RegexPrompt() {} + + @Override + protected boolean isInputValid(@NotNull ConversationContext context, @NotNull String input) { + return pattern.matcher(input).matches(); + } +} diff --git a/api/src/main/java/org/bukkit/conversations/StringPrompt.java b/api/src/main/java/org/bukkit/conversations/StringPrompt.java new file mode 100644 index 000000000..89efb9131 --- /dev/null +++ b/api/src/main/java/org/bukkit/conversations/StringPrompt.java @@ -0,0 +1,20 @@ +package org.bukkit.conversations; + +import org.jetbrains.annotations.NotNull; + +/** + * StringPrompt is the base class for any prompt that accepts an arbitrary + * string from the user. + */ +public abstract class StringPrompt implements Prompt { + + /** + * Ensures that the prompt waits for the user to provide input. + * + * @param context Context information about the conversation. + * @return True. + */ + public boolean blocksForInput(@NotNull ConversationContext context) { + return true; + } +} diff --git a/api/src/main/java/org/bukkit/conversations/ValidatingPrompt.java b/api/src/main/java/org/bukkit/conversations/ValidatingPrompt.java new file mode 100644 index 000000000..3ecfa59a4 --- /dev/null +++ b/api/src/main/java/org/bukkit/conversations/ValidatingPrompt.java @@ -0,0 +1,83 @@ +package org.bukkit.conversations; + +import org.bukkit.ChatColor; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +/** + * ValidatingPrompt is the base class for any prompt that requires validation. + * ValidatingPrompt will keep replaying the prompt text until the user enters + * a valid response. + */ +public abstract class ValidatingPrompt implements Prompt { + public ValidatingPrompt() { + super(); + } + + /** + * Accepts and processes input from the user and validates it. If + * validation fails, this prompt is returned for re-execution, otherwise + * the next Prompt in the prompt graph is returned. + * + * @param context Context information about the conversation. + * @param input The input text from the user. + * @return This prompt or the next Prompt in the prompt graph. + */ + @Nullable + public Prompt acceptInput(@NotNull ConversationContext context, @Nullable String input) { + if (isInputValid(context, input)) { + return acceptValidatedInput(context, input); + } else { + String failPrompt = getFailedValidationText(context, input); + if (failPrompt != null) { + context.getForWhom().sendRawMessage(ChatColor.RED + failPrompt); + } + // Redisplay this prompt to the user to re-collect input + return this; + } + } + + /** + * Ensures that the prompt waits for the user to provide input. + * + * @param context Context information about the conversation. + * @return True. + */ + public boolean blocksForInput(@NotNull ConversationContext context) { + return true; + } + + /** + * Override this method to check the validity of the player's input. + * + * @param context Context information about the conversation. + * @param input The player's raw console input. + * @return True or false depending on the validity of the input. + */ + protected abstract boolean isInputValid(@NotNull ConversationContext context, @NotNull String input); + + /** + * Override this method to accept and processes the validated input from + * the user. Using the input, the next Prompt in the prompt graph should + * be returned. + * + * @param context Context information about the conversation. + * @param input The validated input text from the user. + * @return The next Prompt in the prompt graph. + */ + @Nullable + protected abstract Prompt acceptValidatedInput(@NotNull ConversationContext context, @NotNull String input); + + /** + * Optionally override this method to display an additional message if the + * user enters an invalid input. + * + * @param context Context information about the conversation. + * @param invalidInput The invalid input provided by the user. + * @return A message explaining how to correct the input. + */ + @Nullable + protected String getFailedValidationText(@NotNull ConversationContext context, @NotNull String invalidInput) { + return null; + } +} diff --git a/api/src/main/java/org/bukkit/enchantments/Enchantment.java b/api/src/main/java/org/bukkit/enchantments/Enchantment.java new file mode 100644 index 000000000..705b948e1 --- /dev/null +++ b/api/src/main/java/org/bukkit/enchantments/Enchantment.java @@ -0,0 +1,373 @@ +package org.bukkit.enchantments; + +import java.util.HashMap; +import java.util.Map; + +import org.bukkit.Keyed; +import org.bukkit.NamespacedKey; +import org.bukkit.inventory.ItemStack; +import org.jetbrains.annotations.Contract; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +/** + * The various type of enchantments that may be added to armour or weapons + */ +public abstract class Enchantment implements Keyed { + /** + * Provides protection against environmental damage + */ + public static final Enchantment PROTECTION_ENVIRONMENTAL = new EnchantmentWrapper("protection"); + + /** + * Provides protection against fire damage + */ + public static final Enchantment PROTECTION_FIRE = new EnchantmentWrapper("fire_protection"); + + /** + * Provides protection against fall damage + */ + public static final Enchantment PROTECTION_FALL = new EnchantmentWrapper("feather_falling"); + + /** + * Provides protection against explosive damage + */ + public static final Enchantment PROTECTION_EXPLOSIONS = new EnchantmentWrapper("blast_protection"); + + /** + * Provides protection against projectile damage + */ + public static final Enchantment PROTECTION_PROJECTILE = new EnchantmentWrapper("projectile_protection"); + + /** + * Decreases the rate of air loss whilst underwater + */ + public static final Enchantment OXYGEN = new EnchantmentWrapper("respiration"); + + /** + * Increases the speed at which a player may mine underwater + */ + public static final Enchantment WATER_WORKER = new EnchantmentWrapper("aqua_affinity"); + + /** + * Damages the attacker + */ + public static final Enchantment THORNS = new EnchantmentWrapper("thorns"); + + /** + * Increases walking speed while in water + */ + public static final Enchantment DEPTH_STRIDER = new EnchantmentWrapper("depth_strider"); + + /** + * Freezes any still water adjacent to ice / frost which player is walking on + */ + public static final Enchantment FROST_WALKER = new EnchantmentWrapper("frost_walker"); + + /** + * Item cannot be removed + */ + public static final Enchantment BINDING_CURSE = new EnchantmentWrapper("binding_curse"); + + /** + * Increases damage against all targets + */ + public static final Enchantment DAMAGE_ALL = new EnchantmentWrapper("sharpness"); + + /** + * Increases damage against undead targets + */ + public static final Enchantment DAMAGE_UNDEAD = new EnchantmentWrapper("smite"); + + /** + * Increases damage against arthropod targets + */ + public static final Enchantment DAMAGE_ARTHROPODS = new EnchantmentWrapper("bane_of_arthropods"); + + /** + * All damage to other targets will knock them back when hit + */ + public static final Enchantment KNOCKBACK = new EnchantmentWrapper("knockback"); + + /** + * When attacking a target, has a chance to set them on fire + */ + public static final Enchantment FIRE_ASPECT = new EnchantmentWrapper("fire_aspect"); + + /** + * Provides a chance of gaining extra loot when killing monsters + */ + public static final Enchantment LOOT_BONUS_MOBS = new EnchantmentWrapper("looting"); + + /** + * Increases damage against targets when using a sweep attack + */ + public static final Enchantment SWEEPING_EDGE = new EnchantmentWrapper("sweeping"); + + /** + * Increases the rate at which you mine/dig + */ + public static final Enchantment DIG_SPEED = new EnchantmentWrapper("efficiency"); + + /** + * Allows blocks to drop themselves instead of fragments (for example, + * stone instead of cobblestone) + */ + public static final Enchantment SILK_TOUCH = new EnchantmentWrapper("silk_touch"); + + /** + * Decreases the rate at which a tool looses durability + */ + public static final Enchantment DURABILITY = new EnchantmentWrapper("unbreaking"); + + /** + * Provides a chance of gaining extra loot when destroying blocks + */ + public static final Enchantment LOOT_BONUS_BLOCKS = new EnchantmentWrapper("fortune"); + + /** + * Provides extra damage when shooting arrows from bows + */ + public static final Enchantment ARROW_DAMAGE = new EnchantmentWrapper("power"); + + /** + * Provides a knockback when an entity is hit by an arrow from a bow + */ + public static final Enchantment ARROW_KNOCKBACK = new EnchantmentWrapper("punch"); + + /** + * Sets entities on fire when hit by arrows shot from a bow + */ + public static final Enchantment ARROW_FIRE = new EnchantmentWrapper("flame"); + + /** + * Provides infinite arrows when shooting a bow + */ + public static final Enchantment ARROW_INFINITE = new EnchantmentWrapper("infinity"); + + /** + * Decreases odds of catching worthless junk + */ + public static final Enchantment LUCK = new EnchantmentWrapper("luck_of_the_sea"); + + /** + * Increases rate of fish biting your hook + */ + public static final Enchantment LURE = new EnchantmentWrapper("lure"); + + /** + * Causes a thrown trident to return to the player who threw it + */ + public static final Enchantment LOYALTY = new EnchantmentWrapper("loyalty"); + + /** + * Deals more damage to mobs that live in the ocean + */ + public static final Enchantment IMPALING = new EnchantmentWrapper("impaling"); + + /** + * When it is rainy, launches the player in the direction their trident is thrown + */ + public static final Enchantment RIPTIDE = new EnchantmentWrapper("riptide"); + + /** + * Strikes lightning when a mob is hit with a trident if conditions are + * stormy + */ + public static final Enchantment CHANNELING = new EnchantmentWrapper("channeling"); + + /** + * Allows mending the item using experience orbs + */ + public static final Enchantment MENDING = new EnchantmentWrapper("mending"); + + /** + * Item disappears instead of dropping + */ + public static final Enchantment VANISHING_CURSE = new EnchantmentWrapper("vanishing_curse"); + + private static final Map byKey = new HashMap(); + private static final Map byName = new HashMap(); + private static boolean acceptingNew = true; + private final NamespacedKey key; + + public Enchantment(@NotNull NamespacedKey key) { + this.key = key; + } + + @NotNull + @Override + public NamespacedKey getKey() { + return key; + } + + /** + * Gets the unique name of this enchantment + * + * @return Unique name + * @deprecated enchantments are badly named, use {@link #getKey()}. + */ + @NotNull + @Deprecated + public abstract String getName(); + + /** + * Gets the maximum level that this Enchantment may become. + * + * @return Maximum level of the Enchantment + */ + public abstract int getMaxLevel(); + + /** + * Gets the level that this Enchantment should start at + * + * @return Starting level of the Enchantment + */ + public abstract int getStartLevel(); + + /** + * Gets the type of {@link ItemStack} that may fit this Enchantment. + * + * @return Target type of the Enchantment + */ + @NotNull + public abstract EnchantmentTarget getItemTarget(); + + /** + * Checks if this enchantment is a treasure enchantment. + *
+ * Treasure enchantments can only be received via looting, trading, or + * fishing. + * + * @return true if the enchantment is a treasure enchantment + */ + public abstract boolean isTreasure(); + + /** + * Checks if this enchantment is a cursed enchantment + *
+ * Cursed enchantments are found the same way treasure enchantments are + * + * @return true if the enchantment is cursed + * @deprecated cursed enchantments are no longer special. Will return true + * only for {@link Enchantment#BINDING_CURSE} and + * {@link Enchantment#VANISHING_CURSE}. + */ + @Deprecated + public abstract boolean isCursed(); + + /** + * Check if this enchantment conflicts with another enchantment. + * + * @param other The enchantment to check against + * @return True if there is a conflict. + */ + public abstract boolean conflictsWith(@NotNull Enchantment other); + + /** + * Checks if this Enchantment may be applied to the given {@link + * ItemStack}. + *

+ * This does not check if it conflicts with any enchantments already + * applied to the item. + * + * @param item Item to test + * @return True if the enchantment may be applied, otherwise False + */ + public abstract boolean canEnchantItem(@NotNull ItemStack item); + + @Override + public boolean equals(Object obj) { + if (obj == null) { + return false; + } + if (!(obj instanceof Enchantment)) { + return false; + } + final Enchantment other = (Enchantment) obj; + if (!this.key.equals(other.key)) { + return false; + } + return true; + } + + @Override + public int hashCode() { + return key.hashCode(); + } + + @Override + public String toString() { + return "Enchantment[" + key + ", " + getName() + "]"; + } + + /** + * Registers an enchantment with the given ID and object. + *

+ * Generally not to be used from within a plugin. + * + * @param enchantment Enchantment to register + */ + public static void registerEnchantment(@NotNull Enchantment enchantment) { + if (byKey.containsKey(enchantment.key) || byName.containsKey(enchantment.getName())) { + throw new IllegalArgumentException("Cannot set already-set enchantment"); + } else if (!isAcceptingRegistrations()) { + throw new IllegalStateException("No longer accepting new enchantments (can only be done by the server implementation)"); + } + + byKey.put(enchantment.key, enchantment); + byName.put(enchantment.getName(), enchantment); + } + + /** + * Checks if this is accepting Enchantment registrations. + * + * @return True if the server Implementation may add enchantments + */ + public static boolean isAcceptingRegistrations() { + return acceptingNew; + } + + /** + * Stops accepting any enchantment registrations + */ + public static void stopAcceptingRegistrations() { + acceptingNew = false; + } + + /** + * Gets the Enchantment at the specified key + * + * @param key key to fetch + * @return Resulting Enchantment, or null if not found + */ + @Contract("null -> null") + @Nullable + public static Enchantment getByKey(@Nullable NamespacedKey key) { + return byKey.get(key); + } + + /** + * Gets the Enchantment at the specified name + * + * @param name Name to fetch + * @return Resulting Enchantment, or null if not found + * @deprecated enchantments are badly named, use {@link #getByKey(org.bukkit.NamespacedKey)}. + */ + @Deprecated + @Contract("null -> null") + @Nullable + public static Enchantment getByName(@Nullable String name) { + return byName.get(name); + } + + /** + * Gets an array of all the registered {@link Enchantment}s + * + * @return Array of enchantments + */ + @NotNull + public static Enchantment[] values() { + return byName.values().toArray(new Enchantment[byName.size()]); + } +} diff --git a/api/src/main/java/org/bukkit/enchantments/EnchantmentOffer.java b/api/src/main/java/org/bukkit/enchantments/EnchantmentOffer.java new file mode 100644 index 000000000..a830610dc --- /dev/null +++ b/api/src/main/java/org/bukkit/enchantments/EnchantmentOffer.java @@ -0,0 +1,83 @@ +package org.bukkit.enchantments; + +import org.apache.commons.lang.Validate; +import org.jetbrains.annotations.NotNull; + +/** + * A class for the available enchantment offers in the enchantment table. + */ +public class EnchantmentOffer { + + private Enchantment enchantment; + private int enchantmentLevel; + private int cost; + + public EnchantmentOffer(@NotNull Enchantment enchantment, int enchantmentLevel, int cost) { + this.enchantment = enchantment; + this.enchantmentLevel = enchantmentLevel; + this.cost = cost; + } + + /** + * Get the type of the enchantment. + * + * @return type of enchantment + */ + @NotNull + public Enchantment getEnchantment() { + return enchantment; + } + + /** + * Sets the type of the enchantment. + * + * @param enchantment type of the enchantment + */ + public void setEnchantment(@NotNull Enchantment enchantment) { + Validate.notNull(enchantment, "The enchantment may not be null!"); + + this.enchantment = enchantment; + } + + /** + * Gets the level of the enchantment. + * + * @return level of the enchantment + */ + public int getEnchantmentLevel() { + return enchantmentLevel; + } + + /** + * Sets the level of the enchantment. + * + * @param enchantmentLevel level of the enchantment + */ + public void setEnchantmentLevel(int enchantmentLevel) { + Validate.isTrue(enchantmentLevel > 0, "The enchantment level must be greater than 0!"); + + this.enchantmentLevel = enchantmentLevel; + } + + /** + * Gets the cost in experience levels the player has to pay to enchant his + * item with this enchantment. + * + * @return cost for this enchantment + */ + public int getCost() { + return cost; + } + + /** + * Sets the cost in experience levels the player has to pay to enchant his + * item with this enchantment + * + * @param cost cost for this enchantment + */ + public void setCost(int cost) { + Validate.isTrue(cost > 0, "The cost must be greater than 0!"); + + this.cost = cost; + } +} diff --git a/api/src/main/java/org/bukkit/enchantments/EnchantmentTarget.java b/api/src/main/java/org/bukkit/enchantments/EnchantmentTarget.java new file mode 100644 index 000000000..9f30c869e --- /dev/null +++ b/api/src/main/java/org/bukkit/enchantments/EnchantmentTarget.java @@ -0,0 +1,213 @@ +package org.bukkit.enchantments; + +import org.bukkit.Material; +import org.bukkit.inventory.ItemStack; +import org.jetbrains.annotations.NotNull; + +/** + * Represents the applicable target for a {@link Enchantment} + */ +public enum EnchantmentTarget { + /** + * Allows the Enchantment to be placed on all items + */ + ALL { + @Override + public boolean includes(@NotNull Material item) { + return true; + } + }, + + /** + * Allows the Enchantment to be placed on armor + */ + ARMOR { + @Override + public boolean includes(@NotNull Material item) { + return ARMOR_FEET.includes(item) + || ARMOR_LEGS.includes(item) + || ARMOR_HEAD.includes(item) + || ARMOR_TORSO.includes(item); + } + }, + + /** + * Allows the Enchantment to be placed on feet slot armor + */ + ARMOR_FEET { + @Override + public boolean includes(@NotNull Material item) { + return item.equals(Material.LEATHER_BOOTS) + || item.equals(Material.CHAINMAIL_BOOTS) + || item.equals(Material.IRON_BOOTS) + || item.equals(Material.DIAMOND_BOOTS) + || item.equals(Material.GOLDEN_BOOTS); + } + }, + + /** + * Allows the Enchantment to be placed on leg slot armor + */ + ARMOR_LEGS { + @Override + public boolean includes(@NotNull Material item) { + return item.equals(Material.LEATHER_LEGGINGS) + || item.equals(Material.CHAINMAIL_LEGGINGS) + || item.equals(Material.IRON_LEGGINGS) + || item.equals(Material.DIAMOND_LEGGINGS) + || item.equals(Material.GOLDEN_LEGGINGS); + } + }, + + /** + * Allows the Enchantment to be placed on torso slot armor + */ + ARMOR_TORSO { + @Override + public boolean includes(@NotNull Material item) { + return item.equals(Material.LEATHER_CHESTPLATE) + || item.equals(Material.CHAINMAIL_CHESTPLATE) + || item.equals(Material.IRON_CHESTPLATE) + || item.equals(Material.DIAMOND_CHESTPLATE) + || item.equals(Material.GOLDEN_CHESTPLATE); + } + }, + + /** + * Allows the Enchantment to be placed on head slot armor + */ + ARMOR_HEAD { + @Override + public boolean includes(@NotNull Material item) { + return item.equals(Material.LEATHER_HELMET) + || item.equals(Material.CHAINMAIL_HELMET) + || item.equals(Material.DIAMOND_HELMET) + || item.equals(Material.IRON_HELMET) + || item.equals(Material.GOLDEN_HELMET); + } + }, + + /** + * Allows the Enchantment to be placed on weapons (swords) + */ + WEAPON { + @Override + public boolean includes(@NotNull Material item) { + return item.equals(Material.WOODEN_SWORD) + || item.equals(Material.STONE_SWORD) + || item.equals(Material.IRON_SWORD) + || item.equals(Material.DIAMOND_SWORD) + || item.equals(Material.GOLDEN_SWORD); + } + }, + + /** + * Allows the Enchantment to be placed on tools (spades, pickaxe, hoes, + * axes) + */ + TOOL { + @Override + public boolean includes(@NotNull Material item) { + return item.equals(Material.WOODEN_SHOVEL) + || item.equals(Material.STONE_SHOVEL) + || item.equals(Material.IRON_SHOVEL) + || item.equals(Material.DIAMOND_SHOVEL) + || item.equals(Material.GOLDEN_SHOVEL) + || item.equals(Material.WOODEN_PICKAXE) + || item.equals(Material.STONE_PICKAXE) + || item.equals(Material.IRON_PICKAXE) + || item.equals(Material.DIAMOND_PICKAXE) + || item.equals(Material.GOLDEN_PICKAXE) + || item.equals(Material.WOODEN_HOE) + || item.equals(Material.STONE_HOE) + || item.equals(Material.IRON_HOE) + || item.equals(Material.DIAMOND_HOE) + || item.equals(Material.GOLDEN_HOE) + || item.equals(Material.WOODEN_AXE) + || item.equals(Material.STONE_AXE) + || item.equals(Material.IRON_AXE) + || item.equals(Material.DIAMOND_AXE) + || item.equals(Material.GOLDEN_AXE) + || item.equals(Material.SHEARS) + || item.equals(Material.FLINT_AND_STEEL); + } + }, + + /** + * Allows the Enchantment to be placed on bows. + */ + BOW { + @Override + public boolean includes(@NotNull Material item) { + return item.equals(Material.BOW); + } + }, + + /** + * Allows the Enchantment to be placed on fishing rods. + */ + FISHING_ROD { + @Override + public boolean includes(@NotNull Material item) { + return item.equals(Material.FISHING_ROD); + } + }, + + /** + * Allows the enchantment to be placed on items with durability. + */ + BREAKABLE { + @Override + public boolean includes(@NotNull Material item) { + return item.getMaxDurability() > 0 && item.getMaxStackSize() == 1; + } + }, + + /** + * Allows the enchantment to be placed on wearable items. + */ + WEARABLE { + @Override + public boolean includes(@NotNull Material item) { + return ARMOR.includes(item) + || item.equals(Material.ELYTRA) + || item.equals(Material.PUMPKIN) + || item.equals(Material.CARVED_PUMPKIN) + || item.equals(Material.JACK_O_LANTERN) + || item.equals(Material.SKELETON_SKULL) + || item.equals(Material.WITHER_SKELETON_SKULL) + || item.equals(Material.ZOMBIE_HEAD) + || item.equals(Material.PLAYER_HEAD) + || item.equals(Material.CREEPER_HEAD) + || item.equals(Material.DRAGON_HEAD); + } + }, + + /** + * Allow the Enchantment to be placed on tridents. + */ + TRIDENT { + @Override + public boolean includes(@NotNull Material item) { + return item.equals(Material.TRIDENT); + } + }; + + /** + * Check whether this target includes the specified item. + * + * @param item The item to check + * @return True if the target includes the item + */ + public abstract boolean includes(@NotNull Material item); + + /** + * Check whether this target includes the specified item. + * + * @param item The item to check + * @return True if the target includes the item + */ + public boolean includes(@NotNull ItemStack item) { + return includes(item.getType()); + } +} diff --git a/api/src/main/java/org/bukkit/enchantments/EnchantmentWrapper.java b/api/src/main/java/org/bukkit/enchantments/EnchantmentWrapper.java new file mode 100644 index 000000000..9566e4306 --- /dev/null +++ b/api/src/main/java/org/bukkit/enchantments/EnchantmentWrapper.java @@ -0,0 +1,66 @@ +package org.bukkit.enchantments; + +import org.bukkit.NamespacedKey; +import org.bukkit.inventory.ItemStack; +import org.jetbrains.annotations.NotNull; + +/** + * A simple wrapper for ease of selecting {@link Enchantment}s + */ +public class EnchantmentWrapper extends Enchantment { + public EnchantmentWrapper(@NotNull String name) { + super(NamespacedKey.minecraft(name)); + } + + /** + * Gets the enchantment bound to this wrapper + * + * @return Enchantment + */ + @NotNull + public Enchantment getEnchantment() { + return Enchantment.getByKey(getKey()); + } + + @Override + public int getMaxLevel() { + return getEnchantment().getMaxLevel(); + } + + @Override + public int getStartLevel() { + return getEnchantment().getStartLevel(); + } + + @NotNull + @Override + public EnchantmentTarget getItemTarget() { + return getEnchantment().getItemTarget(); + } + + @Override + public boolean canEnchantItem(@NotNull ItemStack item) { + return getEnchantment().canEnchantItem(item); + } + + @NotNull + @Override + public String getName() { + return getEnchantment().getName(); + } + + @Override + public boolean isTreasure() { + return getEnchantment().isTreasure(); + } + + @Override + public boolean isCursed() { + return getEnchantment().isCursed(); + } + + @Override + public boolean conflictsWith(@NotNull Enchantment other) { + return getEnchantment().conflictsWith(other); + } +} diff --git a/api/src/main/java/org/bukkit/entity/AbstractHorse.java b/api/src/main/java/org/bukkit/entity/AbstractHorse.java new file mode 100644 index 000000000..234aa9dd2 --- /dev/null +++ b/api/src/main/java/org/bukkit/entity/AbstractHorse.java @@ -0,0 +1,108 @@ +package org.bukkit.entity; + +import org.bukkit.inventory.AbstractHorseInventory; +import org.bukkit.inventory.InventoryHolder; +import org.jetbrains.annotations.Contract; +import org.jetbrains.annotations.NotNull; + +/** + * Represents a Horse-like creature. + */ +public interface AbstractHorse extends Animals, Vehicle, InventoryHolder, Tameable { + + /** + * Gets the horse's variant. + *

+ * A horse's variant defines its physical appearance and capabilities. + * Whether a horse is a regular horse, donkey, mule, or other kind of horse + * is determined using the variant. + * + * @return a {@link Horse.Variant} representing the horse's variant + * @deprecated different variants are different classes + */ + @Deprecated + @NotNull + public Horse.Variant getVariant(); + + /** + * @param variant Variant to set + * @deprecated you are required to spawn a different entity + */ + @Deprecated + @Contract("_ -> fail") + public void setVariant(Horse.Variant variant); + + /** + * Gets the domestication level of this horse. + *

+ * A higher domestication level indicates that the horse is closer to + * becoming tame. As the domestication level gets closer to the max + * domestication level, the chance of the horse becoming tame increases. + * + * @return domestication level + */ + public int getDomestication(); + + /** + * Sets the domestication level of this horse. + *

+ * Setting the domestication level to a high value will increase the + * horse's chances of becoming tame. + *

+ * Domestication level must be greater than zero and no greater than + * the max domestication level of the horse, determined with + * {@link #getMaxDomestication()} + * + * @param level domestication level + */ + public void setDomestication(int level); + + /** + * Gets the maximum domestication level of this horse. + *

+ * The higher this level is, the longer it will likely take + * for the horse to be tamed. + * + * @return the max domestication level + */ + public int getMaxDomestication(); + + /** + * Sets the maximum domestication level of this horse. + *

+ * Setting a higher max domestication will increase the amount of + * domesticating (feeding, riding, etc.) necessary in order to tame it, + * while setting a lower max value will have the opposite effect. + *

+ * Maximum domestication must be greater than zero. + * + * @param level the max domestication level + */ + public void setMaxDomestication(int level); + + /** + * Gets the jump strength of this horse. + *

+ * Jump strength defines how high the horse can jump. A higher jump strength + * increases how high a jump will go. + * + * @return the horse's jump strength + */ + public double getJumpStrength(); + + /** + * Sets the jump strength of this horse. + *

+ * A higher jump strength increases how high a jump will go. + * Setting a jump strength to 0 will result in no jump. + * You cannot set a jump strength to a value below 0 or + * above 2. + * + * @param strength jump strength for this horse + */ + public void setJumpStrength(double strength); + + @NotNull + @Override + public AbstractHorseInventory getInventory(); +} diff --git a/api/src/main/java/org/bukkit/entity/Ageable.java b/api/src/main/java/org/bukkit/entity/Ageable.java new file mode 100644 index 000000000..e9fccb294 --- /dev/null +++ b/api/src/main/java/org/bukkit/entity/Ageable.java @@ -0,0 +1,67 @@ +package org.bukkit.entity; + +/** + * Represents an entity that can age and breed. + */ +public interface Ageable extends Creature { + /** + * Gets the age of this animal. + * + * @return Age + */ + public int getAge(); + + /** + * Sets the age of this animal. + * + * @param age New age + */ + public void setAge(int age); + + /** + * Lock the age of the animal, setting this will prevent the animal from + * maturing or getting ready for mating. + * + * @param lock new lock + */ + public void setAgeLock(boolean lock); + + /** + * Gets the current agelock. + * + * @return the current agelock + */ + public boolean getAgeLock(); + + /** + * Sets the age of the animal to a baby + */ + public void setBaby(); + + /** + * Sets the age of the animal to an adult + */ + public void setAdult(); + + /** + * Returns true if the animal is an adult. + * + * @return return true if the animal is an adult + */ + public boolean isAdult(); + + /** + * Return the ability to breed of the animal. + * + * @return the ability to breed of the animal + */ + public boolean canBreed(); + + /** + * Set breedability of the animal, if the animal is a baby and set to + * breed it will instantly grow up. + * + * @param breed breedability of the animal + */ + public void setBreed(boolean breed); +} diff --git a/api/src/main/java/org/bukkit/entity/Ambient.java b/api/src/main/java/org/bukkit/entity/Ambient.java new file mode 100644 index 000000000..613830a74 --- /dev/null +++ b/api/src/main/java/org/bukkit/entity/Ambient.java @@ -0,0 +1,6 @@ +package org.bukkit.entity; + +/** + * Represents an ambient mob + */ +public interface Ambient extends Mob {} diff --git a/api/src/main/java/org/bukkit/entity/AnimalTamer.java b/api/src/main/java/org/bukkit/entity/AnimalTamer.java new file mode 100644 index 000000000..9f1eed9ba --- /dev/null +++ b/api/src/main/java/org/bukkit/entity/AnimalTamer.java @@ -0,0 +1,25 @@ +package org.bukkit.entity; + +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +import java.util.UUID; + +public interface AnimalTamer { + + /** + * This is the name of the specified AnimalTamer. + * + * @return The name to reference on tamed animals or null if a name cannot be obtained + */ + @Nullable + public String getName(); + + /** + * This is the UUID of the specified AnimalTamer. + * + * @return The UUID to reference on tamed animals + */ + @NotNull + public UUID getUniqueId(); +} diff --git a/api/src/main/java/org/bukkit/entity/Animals.java b/api/src/main/java/org/bukkit/entity/Animals.java new file mode 100644 index 000000000..3d4f8c3d4 --- /dev/null +++ b/api/src/main/java/org/bukkit/entity/Animals.java @@ -0,0 +1,54 @@ +package org.bukkit.entity; + +import org.jetbrains.annotations.Nullable; + +import java.util.UUID; + +/** + * Represents an Animal. + */ +public interface Animals extends Ageable { + + /** + * Get the UUID of the entity that caused this entity to enter the + * {@link #canBreed()} state. + * + * @return uuid if set, or null + */ + @Nullable + UUID getBreedCause(); + + /** + * Set the UUID of the entity that caused this entity to enter the + * {@link #canBreed()} state. + * + * @param uuid new uuid, or null + */ + void setBreedCause(@Nullable UUID uuid); + + /** + * Get whether or not this entity is in love mode and will produce + * offspring with another entity in love mode. Will return true if + * and only if {@link #getLoveModeTicks()} is greater than 0. + * + * @return true if in love mode, false otherwise + */ + boolean isLoveMode(); + + /** + * Get the amount of ticks remaining for this entity in love mode. + * If the entity is not in love mode, 0 will be returned. + * + * @return the remaining love mode ticks + */ + int getLoveModeTicks(); + + /** + * Set the amount of ticks for which this entity should be in love mode. + * Setting the love mode ticks to 600 is the equivalent of a player + * feeding the entity their breeding item of choice. + * + * @param ticks the love mode ticks. Must be positive + */ + void setLoveModeTicks(int ticks); +} diff --git a/api/src/main/java/org/bukkit/entity/AreaEffectCloud.java b/api/src/main/java/org/bukkit/entity/AreaEffectCloud.java new file mode 100644 index 000000000..123d87954 --- /dev/null +++ b/api/src/main/java/org/bukkit/entity/AreaEffectCloud.java @@ -0,0 +1,242 @@ +package org.bukkit.entity; + +import java.util.List; +import org.bukkit.Color; +import org.bukkit.Particle; +import org.bukkit.potion.PotionData; +import org.bukkit.potion.PotionEffect; +import org.bukkit.potion.PotionEffectType; +import org.bukkit.projectiles.ProjectileSource; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +/** + * Represents an area effect cloud which will imbue a potion effect onto + * entities which enter it. + */ +public interface AreaEffectCloud extends Entity { + + /** + * Gets the duration which this cloud will exist for (in ticks). + * + * @return cloud duration + */ + int getDuration(); + + /** + * Sets the duration which this cloud will exist for (in ticks). + * + * @param duration cloud duration + */ + void setDuration(int duration); + + /** + * Gets the time which an entity has to be exposed to the cloud before the + * effect is applied. + * + * @return wait time + */ + int getWaitTime(); + + /** + * Sets the time which an entity has to be exposed to the cloud before the + * effect is applied. + * + * @param waitTime wait time + */ + void setWaitTime(int waitTime); + + /** + * Gets the time that an entity will be immune from subsequent exposure. + * + * @return reapplication delay + */ + int getReapplicationDelay(); + + /** + * Sets the time that an entity will be immune from subsequent exposure. + * + * @param delay reapplication delay + */ + void setReapplicationDelay(int delay); + + /** + * Gets the amount that the duration of this cloud will decrease by when it + * applies an effect to an entity. + * + * @return duration on use delta + */ + int getDurationOnUse(); + + /** + * Sets the amount that the duration of this cloud will decrease by when it + * applies an effect to an entity. + * + * @param duration duration on use delta + */ + void setDurationOnUse(int duration); + + /** + * Gets the initial radius of the cloud. + * + * @return radius + */ + float getRadius(); + + /** + * Sets the initial radius of the cloud. + * + * @param radius radius + */ + void setRadius(float radius); + + /** + * Gets the amount that the radius of this cloud will decrease by when it + * applies an effect to an entity. + * + * @return radius on use delta + */ + float getRadiusOnUse(); + + /** + * Sets the amount that the radius of this cloud will decrease by when it + * applies an effect to an entity. + * + * @param radius radius on use delta + */ + void setRadiusOnUse(float radius); + + /** + * Gets the amount that the radius of this cloud will decrease by each tick. + * + * @return radius per tick delta + */ + float getRadiusPerTick(); + + /** + * Gets the amount that the radius of this cloud will decrease by each tick. + * + * @param radius per tick delta + */ + void setRadiusPerTick(float radius); + + /** + * Gets the particle which this cloud will be composed of + * + * @return particle the set particle type + */ + @NotNull + Particle getParticle(); + + /** + * Sets the particle which this cloud will be composed of + * + * @param particle the new particle type + */ + void setParticle(@NotNull Particle particle); + + /** + * Sets the particle which this cloud will be composed of + * + * @param particle the new particle type + * @param data the data to use for the particle or null, + * the type of this depends on {@link Particle#getDataType()} + * @param the particle data type // Paper + */ + void setParticle(@NotNull Particle particle, @Nullable T data); + + /** + * Sets the underlying potion data + * + * @param data PotionData to set the base potion state to + */ + void setBasePotionData(@NotNull PotionData data); + + /** + * Returns the potion data about the base potion + * + * @return a PotionData object + */ + @NotNull + PotionData getBasePotionData(); + + /** + * Checks for the presence of custom potion effects. + * + * @return true if custom potion effects are applied + */ + boolean hasCustomEffects(); + + /** + * Gets an immutable list containing all custom potion effects applied to + * this cloud. + *

+ * Plugins should check that hasCustomEffects() returns true before calling + * this method. + * + * @return the immutable list of custom potion effects + */ + @NotNull + List getCustomEffects(); + + /** + * Adds a custom potion effect to this cloud. + * + * @param effect the potion effect to add + * @param overwrite true if any existing effect of the same type should be + * overwritten + * @return true if the effect was added as a result of this call + */ + boolean addCustomEffect(@NotNull PotionEffect effect, boolean overwrite); + + /** + * Removes a custom potion effect from this cloud. + * + * @param type the potion effect type to remove + * @return true if the an effect was removed as a result of this call + */ + boolean removeCustomEffect(@NotNull PotionEffectType type); + + /** + * Checks for a specific custom potion effect type on this cloud. + * + * @param type the potion effect type to check for + * @return true if the potion has this effect + */ + boolean hasCustomEffect(@Nullable PotionEffectType type); + + /** + * Removes all custom potion effects from this cloud. + */ + void clearCustomEffects(); + + /** + * Gets the color of this cloud. Will be applied as a tint to its particles. + * + * @return cloud color + */ + @NotNull + Color getColor(); + + /** + * Sets the color of this cloud. Will be applied as a tint to its particles. + * + * @param color cloud color + */ + void setColor(@NotNull Color color); + + /** + * Retrieve the original source of this cloud. + * + * @return the {@link ProjectileSource} that threw the LingeringPotion + */ + @Nullable + public ProjectileSource getSource(); + + /** + * Set the original source of this cloud. + * + * @param source the {@link ProjectileSource} that threw the LingeringPotion + */ + public void setSource(@Nullable ProjectileSource source); +} diff --git a/api/src/main/java/org/bukkit/entity/ArmorStand.java b/api/src/main/java/org/bukkit/entity/ArmorStand.java new file mode 100644 index 000000000..e7f71e65e --- /dev/null +++ b/api/src/main/java/org/bukkit/entity/ArmorStand.java @@ -0,0 +1,385 @@ +package org.bukkit.entity; + +import org.bukkit.inventory.EquipmentSlot; +import org.bukkit.inventory.ItemStack; +import org.bukkit.util.EulerAngle; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +public interface ArmorStand extends LivingEntity { + + /** + * Returns the item the armor stand is + * currently holding + * + * @return the held item + // Paper start - Deprecate in favor of setItemInMainHand + * @deprecated use {@link ArmorStand#getItem(EquipmentSlot)} instead + * @see ArmorStand#getItem(EquipmentSlot) + // Paper end + */ + @NotNull + @Deprecated // Paper + ItemStack getItemInHand(); + + /** + * Sets the item the armor stand is currently + * holding + * + * @param item the item to hold + // Paper start - Deprecate in favor of setItemInMainHand + * @deprecated use {@link ArmorStand#setItem(EquipmentSlot, ItemStack)} instead + * @see ArmorStand#setItem(EquipmentSlot, ItemStack) + // Paper end + */ + @Deprecated // Paper + void setItemInHand(@Nullable ItemStack item); + + /** + * Returns the item currently being worn + * by the armor stand on its feet + * + * @return the worn item + */ + @NotNull + ItemStack getBoots(); + + /** + * Sets the item currently being worn + * by the armor stand on its feet + * + * @param item the item to wear + */ + void setBoots(@Nullable ItemStack item); + + /** + * Returns the item currently being worn + * by the armor stand on its legs + * + * @return the worn item + */ + @NotNull + ItemStack getLeggings(); + + /** + * Sets the item currently being worn + * by the armor stand on its legs + * + * @param item the item to wear + */ + void setLeggings(@Nullable ItemStack item); + + /** + * Returns the item currently being worn + * by the armor stand on its chest + * + * @return the worn item + */ + @NotNull + ItemStack getChestplate(); + + /** + * Sets the item currently being worn + * by the armor stand on its chest + * + * @param item the item to wear + */ + void setChestplate(@Nullable ItemStack item); + + /** + * Returns the item currently being worn + * by the armor stand on its head + * + * @return the worn item + */ + @NotNull + ItemStack getHelmet(); + + /** + * Sets the item currently being worn + * by the armor stand on its head + * + * @param item the item to wear + */ + void setHelmet(@Nullable ItemStack item); + + /** + * Returns the armor stand's body's + * current pose as a {@link org.bukkit.util.EulerAngle} + * + * @return the current pose + */ + @NotNull + EulerAngle getBodyPose(); + + /** + * Sets the armor stand's body's + * current pose as a {@link org.bukkit.util.EulerAngle} + * + * @param pose the current pose + */ + void setBodyPose(@NotNull EulerAngle pose); + + /** + * Returns the armor stand's left arm's + * current pose as a {@link org.bukkit.util.EulerAngle} + * + * @return the current pose + */ + @NotNull + EulerAngle getLeftArmPose(); + + /** + * Sets the armor stand's left arm's + * current pose as a {@link org.bukkit.util.EulerAngle} + * + * @param pose the current pose + */ + void setLeftArmPose(@NotNull EulerAngle pose); + + /** + * Returns the armor stand's right arm's + * current pose as a {@link org.bukkit.util.EulerAngle} + * + * @return the current pose + */ + @NotNull + EulerAngle getRightArmPose(); + + /** + * Sets the armor stand's right arm's + * current pose as a {@link org.bukkit.util.EulerAngle} + * + * @param pose the current pose + */ + void setRightArmPose(@NotNull EulerAngle pose); + + /** + * Returns the armor stand's left leg's + * current pose as a {@link org.bukkit.util.EulerAngle} + * + * @return the current pose + */ + @NotNull + EulerAngle getLeftLegPose(); + + /** + * Sets the armor stand's left leg's + * current pose as a {@link org.bukkit.util.EulerAngle} + * + * @param pose the current pose + */ + void setLeftLegPose(@NotNull EulerAngle pose); + + /** + * Returns the armor stand's right leg's + * current pose as a {@link org.bukkit.util.EulerAngle} + * + * @return the current pose + */ + @NotNull + EulerAngle getRightLegPose(); + + /** + * Sets the armor stand's right leg's + * current pose as a {@link org.bukkit.util.EulerAngle} + * + * @param pose the current pose + */ + void setRightLegPose(@NotNull EulerAngle pose); + + /** + * Returns the armor stand's head's + * current pose as a {@link org.bukkit.util.EulerAngle} + * + * @return the current pose + */ + @NotNull + EulerAngle getHeadPose(); + + /** + * Sets the armor stand's head's + * current pose as a {@link org.bukkit.util.EulerAngle} + * + * @param pose the current pose + */ + void setHeadPose(@NotNull EulerAngle pose); + + /** + * Returns whether the armor stand has + * a base plate + * + * @return whether it has a base plate + */ + boolean hasBasePlate(); + + /** + * Sets whether the armor stand has a + * base plate + * + * @param basePlate whether is has a base plate + */ + void setBasePlate(boolean basePlate); + + /** + * Returns whether the armor stand should be + * visible or not + * + * @return whether the stand is visible or not + */ + boolean isVisible(); + + /** + * Sets whether the armor stand should be + * visible or not + * + * @param visible whether the stand is visible or not + */ + void setVisible(boolean visible); + + /** + * Returns whether this armor stand has arms + * + * @return whether this has arms or not + */ + boolean hasArms(); + + /** + * Sets whether this armor stand has arms + * + * @param arms whether this has arms or not + */ + void setArms(boolean arms); + + /** + * Returns whether this armor stand is scaled + * down + * + * @return whether this is scaled down + */ + boolean isSmall(); + + /** + * Sets whether this armor stand is scaled + * down + * + * @param small whether this is scaled down + */ + void setSmall(boolean small); + + /** + * Returns whether this armor stand is a marker, + * meaning it has a very small collision box + * + * @return whether this is a marker + */ + boolean isMarker(); + + /** + * Sets whether this armor stand is a marker, + * meaning it has a very small collision box + * + * @param marker whether this is a marker + */ + void setMarker(boolean marker); + + // Paper start + /** + * Tests if this armor stand can move. + * + *

The default value is {@code true}.

+ * + * @return {@code true} if this armour stand can move, {@code false} otherwise + */ + boolean canMove(); + + /** + * Sets if this armor stand can move. + * + * @param move {@code true} if this armour stand can move, {@code false} otherwise + */ + void setCanMove(boolean move); + + /** + * Tests if this armor stand can tick. + * + *

The default value is defined in {@code paper.yml}.

+ * + * @return {@code true} if this armour stand can tick, {@code false} otherwise + */ + boolean canTick(); + + /** + * Sets if this armor stand can tick. + * + * @param tick {@code true} if this armour stand can tick, {@code false} otherwise + */ + void setCanTick(final boolean tick); + + /** + * Returns the item the armor stand has + * equip in the given equipment slot + * + * @param slot the equipment slot to get + * @return the ItemStack in the equipment slot + */ + @NotNull + ItemStack getItem(@NotNull final org.bukkit.inventory.EquipmentSlot slot); + + /** + * Sets the item the armor stand has + * equip in the given equipment slot + * + * @param slot the equipment slot to set + * @param item the item to hold + */ + void setItem(@NotNull final org.bukkit.inventory.EquipmentSlot slot, @Nullable final ItemStack item); + + /** + * Get the list of disabled slots + * + * @return list of disabled slots + */ + @NotNull + java.util.Set getDisabledSlots(); + + /** + * Set the disabled slots + * + * This makes it so a player is unable to interact with the Armor Stand to place, remove, or replace an item in the given slot(s) + * Note: Once a slot is disabled, the only way to get an item back it to break the armor stand. + * + * @param slots var-arg array of slots to lock + */ + void setDisabledSlots(@NotNull org.bukkit.inventory.EquipmentSlot... slots); + + /** + * Disable specific slots, adding them + * to the currently disabled slots + * + * This makes it so a player is unable to interact with the Armor Stand to place, remove, or replace an item in the given slot(s) + * Note: Once a slot is disabled, the only way to get an item back it to break the armor stand. + * + * @param slots var-arg array of slots to lock + */ + void addDisabledSlots(@NotNull final org.bukkit.inventory.EquipmentSlot... slots); + + /** + * Remove the given slots from the disabled + * slots list, enabling them. + * + * This makes it so a player is able to interact with the Armor Stand to place, remove, or replace an item in the given slot(s) + * + * @param slots var-arg array of slots to unlock + */ + void removeDisabledSlots(@NotNull final org.bukkit.inventory.EquipmentSlot... slots); + + /** + * Check if a specific slot is disabled + * + * @param slot The slot to check + * @return {@code true} if the slot is disabled, else {@code false}. + */ + boolean isSlotDisabled(@NotNull org.bukkit.inventory.EquipmentSlot slot); + // Paper end +} diff --git a/api/src/main/java/org/bukkit/entity/Arrow.java b/api/src/main/java/org/bukkit/entity/Arrow.java new file mode 100644 index 000000000..b63f2fd9d --- /dev/null +++ b/api/src/main/java/org/bukkit/entity/Arrow.java @@ -0,0 +1,186 @@ +package org.bukkit.entity; + +import org.bukkit.block.Block; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +/** + * Represents an arrow. + */ +public interface Arrow extends Projectile { + + /** + * Gets the knockback strength for an arrow, which is the + * {@link org.bukkit.enchantments.Enchantment#KNOCKBACK KnockBack} level + * of the bow that shot it. + * + * @return the knockback strength value + */ + public int getKnockbackStrength(); + + /** + * Sets the knockback strength for an arrow. + * + * @param knockbackStrength the knockback strength value + */ + public void setKnockbackStrength(int knockbackStrength); + + /** + * Gets the base amount of damage this arrow will do. + * + * Defaults to 2.0 for a normal arrow with + * 0.5 * (1 + power level) added for arrows fired from + * enchanted bows. + * + * @return base damage amount + */ + public double getDamage(); + + /** + * Sets the base amount of damage this arrow will do. + * + * @param damage new damage amount + */ + public void setDamage(double damage); + + /** + * Gets whether this arrow is critical. + *

+ * Critical arrows have increased damage and cause particle effects. + *

+ * Critical arrows generally occur when a player fully draws a bow before + * firing. + * + * @return true if it is critical + */ + public boolean isCritical(); + + /** + * Sets whether or not this arrow should be critical. + * + * @param critical whether or not it should be critical + */ + public void setCritical(boolean critical); + + /** + * Gets whether this arrow is in a block or not. + *

+ * Arrows in a block are motionless and may be picked up by players. + * + * @return true if in a block + */ + public boolean isInBlock(); + + /** + * Gets the block to which this arrow is attached. + * + * @return the attached block or null if not attached + */ + @Nullable + public Block getAttachedBlock(); + + /** + * Gets the current pickup status of this arrow. + * + * @return the pickup status of this arrow. + */ + @NotNull + public PickupStatus getPickupStatus(); + + /** + * Sets the current pickup status of this arrow. + * + * @param status new pickup status of this arrow. + */ + public void setPickupStatus(@NotNull PickupStatus status); + + /** + * Represents the pickup status of this arrow. + */ + public enum PickupStatus { + /** + * The arrow cannot be picked up. + */ + DISALLOWED, + /** + * The arrow can be picked up. + */ + ALLOWED, + /** + * The arrow can only be picked up by players in creative mode. + */ + CREATIVE_ONLY + } + + // Spigot start + public class Spigot extends Entity.Spigot + { + + /** + * Gets the base amount of damage this arrow will do. + * + * Defaults to 2.0 for a normal arrow with + * 0.5 * (1 + power level) added for arrows fired from + * enchanted bows. + * + * @return base damage amount + * @deprecated {@link Arrow#getDamage()} + */ + @Deprecated + public double getDamage() + { + throw new UnsupportedOperationException( "Not supported yet." ); + } + + /** + * Sets the base amount of damage this arrow will do. + * + * @param damage new damage amount + * @deprecated {@link Arrow#setDamage(double)} + */ + @Deprecated + public void setDamage(double damage) + { + throw new UnsupportedOperationException( "Not supported yet." ); + } + } + + @NotNull + @Override + Spigot spigot(); + // Spigot end + + // Paper start + /** + * Gets the {@link PickupRule} for this arrow. + * + *

This is generally {@link PickupRule#ALLOWED} only if the arrow was + * not fired from a bow with the infinity enchantment.

+ * + * @return The pickup rule + * @deprecated Use {@link Arrow#getPickupStatus()} as an upstream compatible replacement for this function + */ + @Deprecated + default PickupRule getPickupRule() { + return PickupRule.valueOf(this.getPickupStatus().name()); + } + + /** + * Set the rule for which players can pickup this arrow as an item. + * + * @param rule The pickup rule + * @deprecated Use {@link Arrow#setPickupStatus(PickupStatus)} with {@link PickupStatus} as an upstream compatible replacement for this function + */ + @Deprecated + default void setPickupRule(PickupRule rule) { + this.setPickupStatus(PickupStatus.valueOf(rule.name())); + } + + @Deprecated + enum PickupRule { + DISALLOWED, + ALLOWED, + CREATIVE_ONLY; + } + // Paper end +} diff --git a/api/src/main/java/org/bukkit/entity/Bat.java b/api/src/main/java/org/bukkit/entity/Bat.java new file mode 100644 index 000000000..bd73f22ef --- /dev/null +++ b/api/src/main/java/org/bukkit/entity/Bat.java @@ -0,0 +1,27 @@ +package org.bukkit.entity; + +/** + * Represents a Bat + */ +public interface Bat extends Ambient { + + /** + * Checks the current waking state of this bat. + *

+ * This does not imply any persistence of state past the method call. + * + * @return true if the bat is awake or false if it is currently hanging + * from a block + */ + boolean isAwake(); + + /** + * This method modifies the current waking state of this bat. + *

+ * This does not prevent a bat from spontaneously awaking itself, or from + * reattaching itself to a block. + * + * @param state the new state + */ + void setAwake(boolean state); +} diff --git a/api/src/main/java/org/bukkit/entity/Blaze.java b/api/src/main/java/org/bukkit/entity/Blaze.java new file mode 100644 index 000000000..7a5505b7f --- /dev/null +++ b/api/src/main/java/org/bukkit/entity/Blaze.java @@ -0,0 +1,8 @@ +package org.bukkit.entity; + +/** + * Represents a Blaze monster + */ +public interface Blaze extends Monster { + +} diff --git a/api/src/main/java/org/bukkit/entity/Boat.java b/api/src/main/java/org/bukkit/entity/Boat.java new file mode 100644 index 000000000..00001621b --- /dev/null +++ b/api/src/main/java/org/bukkit/entity/Boat.java @@ -0,0 +1,106 @@ +package org.bukkit.entity; + +import org.bukkit.TreeSpecies; +import org.jetbrains.annotations.NotNull; + +/** + * Represents a boat entity. + */ +public interface Boat extends Vehicle { + + /** + * Gets the wood type of the boat. + * + * @return the wood type + */ + @NotNull + TreeSpecies getWoodType(); + + /** + * Sets the wood type of the boat. + * + * @param species the new wood type + */ + void setWoodType(@NotNull TreeSpecies species); + + /** + * Gets the maximum speed of a boat. The speed is unrelated to the + * velocity. + * + * @return The max speed. + * @deprecated boats are complex and many of these methods do not work correctly across multiple versions. + */ + @Deprecated + public double getMaxSpeed(); + + /** + * Sets the maximum speed of a boat. Must be nonnegative. Default is 0.4D. + * + * @param speed The max speed. + * @deprecated boats are complex and many of these methods do not work correctly across multiple versions. + */ + @Deprecated + public void setMaxSpeed(double speed); + + /** + * Gets the deceleration rate (newSpeed = curSpeed * rate) of occupied + * boats. The default is 0.2. + * + * @return The rate of deceleration + * @deprecated boats are complex and many of these methods do not work correctly across multiple versions. + */ + @Deprecated + public double getOccupiedDeceleration(); + + /** + * Sets the deceleration rate (newSpeed = curSpeed * rate) of occupied + * boats. Setting this to a higher value allows for quicker acceleration. + * The default is 0.2. + * + * @param rate deceleration rate + * @deprecated boats are complex and many of these methods do not work correctly across multiple versions. + */ + @Deprecated + public void setOccupiedDeceleration(double rate); + + /** + * Gets the deceleration rate (newSpeed = curSpeed * rate) of unoccupied + * boats. The default is -1. Values below 0 indicate that no additional + * deceleration is imposed. + * + * @return The rate of deceleration + * @deprecated boats are complex and many of these methods do not work correctly across multiple versions. + */ + @Deprecated + public double getUnoccupiedDeceleration(); + + /** + * Sets the deceleration rate (newSpeed = curSpeed * rate) of unoccupied + * boats. Setting this to a higher value allows for quicker deceleration + * of boats when a player disembarks. The default is -1. Values below 0 + * indicate that no additional deceleration is imposed. + * + * @param rate deceleration rate + * @deprecated boats are complex and many of these methods do not work correctly across multiple versions. + */ + @Deprecated + public void setUnoccupiedDeceleration(double rate); + + /** + * Get whether boats can work on land. + * + * @return whether boats can work on land + * @deprecated boats are complex and many of these methods do not work correctly across multiple versions. + */ + @Deprecated + public boolean getWorkOnLand(); + + /** + * Set whether boats can work on land. + * + * @param workOnLand whether boats can work on land + * @deprecated boats are complex and many of these methods do not work correctly across multiple versions. + */ + @Deprecated + public void setWorkOnLand(boolean workOnLand); +} diff --git a/api/src/main/java/org/bukkit/entity/Boss.java b/api/src/main/java/org/bukkit/entity/Boss.java new file mode 100644 index 000000000..ba459279c --- /dev/null +++ b/api/src/main/java/org/bukkit/entity/Boss.java @@ -0,0 +1,18 @@ +package org.bukkit.entity; + +import org.bukkit.boss.BossBar; +import org.jetbrains.annotations.Nullable; + +/** + * Represents the Boss Entity. + */ +public interface Boss extends Entity { + + /** + * Returns the {@link BossBar} of the {@link Boss} + * + * @return the {@link BossBar} of the entity + */ + @Nullable + BossBar getBossBar(); +} diff --git a/api/src/main/java/org/bukkit/entity/CaveSpider.java b/api/src/main/java/org/bukkit/entity/CaveSpider.java new file mode 100644 index 000000000..9c3764699 --- /dev/null +++ b/api/src/main/java/org/bukkit/entity/CaveSpider.java @@ -0,0 +1,6 @@ +package org.bukkit.entity; + +/** + * Represents a Spider. + */ +public interface CaveSpider extends Spider {} diff --git a/api/src/main/java/org/bukkit/entity/ChestedHorse.java b/api/src/main/java/org/bukkit/entity/ChestedHorse.java new file mode 100644 index 000000000..24780af64 --- /dev/null +++ b/api/src/main/java/org/bukkit/entity/ChestedHorse.java @@ -0,0 +1,22 @@ +package org.bukkit.entity; + +/** + * Represents Horse-like creatures which can carry an inventory. + */ +public interface ChestedHorse extends AbstractHorse { + + /** + * Gets whether the horse has a chest equipped. + * + * @return true if the horse has chest storage + */ + public boolean isCarryingChest(); + + /** + * Sets whether the horse has a chest equipped. Removing a chest will also + * clear the chest's inventory. + * + * @param chest true if the horse should have a chest + */ + public void setCarryingChest(boolean chest); +} diff --git a/api/src/main/java/org/bukkit/entity/Chicken.java b/api/src/main/java/org/bukkit/entity/Chicken.java new file mode 100644 index 000000000..cb3ec6ef6 --- /dev/null +++ b/api/src/main/java/org/bukkit/entity/Chicken.java @@ -0,0 +1,6 @@ +package org.bukkit.entity; + +/** + * Represents a Chicken. + */ +public interface Chicken extends Animals {} diff --git a/api/src/main/java/org/bukkit/entity/Cod.java b/api/src/main/java/org/bukkit/entity/Cod.java new file mode 100644 index 000000000..191ce6c0e --- /dev/null +++ b/api/src/main/java/org/bukkit/entity/Cod.java @@ -0,0 +1,7 @@ + +package org.bukkit.entity; + +/** + * Represents a cod fish. + */ +public interface Cod extends Fish { } diff --git a/api/src/main/java/org/bukkit/entity/ComplexEntityPart.java b/api/src/main/java/org/bukkit/entity/ComplexEntityPart.java new file mode 100644 index 000000000..937f53499 --- /dev/null +++ b/api/src/main/java/org/bukkit/entity/ComplexEntityPart.java @@ -0,0 +1,17 @@ +package org.bukkit.entity; + +import org.jetbrains.annotations.NotNull; + +/** + * Represents a single part of a {@link ComplexLivingEntity} + */ +public interface ComplexEntityPart extends Entity { + + /** + * Gets the parent {@link ComplexLivingEntity} of this part. + * + * @return Parent complex entity + */ + @NotNull + public ComplexLivingEntity getParent(); +} diff --git a/api/src/main/java/org/bukkit/entity/ComplexLivingEntity.java b/api/src/main/java/org/bukkit/entity/ComplexLivingEntity.java new file mode 100644 index 000000000..038bcb6dc --- /dev/null +++ b/api/src/main/java/org/bukkit/entity/ComplexLivingEntity.java @@ -0,0 +1,19 @@ +package org.bukkit.entity; + +import org.jetbrains.annotations.NotNull; + +import java.util.Set; + +/** + * Represents a complex living entity - one that is made up of various smaller + * parts + */ +public interface ComplexLivingEntity extends LivingEntity { + /** + * Gets a list of parts that belong to this complex entity + * + * @return List of parts + */ + @NotNull + public Set getParts(); +} diff --git a/api/src/main/java/org/bukkit/entity/Cow.java b/api/src/main/java/org/bukkit/entity/Cow.java new file mode 100644 index 000000000..cd4ed4de0 --- /dev/null +++ b/api/src/main/java/org/bukkit/entity/Cow.java @@ -0,0 +1,6 @@ +package org.bukkit.entity; + +/** + * Represents a Cow. + */ +public interface Cow extends Animals {} diff --git a/api/src/main/java/org/bukkit/entity/Creature.java b/api/src/main/java/org/bukkit/entity/Creature.java new file mode 100644 index 000000000..6c9c5e85e --- /dev/null +++ b/api/src/main/java/org/bukkit/entity/Creature.java @@ -0,0 +1,7 @@ +package org.bukkit.entity; + +/** + * Represents a Creature. Creatures are non-intelligent monsters or animals + * which have very simple abilities. + */ +public interface Creature extends Mob {} diff --git a/api/src/main/java/org/bukkit/entity/Creeper.java b/api/src/main/java/org/bukkit/entity/Creeper.java new file mode 100644 index 000000000..b9877fb88 --- /dev/null +++ b/api/src/main/java/org/bukkit/entity/Creeper.java @@ -0,0 +1,81 @@ +package org.bukkit.entity; + +/** + * Represents a Creeper + */ +public interface Creeper extends Monster { + + /** + * Checks if this Creeper is powered (Electrocuted) + * + * @return true if this creeper is powered + */ + public boolean isPowered(); + + /** + * Sets the Powered status of this Creeper + * + * @param value New Powered status + */ + public void setPowered(boolean value); + + /** + * Set the maximum fuse ticks for this Creeper, where the maximum ticks + * is the amount of time in which a creeper is allowed to be in the + * primed state before exploding. + * + * @param ticks the new maximum fuse ticks + */ + public void setMaxFuseTicks(int ticks); + + /** + * Get the maximum fuse ticks for this Creeper, where the maximum ticks + * is the amount of time in which a creeper is allowed to be in the + * primed state before exploding. + * + * @return the maximum fuse ticks + */ + public int getMaxFuseTicks(); + + /** + * Set the explosion radius in which this Creeper's explosion will affect. + * + * @param radius the new explosion radius + */ + public void setExplosionRadius(int radius); + + /** + * Get the explosion radius in which this Creeper's explosion will affect. + * + * @return the explosion radius + */ + public int getExplosionRadius(); + + // Paper start + /** + * Set whether creeper is ignited or not (armed to explode) + * + * @param ignited New ignited state + */ + public void setIgnited(boolean ignited); + + /** + * Check if creeper is ignited or not (armed to explode) + * + * @return Ignited state + */ + public boolean isIgnited(); + + /** + * Get the number of ticks this creeper has been ignited (armed to explode) + * + * @return Ticks creeper has been ignited + */ + public int getFuseTicks(); + + /** + * Make the creeper explode (no waiting for fuse) + */ + public void explode(); + // Paper end +} diff --git a/api/src/main/java/org/bukkit/entity/Damageable.java b/api/src/main/java/org/bukkit/entity/Damageable.java new file mode 100644 index 000000000..b0fe04423 --- /dev/null +++ b/api/src/main/java/org/bukkit/entity/Damageable.java @@ -0,0 +1,73 @@ +package org.bukkit.entity; + +import org.bukkit.attribute.Attribute; +import org.jetbrains.annotations.Nullable; + +/** + * Represents an {@link Entity} that has health and can take damage. + */ +public interface Damageable extends Entity { + /** + * Deals the given amount of damage to this entity. + * + * @param amount Amount of damage to deal + */ + void damage(double amount); + + /** + * Deals the given amount of damage to this entity, from a specified + * entity. + * + * @param amount Amount of damage to deal + * @param source Entity which to attribute this damage from + */ + void damage(double amount, @Nullable Entity source); + + /** + * Gets the entity's health from 0 to {@link #getMaxHealth()}, where 0 is dead. + * + * @return Health represented from 0 to max + */ + double getHealth(); + + /** + * Sets the entity's health from 0 to {@link #getMaxHealth()}, where 0 is + * dead. + * + * @param health New health represented from 0 to max + * @throws IllegalArgumentException Thrown if the health is {@literal < 0 or >} + * {@link #getMaxHealth()} + */ + void setHealth(double health); + + /** + * Gets the maximum health this entity has. + * + * @return Maximum health + * @deprecated use {@link Attribute#GENERIC_MAX_HEALTH}. + */ + @Deprecated + double getMaxHealth(); + + /** + * Sets the maximum health this entity can have. + *

+ * If the health of the entity is above the value provided it will be set + * to that value. + *

+ * Note: An entity with a health bar ({@link Player}, {@link EnderDragon}, + * {@link Wither}, etc...} will have their bar scaled accordingly. + * + * @param health amount of health to set the maximum to + * @deprecated use {@link Attribute#GENERIC_MAX_HEALTH}. + */ + @Deprecated + void setMaxHealth(double health); + + /** + * Resets the max health to the original amount. + * @deprecated use {@link Attribute#GENERIC_MAX_HEALTH}. + */ + @Deprecated + void resetMaxHealth(); +} diff --git a/api/src/main/java/org/bukkit/entity/Dolphin.java b/api/src/main/java/org/bukkit/entity/Dolphin.java new file mode 100644 index 000000000..f00eaadcd --- /dev/null +++ b/api/src/main/java/org/bukkit/entity/Dolphin.java @@ -0,0 +1,3 @@ +package org.bukkit.entity; + +public interface Dolphin extends WaterMob { } diff --git a/api/src/main/java/org/bukkit/entity/Donkey.java b/api/src/main/java/org/bukkit/entity/Donkey.java new file mode 100644 index 000000000..b554e3309 --- /dev/null +++ b/api/src/main/java/org/bukkit/entity/Donkey.java @@ -0,0 +1,6 @@ +package org.bukkit.entity; + +/** + * Represents a Donkey - variant of {@link ChestedHorse}. + */ +public interface Donkey extends ChestedHorse { } diff --git a/api/src/main/java/org/bukkit/entity/DragonFireball.java b/api/src/main/java/org/bukkit/entity/DragonFireball.java new file mode 100644 index 000000000..6c475a372 --- /dev/null +++ b/api/src/main/java/org/bukkit/entity/DragonFireball.java @@ -0,0 +1,3 @@ +package org.bukkit.entity; + +public interface DragonFireball extends Fireball {} diff --git a/api/src/main/java/org/bukkit/entity/Drowned.java b/api/src/main/java/org/bukkit/entity/Drowned.java new file mode 100644 index 000000000..1dee177ae --- /dev/null +++ b/api/src/main/java/org/bukkit/entity/Drowned.java @@ -0,0 +1,6 @@ +package org.bukkit.entity; + +/** + * Drowned zombie. + */ +public interface Drowned extends Zombie { } diff --git a/api/src/main/java/org/bukkit/entity/Egg.java b/api/src/main/java/org/bukkit/entity/Egg.java new file mode 100644 index 000000000..2dcc00b94 --- /dev/null +++ b/api/src/main/java/org/bukkit/entity/Egg.java @@ -0,0 +1,6 @@ +package org.bukkit.entity; + +/** + * Represents a thrown egg. + */ +public interface Egg extends Projectile {} diff --git a/api/src/main/java/org/bukkit/entity/ElderGuardian.java b/api/src/main/java/org/bukkit/entity/ElderGuardian.java new file mode 100644 index 000000000..5ca1d4c9f --- /dev/null +++ b/api/src/main/java/org/bukkit/entity/ElderGuardian.java @@ -0,0 +1,6 @@ +package org.bukkit.entity; + +/** + * Represents an ElderGuardian - variant of {@link Guardian}. + */ +public interface ElderGuardian extends Guardian { } diff --git a/api/src/main/java/org/bukkit/entity/EnderCrystal.java b/api/src/main/java/org/bukkit/entity/EnderCrystal.java new file mode 100644 index 000000000..febc7b3f4 --- /dev/null +++ b/api/src/main/java/org/bukkit/entity/EnderCrystal.java @@ -0,0 +1,43 @@ +package org.bukkit.entity; + +import org.bukkit.Location; +import org.jetbrains.annotations.Nullable; + +/** + * A crystal that heals nearby EnderDragons + */ +public interface EnderCrystal extends Entity { + + /** + * Return whether or not this end crystal is showing the + * bedrock slate underneath it. + * + * @return true if the bottom is being shown + */ + boolean isShowingBottom(); + + /** + * Sets whether or not this end crystal is showing the + * bedrock slate underneath it. + * + * @param showing whether the bedrock slate should be shown + */ + void setShowingBottom(boolean showing); + + /** + * Gets the location that this end crystal is pointing its beam to. + * + * @return the location that the beam is pointed to, or null if the beam is not shown + */ + @Nullable + Location getBeamTarget(); + + /** + * Sets the location that this end crystal is pointing to. Passing a null + * value will remove the current beam. + * + * @param location the location to point the beam to + * @throws IllegalArgumentException for differing worlds + */ + void setBeamTarget(@Nullable Location location); +} diff --git a/api/src/main/java/org/bukkit/entity/EnderDragon.java b/api/src/main/java/org/bukkit/entity/EnderDragon.java new file mode 100644 index 000000000..e800259a2 --- /dev/null +++ b/api/src/main/java/org/bukkit/entity/EnderDragon.java @@ -0,0 +1,81 @@ +package org.bukkit.entity; + +import org.jetbrains.annotations.NotNull; + +/** + * Represents an Ender Dragon + */ +public interface EnderDragon extends ComplexLivingEntity, Boss, org.bukkit.entity.Mob { // Paper - add Mob + + /** + * Represents a phase or action that an Ender Dragon can perform. + */ + enum Phase { + /** + * The dragon will circle outside the ring of pillars if ender + * crystals remain or inside the ring if not. + */ + CIRCLING, + /** + * The dragon will fly towards a targeted player and shoot a + * fireball when within 64 blocks. + */ + STRAFING, + /** + * The dragon will fly towards the empty portal (approaching + * from the other side, if applicable). + */ + FLY_TO_PORTAL, + /** + * The dragon will land on on the portal. If the dragon is not near + * the portal, it will fly to it before mounting. + */ + LAND_ON_PORTAL, + /** + * The dragon will leave the portal. + */ + LEAVE_PORTAL, + /** + * The dragon will attack with dragon breath at its current location. + */ + BREATH_ATTACK, + /** + * The dragon will search for a player to attack with dragon breath. + * If no player is close enough to the dragon for 5 seconds, the + * dragon will charge at a player within 150 blocks or will take off + * and begin circling if no player is found. + */ + SEARCH_FOR_BREATH_ATTACK_TARGET, + /** + * The dragon will roar before performing a breath attack. + */ + ROAR_BEFORE_ATTACK, + /** + * The dragon will charge a player. + */ + CHARGE_PLAYER, + /** + * The dragon will fly to the vicinity of the portal and die. + */ + DYING, + /** + * The dragon will hover at its current location, not performing any actions. + */ + HOVER + } + + /** + * Gets the current phase that the dragon is performing. + * + * @return the current phase + */ + @NotNull + Phase getPhase(); + + /** + * Sets the next phase for the dragon to perform. + * + * @param phase the next phase + */ + void setPhase(@NotNull Phase phase); +} diff --git a/api/src/main/java/org/bukkit/entity/EnderDragonPart.java b/api/src/main/java/org/bukkit/entity/EnderDragonPart.java new file mode 100644 index 000000000..77fc93378 --- /dev/null +++ b/api/src/main/java/org/bukkit/entity/EnderDragonPart.java @@ -0,0 +1,11 @@ +package org.bukkit.entity; + +import org.jetbrains.annotations.NotNull; + +/** + * Represents an ender dragon part + */ +public interface EnderDragonPart extends ComplexEntityPart, Damageable { + @NotNull + public EnderDragon getParent(); +} diff --git a/api/src/main/java/org/bukkit/entity/EnderPearl.java b/api/src/main/java/org/bukkit/entity/EnderPearl.java new file mode 100644 index 000000000..db18a90ba --- /dev/null +++ b/api/src/main/java/org/bukkit/entity/EnderPearl.java @@ -0,0 +1,8 @@ +package org.bukkit.entity; + +/** + * Represents a thrown Ender Pearl entity + */ +public interface EnderPearl extends Projectile { + +} diff --git a/api/src/main/java/org/bukkit/entity/EnderSignal.java b/api/src/main/java/org/bukkit/entity/EnderSignal.java new file mode 100644 index 000000000..e90bca822 --- /dev/null +++ b/api/src/main/java/org/bukkit/entity/EnderSignal.java @@ -0,0 +1,64 @@ +package org.bukkit.entity; + +import org.bukkit.Location; +import org.jetbrains.annotations.NotNull; + +/** + * Represents an EnderSignal, which is created upon throwing an ender eye. + */ +public interface EnderSignal extends Entity { + + /** + * Get the location this EnderSignal is moving towards. + * + * @return the {@link Location} this EnderSignal is moving towards. + */ + @NotNull + public Location getTargetLocation(); + + /** + * Set the {@link Location} this EnderSignal is moving towards. + *
+ * When setting a new target location, the {@link #getDropItem()} resets to + * a random value and the despawn timer gets set back to 0. + * + * @param location the new target location + */ + public void setTargetLocation(@NotNull Location location); + + /** + * Gets if the EnderSignal should drop an item on death.
+ * If {@code true}, it will drop an item. If {@code false}, it will shatter. + * + * @return true if the EnderSignal will drop an item on death, or false if + * it will shatter + */ + public boolean getDropItem(); + + /** + * Sets if the EnderSignal should drop an item on death; or if it should + * shatter. + * + * @param drop true if the EnderSignal should drop an item on death, or + * false if it should shatter. + */ + public void setDropItem(boolean drop); + + /** + * Gets the amount of time this entity has been alive (in ticks). + *
+ * When this number is greater than 80, it will despawn on the next tick. + * + * @return the number of ticks this EnderSignal has been alive. + */ + public int getDespawnTimer(); + + /** + * Set how long this entity has been alive (in ticks). + *
+ * When this number is greater than 80, it will despawn on the next tick. + * + * @param timer how long (in ticks) this EnderSignal has been alive. + */ + public void setDespawnTimer(int timer); +} diff --git a/api/src/main/java/org/bukkit/entity/Enderman.java b/api/src/main/java/org/bukkit/entity/Enderman.java new file mode 100644 index 000000000..821c690f8 --- /dev/null +++ b/api/src/main/java/org/bukkit/entity/Enderman.java @@ -0,0 +1,53 @@ +package org.bukkit.entity; + +import org.bukkit.block.data.BlockData; +import org.bukkit.material.MaterialData; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +/** + * Represents an Enderman. + */ +public interface Enderman extends Monster { + + // Paper start + /** + * Try to teleport the enderman to a random nearby location. + * + * May conditionally fail if the random location was not valid + * @return If the enderman teleported successfully or not + */ + + public boolean teleportRandomly(); + // Paper end + + /** + * Gets the id and data of the block that the Enderman is carrying. + * + * @return MaterialData containing the id and data of the block + */ + @NotNull + public MaterialData getCarriedMaterial(); + + /** + * Sets the id and data of the block that the Enderman is carrying. + * + * @param material data to set the carried block to + */ + public void setCarriedMaterial(@NotNull MaterialData material); + + /** + * Gets the data of the block that the Enderman is carrying. + * + * @return BlockData containing the carried block, or null if none + */ + @Nullable + public BlockData getCarriedBlock(); + + /** + * Sets the data of the block that the Enderman is carrying. + * + * @param blockData data to set the carried block to, or null to remove + */ + public void setCarriedBlock(@Nullable BlockData blockData); +} diff --git a/api/src/main/java/org/bukkit/entity/Endermite.java b/api/src/main/java/org/bukkit/entity/Endermite.java new file mode 100644 index 000000000..d9be83961 --- /dev/null +++ b/api/src/main/java/org/bukkit/entity/Endermite.java @@ -0,0 +1,22 @@ +package org.bukkit.entity; + +public interface Endermite extends Monster { + + /** + * Gets whether this Endermite was spawned by a player. + * + * An Endermite spawned by a player will be attacked by nearby Enderman. + * + * @return player spawned status + */ + boolean isPlayerSpawned(); + + /** + * Sets whether this Endermite was spawned by a player. + * + * An Endermite spawned by a player will be attacked by nearby Enderman. + * + * @param playerSpawned player spawned status + */ + void setPlayerSpawned(boolean playerSpawned); +} diff --git a/api/src/main/java/org/bukkit/entity/Entity.java b/api/src/main/java/org/bukkit/entity/Entity.java new file mode 100644 index 000000000..148c7b1fd --- /dev/null +++ b/api/src/main/java/org/bukkit/entity/Entity.java @@ -0,0 +1,655 @@ +package org.bukkit.entity; + +import org.bukkit.Chunk; +import org.bukkit.Location; +import org.bukkit.EntityEffect; +import org.bukkit.Location; +import org.bukkit.Nameable; +import org.bukkit.Server; +import org.bukkit.World; +import org.bukkit.block.BlockFace; +import org.bukkit.event.entity.EntityDamageEvent; +import org.bukkit.material.Directional; +import org.bukkit.metadata.Metadatable; +import org.bukkit.util.BoundingBox; +import org.bukkit.util.Vector; + +import java.util.List; +import java.util.Set; +import java.util.UUID; +import org.bukkit.block.PistonMoveReaction; +import org.bukkit.command.CommandSender; +import org.bukkit.event.player.PlayerTeleportEvent.TeleportCause; +import org.jetbrains.annotations.Contract; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +/** + * Represents a base entity in the world + */ +public interface Entity extends Metadatable, CommandSender, Nameable { + + /** + * Gets the entity's current position + * + * @return a new copy of Location containing the position of this entity + */ + @NotNull + public Location getLocation(); + + /** + * Stores the entity's current position in the provided Location object. + *

+ * If the provided Location is null this method does nothing and returns + * null. + * + * @param loc the location to copy into + * @return The Location object provided or null + */ + @Contract("null -> null; !null -> !null") + @Nullable + public Location getLocation(@Nullable Location loc); + + /** + * Sets this entity's velocity + * + * @param velocity New velocity to travel with + */ + public void setVelocity(@NotNull Vector velocity); + + /** + * Gets this entity's current velocity + * + * @return Current traveling velocity of this entity + */ + @NotNull + public Vector getVelocity(); + + /** + * Gets the entity's height + * + * @return height of entity + */ + public double getHeight(); + + /** + * Gets the entity's width + * + * @return width of entity + */ + public double getWidth(); + + /** + * Gets the entity's current bounding box. + *

+ * The returned bounding box reflects the entity's current location and + * size. + * + * @return the entity's current bounding box + */ + @NotNull + public BoundingBox getBoundingBox(); + + /** + * Returns true if the entity is supported by a block. This value is a + * state updated by the server and is not recalculated unless the entity + * moves. + * + * @return True if entity is on ground. + */ + public boolean isOnGround(); + + /** + * Gets the current world this entity resides in + * + * @return World + */ + @NotNull + public World getWorld(); + + /** + * Sets the entity's rotation. + *

+ * Note that if the entity is affected by AI, it may override this rotation. + * + * @param yaw the yaw + * @param pitch the pitch + * @throws UnsupportedOperationException if used for players + * @deprecated draft API + */ + @Deprecated + public void setRotation(float yaw, float pitch); + + /** + * Teleports this entity to the given location. If this entity is riding a + * vehicle, it will be dismounted prior to teleportation. + * + * @param location New location to teleport this entity to + * @return true if the teleport was successful + */ + public boolean teleport(@NotNull Location location); + + /** + * Teleports this entity to the given location. If this entity is riding a + * vehicle, it will be dismounted prior to teleportation. + * + * @param location New location to teleport this entity to + * @param cause The cause of this teleportation + * @return true if the teleport was successful + */ + public boolean teleport(@NotNull Location location, @NotNull TeleportCause cause); + + /** + * Teleports this entity to the target Entity. If this entity is riding a + * vehicle, it will be dismounted prior to teleportation. + * + * @param destination Entity to teleport this entity to + * @return true if the teleport was successful + */ + public boolean teleport(@NotNull Entity destination); + + /** + * Teleports this entity to the target Entity. If this entity is riding a + * vehicle, it will be dismounted prior to teleportation. + * + * @param destination Entity to teleport this entity to + * @param cause The cause of this teleportation + * @return true if the teleport was successful + */ + public boolean teleport(@NotNull Entity destination, @NotNull TeleportCause cause); + + // Paper start + /** + * Loads/Generates(in 1.13+) the Chunk asynchronously, and then teleports the entity when the chunk is ready. + * @param loc Location to teleport to + * @return A future that will be completed with the result of the teleport + */ + @NotNull + public default java.util.concurrent.CompletableFuture teleportAsync(@NotNull Location loc) { + return teleportAsync(loc, TeleportCause.PLUGIN); + } + /** + * Loads/Generates(in 1.13+) the Chunk asynchronously, and then teleports the entity when the chunk is ready. + * @param loc Location to teleport to + * @param cause Reason for teleport + * @return A future that will be completed with the result of the teleport + */ + @NotNull + public default java.util.concurrent.CompletableFuture teleportAsync(@NotNull Location loc, @NotNull TeleportCause cause) { + java.util.concurrent.CompletableFuture future = new java.util.concurrent.CompletableFuture<>(); + loc.getWorld().getChunkAtAsync(loc).thenAccept((chunk) -> future.complete(teleport(loc, cause))); + return future; + } + // Paper end + + /** + * Returns a list of entities within a bounding box centered around this + * entity + * + * @param x 1/2 the size of the box along x axis + * @param y 1/2 the size of the box along y axis + * @param z 1/2 the size of the box along z axis + * @return {@code List} List of entities nearby + */ + @NotNull + public List getNearbyEntities(double x, double y, double z); + + /** + * Returns a unique id for this entity + * + * @return Entity id + */ + public int getEntityId(); + + /** + * Returns the entity's current fire ticks (ticks before the entity stops + * being on fire). + * + * @return int fireTicks + */ + public int getFireTicks(); + + /** + * Returns the entity's maximum fire ticks. + * + * @return int maxFireTicks + */ + public int getMaxFireTicks(); + + /** + * Sets the entity's current fire ticks (ticks before the entity stops + * being on fire). + * + * @param ticks Current ticks remaining + */ + public void setFireTicks(int ticks); + + /** + * Mark the entity's removal. + */ + public void remove(); + + /** + * Returns true if this entity has been marked for removal. + * + * @return True if it is dead. + */ + public boolean isDead(); + + /** + * Returns false if the entity has died or been despawned for some other + * reason. + * + * @return True if valid. + */ + public boolean isValid(); + + /** + * Gets the {@link Server} that contains this Entity + * + * @return Server instance running this Entity + */ + @NotNull + public Server getServer(); + + /** + * Returns true if the entity gets persisted. + *

+ * By default all entities are persistent. An entity will also not get + * persisted, if it is riding an entity that is not persistent. + *

+ * The persistent flag on players controls whether or not to save their + * playerdata file when they quit. If a player is directly or indirectly + * riding a non-persistent entity, the vehicle at the root and all its + * passengers won't get persisted. + *

+ * This should not be confused with + * {@link LivingEntity#setRemoveWhenFarAway(boolean)} which controls + * despawning of living entities. + * + * @return true if this entity is persistent + * @deprecated draft API + */ + @Deprecated + public boolean isPersistent(); + + /** + * Sets whether or not the entity gets persisted. + * + * @param persistent the persistence status + * @see #isPersistent() + * @deprecated draft API + */ + @Deprecated + public void setPersistent(boolean persistent); + + /** + * Gets the primary passenger of a vehicle. For vehicles that could have + * multiple passengers, this will only return the primary passenger. + * + * @return an entity + * @deprecated entities may have multiple passengers, use + * {@link #getPassengers()} + */ + @Deprecated + @Nullable + public Entity getPassenger(); + + /** + * Set the passenger of a vehicle. + * + * @param passenger The new passenger. + * @return false if it could not be done for whatever reason + * @deprecated entities may have multiple passengers, use + * {@link #getPassengers()} + */ + @Deprecated + public boolean setPassenger(@NotNull Entity passenger); + + /** + * Gets a list of passengers of this vehicle. + *

+ * The returned list will not be directly linked to the entity's current + * passengers, and no guarantees are made as to its mutability. + * + * @return list of entities corresponding to current passengers. + */ + @NotNull + public List getPassengers(); + + /** + * Add a passenger to the vehicle. + * + * @param passenger The passenger to add + * @return false if it could not be done for whatever reason + */ + public boolean addPassenger(@NotNull Entity passenger); + + /** + * Remove a passenger from the vehicle. + * + * @param passenger The passenger to remove + * @return false if it could not be done for whatever reason + */ + public boolean removePassenger(@NotNull Entity passenger); + + /** + * Check if a vehicle has passengers. + * + * @return True if the vehicle has no passengers. + */ + public boolean isEmpty(); + + /** + * Eject any passenger. + * + * @return True if there was a passenger. + */ + public boolean eject(); + + /** + * Returns the distance this entity has fallen + * + * @return The distance. + */ + public float getFallDistance(); + + /** + * Sets the fall distance for this entity + * + * @param distance The new distance. + */ + public void setFallDistance(float distance); + + /** + * Record the last {@link EntityDamageEvent} inflicted on this entity + * + * @param event a {@link EntityDamageEvent} + */ + public void setLastDamageCause(@Nullable EntityDamageEvent event); + + /** + * Retrieve the last {@link EntityDamageEvent} inflicted on this entity. + * This event may have been cancelled. + * + * @return the last known {@link EntityDamageEvent} or null if hitherto + * unharmed + */ + @Nullable + public EntityDamageEvent getLastDamageCause(); + + /** + * Returns a unique and persistent id for this entity + * + * @return unique id + */ + @NotNull + public UUID getUniqueId(); + + /** + * Gets the amount of ticks this entity has lived for. + *

+ * This is the equivalent to "age" in entities. + * + * @return Age of entity + */ + public int getTicksLived(); + + /** + * Sets the amount of ticks this entity has lived for. + *

+ * This is the equivalent to "age" in entities. May not be less than one + * tick. + * + * @param value Age of entity + */ + public void setTicksLived(int value); + + /** + * Performs the specified {@link EntityEffect} for this entity. + *

+ * This will be viewable to all players near the entity. + *

+ * If the effect is not applicable to this class of entity, it will not play. + * + * @param type Effect to play. + */ + public void playEffect(@NotNull EntityEffect type); + + /** + * Get the type of the entity. + * + * @return The entity type. + */ + @NotNull + public EntityType getType(); + + /** + * Returns whether this entity is inside a vehicle. + * + * @return True if the entity is in a vehicle. + */ + public boolean isInsideVehicle(); + + /** + * Leave the current vehicle. If the entity is currently in a vehicle (and + * is removed from it), true will be returned, otherwise false will be + * returned. + * + * @return True if the entity was in a vehicle. + */ + public boolean leaveVehicle(); + + /** + * Get the vehicle that this player is inside. If there is no vehicle, + * null will be returned. + * + * @return The current vehicle. + */ + @Nullable + public Entity getVehicle(); + + /** + * Sets whether or not to display the mob's custom name client side. The + * name will be displayed above the mob similarly to a player. + *

+ * This value has no effect on players, they will always display their + * name. + * + * @param flag custom name or not + */ + public void setCustomNameVisible(boolean flag); + + /** + * Gets whether or not the mob's custom name is displayed client side. + *

+ * This value has no effect on players, they will always display their + * name. + * + * @return if the custom name is displayed + */ + public boolean isCustomNameVisible(); + + /** + * Sets whether the entity has a team colored (default: white) glow. + * + * @param flag if the entity is glowing + */ + void setGlowing(boolean flag); + + /** + * Gets whether the entity is glowing or not. + * + * @return whether the entity is glowing + */ + boolean isGlowing(); + + /** + * Sets whether the entity is invulnerable or not. + *

+ * When an entity is invulnerable it can only be damaged by players in + * creative mode. + * + * @param flag if the entity is invulnerable + */ + public void setInvulnerable(boolean flag); + + /** + * Gets whether the entity is invulnerable or not. + * + * @return whether the entity is + */ + public boolean isInvulnerable(); + + /** + * Gets whether the entity is silent or not. + * + * @return whether the entity is silent. + */ + public boolean isSilent(); + + /** + * Sets whether the entity is silent or not. + *

+ * When an entity is silent it will not produce any sound. + * + * @param flag if the entity is silent + */ + public void setSilent(boolean flag); + + /** + * Returns whether gravity applies to this entity. + * + * @return whether gravity applies + */ + boolean hasGravity(); + + /** + * Sets whether gravity applies to this entity. + * + * @param gravity whether gravity should apply + */ + void setGravity(boolean gravity); + + /** + * Gets the period of time (in ticks) before this entity can use a portal. + * + * @return portal cooldown ticks + */ + int getPortalCooldown(); + + /** + * Sets the period of time (in ticks) before this entity can use a portal. + * + * @param cooldown portal cooldown ticks + */ + void setPortalCooldown(int cooldown); + + /** + * Returns a set of tags for this entity. + *
+ * Entities can have no more than 1024 tags. + * + * @return a set of tags for this entity + */ + @NotNull + Set getScoreboardTags(); + + /** + * Add a tag to this entity. + *
+ * Entities can have no more than 1024 tags. + * + * @param tag the tag to add + * @return true if the tag was successfully added + */ + boolean addScoreboardTag(@NotNull String tag); + + /** + * Removes a given tag from this entity. + * + * @param tag the tag to remove + * @return true if the tag was successfully removed + */ + boolean removeScoreboardTag(@NotNull String tag); + + /** + * Returns the reaction of the entity when moved by a piston. + * + * @return reaction + */ + @NotNull + PistonMoveReaction getPistonMoveReaction(); + + /** + * Get the closest cardinal {@link BlockFace} direction an entity is + * currently facing. + *
+ * This will not return any non-cardinal directions such as + * {@link BlockFace#UP} or {@link BlockFace#DOWN}. + *
+ * {@link Hanging} entities will override this call and thus their behavior + * may be different. + * + * @return the entity's current cardinal facing. + * @see Hanging + * @see Directional#getFacing() + */ + @NotNull + BlockFace getFacing(); + + // Spigot start + public class Spigot extends CommandSender.Spigot + { + + /** + * Returns whether this entity is invulnerable. + * + * @return True if the entity is invulnerable. + * @deprecated {@link Entity#isInvulnerable()} + */ + @Deprecated + public boolean isInvulnerable() + { + throw new UnsupportedOperationException( "Not supported yet." ); + } + } + + @NotNull + @Override + Spigot spigot(); + // Spigot end + + // Paper start + /** + * Gets the location where this entity originates from. + *

+ * This value can be null if the entity hasn't yet been added to the world. + * + * @return Location where entity originates or null if not yet added + */ + @Nullable + Location getOrigin(); + + /** + * Returns whether this entity was spawned from a mob spawner. + * + * @return True if entity spawned from a mob spawner + */ + boolean fromMobSpawner(); + + /** + * Gets the latest chunk an entity is currently or was in. + * + * @return The current, or most recent chunk if the entity is invalid (which may load the chunk) + */ + @NotNull + Chunk getChunk(); + + /** + * @return The {@link org.bukkit.event.entity.CreatureSpawnEvent.SpawnReason} that spawned this entity. + */ + @NotNull + org.bukkit.event.entity.CreatureSpawnEvent.SpawnReason getEntitySpawnReason(); + // Paper end +} diff --git a/api/src/main/java/org/bukkit/entity/EntityType.java b/api/src/main/java/org/bukkit/entity/EntityType.java new file mode 100644 index 000000000..395f74c1c --- /dev/null +++ b/api/src/main/java/org/bukkit/entity/EntityType.java @@ -0,0 +1,400 @@ +package org.bukkit.entity; + +import java.util.HashMap; +import java.util.Map; + +import org.bukkit.entity.minecart.CommandMinecart; +import org.bukkit.entity.minecart.HopperMinecart; +import org.bukkit.entity.minecart.SpawnerMinecart; +import org.bukkit.entity.minecart.RideableMinecart; +import org.bukkit.entity.minecart.ExplosiveMinecart; +import org.bukkit.entity.minecart.PoweredMinecart; +import org.bukkit.entity.minecart.StorageMinecart; +import org.bukkit.inventory.ItemStack; +import org.bukkit.Location; +import org.bukkit.World; +import org.bukkit.potion.PotionEffectType; +import org.jetbrains.annotations.Contract; +import org.jetbrains.annotations.Nullable; + +public enum EntityType { + + // These strings MUST match the strings in nms.EntityTypes and are case sensitive. + /** + * An item resting on the ground. + *

+ * Spawn with {@link World#dropItem(Location, ItemStack)} or {@link + * World#dropItemNaturally(Location, ItemStack)} + */ + DROPPED_ITEM("item", Item.class, 1, false), + /** + * An experience orb. + */ + EXPERIENCE_ORB("experience_orb", ExperienceOrb.class, 2), + /** + * @see AreaEffectCloud + */ + AREA_EFFECT_CLOUD("area_effect_cloud", AreaEffectCloud.class, 3), + /** + * @see ElderGuardian + */ + ELDER_GUARDIAN("elder_guardian", ElderGuardian.class, 4), + /** + * @see WitherSkeleton + */ + WITHER_SKELETON("wither_skeleton", WitherSkeleton.class, 5), + /** + * @see Stray + */ + STRAY("stray", Stray.class, 6), + /** + * A flying chicken egg. + */ + EGG("egg", Egg.class, 7), + /** + * A leash attached to a fencepost. + */ + LEASH_HITCH("leash_knot", LeashHitch.class, 8), + /** + * A painting on a wall. + */ + PAINTING("painting", Painting.class, 9), + /** + * An arrow projectile; may get stuck in the ground. + */ + ARROW("arrow", Arrow.class, 10), + /** + * A flying snowball. + */ + SNOWBALL("snowball", Snowball.class, 11), + /** + * A flying large fireball, as thrown by a Ghast for example. + */ + FIREBALL("fireball", LargeFireball.class, 12), + /** + * A flying small fireball, such as thrown by a Blaze or player. + */ + SMALL_FIREBALL("small_fireball", SmallFireball.class, 13), + /** + * A flying ender pearl. + */ + ENDER_PEARL("ender_pearl", EnderPearl.class, 14), + /** + * An ender eye signal. + */ + ENDER_SIGNAL("eye_of_ender", EnderSignal.class, 15), + /** + * A flying splash potion. + */ + SPLASH_POTION("potion", SplashPotion.class, 16, false), + /** + * A flying experience bottle. + */ + THROWN_EXP_BOTTLE("experience_bottle", ThrownExpBottle.class, 17), + /** + * An item frame on a wall. + */ + ITEM_FRAME("item_frame", ItemFrame.class, 18), + /** + * A flying wither skull projectile. + */ + WITHER_SKULL("wither_skull", WitherSkull.class, 19), + /** + * Primed TNT that is about to explode. + */ + PRIMED_TNT("tnt", TNTPrimed.class, 20), + /** + * A block that is going to or is about to fall. + */ + FALLING_BLOCK("falling_block", FallingBlock.class, 21, false), + /** + * Internal representation of a Firework once it has been launched. + */ + FIREWORK("firework_rocket", Firework.class, 22, false), + /** + * @see Husk + */ + HUSK("husk", Husk.class, 23), + /** + * Like {@link #TIPPED_ARROW} but causes the {@link PotionEffectType#GLOWING} effect on all team members. + */ + SPECTRAL_ARROW("spectral_arrow", SpectralArrow.class, 24), + /** + * Bullet fired by {@link #SHULKER}. + */ + SHULKER_BULLET("shulker_bullet", ShulkerBullet.class, 25), + /** + * Like {@link #FIREBALL} but with added effects. + */ + DRAGON_FIREBALL("dragon_fireball", DragonFireball.class, 26), + /** + * @see ZombieVillager + */ + ZOMBIE_VILLAGER("zombie_villager", ZombieVillager.class, 27), + /** + * @see SkeletonHorse + */ + SKELETON_HORSE("skeleton_horse", SkeletonHorse.class, 28), + /** + * @see ZombieHorse + */ + ZOMBIE_HORSE("zombie_horse", ZombieHorse.class, 29), + /** + * Mechanical entity with an inventory for placing weapons / armor into. + */ + ARMOR_STAND("armor_stand", ArmorStand.class, 30), + /** + * @see Donkey + */ + DONKEY("donkey", Donkey.class, 31), + /** + * @see Mule + */ + MULE("mule", Mule.class, 32), + /** + * @see EvokerFangs + */ + EVOKER_FANGS("evoker_fangs", EvokerFangs.class, 33), + /** + * @see Evoker + */ + EVOKER("evoker", Evoker.class, 34), + /** + * @see Vex + */ + VEX("vex", Vex.class, 35), + /** + * @see Vindicator + */ + VINDICATOR("vindicator", Vindicator.class, 36), + /** + * @see Illusioner + */ + ILLUSIONER("illusioner", Illusioner.class, 37), + /** + * @see CommandMinecart + */ + MINECART_COMMAND("command_block_minecart", CommandMinecart.class, 40), + /** + * A placed boat. + */ + BOAT("boat", Boat.class, 41), + /** + * @see RideableMinecart + */ + MINECART("minecart", RideableMinecart.class, 42), + /** + * @see StorageMinecart + */ + MINECART_CHEST("chest_minecart", StorageMinecart.class, 43), + /** + * @see PoweredMinecart + */ + MINECART_FURNACE("furnace_minecart", PoweredMinecart.class, 44), + /** + * @see ExplosiveMinecart + */ + MINECART_TNT("tnt_minecart", ExplosiveMinecart.class, 45), + /** + * @see HopperMinecart + */ + MINECART_HOPPER("hopper_minecart", HopperMinecart.class, 46), + /** + * @see SpawnerMinecart + */ + MINECART_MOB_SPAWNER("spawner_minecart", SpawnerMinecart.class, 47), + CREEPER("creeper", Creeper.class, 50), + SKELETON("skeleton", Skeleton.class, 51), + SPIDER("spider", Spider.class, 52), + GIANT("giant", Giant.class, 53), + ZOMBIE("zombie", Zombie.class, 54), + SLIME("slime", Slime.class, 55), + GHAST("ghast", Ghast.class, 56), + PIG_ZOMBIE("zombie_pigman", PigZombie.class, 57), + ENDERMAN("enderman", Enderman.class, 58), + CAVE_SPIDER("cave_spider", CaveSpider.class, 59), + SILVERFISH("silverfish", Silverfish.class, 60), + BLAZE("blaze", Blaze.class, 61), + MAGMA_CUBE("magma_cube", MagmaCube.class, 62), + ENDER_DRAGON("ender_dragon", EnderDragon.class, 63), + WITHER("wither", Wither.class, 64), + BAT("bat", Bat.class, 65), + WITCH("witch", Witch.class, 66), + ENDERMITE("endermite", Endermite.class, 67), + GUARDIAN("guardian", Guardian.class, 68), + SHULKER("shulker", Shulker.class, 69), + PIG("pig", Pig.class, 90), + SHEEP("sheep", Sheep.class, 91), + COW("cow", Cow.class, 92), + CHICKEN("chicken", Chicken.class, 93), + SQUID("squid", Squid.class, 94), + WOLF("wolf", Wolf.class, 95), + MUSHROOM_COW("mooshroom", MushroomCow.class, 96), + SNOWMAN("snow_golem", Snowman.class, 97), + OCELOT("ocelot", Ocelot.class, 98), + IRON_GOLEM("iron_golem", IronGolem.class, 99), + HORSE("horse", Horse.class, 100), + RABBIT("rabbit", Rabbit.class, 101), + POLAR_BEAR("polar_bear", PolarBear.class, 102), + LLAMA("llama", Llama.class, 103), + LLAMA_SPIT("llama_spit", LlamaSpit.class, 104), + PARROT("parrot", Parrot.class, 105), + VILLAGER("villager", Villager.class, 120), + ENDER_CRYSTAL("end_crystal", EnderCrystal.class, 200), + TURTLE("turtle", Turtle.class, -1), + PHANTOM("phantom", Phantom.class, -1), + TRIDENT("trident", Trident.class, -1), + COD("cod", Cod.class, -1), + SALMON("salmon", Salmon.class, -1), + PUFFERFISH("pufferfish", PufferFish.class, -1), + TROPICAL_FISH("tropical_fish", TropicalFish.class, -1), + DROWNED("drowned", Drowned.class, -1), + DOLPHIN("dolphin", Dolphin.class, -1), + // These don't have an entity ID in nms.EntityTypes. + /** + * A flying lingering potion + */ + LINGERING_POTION(null, LingeringPotion.class, -1, false), + /** + * A fishing line and bobber. + */ + FISHING_HOOK("fishing_bobber", FishHook.class, -1, false), + /** + * A bolt of lightning. + *

+ * Spawn with {@link World#strikeLightning(Location)}. + */ + LIGHTNING("lightning_bolt", LightningStrike.class, -1, false), + WEATHER(null, Weather.class, -1, false), + PLAYER("player", Player.class, -1, false), + COMPLEX_PART(null, ComplexEntityPart.class, -1, false), + /** + * Like {@link #ARROW} but tipped with a specific potion which is applied on + * contact. + */ + TIPPED_ARROW(null, TippedArrow.class, -1), + /** + * An unknown entity without an Entity Class + */ + UNKNOWN(null, null, -1, false); + + private String name; + private Class clazz; + private short typeId; + private boolean independent, living; + + private static final Map NAME_MAP = new HashMap(); + private static final Map ID_MAP = new HashMap(); + + static { + for (EntityType type : values()) { + if (type.name != null) { + NAME_MAP.put(type.name.toLowerCase(java.util.Locale.ENGLISH), type); + } + if (type.typeId > 0) { + ID_MAP.put(type.typeId, type); + } + } + + // Add legacy names + NAME_MAP.put("xp_orb", EXPERIENCE_ORB); + NAME_MAP.put("eye_of_ender_signal", ENDER_SIGNAL); + NAME_MAP.put("xp_bottle", THROWN_EXP_BOTTLE); + NAME_MAP.put("fireworks_rocket", FIREWORK); + NAME_MAP.put("evocation_fangs", EVOKER_FANGS); + NAME_MAP.put("evocation_illager", EVOKER); + NAME_MAP.put("vindication_illager", VINDICATOR); + NAME_MAP.put("illusion_illager", ILLUSIONER); + NAME_MAP.put("commandblock_minecart", MINECART_COMMAND); + NAME_MAP.put("snowman", SNOWMAN); + NAME_MAP.put("villager_golem", IRON_GOLEM); + NAME_MAP.put("ender_crystal", ENDER_CRYSTAL); + } + + private EntityType(/*@Nullable*/ String name, /*@Nullable*/ Class clazz, int typeId) { + this(name, clazz, typeId, true); + } + + private EntityType(/*@Nullable*/ String name, /*@Nullable*/ Class clazz, int typeId, boolean independent) { + this.name = name; + this.clazz = clazz; + this.typeId = (short) typeId; + this.independent = independent; + if (clazz != null) { + this.living = LivingEntity.class.isAssignableFrom(clazz); + } + } + + /** + * + * @return the entity type's name + * @deprecated Magic value + */ + @Deprecated + @Nullable + public String getName() { + return name; + } + + @Nullable + public Class getEntityClass() { + return clazz; + } + + /** + * + * @return the raw type id + * @deprecated Magic value + */ + @Deprecated + public short getTypeId() { + return typeId; + } + + /** + * + * @param name the entity type's name + * @return the matching entity type or null + * @deprecated Magic value + */ + @Deprecated + @Contract("null -> null") + @Nullable + public static EntityType fromName(@Nullable String name) { + if (name == null) { + return null; + } + return NAME_MAP.get(name.toLowerCase(java.util.Locale.ENGLISH)); + } + + /** + * + * @param id the raw type id + * @return the matching entity type or null + * @deprecated Magic value + */ + @Deprecated + @Nullable + public static EntityType fromId(int id) { + if (id > Short.MAX_VALUE) { + return null; + } + return ID_MAP.get((short) id); + } + + /** + * Some entities cannot be spawned using {@link + * World#spawnEntity(Location, EntityType)} or {@link + * World#spawn(Location, Class)}, usually because they require additional + * information in order to spawn. + * + * @return False if the entity type cannot be spawned + */ + public boolean isSpawnable() { + return independent; + } + + public boolean isAlive() { + return living; + } +} diff --git a/api/src/main/java/org/bukkit/entity/Evoker.java b/api/src/main/java/org/bukkit/entity/Evoker.java new file mode 100644 index 000000000..f8d173adc --- /dev/null +++ b/api/src/main/java/org/bukkit/entity/Evoker.java @@ -0,0 +1,67 @@ +package org.bukkit.entity; + +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +/** + * Represents an Evoker "Illager". + */ +public interface Evoker extends Spellcaster { + + /** + * Represents the current spell the Evoker is using. + * + * @deprecated future versions of Minecraft have additional spell casting + * entities. + */ + @Deprecated + public enum Spell { + + /** + * No spell is being evoked. + */ + NONE, + /** + * The spell that summons Vexes. + */ + SUMMON, + /** + * The spell that summons Fangs. + */ + FANGS, + /** + * The "wololo" spell. + */ + WOLOLO, + /** + * The spell that makes the casting entity invisible. + */ + DISAPPEAR, + /** + * The spell that makes the target blind. + */ + BLINDNESS; + } + + /** + * Gets the {@link Spell} the Evoker is currently using. + * + * @return the current spell + * @deprecated future versions of Minecraft have additional spell casting + * entities. + * + */ + @Deprecated + @NotNull + Spell getCurrentSpell(); + + /** + * Sets the {@link Spell} the Evoker is currently using. + * + * @param spell the spell the evoker should be using + * @deprecated future versions of Minecraft have additional spell casting + * entities. + */ + @Deprecated + void setCurrentSpell(@Nullable Spell spell); +} diff --git a/api/src/main/java/org/bukkit/entity/EvokerFangs.java b/api/src/main/java/org/bukkit/entity/EvokerFangs.java new file mode 100644 index 000000000..7cc1dcdab --- /dev/null +++ b/api/src/main/java/org/bukkit/entity/EvokerFangs.java @@ -0,0 +1,24 @@ +package org.bukkit.entity; + +import org.jetbrains.annotations.Nullable; + +/** + * Represents Evoker Fangs. + */ +public interface EvokerFangs extends Entity { + + /** + * Gets the {@link LivingEntity} which summoned the fangs. + * + * @return the {@link LivingEntity} which summoned the fangs + */ + @Nullable + LivingEntity getOwner(); + + /** + * Sets the {@link LivingEntity} which summoned the fangs. + * + * @param owner the {@link LivingEntity} which summoned the fangs + */ + void setOwner(@Nullable LivingEntity owner); +} diff --git a/api/src/main/java/org/bukkit/entity/ExperienceOrb.java b/api/src/main/java/org/bukkit/entity/ExperienceOrb.java new file mode 100644 index 000000000..57029d9bc --- /dev/null +++ b/api/src/main/java/org/bukkit/entity/ExperienceOrb.java @@ -0,0 +1,115 @@ +package org.bukkit.entity; + +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; // Paper + +/** + * Represents an Experience Orb. + */ +public interface ExperienceOrb extends Entity { + + /** + * Gets how much experience is contained within this orb + * + * @return Amount of experience + */ + public int getExperience(); + + /** + * Sets how much experience is contained within this orb + * + * @param value Amount of experience + */ + public void setExperience(int value); + + // Paper start + /** + * Check if this orb was spawned from a {@link ThrownExpBottle} + * + * @return if orb was spawned from a bottle + * @deprecated Use getSpawnReason() == EXP_BOTTLE + */ + @Deprecated + default boolean isFromBottle() { + return getSpawnReason() == SpawnReason.EXP_BOTTLE; + } + + /** + * Reasons for why this Experience Orb was spawned + */ + enum SpawnReason { + /** + * Spawned by a player dying + */ + PLAYER_DEATH, + /** + * Spawned by an entity dying after being damaged by a player + */ + ENTITY_DEATH, + /** + * Spawned by player using a furnace + */ + FURNACE, + /** + * Spawned by player breeding animals + */ + BREED, + /** + * Spawned by player trading with a villager + */ + VILLAGER_TRADE, + /** + * Spawned by player fishing + */ + FISHING, + /** + * Spawned by player breaking a block that gives experience points such as Diamond Ore + */ + BLOCK_BREAK, + /** + * Spawned by Bukkit API + */ + CUSTOM, + /** + * Spawned by a player throwing an experience points bottle + */ + EXP_BOTTLE, + /** + * We do not know why it was spawned + */ + UNKNOWN + } + + /** + * If this experience orb was triggered to be spawned by + * an entity such as a player, due to events such as killing entity, + * breaking blocks, smelting in a furnace, etc, this will return the UUID + * of the entity that triggered this orb to drop. + * + * In the case of an entity being killed, this will be the killers UUID. + * + * @return UUID of the player that triggered this orb to drop, or null if unknown/no triggering entity + */ + @Nullable java.util.UUID getTriggerEntityId(); + + /** + * If this experience orb was spawned in relation to another + * entity, such as a player or other living entity death, or breeding, + * return the source entity UUID. + * + * In the case of breeding, this will be the new baby entities UUID. + * In the case of an entity being killed, this will be the dead entities UUID. + * + * @return The UUID of the entity that sourced this experience orb + */ + @Nullable java.util.UUID getSourceEntityId(); + + /** + * Gets the reason that this experience orb was spawned. For any case that we + * do not know, such as orbs spawned before this API was added, UNKNOWN is returned. + * @return The reason for this orb being spawned. + */ + @NotNull + SpawnReason getSpawnReason(); + // Paper end +} diff --git a/api/src/main/java/org/bukkit/entity/Explosive.java b/api/src/main/java/org/bukkit/entity/Explosive.java new file mode 100644 index 000000000..48650f6ab --- /dev/null +++ b/api/src/main/java/org/bukkit/entity/Explosive.java @@ -0,0 +1,35 @@ +package org.bukkit.entity; + +/** + * A representation of an explosive entity + */ +public interface Explosive extends Entity { + + /** + * Set the radius affected by this explosive's explosion + * + * @param yield The explosive yield + */ + public void setYield(float yield); + + /** + * Return the radius or yield of this explosive's explosion + * + * @return the radius of blocks affected + */ + public float getYield(); + + /** + * Set whether or not this explosive's explosion causes fire + * + * @param isIncendiary Whether it should cause fire + */ + public void setIsIncendiary(boolean isIncendiary); + + /** + * Return whether or not this explosive creates a fire when exploding + * + * @return true if the explosive creates fire, false otherwise + */ + public boolean isIncendiary(); +} diff --git a/api/src/main/java/org/bukkit/entity/FallingBlock.java b/api/src/main/java/org/bukkit/entity/FallingBlock.java new file mode 100644 index 000000000..14cb0d770 --- /dev/null +++ b/api/src/main/java/org/bukkit/entity/FallingBlock.java @@ -0,0 +1,68 @@ +package org.bukkit.entity; + +import org.bukkit.Material; +import org.bukkit.block.data.BlockData; +import org.jetbrains.annotations.NotNull; + +/** + * Represents a falling block + */ +public interface FallingBlock extends Entity { + + /** + * Get the Material of the falling block + * + * @return Material of the block + * @deprecated use {@link #getBlockData()} + */ + @Deprecated + @NotNull + Material getMaterial(); + + /** + * Get the data for the falling block + * + * @return data of the block + */ + @NotNull + BlockData getBlockData(); + + /** + * Get if the falling block will break into an item if it cannot be placed + * + * @return true if the block will break into an item when obstructed + */ + boolean getDropItem(); + + /** + * Set if the falling block will break into an item if it cannot be placed + * + * @param drop true to break into an item when obstructed + */ + void setDropItem(boolean drop); + + /** + * Get the HurtEntities state of this block. + * + * @return whether entities will be damaged by this block. + */ + boolean canHurtEntities(); + + /** + * Set the HurtEntities state of this block. + * + * @param hurtEntities whether entities will be damaged by this block. + */ + void setHurtEntities(boolean hurtEntities); + + /** + * Gets the source block location of the FallingBlock + * + * @return the source block location the FallingBlock was spawned from + * @deprecated replaced by {@link Entity#getOrigin()} + */ + @Deprecated + default org.bukkit.Location getSourceLoc() { + return this.getOrigin(); + } +} diff --git a/api/src/main/java/org/bukkit/entity/Fireball.java b/api/src/main/java/org/bukkit/entity/Fireball.java new file mode 100644 index 000000000..7a44707f2 --- /dev/null +++ b/api/src/main/java/org/bukkit/entity/Fireball.java @@ -0,0 +1,26 @@ +package org.bukkit.entity; + +import org.bukkit.util.Vector; +import org.jetbrains.annotations.NotNull; + +/** + * Represents a Fireball. + */ +public interface Fireball extends Projectile, Explosive { + + /** + * Fireballs fly straight and do not take setVelocity(...) well. + * + * @param direction the direction this fireball is flying toward + */ + public void setDirection(@NotNull Vector direction); + + /** + * Retrieve the direction this fireball is heading toward + * + * @return the direction + */ + @NotNull + public Vector getDirection(); + +} diff --git a/api/src/main/java/org/bukkit/entity/Firework.java b/api/src/main/java/org/bukkit/entity/Firework.java new file mode 100644 index 000000000..7df26cf7f --- /dev/null +++ b/api/src/main/java/org/bukkit/entity/Firework.java @@ -0,0 +1,42 @@ +package org.bukkit.entity; + +import org.bukkit.inventory.meta.FireworkMeta; +import org.jetbrains.annotations.NotNull; + +import java.util.UUID; +import org.jetbrains.annotations.Nullable; + +public interface Firework extends Entity { + + /** + * Get a copy of the fireworks meta + * + * @return A copy of the current Firework meta + */ + @NotNull + FireworkMeta getFireworkMeta(); + + /** + * Apply the provided meta to the fireworks + * + * @param meta The FireworkMeta to apply + */ + void setFireworkMeta(@NotNull FireworkMeta meta); + + /** + * Cause this firework to explode at earliest opportunity, as if it has no + * remaining fuse. + */ + void detonate(); + + // Paper start + @Nullable + public UUID getSpawningEntity(); + /** + * If this firework is boosting an entity, return it + * @return The entity being boosted + */ + @Nullable + public LivingEntity getBoostedEntity(); + // Paper end +} diff --git a/api/src/main/java/org/bukkit/entity/Fish.java b/api/src/main/java/org/bukkit/entity/Fish.java new file mode 100644 index 000000000..82e390b21 --- /dev/null +++ b/api/src/main/java/org/bukkit/entity/Fish.java @@ -0,0 +1,6 @@ +package org.bukkit.entity; + +/** + * Represents a fish entity. + */ +public interface Fish extends WaterMob { } diff --git a/api/src/main/java/org/bukkit/entity/FishHook.java b/api/src/main/java/org/bukkit/entity/FishHook.java new file mode 100644 index 000000000..fdd48d0f1 --- /dev/null +++ b/api/src/main/java/org/bukkit/entity/FishHook.java @@ -0,0 +1,32 @@ +package org.bukkit.entity; + +/** + * Represents a fishing hook. + */ +public interface FishHook extends Projectile { + /** + * Gets the chance of a fish biting. + *

+ * 0.0 = No Chance.
+ * 1.0 = Instant catch. + * + * @return chance the bite chance + * @deprecated has no effect in newer Minecraft versions + */ + @Deprecated + public double getBiteChance(); + + /** + * Sets the chance of a fish biting. + *

+ * 0.0 = No Chance.
+ * 1.0 = Instant catch. + * + * @param chance the bite chance + * @throws IllegalArgumentException if the bite chance is not between 0 + * and 1 + * @deprecated has no effect in newer Minecraft versions + */ + @Deprecated + public void setBiteChance(double chance) throws IllegalArgumentException; +} diff --git a/api/src/main/java/org/bukkit/entity/Flying.java b/api/src/main/java/org/bukkit/entity/Flying.java new file mode 100644 index 000000000..580ce18bf --- /dev/null +++ b/api/src/main/java/org/bukkit/entity/Flying.java @@ -0,0 +1,6 @@ +package org.bukkit.entity; + +/** + * Represents a Flying Entity. + */ +public interface Flying extends Mob {} diff --git a/api/src/main/java/org/bukkit/entity/Ghast.java b/api/src/main/java/org/bukkit/entity/Ghast.java new file mode 100644 index 000000000..3f5edf76c --- /dev/null +++ b/api/src/main/java/org/bukkit/entity/Ghast.java @@ -0,0 +1,6 @@ +package org.bukkit.entity; + +/** + * Represents a Ghast. + */ +public interface Ghast extends Flying {} diff --git a/api/src/main/java/org/bukkit/entity/Giant.java b/api/src/main/java/org/bukkit/entity/Giant.java new file mode 100644 index 000000000..610de5779 --- /dev/null +++ b/api/src/main/java/org/bukkit/entity/Giant.java @@ -0,0 +1,6 @@ +package org.bukkit.entity; + +/** + * Represents a Giant. + */ +public interface Giant extends Monster {} diff --git a/api/src/main/java/org/bukkit/entity/Golem.java b/api/src/main/java/org/bukkit/entity/Golem.java new file mode 100644 index 000000000..4165977e7 --- /dev/null +++ b/api/src/main/java/org/bukkit/entity/Golem.java @@ -0,0 +1,8 @@ +package org.bukkit.entity; + +/** + * A mechanical creature that may harm enemies. + */ +public interface Golem extends Creature { + +} diff --git a/api/src/main/java/org/bukkit/entity/Guardian.java b/api/src/main/java/org/bukkit/entity/Guardian.java new file mode 100644 index 000000000..ec6890ae6 --- /dev/null +++ b/api/src/main/java/org/bukkit/entity/Guardian.java @@ -0,0 +1,20 @@ +package org.bukkit.entity; + +public interface Guardian extends Monster { + + /** + * Check if the Guardian is an elder Guardian + * + * @return true if the Guardian is an Elder Guardian, false if not + * @deprecated should check if instance of {@link ElderGuardian}. + */ + @Deprecated + public boolean isElder(); + + /** + * @param shouldBeElder Sets whether the Guardian is an Elder + * @deprecated Must spawn a new {@link ElderGuardian}. + */ + @Deprecated + public void setElder(boolean shouldBeElder); +} diff --git a/api/src/main/java/org/bukkit/entity/Hanging.java b/api/src/main/java/org/bukkit/entity/Hanging.java new file mode 100644 index 000000000..2f07efac0 --- /dev/null +++ b/api/src/main/java/org/bukkit/entity/Hanging.java @@ -0,0 +1,23 @@ +package org.bukkit.entity; + +import org.bukkit.block.BlockFace; +import org.bukkit.material.Attachable; +import org.jetbrains.annotations.NotNull; + +/** + * Represents a Hanging entity + */ +public interface Hanging extends Entity, Attachable { + + /** + * Sets the direction of the hanging entity, potentially overriding rules + * of placement. Note that if the result is not valid the object would + * normally drop as an item. + * + * @param face The new direction. + * @param force Whether to force it. + * @return False if force was false and there was no block for it to + * attach to in order to face the given direction. + */ + public boolean setFacingDirection(@NotNull BlockFace face, boolean force); +} diff --git a/api/src/main/java/org/bukkit/entity/Horse.java b/api/src/main/java/org/bukkit/entity/Horse.java new file mode 100644 index 000000000..5efa4e606 --- /dev/null +++ b/api/src/main/java/org/bukkit/entity/Horse.java @@ -0,0 +1,166 @@ +package org.bukkit.entity; + +import org.bukkit.inventory.HorseInventory; +import org.jetbrains.annotations.NotNull; + +/** + * Represents a Horse. + */ +public interface Horse extends AbstractHorse { + + /** + * @deprecated different variants are differing classes + */ + @Deprecated + public enum Variant { + /** + * A normal horse + */ + HORSE, + /** + * A donkey + */ + DONKEY, + /** + * A mule + */ + MULE, + /** + * An undead horse + */ + UNDEAD_HORSE, + /** + * A skeleton horse + */ + SKELETON_HORSE, + /** + * Not really a horse :) + */ + LLAMA + ; + } + + /** + * Represents the base color that the horse has. + */ + public enum Color { + /** + * Snow white + */ + WHITE, + /** + * Very light brown + */ + CREAMY, + /** + * Chestnut + */ + CHESTNUT, + /** + * Light brown + */ + BROWN, + /** + * Pitch black + */ + BLACK, + /** + * Gray + */ + GRAY, + /** + * Dark brown + */ + DARK_BROWN, + ; + } + + /** + * Represents the style, or markings, that the horse has. + */ + public enum Style { + /** + * No markings + */ + NONE, + /** + * White socks or stripes + */ + WHITE, + /** + * Milky splotches + */ + WHITEFIELD, + /** + * Round white dots + */ + WHITE_DOTS, + /** + * Small black dots + */ + BLACK_DOTS, + ; + } + + /** + * Gets the horse's color. + *

+ * Colors only apply to horses, not to donkeys, mules, skeleton horses + * or undead horses. + * + * @return a {@link Color} representing the horse's group + */ + @NotNull + public Color getColor(); + + /** + * Sets the horse's color. + *

+ * Attempting to set a color for any donkey, mule, skeleton horse or + * undead horse will not result in a change. + * + * @param color a {@link Color} for this horse + */ + public void setColor(@NotNull Color color); + + /** + * Gets the horse's style. + * Styles determine what kind of markings or patterns a horse has. + *

+ * Styles only apply to horses, not to donkeys, mules, skeleton horses + * or undead horses. + * + * @return a {@link Style} representing the horse's style + */ + @NotNull + public Style getStyle(); + + /** + * Sets the style of this horse. + * Styles determine what kind of markings or patterns a horse has. + *

+ * Attempting to set a style for any donkey, mule, skeleton horse or + * undead horse will not result in a change. + * + * @param style a {@link Style} for this horse + */ + public void setStyle(@NotNull Style style); + + /** + * @return carrying chest status + * @deprecated see {@link ChestedHorse} + */ + @Deprecated + public boolean isCarryingChest(); + + /** + * @param chest Sets whether the Horse is carrying a chest + * @deprecated see {@link ChestedHorse} + */ + @Deprecated + public void setCarryingChest(boolean chest); + + @NotNull + @Override + public HorseInventory getInventory(); +} diff --git a/api/src/main/java/org/bukkit/entity/HumanEntity.java b/api/src/main/java/org/bukkit/entity/HumanEntity.java new file mode 100644 index 000000000..a372c74b8 --- /dev/null +++ b/api/src/main/java/org/bukkit/entity/HumanEntity.java @@ -0,0 +1,486 @@ +package org.bukkit.entity; + +import java.util.Collection; +import org.bukkit.GameMode; +import org.bukkit.Location; +import org.bukkit.Material; +import org.bukkit.NamespacedKey; +import org.bukkit.inventory.MainHand; +import org.bukkit.inventory.Merchant; +import org.bukkit.inventory.Inventory; +import org.bukkit.inventory.InventoryHolder; +import org.bukkit.inventory.InventoryView; +import org.bukkit.inventory.ItemStack; +import org.bukkit.inventory.PlayerInventory; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +/** + * Represents a human entity, such as an NPC or a player + */ +public interface HumanEntity extends LivingEntity, AnimalTamer, InventoryHolder { + + /** + * Returns the name of this player + * + * @return Player name + */ + @NotNull + @Override + public String getName(); + + /** + * Get the player's inventory. + * + * @return The inventory of the player, this also contains the armor + * slots. + */ + @NotNull + @Override + public PlayerInventory getInventory(); + + /** + * Get the player's EnderChest inventory + * + * @return The EnderChest of the player + */ + @NotNull + public Inventory getEnderChest(); + + /** + * Gets the player's selected main hand + * + * @return the players main hand + */ + @NotNull + public MainHand getMainHand(); + + /** + * If the player currently has an inventory window open, this method will + * set a property of that window, such as the state of a progress bar. + * + * @param prop The property. + * @param value The value to set the property to. + * @return True if the property was successfully set. + */ + public boolean setWindowProperty(@NotNull InventoryView.Property prop, int value); + + /** + * Gets the inventory view the player is currently viewing. If they do not + * have an inventory window open, it returns their internal crafting view. + * + * @return The inventory view. + */ + @NotNull + public InventoryView getOpenInventory(); + + /** + * Opens an inventory window with the specified inventory on the top and + * the player's inventory on the bottom. + * + * @param inventory The inventory to open + * @return The newly opened inventory view + */ + @Nullable + public InventoryView openInventory(@NotNull Inventory inventory); + + /** + * Opens an empty workbench inventory window with the player's inventory + * on the bottom. + * + * @param location The location to attach it to. If null, the player's + * location is used. + * @param force If false, and there is no workbench block at the location, + * no inventory will be opened and null will be returned. + * @return The newly opened inventory view, or null if it could not be + * opened. + */ + @Nullable + public InventoryView openWorkbench(@Nullable Location location, boolean force); + + /** + * Opens an empty enchanting inventory window with the player's inventory + * on the bottom. + * + * @param location The location to attach it to. If null, the player's + * location is used. + * @param force If false, and there is no enchanting table at the + * location, no inventory will be opened and null will be returned. + * @return The newly opened inventory view, or null if it could not be + * opened. + */ + @Nullable + public InventoryView openEnchanting(@Nullable Location location, boolean force); + + /** + * Opens an inventory window to the specified inventory view. + * + * @param inventory The view to open + */ + public void openInventory(@NotNull InventoryView inventory); + + /** + * Starts a trade between the player and the villager. + * + * Note that only one player may trade with a villager at once. You must use + * the force parameter for this. + * + * @param trader The merchant to trade with. Cannot be null. + * @param force whether to force the trade even if another player is trading + * @return The newly opened inventory view, or null if it could not be + * opened. + */ + @Nullable + public InventoryView openMerchant(@NotNull Villager trader, boolean force); + + /** + * Starts a trade between the player and the merchant. + * + * Note that only one player may trade with a merchant at once. You must use + * the force parameter for this. + * + * @param merchant The merchant to trade with. Cannot be null. + * @param force whether to force the trade even if another player is trading + * @return The newly opened inventory view, or null if it could not be + * opened. + */ + @Nullable + public InventoryView openMerchant(@NotNull Merchant merchant, boolean force); + + /** + * Force-closes the currently open inventory view for this player, if any. + */ + public void closeInventory(); + + // Paper start + /** + * Force-closes the currently open inventory view for this player, if any. + * + * @param reason why the inventory is closing + */ + public void closeInventory(@NotNull org.bukkit.event.inventory.InventoryCloseEvent.Reason reason); + // Paper end + + /** + * Returns the ItemStack currently in your hand, can be empty. + * + * @return The ItemStack of the item you are currently holding. + * @deprecated Humans may now dual wield in their off hand, use explicit + * methods in {@link PlayerInventory}. + */ + @Deprecated + @NotNull + public ItemStack getItemInHand(); + + /** + * Sets the item to the given ItemStack, this will replace whatever the + * user was holding. + * + * @param item The ItemStack which will end up in the hand + * @deprecated Humans may now dual wield in their off hand, use explicit + * methods in {@link PlayerInventory}. + */ + @Deprecated + public void setItemInHand(@Nullable ItemStack item); + + /** + * Returns the ItemStack currently on your cursor, can be empty. Will + * always be empty if the player currently has no open window. + * + * @return The ItemStack of the item you are currently moving around. + */ + @NotNull + public ItemStack getItemOnCursor(); + + /** + * Sets the item to the given ItemStack, this will replace whatever the + * user was moving. Will always be empty if the player currently has no + * open window. + * + * @param item The ItemStack which will end up in the hand + */ + public void setItemOnCursor(@Nullable ItemStack item); + + /** + * Check whether a cooldown is active on the specified material. + * + * @param material the material to check + * @return if a cooldown is active on the material + */ + public boolean hasCooldown(@NotNull Material material); + + /** + * Get the cooldown time in ticks remaining for the specified material. + * + * @param material the material to check + * @return the remaining cooldown time in ticks + */ + public int getCooldown(@NotNull Material material); + + /** + * Set a cooldown on the specified material for a certain amount of ticks. + * ticks. 0 ticks will result in the removal of the cooldown. + *

+ * Cooldowns are used by the server for items such as ender pearls and + * shields to prevent them from being used repeatedly. + *

+ * Note that cooldowns will not by themselves stop an item from being used + * for attacking. + * + * @param material the material to set the cooldown for + * @param ticks the amount of ticks to set or 0 to remove + */ + public void setCooldown(@NotNull Material material, int ticks); + + /** + * Returns whether this player is slumbering. + * + * @return slumber state + */ + public boolean isSleeping(); + + /** + * Get the sleep ticks of the player. This value may be capped. + * + * @return slumber ticks + */ + public int getSleepTicks(); + + /** + * Gets the Location where the player will spawn at their bed, null if + * they have not slept in one or their current bed spawn is invalid. + * + * @return Bed Spawn Location if bed exists, otherwise null. + */ + @Nullable + public Location getBedSpawnLocation(); + + /** + * Sets the Location where the player will spawn at their bed. + * + * @param location where to set the respawn location + */ + public void setBedSpawnLocation(@Nullable Location location); + + /** + * Sets the Location where the player will spawn at their bed. + * + * @param location where to set the respawn location + * @param force whether to forcefully set the respawn location even if a + * valid bed is not present + */ + public void setBedSpawnLocation(@Nullable Location location, boolean force); + + /** + * Attempts to make the entity sleep at the given location. + *
+ * The location must be in the current world and have a bed placed at the + * location. The game may also enforce other requirements such as proximity + * to bed, monsters, and dimension type if force is not set. + * + * @param location the location of the bed + * @param force whether to try and sleep at the location even if not + * normally possible + * @return whether the sleep was successful + */ + public boolean sleep(@NotNull Location location, boolean force); + + /** + * Causes the player to wakeup if they are currently sleeping. + * + * @param setSpawnLocation whether to set their spawn location to the bed + * they are currently sleeping in + * @throws IllegalStateException if not sleeping + */ + public void wakeup(boolean setSpawnLocation); + + /** + * Gets the location of the bed the player is currently sleeping in + * + * @return location + * @throws IllegalStateException if not sleeping + */ + @NotNull + public Location getBedLocation(); + + /** + * Gets this human's current {@link GameMode} + * + * @return Current game mode + */ + @NotNull + public GameMode getGameMode(); + + /** + * Sets this human's current {@link GameMode} + * + * @param mode New game mode + */ + public void setGameMode(@NotNull GameMode mode); + + /** + * Check if the player is currently blocking (ie with a shield). + * + * @return Whether they are blocking. + */ + public boolean isBlocking(); + + /** + * Check if the player currently has their hand raised (ie about to begin + * blocking). + * + * @return Whether their hand is raised + */ + public boolean isHandRaised(); + + /** + * Get the total amount of experience required for the player to level + * + * @return Experience required to level up + */ + public int getExpToLevel(); + + // Paper start + /** + * If there is an Entity on this entities left shoulder, it will be released to the world and returned. + * If no Entity is released, null will be returned. + * + * @return The released entity, or null + */ + @Nullable + public Entity releaseLeftShoulderEntity(); + + /** + * If there is an Entity on this entities left shoulder, it will be released to the world and returned. + * If no Entity is released, null will be returned. + * + * @return The released entity, or null + */ + @Nullable + public Entity releaseRightShoulderEntity(); + // Paper end + + /** + * Discover a recipe for this player such that it has not already been + * discovered. This method will add the key's associated recipe to the + * player's recipe book. + * + * @param recipe the key of the recipe to discover + * + * @return whether or not the recipe was newly discovered + */ + public boolean discoverRecipe(@NotNull NamespacedKey recipe); + + /** + * Discover a collection of recipes for this player such that they have not + * already been discovered. This method will add the keys' associated + * recipes to the player's recipe book. If a recipe in the provided + * collection has already been discovered, it will be silently ignored. + * + * @param recipes the keys of the recipes to discover + * + * @return the amount of newly discovered recipes where 0 indicates that + * none were newly discovered and a number equal to {@code recipes.size()} + * indicates that all were new + */ + public int discoverRecipes(@NotNull Collection recipes); + + /** + * Undiscover a recipe for this player such that it has already been + * discovered. This method will remove the key's associated recipe from the + * player's recipe book. + * + * @param recipe the key of the recipe to undiscover + * + * @return whether or not the recipe was successfully undiscovered (i.e. it + * was previously discovered) + */ + public boolean undiscoverRecipe(@NotNull NamespacedKey recipe); + + /** + * Undiscover a collection of recipes for this player such that they have + * already been discovered. This method will remove the keys' associated + * recipes from the player's recipe book. If a recipe in the provided + * collection has not yet been discovered, it will be silently ignored. + * + * @param recipes the keys of the recipes to undiscover + * + * @return the amount of undiscovered recipes where 0 indicates that none + * were undiscovered and a number equal to {@code recipes.size()} indicates + * that all were undiscovered + */ + public int undiscoverRecipes(@NotNull Collection recipes); + + /** + * Gets the entity currently perched on the left shoulder or null if no + * entity. + *
+ * The returned entity will not be spawned within the world, so most + * operations are invalid unless the entity is first spawned in. + * + * @return left shoulder entity + * @deprecated There are currently no well defined semantics regarding + * serialized entities in Bukkit. Use with care. + */ + @Deprecated + @Nullable + public Entity getShoulderEntityLeft(); + + /** + * Sets the entity currently perched on the left shoulder, or null to + * remove. This method will remove the entity from the world. + *
+ * Note that only a copy of the entity will be set to display on the + * shoulder. + *
+ * Also note that the client will currently only render {@link Parrot} + * entities. + * + * @param entity left shoulder entity + * @deprecated There are currently no well defined semantics regarding + * serialized entities in Bukkit. Use with care. + */ + @Deprecated + public void setShoulderEntityLeft(@Nullable Entity entity); + + /** + * Gets the entity currently perched on the right shoulder or null if no + * entity. + *
+ * The returned entity will not be spawned within the world, so most + * operations are invalid unless the entity is first spawned in. + * + * @return right shoulder entity + * @deprecated There are currently no well defined semantics regarding + * serialized entities in Bukkit. Use with care. + */ + @Deprecated + @Nullable + public Entity getShoulderEntityRight(); + + /** + * Sets the entity currently perched on the right shoulder, or null to + * remove. This method will remove the entity from the world. + *
+ * Note that only a copy of the entity will be set to display on the + * shoulder. + *
+ * Also note that the client will currently only render {@link Parrot} + * entities. + * + * @param entity right shoulder entity + * @deprecated There are currently no well defined semantics regarding + * serialized entities in Bukkit. Use with care. + */ + @Deprecated + public void setShoulderEntityRight(@Nullable Entity entity); + + // Paper start - Add method to open already placed sign + /** + * Opens an editor window for the specified sign + * + * @param sign The sign to open + */ + void openSign(@NotNull org.bukkit.block.Sign sign); + // Paper end +} diff --git a/api/src/main/java/org/bukkit/entity/Husk.java b/api/src/main/java/org/bukkit/entity/Husk.java new file mode 100644 index 000000000..85ada1c67 --- /dev/null +++ b/api/src/main/java/org/bukkit/entity/Husk.java @@ -0,0 +1,41 @@ +package org.bukkit.entity; + +/** + * Represents a Husk - variant of {@link Zombie}. + */ +public interface Husk extends Zombie { + + /** + * Get if this entity is in the process of converting to a Zombie as a + * result of being underwater. + * + * @return conversion status + */ + @Override + boolean isConverting(); + + /** + * Gets the amount of ticks until this entity will be converted to a Zombie + * as a result of being underwater. + * + * When this reaches 0, the entity will be converted. + * + * @return conversion time + * @throws IllegalStateException if {@link #isConverting()} is false. + */ + @Override + int getConversionTime(); + + /** + * Sets the amount of ticks until this entity will be converted to a Zombie + * as a result of being underwater. + * + * When this reaches 0, the entity will be converted. A value of less than 0 + * will stop the current conversion process without converting the current + * entity. + * + * @param time new conversion time + */ + @Override + void setConversionTime(int time); +} diff --git a/api/src/main/java/org/bukkit/entity/Illager.java b/api/src/main/java/org/bukkit/entity/Illager.java new file mode 100644 index 000000000..b723fbac3 --- /dev/null +++ b/api/src/main/java/org/bukkit/entity/Illager.java @@ -0,0 +1,6 @@ +package org.bukkit.entity; + +/** + * Represents a type of "Illager". + */ +public interface Illager extends Monster { } diff --git a/api/src/main/java/org/bukkit/entity/Illusioner.java b/api/src/main/java/org/bukkit/entity/Illusioner.java new file mode 100644 index 000000000..14e6c5ee0 --- /dev/null +++ b/api/src/main/java/org/bukkit/entity/Illusioner.java @@ -0,0 +1,10 @@ +package org.bukkit.entity; + +import com.destroystokyo.paper.entity.RangedEntity; + +/** + * Represents an Illusioner "Illager". + */ +public interface Illusioner extends Spellcaster, RangedEntity { // Paper + +} diff --git a/api/src/main/java/org/bukkit/entity/IronGolem.java b/api/src/main/java/org/bukkit/entity/IronGolem.java new file mode 100644 index 000000000..655e37cb3 --- /dev/null +++ b/api/src/main/java/org/bukkit/entity/IronGolem.java @@ -0,0 +1,22 @@ +package org.bukkit.entity; + +/** + * An iron Golem that protects Villages. + */ +public interface IronGolem extends Golem { + + /** + * Gets whether this iron golem was built by a player. + * + * @return Whether this iron golem was built by a player + */ + public boolean isPlayerCreated(); + + /** + * Sets whether this iron golem was built by a player or not. + * + * @param playerCreated true if you want to set the iron golem as being + * player created, false if you want it to be a natural village golem. + */ + public void setPlayerCreated(boolean playerCreated); +} diff --git a/api/src/main/java/org/bukkit/entity/Item.java b/api/src/main/java/org/bukkit/entity/Item.java new file mode 100644 index 000000000..a15f70ff4 --- /dev/null +++ b/api/src/main/java/org/bukkit/entity/Item.java @@ -0,0 +1,90 @@ +package org.bukkit.entity; + +import org.bukkit.inventory.ItemStack; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +// Paper start +import java.util.UUID; +// Paper end + +/** + * Represents a dropped item. + */ +public interface Item extends Entity { + + /** + * Gets the item stack associated with this item drop. + * + * @return An item stack. + */ + @NotNull + public ItemStack getItemStack(); + + /** + * Sets the item stack associated with this item drop. + * + * @param stack An item stack. + */ + public void setItemStack(@Nullable ItemStack stack); + + /** + * Gets the delay before this Item is available to be picked up by players + * + * @return Remaining delay + */ + public int getPickupDelay(); + + /** + * Sets the delay before this Item is available to be picked up by players + * + * @param delay New delay + */ + public void setPickupDelay(int delay); + + // Paper Start + /** + * Gets if non-player entities can pick this Item up + * + * @return True if non-player entities can pickup + */ + public boolean canMobPickup(); + + /** + * Sets if non-player entities can pick this Item up + * + * @param canMobPickup True to allow non-player entity pickup + */ + public void setCanMobPickup(boolean canMobPickup); + + /** + * The owner of this item. Only the owner can pick up the item until it is within 10 seconds of despawning + * + * @return The owner's UUID + */ + @Nullable + public UUID getOwner(); + + /** + * Set the owner of this item. Only the owner can pick up the item until it is within 10 seconds of despawning + * + * @param owner The owner's UUID + */ + public void setOwner(@Nullable UUID owner); + + /** + * Get the thrower of this item. + * + * @return The thrower's UUID + */ + @Nullable + public UUID getThrower(); + + /** + * Set the thrower of this item. + * + * @param thrower The thrower's UUID + */ + public void setThrower(@Nullable UUID thrower); + // Paper end +} diff --git a/api/src/main/java/org/bukkit/entity/ItemFrame.java b/api/src/main/java/org/bukkit/entity/ItemFrame.java new file mode 100644 index 000000000..e876bd20a --- /dev/null +++ b/api/src/main/java/org/bukkit/entity/ItemFrame.java @@ -0,0 +1,51 @@ +package org.bukkit.entity; + +import org.bukkit.Rotation; +import org.bukkit.inventory.ItemStack; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +/** + * Represents an Item Frame + */ +public interface ItemFrame extends Hanging { + + /** + * Get the item in this frame + * + * @return a defensive copy the item in this item frame + */ + @NotNull + public ItemStack getItem(); + + /** + * Set the item in this frame + * + * @param item the new item + */ + public void setItem(@Nullable ItemStack item); + + /** + * Set the item in this frame + * + * @param item the new item + * @param playSound whether or not to play the item placement sound + */ + public void setItem(@Nullable ItemStack item, boolean playSound); + + /** + * Get the rotation of the frame's item + * + * @return the direction + */ + @NotNull + public Rotation getRotation(); + + /** + * Set the rotation of the frame's item + * + * @param rotation the new rotation + * @throws IllegalArgumentException if rotation is null + */ + public void setRotation(@NotNull Rotation rotation) throws IllegalArgumentException; +} diff --git a/api/src/main/java/org/bukkit/entity/LargeFireball.java b/api/src/main/java/org/bukkit/entity/LargeFireball.java new file mode 100644 index 000000000..fc3a1093c --- /dev/null +++ b/api/src/main/java/org/bukkit/entity/LargeFireball.java @@ -0,0 +1,7 @@ +package org.bukkit.entity; + +/** + * Represents a large {@link Fireball} + */ +public interface LargeFireball extends Fireball { +} diff --git a/api/src/main/java/org/bukkit/entity/LeashHitch.java b/api/src/main/java/org/bukkit/entity/LeashHitch.java new file mode 100644 index 000000000..9ac04c182 --- /dev/null +++ b/api/src/main/java/org/bukkit/entity/LeashHitch.java @@ -0,0 +1,7 @@ +package org.bukkit.entity; + +/** + * Represents a Leash Hitch on a fence + */ +public interface LeashHitch extends Hanging { +} diff --git a/api/src/main/java/org/bukkit/entity/LightningStrike.java b/api/src/main/java/org/bukkit/entity/LightningStrike.java new file mode 100644 index 000000000..cbc5ee8fe --- /dev/null +++ b/api/src/main/java/org/bukkit/entity/LightningStrike.java @@ -0,0 +1,36 @@ +package org.bukkit.entity; + +import org.jetbrains.annotations.NotNull; + +/** + * Represents an instance of a lightning strike. May or may not do damage. + */ +public interface LightningStrike extends Weather { + + /** + * Returns whether the strike is an effect that does no damage. + * + * @return whether the strike is an effect + */ + public boolean isEffect(); + + // Spigot start + public class Spigot extends Entity.Spigot + { + + /* + * Returns whether the strike is silent. + * + * @return whether the strike is silent. + */ + public boolean isSilent() + { + throw new UnsupportedOperationException( "Not supported yet." ); + } + } + + @NotNull + @Override + Spigot spigot(); + // Spigot end +} diff --git a/api/src/main/java/org/bukkit/entity/LingeringPotion.java b/api/src/main/java/org/bukkit/entity/LingeringPotion.java new file mode 100644 index 000000000..5aca7cb84 --- /dev/null +++ b/api/src/main/java/org/bukkit/entity/LingeringPotion.java @@ -0,0 +1,6 @@ +package org.bukkit.entity; + +/** + * Represents a thrown lingering potion bottle + */ +public interface LingeringPotion extends ThrownPotion { } diff --git a/api/src/main/java/org/bukkit/entity/LivingEntity.java b/api/src/main/java/org/bukkit/entity/LivingEntity.java new file mode 100644 index 000000000..956d68867 --- /dev/null +++ b/api/src/main/java/org/bukkit/entity/LivingEntity.java @@ -0,0 +1,652 @@ +package org.bukkit.entity; + +import java.util.Collection; +import java.util.List; +import java.util.Set; + +import org.bukkit.FluidCollisionMode; +import org.bukkit.Location; +import org.bukkit.Material; +import org.bukkit.World; +import org.bukkit.attribute.Attributable; +import org.bukkit.block.Block; +import org.bukkit.inventory.EntityEquipment; +import org.bukkit.inventory.ItemStack; +import org.bukkit.potion.PotionEffect; +import org.bukkit.potion.PotionEffectType; +import org.bukkit.projectiles.ProjectileSource; +import org.bukkit.util.RayTraceResult; +import org.bukkit.util.Vector; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +/** + * Represents a living entity, such as a monster or player + */ +public interface LivingEntity extends Attributable, Damageable, ProjectileSource { + + /** + * Gets the height of the living entity's eyes above its Location. + * + * @return height of the living entity's eyes above its location + */ + public double getEyeHeight(); + + /** + * Gets the height of the living entity's eyes above its Location. + * + * @param ignorePose if set to true, the effects of pose changes, eg + * sneaking and gliding will be ignored + * @return height of the living entity's eyes above its location + */ + public double getEyeHeight(boolean ignorePose); + + /** + * Get a Location detailing the current eye position of the living entity. + * + * @return a location at the eyes of the living entity + */ + @NotNull + public Location getEyeLocation(); + + /** + * Gets all blocks along the living entity's line of sight. + *

+ * This list contains all blocks from the living entity's eye position to + * target inclusive. This method considers all blocks as 1x1x1 in size. + * + * @param transparent HashSet containing all transparent block Materials (set to + * null for only air) + * @param maxDistance this is the maximum distance to scan (may be limited + * by server by at least 100 blocks, no less) + * @return list containing all blocks along the living entity's line of + * sight + */ + @NotNull + public List getLineOfSight(@Nullable Set transparent, int maxDistance); + + /** + * Gets the block that the living entity has targeted. + *

+ * This method considers all blocks as 1x1x1 in size. To take exact block + * collision shapes into account, see {@link #getTargetBlockExact(int, + * FluidCollisionMode)}. + * + * @param transparent HashSet containing all transparent block Materials (set to + * null for only air) + * @param maxDistance this is the maximum distance to scan (may be limited + * by server by at least 100 blocks, no less) + * @return block that the living entity has targeted + */ + @NotNull + public Block getTargetBlock(@Nullable Set transparent, int maxDistance); + + // Paper start + /** + * Gets the block that the living entity has targeted, ignoring fluids + * + * @param maxDistance this is the maximum distance to scan + * @return block that the living entity has targeted, + * or null if no block is within maxDistance + */ + @Nullable + public default Block getTargetBlock(int maxDistance) { + return getTargetBlock(maxDistance, com.destroystokyo.paper.block.TargetBlockInfo.FluidMode.NEVER); + } + + /** + * Gets the block that the living entity has targeted + * + * @param maxDistance this is the maximum distance to scan + * @param fluidMode whether to check fluids or not + * @return block that the living entity has targeted, + * or null if no block is within maxDistance + */ + @Nullable + public Block getTargetBlock(int maxDistance, @NotNull com.destroystokyo.paper.block.TargetBlockInfo.FluidMode fluidMode); + + /** + * Gets the blockface of that block that the living entity has targeted, ignoring fluids + * + * @param maxDistance this is the maximum distance to scan + * @return blockface of the block that the living entity has targeted, + * or null if no block is targeted + */ + @Nullable + public default org.bukkit.block.BlockFace getTargetBlockFace(int maxDistance) { + return getTargetBlockFace(maxDistance, com.destroystokyo.paper.block.TargetBlockInfo.FluidMode.NEVER); + } + + /** + * Gets the blockface of that block that the living entity has targeted + * + * @param maxDistance this is the maximum distance to scan + * @param fluidMode whether to check fluids or not + * @return blockface of the block that the living entity has targeted, + * or null if no block is targeted + */ + @Nullable + public org.bukkit.block.BlockFace getTargetBlockFace(int maxDistance, @NotNull com.destroystokyo.paper.block.TargetBlockInfo.FluidMode fluidMode); + + /** + * Gets information about the block the living entity has targeted, ignoring fluids + * + * @param maxDistance this is the maximum distance to scan + * @return TargetBlockInfo about the block the living entity has targeted, + * or null if no block is targeted + */ + @Nullable + public default com.destroystokyo.paper.block.TargetBlockInfo getTargetBlockInfo(int maxDistance) { + return getTargetBlockInfo(maxDistance, com.destroystokyo.paper.block.TargetBlockInfo.FluidMode.NEVER); + } + + /** + * Gets information about the block the living entity has targeted + * + * @param maxDistance this is the maximum distance to scan + * @param fluidMode whether to check fluids or not + * @return TargetBlockInfo about the block the living entity has targeted, + * or null if no block is targeted + */ + @Nullable + public com.destroystokyo.paper.block.TargetBlockInfo getTargetBlockInfo(int maxDistance, @NotNull com.destroystokyo.paper.block.TargetBlockInfo.FluidMode fluidMode); + + /** + * Gets information about the entity being targeted + * + * @param maxDistance this is the maximum distance to scan + * @return entity being targeted, or null if no entity is targeted + */ + @Nullable + public default Entity getTargetEntity(int maxDistance) { + return getTargetEntity(maxDistance, false); + } + + /** + * Gets information about the entity being targeted + * + * @param maxDistance this is the maximum distance to scan + * @param ignoreBlocks true to scan through blocks + * @return entity being targeted, or null if no entity is targeted + */ + @Nullable + public Entity getTargetEntity(int maxDistance, boolean ignoreBlocks); + + /** + * Gets information about the entity being targeted + * + * @param maxDistance this is the maximum distance to scan + * @return TargetEntityInfo about the entity being targeted, + * or null if no entity is targeted + */ + @Nullable + public default com.destroystokyo.paper.entity.TargetEntityInfo getTargetEntityInfo(int maxDistance) { + return getTargetEntityInfo(maxDistance, false); + } + + /** + * Gets information about the entity being targeted + * + * @param maxDistance this is the maximum distance to scan + * @param ignoreBlocks true to scan through blocks + * @return TargetEntityInfo about the entity being targeted, + * or null if no entity is targeted + */ + @Nullable + public com.destroystokyo.paper.entity.TargetEntityInfo getTargetEntityInfo(int maxDistance, boolean ignoreBlocks); + // Paper end + + /** + * Gets the last two blocks along the living entity's line of sight. + *

+ * The target block will be the last block in the list. This method + * considers all blocks as 1x1x1 in size. + * + * @param transparent HashSet containing all transparent block Materials (set to + * null for only air) + * @param maxDistance this is the maximum distance to scan. This may be + * further limited by the server, but never to less than 100 blocks + * @return list containing the last 2 blocks along the living entity's + * line of sight + */ + @NotNull + public List getLastTwoTargetBlocks(@Nullable Set transparent, int maxDistance); + + /** + * Gets the block that the living entity has targeted. + *

+ * This takes the blocks' precise collision shapes into account. Fluids are + * ignored. + *

+ * This may cause loading of chunks! Some implementations may impose + * artificial restrictions on the maximum distance. + * + * @param maxDistance the maximum distance to scan + * @return block that the living entity has targeted + * @see #getTargetBlockExact(int, org.bukkit.FluidCollisionMode) + */ + @Nullable + public Block getTargetBlockExact(int maxDistance); + + /** + * Gets the block that the living entity has targeted. + *

+ * This takes the blocks' precise collision shapes into account. + *

+ * This may cause loading of chunks! Some implementations may impose + * artificial restrictions on the maximum distance. + * + * @param maxDistance the maximum distance to scan + * @param fluidCollisionMode the fluid collision mode + * @return block that the living entity has targeted + * @see #rayTraceBlocks(double, FluidCollisionMode) + */ + @Nullable + public Block getTargetBlockExact(int maxDistance, @NotNull FluidCollisionMode fluidCollisionMode); + + /** + * Performs a ray trace that provides information on the targeted block. + *

+ * This takes the blocks' precise collision shapes into account. Fluids are + * ignored. + *

+ * This may cause loading of chunks! Some implementations may impose + * artificial restrictions on the maximum distance. + * + * @param maxDistance the maximum distance to scan + * @return information on the targeted block, or null if there + * is no targeted block in range + * @see #rayTraceBlocks(double, FluidCollisionMode) + */ + @Nullable + public RayTraceResult rayTraceBlocks(double maxDistance); + + /** + * Performs a ray trace that provides information on the targeted block. + *

+ * This takes the blocks' precise collision shapes into account. + *

+ * This may cause loading of chunks! Some implementations may impose + * artificial restrictions on the maximum distance. + * + * @param maxDistance the maximum distance to scan + * @param fluidCollisionMode the fluid collision mode + * @return information on the targeted block, or null if there + * is no targeted block in range + * @see World#rayTraceBlocks(Location, Vector, double, FluidCollisionMode) + */ + @Nullable + public RayTraceResult rayTraceBlocks(double maxDistance, @NotNull FluidCollisionMode fluidCollisionMode); + + /** + * Returns the amount of air that the living entity has remaining, in + * ticks. + * + * @return amount of air remaining + */ + public int getRemainingAir(); + + /** + * Sets the amount of air that the living entity has remaining, in ticks. + * + * @param ticks amount of air remaining + */ + public void setRemainingAir(int ticks); + + /** + * Returns the maximum amount of air the living entity can have, in ticks. + * + * @return maximum amount of air + */ + public int getMaximumAir(); + + /** + * Sets the maximum amount of air the living entity can have, in ticks. + * + * @param ticks maximum amount of air + */ + public void setMaximumAir(int ticks); + + /** + * Returns the living entity's current maximum no damage ticks. + *

+ * This is the maximum duration in which the living entity will not take + * damage. + * + * @return maximum no damage ticks + */ + public int getMaximumNoDamageTicks(); + + /** + * Sets the living entity's current maximum no damage ticks. + * + * @param ticks maximum amount of no damage ticks + */ + public void setMaximumNoDamageTicks(int ticks); + + /** + * Returns the living entity's last damage taken in the current no damage + * ticks time. + *

+ * Only damage higher than this amount will further damage the living + * entity. + * + * @return damage taken since the last no damage ticks time period + */ + public double getLastDamage(); + + /** + * Sets the damage dealt within the current no damage ticks time period. + * + * @param damage amount of damage + */ + public void setLastDamage(double damage); + + /** + * Returns the living entity's current no damage ticks. + * + * @return amount of no damage ticks + */ + public int getNoDamageTicks(); + + /** + * Sets the living entity's current no damage ticks. + * + * @param ticks amount of no damage ticks + */ + public void setNoDamageTicks(int ticks); + + /** + * Gets the player identified as the killer of the living entity. + *

+ * May be null. + * + * @return killer player, or null if none found + */ + @Nullable + public Player getKiller(); + + // Paper start + /** + * Sets the player identified as the killer of the living entity. + * + * @param killer player + */ + public void setKiller(@Nullable Player killer); + // Paper end + + /** + * Adds the given {@link PotionEffect} to the living entity. + *

+ * Only one potion effect can be present for a given {@link + * PotionEffectType}. + * + * @param effect PotionEffect to be added + * @return whether the effect could be added + */ + public boolean addPotionEffect(@NotNull PotionEffect effect); + + /** + * Adds the given {@link PotionEffect} to the living entity. + *

+ * Only one potion effect can be present for a given {@link + * PotionEffectType}. + * + * @param effect PotionEffect to be added + * @param force whether conflicting effects should be removed + * @return whether the effect could be added + */ + public boolean addPotionEffect(@NotNull PotionEffect effect, boolean force); + + /** + * Attempts to add all of the given {@link PotionEffect} to the living + * entity. + * + * @param effects the effects to add + * @return whether all of the effects could be added + */ + public boolean addPotionEffects(@NotNull Collection effects); + + /** + * Returns whether the living entity already has an existing effect of + * the given {@link PotionEffectType} applied to it. + * + * @param type the potion type to check + * @return whether the living entity has this potion effect active on them + */ + public boolean hasPotionEffect(@NotNull PotionEffectType type); + + /** + * Returns the active {@link PotionEffect} of the specified type. + *

+ * If the effect is not present on the entity then null will be returned. + * + * @param type the potion type to check + * @return the effect active on this entity, or null if not active. + */ + @Nullable + public PotionEffect getPotionEffect(@NotNull PotionEffectType type); + + /** + * Removes any effects present of the given {@link PotionEffectType}. + * + * @param type the potion type to remove + */ + public void removePotionEffect(@NotNull PotionEffectType type); + + /** + * Returns all currently active {@link PotionEffect}s on the living + * entity. + * + * @return a collection of {@link PotionEffect}s + */ + @NotNull + public Collection getActivePotionEffects(); + + /** + * Checks whether the living entity has block line of sight to another. + *

+ * This uses the same algorithm that hostile mobs use to find the closest + * player. + * + * @param other the entity to determine line of sight to + * @return true if there is a line of sight, false if not + */ + public boolean hasLineOfSight(@NotNull Entity other); + + /** + * Returns if the living entity despawns when away from players or not. + *

+ * By default, animals are not removed while other mobs are. + * + * @return true if the living entity is removed when away from players + */ + public boolean getRemoveWhenFarAway(); + + /** + * Sets whether or not the living entity despawns when away from players + * or not. + * + * @param remove the removal status + */ + public void setRemoveWhenFarAway(boolean remove); + + /** + * Gets the inventory with the equipment worn by the living entity. + * + * @return the living entity's inventory + */ + @Nullable + public EntityEquipment getEquipment(); + + /** + * Sets whether or not the living entity can pick up items. + * + * @param pickup whether or not the living entity can pick up items + */ + public void setCanPickupItems(boolean pickup); + + /** + * Gets if the living entity can pick up items. + * + * @return whether or not the living entity can pick up items + */ + public boolean getCanPickupItems(); + + /** + * Returns whether the entity is currently leashed. + * + * @return whether the entity is leashed + */ + public boolean isLeashed(); + + /** + * Gets the entity that is currently leading this entity. + * + * @return the entity holding the leash + * @throws IllegalStateException if not currently leashed + */ + @NotNull + public Entity getLeashHolder() throws IllegalStateException; + + /** + * Sets the leash on this entity to be held by the supplied entity. + *

+ * This method has no effect on EnderDragons, Withers, Players, or Bats. + * Non-living entities excluding leashes will not persist as leash + * holders. + * + * @param holder the entity to leash this entity to, or null to unleash + * @return whether the operation was successful + */ + public boolean setLeashHolder(@Nullable Entity holder); + + /** + * Checks to see if an entity is gliding, such as using an Elytra. + * @return True if this entity is gliding. + */ + public boolean isGliding(); + + /** + * Makes entity start or stop gliding. This will work even if an Elytra + * is not equipped, but will be reverted by the server immediately after + * unless an event-cancelling mechanism is put in place. + * @param gliding True if the entity is gliding. + */ + public void setGliding(boolean gliding); + + /** + * Checks to see if an entity is swimming. + * + * @return True if this entity is swimming. + */ + public boolean isSwimming(); + + /** + * Makes entity start or stop swimming. + * + * This may have unexpected results if the entity is not in water. + * + * @param swimming True if the entity is swimming. + */ + public void setSwimming(boolean swimming); + + /** + * Checks to see if an entity is currently using the Riptide enchantment. + * + * @return True if this entity is currently riptiding. + */ + public boolean isRiptiding(); + + /** + * Sets whether an entity will have AI. + * + * @param ai whether the mob will have AI or not. + */ + void setAI(boolean ai); + + /** + * Checks whether an entity has AI. + * + * @return true if the entity has AI, otherwise false. + */ + boolean hasAI(); + + /** + * Set if this entity will be subject to collisions other entities. + *

+ * Note that collisions are bidirectional, so this method would need to be + * set to false on both the collidee and the collidant to ensure no + * collisions take place. + * + * @param collidable collision status + */ + void setCollidable(boolean collidable); + + /** + * Gets if this entity is subject to collisions with other entities. + *

+ * Please note that this method returns only the custom collidable state, + * not whether the entity is non-collidable for other reasons such as being + * dead. + * + * @return collision status + */ + boolean isCollidable(); + + // Paper start + /** + * Get the number of arrows stuck in this entity + * @return Number of arrows stuck + */ + int getArrowsStuck(); + + /** + * Set the number of arrows stuck in this entity + * + * @param arrows Number of arrows to stick in this entity + */ + void setArrowsStuck(int arrows); + + /** + * Get the delay (in ticks) before blocking is effective for this entity + * + * @return Delay in ticks + */ + int getShieldBlockingDelay(); + + /** + * Set the delay (in ticks) before blocking is effective for this entity + * + * @param delay Delay in ticks + */ + void setShieldBlockingDelay(int delay); + + /** + * Get's the item being actively "used" or consumed. + * @return The item. Will be null if no active item. + */ + @Nullable + ItemStack getActiveItem(); + + /** + * Get's remaining time a player needs to keep hands raised with an item to finish using it. + * @return Remaining ticks to use the item + */ + int getItemUseRemainingTime(); + + /** + * Get how long the players hands have been raised (Charging Bow attack, using a potion, etc) + * + * @return Get how long the players hands have been raised (Charging Bow attack, using a potion, etc) + */ + int getHandRaisedTime(); + + /** + * Whether or not this entity is using or charging an attack (Bow pulled back, drinking potion, eating food) + * + * @return Whether or not this entity is using or charging an attack (Bow pulled back, drinking potion, eating food) + */ + boolean isHandRaised(); + // Paper end +} diff --git a/api/src/main/java/org/bukkit/entity/Llama.java b/api/src/main/java/org/bukkit/entity/Llama.java new file mode 100644 index 000000000..d23226ccb --- /dev/null +++ b/api/src/main/java/org/bukkit/entity/Llama.java @@ -0,0 +1,70 @@ +package org.bukkit.entity; + +import com.destroystokyo.paper.entity.RangedEntity; +import org.bukkit.inventory.LlamaInventory; +import org.jetbrains.annotations.NotNull; + +/** + * Represents a Llama. + */ +public interface Llama extends ChestedHorse, RangedEntity { // Paper + + /** + * Represents the base color that the llama has. + */ + public enum Color { + + /** + * A cream-colored llama. + */ + CREAMY, + /** + * A white llama. + */ + WHITE, + /** + * A brown llama. + */ + BROWN, + /** + * A gray llama. + */ + GRAY; + } + + /** + * Gets the llama's color. + * + * @return a {@link Color} representing the llama's color + */ + @NotNull + Color getColor(); + + /** + * Sets the llama's color. + * + * @param color a {@link Color} for this llama + */ + void setColor(@NotNull Color color); + + /** + * Gets the llama's strength. A higher strength llama will have more + * inventory slots and be more threatening to entities. + * + * @return llama strength [1,5] + */ + int getStrength(); + + /** + * Sets the llama's strength. A higher strength llama will have more + * inventory slots and be more threatening to entities. Inventory slots are + * equal to strength * 3. + * + * @param strength llama strength [1,5] + */ + void setStrength(int strength); + + @NotNull + @Override + LlamaInventory getInventory(); +} diff --git a/api/src/main/java/org/bukkit/entity/LlamaSpit.java b/api/src/main/java/org/bukkit/entity/LlamaSpit.java new file mode 100644 index 000000000..9890dffe7 --- /dev/null +++ b/api/src/main/java/org/bukkit/entity/LlamaSpit.java @@ -0,0 +1,6 @@ +package org.bukkit.entity; + +/** + * Represents Llama spit. + */ +public interface LlamaSpit extends Projectile { } diff --git a/api/src/main/java/org/bukkit/entity/MagmaCube.java b/api/src/main/java/org/bukkit/entity/MagmaCube.java new file mode 100644 index 000000000..714b4426d --- /dev/null +++ b/api/src/main/java/org/bukkit/entity/MagmaCube.java @@ -0,0 +1,7 @@ +package org.bukkit.entity; + +/** + * Represents a MagmaCube. + */ +public interface MagmaCube extends Slime { +} diff --git a/api/src/main/java/org/bukkit/entity/Minecart.java b/api/src/main/java/org/bukkit/entity/Minecart.java new file mode 100644 index 000000000..5a07493ad --- /dev/null +++ b/api/src/main/java/org/bukkit/entity/Minecart.java @@ -0,0 +1,146 @@ +package org.bukkit.entity; + +import org.bukkit.block.data.BlockData; +import org.bukkit.material.MaterialData; +import org.bukkit.util.Vector; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +/** + * Represents a minecart entity. + */ +public interface Minecart extends Vehicle { + + /** + * Sets a minecart's damage. + * + * @param damage over 40 to "kill" a minecart + */ + public void setDamage(double damage); + + /** + * Gets a minecart's damage. + * + * @return The damage + */ + public double getDamage(); + + /** + * Gets the maximum speed of a minecart. The speed is unrelated to the + * velocity. + * + * @return The max speed + */ + public double getMaxSpeed(); + + /** + * Sets the maximum speed of a minecart. Must be nonnegative. Default is + * 0.4D. + * + * @param speed The max speed + */ + public void setMaxSpeed(double speed); + + /** + * Returns whether this minecart will slow down faster without a passenger + * occupying it + * + * @return Whether it decelerates faster + */ + public boolean isSlowWhenEmpty(); + + /** + * Sets whether this minecart will slow down faster without a passenger + * occupying it + * + * @param slow Whether it will decelerate faster + */ + public void setSlowWhenEmpty(boolean slow); + + /** + * Gets the flying velocity modifier. Used for minecarts that are in + * mid-air. A flying minecart's velocity is multiplied by this factor each + * tick. + * + * @return The vector factor + */ + @NotNull + public Vector getFlyingVelocityMod(); + + /** + * Sets the flying velocity modifier. Used for minecarts that are in + * mid-air. A flying minecart's velocity is multiplied by this factor each + * tick. + * + * @param flying velocity modifier vector + */ + public void setFlyingVelocityMod(@NotNull Vector flying); + + /** + * Gets the derailed velocity modifier. Used for minecarts that are on the + * ground, but not on rails. + *

+ * A derailed minecart's velocity is multiplied by this factor each tick. + * + * @return derailed visible speed + */ + @NotNull + public Vector getDerailedVelocityMod(); + + /** + * Sets the derailed velocity modifier. Used for minecarts that are on the + * ground, but not on rails. A derailed minecart's velocity is multiplied + * by this factor each tick. + * + * @param derailed visible speed + */ + public void setDerailedVelocityMod(@NotNull Vector derailed); + + /** + * Sets the display block for this minecart. + * Passing a null value will set the minecart to have no display block. + * + * @param material the material to set as display block. + */ + public void setDisplayBlock(@Nullable MaterialData material); + + /** + * Gets the display block for this minecart. + * This function will return the type AIR if none is set. + * + * @return the block displayed by this minecart. + */ + @NotNull + public MaterialData getDisplayBlock(); + + /** + * Sets the display block for this minecart. + * Passing a null value will set the minecart to have no display block. + * + * @param blockData the material to set as display block. + */ + public void setDisplayBlockData(@Nullable BlockData blockData); + + /** + * Gets the display block for this minecart. + * This function will return the type AIR if none is set. + * + * @return the block displayed by this minecart. + */ + @NotNull + public BlockData getDisplayBlockData(); + + /** + * Sets the offset of the display block. + * + * @param offset the block offset to set for this minecart. + */ + public void setDisplayBlockOffset(int offset); + + /** + * Gets the offset of the display block. + * + * @return the current block offset for this minecart. + */ + public int getDisplayBlockOffset(); +} diff --git a/api/src/main/java/org/bukkit/entity/Mob.java b/api/src/main/java/org/bukkit/entity/Mob.java new file mode 100644 index 000000000..784db447d --- /dev/null +++ b/api/src/main/java/org/bukkit/entity/Mob.java @@ -0,0 +1,45 @@ +package org.bukkit.entity; + +import org.bukkit.loot.Lootable; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +/** + * Represents a Mob. Mobs are living entities with simple AI. + */ +public interface Mob extends LivingEntity, Lootable { + + // Paper start + /** + * Enables access to control the pathing of an Entity + * @return Pathfinding Manager for this entity + */ + @NotNull + com.destroystokyo.paper.entity.Pathfinder getPathfinder(); + + /** + * Check if this mob is exposed to daylight + * + * @return True if mob is exposed to daylight + */ + boolean isInDaylight(); + // Paper end + + /** + * Instructs this Mob to set the specified LivingEntity as its target. + *

+ * Hostile creatures may attack their target, and friendly creatures may + * follow their target. + * + * @param target New LivingEntity to target, or null to clear the target + */ + public void setTarget(@Nullable LivingEntity target); + + /** + * Gets the current target of this Mob + * + * @return Current target of this creature, or null if none exists + */ + @Nullable + public LivingEntity getTarget(); +} diff --git a/api/src/main/java/org/bukkit/entity/Monster.java b/api/src/main/java/org/bukkit/entity/Monster.java new file mode 100644 index 000000000..fce2efd82 --- /dev/null +++ b/api/src/main/java/org/bukkit/entity/Monster.java @@ -0,0 +1,6 @@ +package org.bukkit.entity; + +/** + * Represents a Monster. + */ +public interface Monster extends Creature {} diff --git a/api/src/main/java/org/bukkit/entity/Mule.java b/api/src/main/java/org/bukkit/entity/Mule.java new file mode 100644 index 000000000..4f5efb36b --- /dev/null +++ b/api/src/main/java/org/bukkit/entity/Mule.java @@ -0,0 +1,6 @@ +package org.bukkit.entity; + +/** + * Represents a Mule - variant of {@link ChestedHorse}. + */ +public interface Mule extends ChestedHorse { } diff --git a/api/src/main/java/org/bukkit/entity/MushroomCow.java b/api/src/main/java/org/bukkit/entity/MushroomCow.java new file mode 100644 index 000000000..84154de1a --- /dev/null +++ b/api/src/main/java/org/bukkit/entity/MushroomCow.java @@ -0,0 +1,8 @@ +package org.bukkit.entity; + +/** + * Represents a mushroom {@link Cow} + */ +public interface MushroomCow extends Cow { + +} diff --git a/api/src/main/java/org/bukkit/entity/NPC.java b/api/src/main/java/org/bukkit/entity/NPC.java new file mode 100644 index 000000000..0c6b175b5 --- /dev/null +++ b/api/src/main/java/org/bukkit/entity/NPC.java @@ -0,0 +1,8 @@ +package org.bukkit.entity; + +/** + * Represents a non-player character + */ +public interface NPC extends Creature { + +} diff --git a/api/src/main/java/org/bukkit/entity/Ocelot.java b/api/src/main/java/org/bukkit/entity/Ocelot.java new file mode 100644 index 000000000..fe75169e5 --- /dev/null +++ b/api/src/main/java/org/bukkit/entity/Ocelot.java @@ -0,0 +1,73 @@ + +package org.bukkit.entity; + +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +/** + * A wild tameable cat + */ +public interface Ocelot extends Animals, Tameable, Sittable { + + /** + * Gets the current type of this cat. + * + * @return Type of the cat. + */ + @NotNull + public Type getCatType(); + + /** + * Sets the current type of this cat. + * + * @param type New type of this cat. + */ + public void setCatType(@NotNull Type type); + + /** + * Represents the various different cat types there are. + */ + public enum Type { + WILD_OCELOT(0), + BLACK_CAT(1), + RED_CAT(2), + SIAMESE_CAT(3); + + private static final Type[] types = new Type[Type.values().length]; + private final int id; + + static { + for (Type type : values()) { + types[type.getId()] = type; + } + } + + private Type(int id) { + this.id = id; + } + + /** + * Gets the ID of this cat type. + * + * @return Type ID. + * @deprecated Magic value + */ + @Deprecated + public int getId() { + return id; + } + + /** + * Gets a cat type by its ID. + * + * @param id ID of the cat type to get. + * @return Resulting type, or null if not found. + * @deprecated Magic value + */ + @Deprecated + @Nullable + public static Type getType(int id) { + return (id >= types.length) ? null : types[id]; + } + } +} diff --git a/api/src/main/java/org/bukkit/entity/Painting.java b/api/src/main/java/org/bukkit/entity/Painting.java new file mode 100644 index 000000000..19e958201 --- /dev/null +++ b/api/src/main/java/org/bukkit/entity/Painting.java @@ -0,0 +1,41 @@ +package org.bukkit.entity; + +import org.bukkit.Art; +import org.bukkit.event.hanging.HangingBreakEvent; +import org.jetbrains.annotations.NotNull; + +/** + * Represents a Painting. + */ +public interface Painting extends Hanging { + + /** + * Get the art on this painting + * + * @return The art + */ + @NotNull + public Art getArt(); + + /** + * Set the art on this painting + * + * @param art The new art + * @return False if the new art won't fit at the painting's current + * location + */ + public boolean setArt(@NotNull Art art); + + /** + * Set the art on this painting + * + * @param art The new art + * @param force If true, force the new art regardless of whether it fits + * at the current location. Note that forcing it where it can't fit + * normally causes it to drop as an item unless you override this by + * catching the {@link HangingBreakEvent}. + * @return False if force was false and the new art won't fit at the + * painting's current location + */ + public boolean setArt(@NotNull Art art, boolean force); +} diff --git a/api/src/main/java/org/bukkit/entity/Parrot.java b/api/src/main/java/org/bukkit/entity/Parrot.java new file mode 100644 index 000000000..95d95cdcc --- /dev/null +++ b/api/src/main/java/org/bukkit/entity/Parrot.java @@ -0,0 +1,50 @@ +package org.bukkit.entity; + +import org.jetbrains.annotations.NotNull; + +/** + * Represents a Parrot. + */ +public interface Parrot extends Animals, Tameable, Sittable { + + /** + * Get the variant of this parrot. + * + * @return parrot variant + */ + @NotNull + public Variant getVariant(); + + /** + * Set the variant of this parrot. + * + * @param variant parrot variant + */ + public void setVariant(@NotNull Variant variant); + + /** + * Represents the variant of a parrot - ie its color. + */ + public enum Variant { + /** + * Classic parrot - red with colored wingtips. + */ + RED, + /** + * Royal blue colored parrot. + */ + BLUE, + /** + * Green colored parrot. + */ + GREEN, + /** + * Cyan colored parrot. + */ + CYAN, + /** + * Gray colored parrot. + */ + GRAY; + } +} diff --git a/api/src/main/java/org/bukkit/entity/Phantom.java b/api/src/main/java/org/bukkit/entity/Phantom.java new file mode 100644 index 000000000..ed4d417c2 --- /dev/null +++ b/api/src/main/java/org/bukkit/entity/Phantom.java @@ -0,0 +1,30 @@ +package org.bukkit.entity; + +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +/** + * Represents a phantom. + */ +public interface Phantom extends Flying { + + /** + * @return The size of the phantom + */ + public int getSize(); + + /** + * @param sz The new size of the phantom. + */ + public void setSize(int sz); + + // Paper start + /** + * Get the UUID of the entity that caused this phantom to spawn + * + * @return UUID + */ + @Nullable + public java.util.UUID getSpawningEntity(); + // Paper end +} diff --git a/api/src/main/java/org/bukkit/entity/Pig.java b/api/src/main/java/org/bukkit/entity/Pig.java new file mode 100644 index 000000000..28f59f2c2 --- /dev/null +++ b/api/src/main/java/org/bukkit/entity/Pig.java @@ -0,0 +1,21 @@ +package org.bukkit.entity; + +/** + * Represents a Pig. + */ +public interface Pig extends Animals, Vehicle { + + /** + * Check if the pig has a saddle. + * + * @return if the pig has been saddled. + */ + public boolean hasSaddle(); + + /** + * Sets if the pig has a saddle or not + * + * @param saddled set if the pig has a saddle or not. + */ + public void setSaddle(boolean saddled); +} diff --git a/api/src/main/java/org/bukkit/entity/PigZombie.java b/api/src/main/java/org/bukkit/entity/PigZombie.java new file mode 100644 index 000000000..ae9eaaa8e --- /dev/null +++ b/api/src/main/java/org/bukkit/entity/PigZombie.java @@ -0,0 +1,60 @@ +package org.bukkit.entity; + +/** + * Represents a Pig Zombie. + */ +public interface PigZombie extends Zombie { + + /** + * Get the pig zombie's current anger level. + * + * @return The anger level. + */ + int getAnger(); + + /** + * Set the pig zombie's current anger level. + * + * @param level The anger level. Higher levels of anger take longer to + * wear off. + */ + void setAnger(int level); + + /** + * Shorthand; sets to either 0 or the default level. + * + * @param angry Whether the zombie should be angry. + */ + void setAngry(boolean angry); + + /** + * Shorthand; gets whether the zombie is angry. + * + * @return True if the zombie is angry, otherwise false. + */ + boolean isAngry(); + + /** + * Not applicable to this entity + * + * @return false + */ + @Override + public boolean isConverting(); + + /** + * Not applicable to this entity + * + * @return UnsuppotedOperationException + */ + @Override + public int getConversionTime(); + + /** + * Not applicable to this entity + * + * @param time unused + */ + @Override + public void setConversionTime(int time); +} diff --git a/api/src/main/java/org/bukkit/entity/Player.java b/api/src/main/java/org/bukkit/entity/Player.java new file mode 100644 index 000000000..1b3a6a91c --- /dev/null +++ b/api/src/main/java/org/bukkit/entity/Player.java @@ -0,0 +1,2064 @@ +package org.bukkit.entity; + +import java.net.InetSocketAddress; +import java.util.Date; + +import com.destroystokyo.paper.Title; +import com.destroystokyo.paper.profile.PlayerProfile; +import org.bukkit.Achievement; +import org.bukkit.BanEntry; +import org.bukkit.BanList; +import org.bukkit.Bukkit; +import org.bukkit.ChatColor; +import org.bukkit.Effect; +import org.bukkit.GameMode; +import org.bukkit.Instrument; +import org.bukkit.Location; +import org.bukkit.Material; +import org.bukkit.Note; +import org.bukkit.OfflinePlayer; +import org.bukkit.Particle; +import org.bukkit.Sound; +import org.bukkit.SoundCategory; +import org.bukkit.Statistic; +import org.bukkit.WeatherType; +import org.bukkit.advancement.Advancement; +import org.bukkit.advancement.AdvancementProgress; +import org.bukkit.block.data.BlockData; +import org.bukkit.conversations.Conversable; +import org.bukkit.event.player.PlayerResourcePackStatusEvent; +import org.bukkit.map.MapView; +import org.bukkit.plugin.Plugin; +import org.bukkit.plugin.messaging.PluginMessageRecipient; +import org.bukkit.scoreboard.Scoreboard; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +/** + * Represents a player, connected or not + */ +public interface Player extends HumanEntity, Conversable, OfflinePlayer, PluginMessageRecipient, com.destroystokyo.paper.network.NetworkClient { // Paper - Extend NetworkClient + + /** + * Gets the "friendly" name to display of this player. This may include + * color. + *

+ * Note that this name will not be displayed in game, only in chat and + * places defined by plugins. + * + * @return the friendly name + */ + @NotNull + public String getDisplayName(); + + /** + * Sets the "friendly" name to display of this player. This may include + * color. + *

+ * Note that this name will not be displayed in game, only in chat and + * places defined by plugins. + * + * @param name The new display name. + */ + public void setDisplayName(@Nullable String name); + + /** + * Gets the name that is shown on the player list. + * + * @return the player list name + */ + @NotNull + public String getPlayerListName(); + + /** + * Sets the name that is shown on the in-game player list. + *

+ * If the value is null, the name will be identical to {@link #getName()}. + * + * @param name new player list name + */ + public void setPlayerListName(@Nullable String name); + + /** + * Gets the currently displayed player list header for this player. + * + * @return player list header or null + */ + @Nullable + public String getPlayerListHeader(); + + /** + * Gets the currently displayed player list footer for this player. + * + * @return player list header or null + */ + @Nullable + public String getPlayerListFooter(); + + /** + * Sets the currently displayed player list header for this player. + * + * @param header player list header, null for empty + */ + public void setPlayerListHeader(@Nullable String header); + + /** + * Sets the currently displayed player list footer for this player. + * + * @param footer player list footer, null for empty + */ + public void setPlayerListFooter(@Nullable String footer); + + /** + * Sets the currently displayed player list header and footer for this + * player. + * + * @param header player list header, null for empty + * @param footer player list footer, null for empty + */ + public void setPlayerListHeaderFooter(@Nullable String header, @Nullable String footer); + + /** + * Set the target of the player's compass. + * + * @param loc Location to point to + */ + public void setCompassTarget(@NotNull Location loc); + + /** + * Get the previously set compass target. + * + * @return location of the target + */ + @NotNull + public Location getCompassTarget(); + + /** + * Gets the socket address of this player + * + * @return the player's address + */ + @Nullable + public InetSocketAddress getAddress(); + + /** + * Sends this sender a message raw + * + * @param message Message to be displayed + */ + public void sendRawMessage(@NotNull String message); + + /** + * Kicks player with custom kick message. + * + * @param message kick message + */ + public void kickPlayer(@Nullable String message); + + /** + * Says a message (or runs a command). + * + * @param msg message to print + */ + public void chat(@NotNull String msg); + + /** + * Makes the player perform the given command + * + * @param command Command to perform + * @return true if the command was successful, otherwise false + */ + public boolean performCommand(@NotNull String command); + + /** + * Returns if the player is in sneak mode + * + * @return true if player is in sneak mode + */ + public boolean isSneaking(); + + /** + * Sets the sneak mode the player + * + * @param sneak true if player should appear sneaking + */ + public void setSneaking(boolean sneak); + + /** + * Gets whether the player is sprinting or not. + * + * @return true if player is sprinting. + */ + public boolean isSprinting(); + + /** + * Sets whether the player is sprinting or not. + * + * @param sprinting true if the player should be sprinting + */ + public void setSprinting(boolean sprinting); + + /** + * Saves the players current location, health, inventory, motion, and + * other information into the username.dat file, in the world/player + * folder + */ + public void saveData(); + + /** + * Loads the players current location, health, inventory, motion, and + * other information from the username.dat file, in the world/player + * folder. + *

+ * Note: This will overwrite the players current inventory, health, + * motion, etc, with the state from the saved dat file. + */ + public void loadData(); + + /** + * Sets whether the player is ignored as not sleeping. If everyone is + * either sleeping or has this flag set, then time will advance to the + * next day. If everyone has this flag set but no one is actually in bed, + * then nothing will happen. + * + * @param isSleeping Whether to ignore. + */ + public void setSleepingIgnored(boolean isSleeping); + + /** + * Returns whether the player is sleeping ignored. + * + * @return Whether player is ignoring sleep. + */ + public boolean isSleepingIgnored(); + + /** + * Play a note for a player at a location. This requires a note block + * at the particular location (as far as the client is concerned). This + * will not work without a note block. This will not work with cake. + * + * @param loc The location of a note block. + * @param instrument The instrument ID. + * @param note The note ID. + * @deprecated Magic value + */ + @Deprecated + public void playNote(@NotNull Location loc, byte instrument, byte note); + + /** + * Play a note for a player at a location. This requires a note block + * at the particular location (as far as the client is concerned). This + * will not work without a note block. This will not work with cake. + * + * @param loc The location of a note block + * @param instrument The instrument + * @param note The note + */ + public void playNote(@NotNull Location loc, @NotNull Instrument instrument, @NotNull Note note); + + + /** + * Play a sound for a player at the location. + *

+ * This function will fail silently if Location or Sound are null. + * + * @param location The location to play the sound + * @param sound The sound to play + * @param volume The volume of the sound + * @param pitch The pitch of the sound + */ + public void playSound(@NotNull Location location, @NotNull Sound sound, float volume, float pitch); + + /** + * Play a sound for a player at the location. + *

+ * This function will fail silently if Location or Sound are null. No + * sound will be heard by the player if their client does not have the + * respective sound for the value passed. + * + * @param location the location to play the sound + * @param sound the internal sound name to play + * @param volume the volume of the sound + * @param pitch the pitch of the sound + */ + public void playSound(@NotNull Location location, @NotNull String sound, float volume, float pitch); + + /** + * Play a sound for a player at the location. + *

+ * This function will fail silently if Location or Sound are null. + * + * @param location The location to play the sound + * @param sound The sound to play + * @param category The category of the sound + * @param volume The volume of the sound + * @param pitch The pitch of the sound + */ + public void playSound(@NotNull Location location, @NotNull Sound sound, @NotNull SoundCategory category, float volume, float pitch); + + /** + * Play a sound for a player at the location. + *

+ * This function will fail silently if Location or Sound are null. No sound + * will be heard by the player if their client does not have the respective + * sound for the value passed. + * + * @param location the location to play the sound + * @param sound the internal sound name to play + * @param category The category of the sound + * @param volume the volume of the sound + * @param pitch the pitch of the sound + */ + public void playSound(@NotNull Location location, @NotNull String sound, @NotNull SoundCategory category, float volume, float pitch); + + /** + * Stop the specified sound from playing. + * + * @param sound the sound to stop + */ + public void stopSound(@NotNull Sound sound); + + /** + * Stop the specified sound from playing. + * + * @param sound the sound to stop + */ + public void stopSound(@NotNull String sound); + + /** + * Stop the specified sound from playing. + * + * @param sound the sound to stop + * @param category the category of the sound + */ + public void stopSound(@NotNull Sound sound, @Nullable SoundCategory category); + + /** + * Stop the specified sound from playing. + * + * @param sound the sound to stop + * @param category the category of the sound + */ + public void stopSound(@NotNull String sound, @Nullable SoundCategory category); + + /** + * Plays an effect to just this player. + * + * @param loc the location to play the effect at + * @param effect the {@link Effect} + * @param data a data bit needed for some effects + * @deprecated Magic value + */ + @Deprecated + public void playEffect(@NotNull Location loc, @NotNull Effect effect, int data); + + /** + * Plays an effect to just this player. + * + * @param the data based based on the type of the effect + * @param loc the location to play the effect at + * @param effect the {@link Effect} + * @param data a data bit needed for some effects + */ + public void playEffect(@NotNull Location loc, @NotNull Effect effect, @Nullable T data); + + /** + * Send a block change. This fakes a block change packet for a user at a + * certain location. This will not actually change the world in any way. + * + * @param loc The location of the changed block + * @param material The new block + * @param data The block data + * @deprecated Magic value + */ + @Deprecated + public void sendBlockChange(@NotNull Location loc, @NotNull Material material, byte data); + + /** + * Send a block change. This fakes a block change packet for a user at a + * certain location. This will not actually change the world in any way. + * + * @param loc The location of the changed block + * @param block The new block + */ + public void sendBlockChange(@NotNull Location loc, @NotNull BlockData block); + + /** + * Send a chunk change. This fakes a chunk change packet for a user at a + * certain location. The updated cuboid must be entirely within a single + * chunk. This will not actually change the world in any way. + *

+ * At least one of the dimensions of the cuboid must be even. The size of + * the data buffer must be 2.5*sx*sy*sz and formatted in accordance with + * the Packet51 format. + * + * @param loc The location of the cuboid + * @param sx The x size of the cuboid + * @param sy The y size of the cuboid + * @param sz The z size of the cuboid + * @param data The data to be sent + * @return true if the chunk change packet was sent + * @deprecated Magic value + */ + @Deprecated + public boolean sendChunkChange(@NotNull Location loc, int sx, int sy, int sz, @NotNull byte[] data); + + /** + * Send a sign change. This fakes a sign change packet for a user at + * a certain location. This will not actually change the world in any way. + * This method will use a sign at the location's block or a faked sign + * sent via + * {@link #sendBlockChange(org.bukkit.Location, org.bukkit.Material, byte)}. + *

+ * If the client does not have a sign at the given location it will + * display an error message to the user. + * + * @param loc the location of the sign + * @param lines the new text on the sign or null to clear it + * @throws IllegalArgumentException if location is null + * @throws IllegalArgumentException if lines is non-null and has a length less than 4 + */ + public void sendSignChange(@NotNull Location loc, @Nullable String[] lines) throws IllegalArgumentException; + + /** + * Render a map and send it to the player in its entirety. This may be + * used when streaming the map in the normal manner is not desirable. + * + * @param map The map to be sent + */ + public void sendMap(@NotNull MapView map); + + // Paper start + /** + * Permanently Bans the Profile and IP address currently used by the player. + * + * @param reason Reason for ban + * @return Ban Entry + */ + // For reference, Bukkit defines this as nullable, while they impl isn't, we'll follow API. + @Nullable + public default BanEntry banPlayerFull(@Nullable String reason) { + return banPlayerFull(reason, null, null); + } + + /** + * Permanently Bans the Profile and IP address currently used by the player. + * + * @param reason Reason for ban + * @param source Source of ban, or null for default + * @return Ban Entry + */ + @Nullable + public default BanEntry banPlayerFull(@Nullable String reason, @Nullable String source) { + return banPlayerFull(reason, null, source); + } + + /** + * Bans the Profile and IP address currently used by the player. + * + * @param reason Reason for Ban + * @param expires When to expire the ban + * @return Ban Entry + */ + @Nullable + public default BanEntry banPlayerFull(@Nullable String reason, @Nullable Date expires) { + return banPlayerFull(reason, expires, null); + } + + /** + * Bans the Profile and IP address currently used by the player. + * + * @param reason Reason for Ban + * @param expires When to expire the ban + * @param source Source of the ban, or null for default + * @return Ban Entry + */ + @Nullable + public default BanEntry banPlayerFull(@Nullable String reason, @Nullable Date expires, @Nullable String source) { + banPlayer(reason, expires, source); + return banPlayerIP(reason, expires, source, true); + } + + /** + * Permanently Bans the IP address currently used by the player. + * Does not ban the Profile, use {@link #banPlayerFull(String, Date, String)} + * + * @param reason Reason for ban + * @param kickPlayer Whether or not to kick the player afterwards + * @return Ban Entry + */ + @Nullable + public default BanEntry banPlayerIP(@Nullable String reason, boolean kickPlayer) { + return banPlayerIP(reason, null, null, kickPlayer); + } + + /** + * Permanently Bans the IP address currently used by the player. + * Does not ban the Profile, use {@link #banPlayerFull(String, Date, String)} + * @param reason Reason for ban + * @param source Source of ban, or null for default + * @param kickPlayer Whether or not to kick the player afterwards + * @return Ban Entry + */ + @Nullable + public default BanEntry banPlayerIP(@Nullable String reason, @Nullable String source, boolean kickPlayer) { + return banPlayerIP(reason, null, source, kickPlayer); + } + + /** + * Bans the IP address currently used by the player. + * Does not ban the Profile, use {@link #banPlayerFull(String, Date, String)} + * @param reason Reason for Ban + * @param expires When to expire the ban + * @param kickPlayer Whether or not to kick the player afterwards + * @return Ban Entry + */ + @Nullable + public default BanEntry banPlayerIP(@Nullable String reason, @Nullable Date expires, boolean kickPlayer) { + return banPlayerIP(reason, expires, null, kickPlayer); + } + + /** + * Permanently Bans the IP address currently used by the player. + * Does not ban the Profile, use {@link #banPlayerFull(String, Date, String)} + * + * @param reason Reason for ban + * @return Ban Entry + */ + @Nullable + public default BanEntry banPlayerIP(@Nullable String reason) { + return banPlayerIP(reason, null, null); + } + + /** + * Permanently Bans the IP address currently used by the player. + * Does not ban the Profile, use {@link #banPlayerFull(String, Date, String)} + * @param reason Reason for ban + * @param source Source of ban, or null for default + * @return Ban Entry + */ + @Nullable + public default BanEntry banPlayerIP(@Nullable String reason, @Nullable String source) { + return banPlayerIP(reason, null, source); + } + + /** + * Bans the IP address currently used by the player. + * Does not ban the Profile, use {@link #banPlayerFull(String, Date, String)} + * @param reason Reason for Ban + * @param expires When to expire the ban + * @return Ban Entry + */ + @Nullable + public default BanEntry banPlayerIP(@Nullable String reason, @Nullable Date expires) { + return banPlayerIP(reason, expires, null); + } + + /** + * Bans the IP address currently used by the player. + * Does not ban the Profile, use {@link #banPlayerFull(String, Date, String)} + * @param reason Reason for Ban + * @param expires When to expire the ban + * @param source Source of the banm or null for default + * @return Ban Entry + */ + @Nullable + public default BanEntry banPlayerIP(@Nullable String reason, @Nullable Date expires, @Nullable String source) { + return banPlayerIP(reason, expires, source, true); + } + + /** + * Bans the IP address currently used by the player. + * Does not ban the Profile, use {@link #banPlayerFull(String, Date, String)} + * @param reason Reason for Ban + * @param expires When to expire the ban + * @param source Source of the banm or null for default + * @param kickPlayer if the targeted player should be kicked + * @return Ban Entry + */ + @Nullable + public default BanEntry banPlayerIP(@Nullable String reason, @Nullable Date expires, @Nullable String source, boolean kickPlayer) { + BanEntry banEntry = Bukkit.getServer().getBanList(BanList.Type.IP).addBan(getAddress().getAddress().getHostAddress(), reason, expires, source); + if (kickPlayer && isOnline()) { + getPlayer().kickPlayer(reason); + } + + return banEntry; + } + + /** + * Sends an Action Bar message to the client. + * + * Use Section symbols for legacy color codes to send formatting. + * + * @param message The message to send + */ + public void sendActionBar(@NotNull String message); + + /** + * Sends an Action Bar message to the client. + * + * Use supplied alternative character to the section symbol to represent legacy color codes. + * + * @param alternateChar Alternate symbol such as '&' + * @param message The message to send + */ + public void sendActionBar(char alternateChar, @NotNull String message); + + /** + * Sends the component to the player + * + * @param component the components to send + */ + @Override + public default void sendMessage(@NotNull net.md_5.bungee.api.chat.BaseComponent component) { + spigot().sendMessage(component); + } + + /** + * Sends an array of components as a single message to the player + * + * @param components the components to send + */ + @Override + public default void sendMessage(@NotNull net.md_5.bungee.api.chat.BaseComponent... components) { + spigot().sendMessage(components); + } + + /** + * Sends an array of components as a single message to the specified screen position of this player + * + * @deprecated This is unlikely the API you want to use. See {@link #sendActionBar(String)} for a more proper Action Bar API. This deprecated API may send unsafe items to the client. + * @param position the screen position + * @param components the components to send + */ + @Deprecated + public default void sendMessage(net.md_5.bungee.api.ChatMessageType position, net.md_5.bungee.api.chat.BaseComponent... components) { + spigot().sendMessage(position, components); + } + + /** + * Set the text displayed in the player list header and footer for this player + * + * @param header content for the top of the player list + * @param footer content for the bottom of the player list + */ + public void setPlayerListHeaderFooter(@Nullable net.md_5.bungee.api.chat.BaseComponent[] header, @Nullable net.md_5.bungee.api.chat.BaseComponent[] footer); + + /** + * Set the text displayed in the player list header and footer for this player + * + * @param header content for the top of the player list + * @param footer content for the bottom of the player list + */ + public void setPlayerListHeaderFooter(@Nullable net.md_5.bungee.api.chat.BaseComponent header, @Nullable net.md_5.bungee.api.chat.BaseComponent footer); + + /** + * Update the times for titles displayed to the player + * + * @param fadeInTicks ticks to fade-in + * @param stayTicks ticks to stay visible + * @param fadeOutTicks ticks to fade-out + * @deprecated Use {@link #updateTitle(Title)} + */ + @Deprecated + public void setTitleTimes(int fadeInTicks, int stayTicks, int fadeOutTicks); + + /** + * Update the subtitle of titles displayed to the player + * + * @param subtitle Subtitle to set + * @deprecated Use {@link #updateTitle(Title)} + */ + @Deprecated + public void setSubtitle(net.md_5.bungee.api.chat.BaseComponent[] subtitle); + + /** + * Update the subtitle of titles displayed to the player + * + * @param subtitle Subtitle to set + * @deprecated Use {@link #updateTitle(Title)} + */ + @Deprecated + public void setSubtitle(net.md_5.bungee.api.chat.BaseComponent subtitle); + + /** + * Show the given title to the player, along with the last subtitle set, using the last set times + * + * @param title Title to set + * @deprecated Use {@link #sendTitle(Title)} or {@link #updateTitle(Title)} + */ + @Deprecated + public void showTitle(@Nullable net.md_5.bungee.api.chat.BaseComponent[] title); + + /** + * Show the given title to the player, along with the last subtitle set, using the last set times + * + * @param title Title to set + * @deprecated Use {@link #sendTitle(Title)} or {@link #updateTitle(Title)} + */ + @Deprecated + public void showTitle(@Nullable net.md_5.bungee.api.chat.BaseComponent title); + + /** + * Show the given title and subtitle to the player using the given times + * + * @param title big text + * @param subtitle little text under it + * @param fadeInTicks ticks to fade-in + * @param stayTicks ticks to stay visible + * @param fadeOutTicks ticks to fade-out + * @deprecated Use {@link #sendTitle(Title)} or {@link #updateTitle(Title)} + */ + @Deprecated + public void showTitle(@Nullable net.md_5.bungee.api.chat.BaseComponent[] title, @Nullable net.md_5.bungee.api.chat.BaseComponent[] subtitle, int fadeInTicks, int stayTicks, int fadeOutTicks); + + /** + * Show the given title and subtitle to the player using the given times + * + * @param title big text + * @param subtitle little text under it + * @param fadeInTicks ticks to fade-in + * @param stayTicks ticks to stay visible + * @param fadeOutTicks ticks to fade-out + * @deprecated Use {@link #sendTitle(Title)} or {@link #updateTitle(Title)} + */ + @Deprecated + public void showTitle(@Nullable net.md_5.bungee.api.chat.BaseComponent title, @Nullable net.md_5.bungee.api.chat.BaseComponent subtitle, int fadeInTicks, int stayTicks, int fadeOutTicks); + + /** + * Show the title to the player, overriding any previously displayed title. + * + *

This method overrides any previous title, use {@link #updateTitle(Title)} to change the existing one.

+ * + * @param title the title to send + * @throws NullPointerException if the title is null + */ + void sendTitle(@NotNull Title title); + + /** + * Show the title to the player, overriding any previously displayed title. + * + *

This method doesn't override previous titles, but changes their values.

+ * + * @param title the title to send + * @throws NullPointerException if title is null + */ + void updateTitle(@NotNull Title title); + + /** + * Hide any title that is currently visible to the player + */ + public void hideTitle(); + // Paper end + + /** + * Forces an update of the player's entire inventory. + * + */ + //@Deprecated // Spigot - undeprecate + public void updateInventory(); + + /** + * Awards the given achievement and any parent achievements that the + * player does not have. + * + * @param achievement Achievement to award + * @throws IllegalArgumentException if achievement is null + * @deprecated future versions of Minecraft do not have achievements + */ + @Deprecated + public void awardAchievement(@NotNull Achievement achievement); + + /** + * Removes the given achievement and any children achievements that the + * player has. + * + * @param achievement Achievement to remove + * @throws IllegalArgumentException if achievement is null + * @deprecated future versions of Minecraft do not have achievements + */ + @Deprecated + public void removeAchievement(@NotNull Achievement achievement); + + /** + * Gets whether this player has the given achievement. + * + * @param achievement the achievement to check + * @return whether the player has the achievement + * @throws IllegalArgumentException if achievement is null + * @deprecated future versions of Minecraft do not have achievements + */ + @Deprecated + public boolean hasAchievement(@NotNull Achievement achievement); + + /** + * Increments the given statistic for this player. + *

+ * This is equivalent to the following code: + * incrementStatistic(Statistic, 1) + * + * @param statistic Statistic to increment + * @throws IllegalArgumentException if statistic is null + * @throws IllegalArgumentException if the statistic requires an + * additional parameter + */ + public void incrementStatistic(@NotNull Statistic statistic) throws IllegalArgumentException; + + /** + * Decrements the given statistic for this player. + *

+ * This is equivalent to the following code: + * decrementStatistic(Statistic, 1) + * + * @param statistic Statistic to decrement + * @throws IllegalArgumentException if statistic is null + * @throws IllegalArgumentException if the statistic requires an + * additional parameter + */ + public void decrementStatistic(@NotNull Statistic statistic) throws IllegalArgumentException; + + /** + * Increments the given statistic for this player. + * + * @param statistic Statistic to increment + * @param amount Amount to increment this statistic by + * @throws IllegalArgumentException if statistic is null + * @throws IllegalArgumentException if amount is negative + * @throws IllegalArgumentException if the statistic requires an + * additional parameter + */ + public void incrementStatistic(@NotNull Statistic statistic, int amount) throws IllegalArgumentException; + + /** + * Decrements the given statistic for this player. + * + * @param statistic Statistic to decrement + * @param amount Amount to decrement this statistic by + * @throws IllegalArgumentException if statistic is null + * @throws IllegalArgumentException if amount is negative + * @throws IllegalArgumentException if the statistic requires an + * additional parameter + */ + public void decrementStatistic(@NotNull Statistic statistic, int amount) throws IllegalArgumentException; + + /** + * Sets the given statistic for this player. + * + * @param statistic Statistic to set + * @param newValue The value to set this statistic to + * @throws IllegalArgumentException if statistic is null + * @throws IllegalArgumentException if newValue is negative + * @throws IllegalArgumentException if the statistic requires an + * additional parameter + */ + public void setStatistic(@NotNull Statistic statistic, int newValue) throws IllegalArgumentException; + + /** + * Gets the value of the given statistic for this player. + * + * @param statistic Statistic to check + * @return the value of the given statistic + * @throws IllegalArgumentException if statistic is null + * @throws IllegalArgumentException if the statistic requires an + * additional parameter + */ + public int getStatistic(@NotNull Statistic statistic) throws IllegalArgumentException; + + /** + * Increments the given statistic for this player for the given material. + *

+ * This is equivalent to the following code: + * incrementStatistic(Statistic, Material, 1) + * + * @param statistic Statistic to increment + * @param material Material to offset the statistic with + * @throws IllegalArgumentException if statistic is null + * @throws IllegalArgumentException if material is null + * @throws IllegalArgumentException if the given parameter is not valid + * for the statistic + */ + public void incrementStatistic(@NotNull Statistic statistic, @NotNull Material material) throws IllegalArgumentException; + + /** + * Decrements the given statistic for this player for the given material. + *

+ * This is equivalent to the following code: + * decrementStatistic(Statistic, Material, 1) + * + * @param statistic Statistic to decrement + * @param material Material to offset the statistic with + * @throws IllegalArgumentException if statistic is null + * @throws IllegalArgumentException if material is null + * @throws IllegalArgumentException if the given parameter is not valid + * for the statistic + */ + public void decrementStatistic(@NotNull Statistic statistic, @NotNull Material material) throws IllegalArgumentException; + + /** + * Gets the value of the given statistic for this player. + * + * @param statistic Statistic to check + * @param material Material offset of the statistic + * @return the value of the given statistic + * @throws IllegalArgumentException if statistic is null + * @throws IllegalArgumentException if material is null + * @throws IllegalArgumentException if the given parameter is not valid + * for the statistic + */ + public int getStatistic(@NotNull Statistic statistic, @NotNull Material material) throws IllegalArgumentException; + + /** + * Increments the given statistic for this player for the given material. + * + * @param statistic Statistic to increment + * @param material Material to offset the statistic with + * @param amount Amount to increment this statistic by + * @throws IllegalArgumentException if statistic is null + * @throws IllegalArgumentException if material is null + * @throws IllegalArgumentException if amount is negative + * @throws IllegalArgumentException if the given parameter is not valid + * for the statistic + */ + public void incrementStatistic(@NotNull Statistic statistic, @NotNull Material material, int amount) throws IllegalArgumentException; + + /** + * Decrements the given statistic for this player for the given material. + * + * @param statistic Statistic to decrement + * @param material Material to offset the statistic with + * @param amount Amount to decrement this statistic by + * @throws IllegalArgumentException if statistic is null + * @throws IllegalArgumentException if material is null + * @throws IllegalArgumentException if amount is negative + * @throws IllegalArgumentException if the given parameter is not valid + * for the statistic + */ + public void decrementStatistic(@NotNull Statistic statistic, @NotNull Material material, int amount) throws IllegalArgumentException; + + /** + * Sets the given statistic for this player for the given material. + * + * @param statistic Statistic to set + * @param material Material to offset the statistic with + * @param newValue The value to set this statistic to + * @throws IllegalArgumentException if statistic is null + * @throws IllegalArgumentException if material is null + * @throws IllegalArgumentException if newValue is negative + * @throws IllegalArgumentException if the given parameter is not valid + * for the statistic + */ + public void setStatistic(@NotNull Statistic statistic, @NotNull Material material, int newValue) throws IllegalArgumentException; + + /** + * Increments the given statistic for this player for the given entity. + *

+ * This is equivalent to the following code: + * incrementStatistic(Statistic, EntityType, 1) + * + * @param statistic Statistic to increment + * @param entityType EntityType to offset the statistic with + * @throws IllegalArgumentException if statistic is null + * @throws IllegalArgumentException if entityType is null + * @throws IllegalArgumentException if the given parameter is not valid + * for the statistic + */ + public void incrementStatistic(@NotNull Statistic statistic, @NotNull EntityType entityType) throws IllegalArgumentException; + + /** + * Decrements the given statistic for this player for the given entity. + *

+ * This is equivalent to the following code: + * decrementStatistic(Statistic, EntityType, 1) + * + * @param statistic Statistic to decrement + * @param entityType EntityType to offset the statistic with + * @throws IllegalArgumentException if statistic is null + * @throws IllegalArgumentException if entityType is null + * @throws IllegalArgumentException if the given parameter is not valid + * for the statistic + */ + public void decrementStatistic(@NotNull Statistic statistic, @NotNull EntityType entityType) throws IllegalArgumentException; + + /** + * Gets the value of the given statistic for this player. + * + * @param statistic Statistic to check + * @param entityType EntityType offset of the statistic + * @return the value of the given statistic + * @throws IllegalArgumentException if statistic is null + * @throws IllegalArgumentException if entityType is null + * @throws IllegalArgumentException if the given parameter is not valid + * for the statistic + */ + public int getStatistic(@NotNull Statistic statistic, @NotNull EntityType entityType) throws IllegalArgumentException; + + /** + * Increments the given statistic for this player for the given entity. + * + * @param statistic Statistic to increment + * @param entityType EntityType to offset the statistic with + * @param amount Amount to increment this statistic by + * @throws IllegalArgumentException if statistic is null + * @throws IllegalArgumentException if entityType is null + * @throws IllegalArgumentException if amount is negative + * @throws IllegalArgumentException if the given parameter is not valid + * for the statistic + */ + public void incrementStatistic(@NotNull Statistic statistic, @NotNull EntityType entityType, int amount) throws IllegalArgumentException; + + /** + * Decrements the given statistic for this player for the given entity. + * + * @param statistic Statistic to decrement + * @param entityType EntityType to offset the statistic with + * @param amount Amount to decrement this statistic by + * @throws IllegalArgumentException if statistic is null + * @throws IllegalArgumentException if entityType is null + * @throws IllegalArgumentException if amount is negative + * @throws IllegalArgumentException if the given parameter is not valid + * for the statistic + */ + public void decrementStatistic(@NotNull Statistic statistic, @NotNull EntityType entityType, int amount); + + /** + * Sets the given statistic for this player for the given entity. + * + * @param statistic Statistic to set + * @param entityType EntityType to offset the statistic with + * @param newValue The value to set this statistic to + * @throws IllegalArgumentException if statistic is null + * @throws IllegalArgumentException if entityType is null + * @throws IllegalArgumentException if newValue is negative + * @throws IllegalArgumentException if the given parameter is not valid + * for the statistic + */ + public void setStatistic(@NotNull Statistic statistic, @NotNull EntityType entityType, int newValue); + + /** + * Sets the current time on the player's client. When relative is true the + * player's time will be kept synchronized to its world time with the + * specified offset. + *

+ * When using non relative time the player's time will stay fixed at the + * specified time parameter. It's up to the caller to continue updating + * the player's time. To restore player time to normal use + * resetPlayerTime(). + * + * @param time The current player's perceived time or the player's time + * offset from the server time. + * @param relative When true the player time is kept relative to its world + * time. + */ + public void setPlayerTime(long time, boolean relative); + + /** + * Returns the player's current timestamp. + * + * @return The player's time + */ + public long getPlayerTime(); + + /** + * Returns the player's current time offset relative to server time, or + * the current player's fixed time if the player's time is absolute. + * + * @return The player's time + */ + public long getPlayerTimeOffset(); + + /** + * Returns true if the player's time is relative to the server time, + * otherwise the player's time is absolute and will not change its current + * time unless done so with setPlayerTime(). + * + * @return true if the player's time is relative to the server time. + */ + public boolean isPlayerTimeRelative(); + + /** + * Restores the normal condition where the player's time is synchronized + * with the server time. + *

+ * Equivalent to calling setPlayerTime(0, true). + */ + public void resetPlayerTime(); + + /** + * Sets the type of weather the player will see. When used, the weather + * status of the player is locked until {@link #resetPlayerWeather()} is + * used. + * + * @param type The WeatherType enum type the player should experience + */ + public void setPlayerWeather(@NotNull WeatherType type); + + /** + * Returns the type of weather the player is currently experiencing. + * + * @return The WeatherType that the player is currently experiencing or + * null if player is seeing server weather. + */ + @Nullable + public WeatherType getPlayerWeather(); + + /** + * Restores the normal condition where the player's weather is controlled + * by server conditions. + */ + public void resetPlayerWeather(); + + // Paper start + /** + * Gives the player the amount of experience specified. + * + * @param amount Exp amount to give + */ + public default void giveExp(int amount) { + giveExp(amount, false); + } + /** + * Gives the player the amount of experience specified. + * + * @param amount Exp amount to give + * @param applyMending Mend players items with mending, with same behavior as picking up orbs. calls {@link #applyMending(int)} + */ + public void giveExp(int amount, boolean applyMending); + + /** + * Applies the mending effect to any items just as picking up an orb would. + * + * Can also be called with {@link #giveExp(int, boolean)} by passing true to applyMending + * + * @param amount Exp to apply + * @return the remaining experience + */ + public int applyMending(int amount); + // Paper end + + /** + * Gives the player the amount of experience levels specified. Levels can + * be taken by specifying a negative amount. + * + * @param amount amount of experience levels to give or take + */ + public void giveExpLevels(int amount); + + /** + * Gets the players current experience points towards the next level. + *

+ * This is a percentage value. 0 is "no progress" and 1 is "next level". + * + * @return Current experience points + */ + public float getExp(); + + /** + * Sets the players current experience points towards the next level + *

+ * This is a percentage value. 0 is "no progress" and 1 is "next level". + * + * @param exp New experience points + */ + public void setExp(float exp); + + /** + * Gets the players current experience level + * + * @return Current experience level + */ + public int getLevel(); + + /** + * Sets the players current experience level + * + * @param level New experience level + */ + public void setLevel(int level); + + /** + * Gets the players total experience points. + *
+ * This refers to the total amount of experience the player has collected + * over time and is only displayed as the player's "score" upon dying. + * + * @return Current total experience points + */ + public int getTotalExperience(); + + /** + * Sets the players current experience points. + *
+ * This refers to the total amount of experience the player has collected + * over time and is only displayed as the player's "score" upon dying. + * + * @param exp New total experience points + */ + public void setTotalExperience(int exp); + + /** + * Gets the players current exhaustion level. + *

+ * Exhaustion controls how fast the food level drops. While you have a + * certain amount of exhaustion, your saturation will drop to zero, and + * then your food will drop to zero. + * + * @return Exhaustion level + */ + public float getExhaustion(); + + /** + * Sets the players current exhaustion level + * + * @param value Exhaustion level + */ + public void setExhaustion(float value); + + /** + * Gets the players current saturation level. + *

+ * Saturation is a buffer for food level. Your food level will not drop if + * you are saturated {@literal >} 0. + * + * @return Saturation level + */ + public float getSaturation(); + + /** + * Sets the players current saturation level + * + * @param value Saturation level + */ + public void setSaturation(float value); + + /** + * Gets the players current food level + * + * @return Food level + */ + public int getFoodLevel(); + + /** + * Sets the players current food level + * + * @param value New food level + */ + public void setFoodLevel(int value); + + /** + * Determines if the Player is allowed to fly via jump key double-tap like + * in creative mode. + * + * @return True if the player is allowed to fly. + */ + public boolean getAllowFlight(); + + /** + * Sets if the Player is allowed to fly via jump key double-tap like in + * creative mode. + * + * @param flight If flight should be allowed. + */ + public void setAllowFlight(boolean flight); + + /** + * Hides a player from this player + * + * @param player Player to hide + * @deprecated see {@link #hidePlayer(Plugin, Player)} + */ + @Deprecated + public void hidePlayer(@NotNull Player player); + + /** + * Hides a player from this player + * + * @param plugin Plugin that wants to hide the player + * @param player Player to hide + */ + public void hidePlayer(@NotNull Plugin plugin, @NotNull Player player); + + /** + * Allows this player to see a player that was previously hidden + * + * @param player Player to show + * @deprecated see {@link #showPlayer(Plugin, Player)} + */ + @Deprecated + public void showPlayer(@NotNull Player player); + + /** + * Allows this player to see a player that was previously hidden. If + * another another plugin had hidden the player too, then the player will + * remain hidden until the other plugin calls this method too. + * + * @param plugin Plugin that wants to show the player + * @param player Player to show + */ + public void showPlayer(@NotNull Plugin plugin, @NotNull Player player); + + /** + * Checks to see if a player has been hidden from this player + * + * @param player Player to check + * @return True if the provided player is not being hidden from this + * player + */ + public boolean canSee(@NotNull Player player); + + /** + * Checks to see if this player is currently flying or not. + * + * @return True if the player is flying, else false. + */ + public boolean isFlying(); + + /** + * Makes this player start or stop flying. + * + * @param value True to fly. + */ + public void setFlying(boolean value); + + /** + * Sets the speed at which a client will fly. Negative values indicate + * reverse directions. + * + * @param value The new speed, from -1 to 1. + * @throws IllegalArgumentException If new speed is less than -1 or + * greater than 1 + */ + public void setFlySpeed(float value) throws IllegalArgumentException; + + /** + * Sets the speed at which a client will walk. Negative values indicate + * reverse directions. + * + * @param value The new speed, from -1 to 1. + * @throws IllegalArgumentException If new speed is less than -1 or + * greater than 1 + */ + public void setWalkSpeed(float value) throws IllegalArgumentException; + + /** + * Gets the current allowed speed that a client can fly. + * + * @return The current allowed speed, from -1 to 1 + */ + public float getFlySpeed(); + + /** + * Gets the current allowed speed that a client can walk. + * + * @return The current allowed speed, from -1 to 1 + */ + public float getWalkSpeed(); + + /** + * Request that the player's client download and switch texture packs. + *

+ * The player's client will download the new texture pack asynchronously + * in the background, and will automatically switch to it once the + * download is complete. If the client has downloaded and cached the same + * texture pack in the past, it will perform a file size check against + * the response content to determine if the texture pack has changed and + * needs to be downloaded again. When this request is sent for the very + * first time from a given server, the client will first display a + * confirmation GUI to the player before proceeding with the download. + *

+ * Notes: + *

    + *
  • Players can disable server textures on their client, in which + * case this method will have no affect on them. Use the + * {@link PlayerResourcePackStatusEvent} to figure out whether or not + * the player loaded the pack! + *
  • There is no concept of resetting texture packs back to default + * within Minecraft, so players will have to relog to do so or you + * have to send an empty pack. + *
  • The request is send with "null" as the hash. This might result + * in newer versions not loading the pack correctly. + *
+ * + * @param url The URL from which the client will download the texture + * pack. The string must contain only US-ASCII characters and should + * be encoded as per RFC 1738. + * @throws IllegalArgumentException Thrown if the URL is null. + * @throws IllegalArgumentException Thrown if the URL is too long. + * @deprecated Minecraft no longer uses textures packs. Instead you + * should use {@link #setResourcePack(String)}. + */ + @Deprecated + public void setTexturePack(@NotNull String url); + + /** + * Request that the player's client download and switch resource packs. + *

+ * The player's client will download the new resource pack asynchronously + * in the background, and will automatically switch to it once the + * download is complete. If the client has downloaded and cached the same + * resource pack in the past, it will perform a file size check against + * the response content to determine if the resource pack has changed and + * needs to be downloaded again. When this request is sent for the very + * first time from a given server, the client will first display a + * confirmation GUI to the player before proceeding with the download. + *

+ * Notes: + *

    + *
  • Players can disable server resources on their client, in which + * case this method will have no affect on them. Use the + * {@link PlayerResourcePackStatusEvent} to figure out whether or not + * the player loaded the pack! + *
  • There is no concept of resetting resource packs back to default + * within Minecraft, so players will have to relog to do so or you + * have to send an empty pack. + *
  • The request is send with "null" as the hash. This might result + * in newer versions not loading the pack correctly. + *
+ * + * @param url The URL from which the client will download the resource + * pack. The string must contain only US-ASCII characters and should + * be encoded as per RFC 1738. + * @throws IllegalArgumentException Thrown if the URL is null. + * @throws IllegalArgumentException Thrown if the URL is too long. The + * length restriction is an implementation specific arbitrary value. + * @deprecated use {@link #setResourcePack(String, String)} + */ + @Deprecated // Paper + public void setResourcePack(@NotNull String url); + + /** + * Request that the player's client download and switch resource packs. + *

+ * The player's client will download the new resource pack asynchronously + * in the background, and will automatically switch to it once the + * download is complete. If the client has downloaded and cached a + * resource pack with the same hash in the past it will not download but + * directly apply the cached pack. When this request is sent for the very + * first time from a given server, the client will first display a + * confirmation GUI to the player before proceeding with the download. + *

+ * Notes: + *

    + *
  • Players can disable server resources on their client, in which + * case this method will have no affect on them. Use the + * {@link PlayerResourcePackStatusEvent} to figure out whether or not + * the player loaded the pack! + *
  • There is no concept of resetting resource packs back to default + * within Minecraft, so players will have to relog to do so or you + * have to send an empty pack. + *
+ * + * @param url The URL from which the client will download the resource + * pack. The string must contain only US-ASCII characters and should + * be encoded as per RFC 1738. + * @param hash The sha1 hash sum of the resource pack file which is used + * to apply a cached version of the pack directly without downloading + * if it is available. Hast to be 20 bytes long! + * @throws IllegalArgumentException Thrown if the URL is null. + * @throws IllegalArgumentException Thrown if the URL is too long. The + * length restriction is an implementation specific arbitrary value. + * @throws IllegalArgumentException Thrown if the hash is null. + * @throws IllegalArgumentException Thrown if the hash is not 20 bytes + * long. + */ + public void setResourcePack(@NotNull String url, @NotNull byte[] hash); + + /** + * Gets the Scoreboard displayed to this player + * + * @return The current scoreboard seen by this player + */ + @NotNull + public Scoreboard getScoreboard(); + + /** + * Sets the player's visible Scoreboard. + * + * @param scoreboard New Scoreboard for the player + * @throws IllegalArgumentException if scoreboard is null + * @throws IllegalArgumentException if scoreboard was not created by the + * {@link org.bukkit.scoreboard.ScoreboardManager scoreboard manager} + * @throws IllegalStateException if this is a player that is not logged + * yet or has logged out + */ + public void setScoreboard(@NotNull Scoreboard scoreboard) throws IllegalArgumentException, IllegalStateException; + + /** + * Gets if the client is displayed a 'scaled' health, that is, health on a + * scale from 0-{@link #getHealthScale()}. + * + * @return if client health display is scaled + * @see Player#setHealthScaled(boolean) + */ + public boolean isHealthScaled(); + + /** + * Sets if the client is displayed a 'scaled' health, that is, health on a + * scale from 0-{@link #getHealthScale()}. + *

+ * Displayed health follows a simple formula displayedHealth = + * getHealth() / getMaxHealth() * getHealthScale(). + * + * @param scale if the client health display is scaled + */ + public void setHealthScaled(boolean scale); + + /** + * Sets the number to scale health to for the client; this will also + * {@link #setHealthScaled(boolean) setHealthScaled(true)}. + *

+ * Displayed health follows a simple formula displayedHealth = + * getHealth() / getMaxHealth() * getHealthScale(). + * + * @param scale the number to scale health to + * @throws IllegalArgumentException if scale is <0 + * @throws IllegalArgumentException if scale is {@link Double#NaN} + * @throws IllegalArgumentException if scale is too high + */ + public void setHealthScale(double scale) throws IllegalArgumentException; + + /** + * Gets the number that health is scaled to for the client. + * + * @return the number that health would be scaled to for the client if + * HealthScaling is set to true + * @see Player#setHealthScale(double) + * @see Player#setHealthScaled(boolean) + */ + public double getHealthScale(); + + /** + * Gets the entity which is followed by the camera when in + * {@link GameMode#SPECTATOR}. + * + * @return the followed entity, or null if not in spectator mode or not + * following a specific entity. + */ + @Nullable + public Entity getSpectatorTarget(); + + /** + * Sets the entity which is followed by the camera when in + * {@link GameMode#SPECTATOR}. + * + * @param entity the entity to follow or null to reset + * @throws IllegalStateException if the player is not in + * {@link GameMode#SPECTATOR} + */ + public void setSpectatorTarget(@Nullable Entity entity); + + /** + * Sends a title and a subtitle message to the player. If either of these + * values are null, they will not be sent and the display will remain + * unchanged. If they are empty strings, the display will be updated as + * such. If the strings contain a new line, only the first line will be + * sent. The titles will be displayed with the client's default timings. + * + * @param title Title text + * @param subtitle Subtitle text + * @deprecated API behavior subject to change + */ + @Deprecated + public void sendTitle(@Nullable String title, @Nullable String subtitle); + + /** + * Sends a title and a subtitle message to the player. If either of these + * values are null, they will not be sent and the display will remain + * unchanged. If they are empty strings, the display will be updated as + * such. If the strings contain a new line, only the first line will be + * sent. All timings values may take a value of -1 to indicate that they + * will use the last value sent (or the defaults if no title has been + * displayed). + * + * @param title Title text + * @param subtitle Subtitle text + * @param fadeIn time in ticks for titles to fade in. Defaults to 10. + * @param stay time in ticks for titles to stay. Defaults to 70. + * @param fadeOut time in ticks for titles to fade out. Defaults to 20. + */ + public void sendTitle(@Nullable String title, @Nullable String subtitle, int fadeIn, int stay, int fadeOut); + + /** + * Resets the title displayed to the player. This will clear the displayed + * title / subtitle and reset timings to their default values. + */ + public void resetTitle(); + + /** + * Spawns the particle (the number of times specified by count) + * at the target location. + * + * @param particle the particle to spawn + * @param location the location to spawn at + * @param count the number of particles + */ + public void spawnParticle(@NotNull Particle particle, @NotNull Location location, int count); + + /** + * Spawns the particle (the number of times specified by count) + * at the target location. + * + * @param particle the particle to spawn + * @param x the position on the x axis to spawn at + * @param y the position on the y axis to spawn at + * @param z the position on the z axis to spawn at + * @param count the number of particles + */ + public void spawnParticle(@NotNull Particle particle, double x, double y, double z, int count); + + /** + * Spawns the particle (the number of times specified by count) + * at the target location. + * + * @param particle the particle to spawn + * @param location the location to spawn at + * @param count the number of particles + * @param data the data to use for the particle or null, + * the type of this depends on {@link Particle#getDataType()} + * @param Type + */ + public void spawnParticle(@NotNull Particle particle, @NotNull Location location, int count, @Nullable T data); + + + /** + * Spawns the particle (the number of times specified by count) + * at the target location. + * + * @param particle the particle to spawn + * @param x the position on the x axis to spawn at + * @param y the position on the y axis to spawn at + * @param z the position on the z axis to spawn at + * @param count the number of particles + * @param data the data to use for the particle or null, + * the type of this depends on {@link Particle#getDataType()} + * @param Type + */ + public void spawnParticle(@NotNull Particle particle, double x, double y, double z, int count, @Nullable T data); + + /** + * Spawns the particle (the number of times specified by count) + * at the target location. The position of each particle will be + * randomized positively and negatively by the offset parameters + * on each axis. + * + * @param particle the particle to spawn + * @param location the location to spawn at + * @param count the number of particles + * @param offsetX the maximum random offset on the X axis + * @param offsetY the maximum random offset on the Y axis + * @param offsetZ the maximum random offset on the Z axis + */ + public void spawnParticle(@NotNull Particle particle, @NotNull Location location, int count, double offsetX, double offsetY, double offsetZ); + + /** + * Spawns the particle (the number of times specified by count) + * at the target location. The position of each particle will be + * randomized positively and negatively by the offset parameters + * on each axis. + * + * @param particle the particle to spawn + * @param x the position on the x axis to spawn at + * @param y the position on the y axis to spawn at + * @param z the position on the z axis to spawn at + * @param count the number of particles + * @param offsetX the maximum random offset on the X axis + * @param offsetY the maximum random offset on the Y axis + * @param offsetZ the maximum random offset on the Z axis + */ + public void spawnParticle(@NotNull Particle particle, double x, double y, double z, int count, double offsetX, double offsetY, double offsetZ); + + /** + * Spawns the particle (the number of times specified by count) + * at the target location. The position of each particle will be + * randomized positively and negatively by the offset parameters + * on each axis. + * + * @param particle the particle to spawn + * @param location the location to spawn at + * @param count the number of particles + * @param offsetX the maximum random offset on the X axis + * @param offsetY the maximum random offset on the Y axis + * @param offsetZ the maximum random offset on the Z axis + * @param data the data to use for the particle or null, + * the type of this depends on {@link Particle#getDataType()} + * @param Type + */ + public void spawnParticle(@NotNull Particle particle, @NotNull Location location, int count, double offsetX, double offsetY, double offsetZ, @Nullable T data); + + /** + * Spawns the particle (the number of times specified by count) + * at the target location. The position of each particle will be + * randomized positively and negatively by the offset parameters + * on each axis. + * + * @param particle the particle to spawn + * @param x the position on the x axis to spawn at + * @param y the position on the y axis to spawn at + * @param z the position on the z axis to spawn at + * @param count the number of particles + * @param offsetX the maximum random offset on the X axis + * @param offsetY the maximum random offset on the Y axis + * @param offsetZ the maximum random offset on the Z axis + * @param data the data to use for the particle or null, + * the type of this depends on {@link Particle#getDataType()} + * @param Type + */ + public void spawnParticle(@NotNull Particle particle, double x, double y, double z, int count, double offsetX, double offsetY, double offsetZ, @Nullable T data); + + /** + * Spawns the particle (the number of times specified by count) + * at the target location. The position of each particle will be + * randomized positively and negatively by the offset parameters + * on each axis. + * + * @param particle the particle to spawn + * @param location the location to spawn at + * @param count the number of particles + * @param offsetX the maximum random offset on the X axis + * @param offsetY the maximum random offset on the Y axis + * @param offsetZ the maximum random offset on the Z axis + * @param extra the extra data for this particle, depends on the + * particle used (normally speed) + */ + public void spawnParticle(@NotNull Particle particle, @NotNull Location location, int count, double offsetX, double offsetY, double offsetZ, double extra); + + /** + * Spawns the particle (the number of times specified by count) + * at the target location. The position of each particle will be + * randomized positively and negatively by the offset parameters + * on each axis. + * + * @param particle the particle to spawn + * @param x the position on the x axis to spawn at + * @param y the position on the y axis to spawn at + * @param z the position on the z axis to spawn at + * @param count the number of particles + * @param offsetX the maximum random offset on the X axis + * @param offsetY the maximum random offset on the Y axis + * @param offsetZ the maximum random offset on the Z axis + * @param extra the extra data for this particle, depends on the + * particle used (normally speed) + */ + public void spawnParticle(@NotNull Particle particle, double x, double y, double z, int count, double offsetX, double offsetY, double offsetZ, double extra); + + /** + * Spawns the particle (the number of times specified by count) + * at the target location. The position of each particle will be + * randomized positively and negatively by the offset parameters + * on each axis. + * + * @param particle the particle to spawn + * @param location the location to spawn at + * @param count the number of particles + * @param offsetX the maximum random offset on the X axis + * @param offsetY the maximum random offset on the Y axis + * @param offsetZ the maximum random offset on the Z axis + * @param extra the extra data for this particle, depends on the + * particle used (normally speed) + * @param data the data to use for the particle or null, + * the type of this depends on {@link Particle#getDataType()} + * @param Type + */ + public void spawnParticle(@NotNull Particle particle, @NotNull Location location, int count, double offsetX, double offsetY, double offsetZ, double extra, @Nullable T data); + + /** + * Spawns the particle (the number of times specified by count) + * at the target location. The position of each particle will be + * randomized positively and negatively by the offset parameters + * on each axis. + * + * @param particle the particle to spawn + * @param x the position on the x axis to spawn at + * @param y the position on the y axis to spawn at + * @param z the position on the z axis to spawn at + * @param count the number of particles + * @param offsetX the maximum random offset on the X axis + * @param offsetY the maximum random offset on the Y axis + * @param offsetZ the maximum random offset on the Z axis + * @param extra the extra data for this particle, depends on the + * particle used (normally speed) + * @param data the data to use for the particle or null, + * the type of this depends on {@link Particle#getDataType()} + * @param Type + */ + public void spawnParticle(@NotNull Particle particle, double x, double y, double z, int count, double offsetX, double offsetY, double offsetZ, double extra, @Nullable T data); + + /** + * Return the player's progression on the specified advancement. + * + * @param advancement advancement + * @return object detailing the player's progress + */ + @NotNull + public AdvancementProgress getAdvancementProgress(@NotNull Advancement advancement); + + /** + * Get the player's current client side view distance. + *
+ * Will default to the server view distance if the client has not yet + * communicated this information, + * + * @return client view distance as above + */ + public int getClientViewDistance(); + + /** + * Gets the player's current locale. + * + * The value of the locale String is not defined properly. + *
+ * The vanilla Minecraft client will use lowercase language / country pairs + * separated by an underscore, but custom resource packs may use any format + * they wish. + * + * @return the player's locale + */ + @NotNull + public String getLocale(); + + // Paper start + /** + * Get whether the player can affect mob spawning + * + * @return if the player can affect mob spawning + */ + public boolean getAffectsSpawning(); + + /** + * Set whether the player can affect mob spawning + * + * @param affects Whether the player can affect mob spawning + */ + public void setAffectsSpawning(boolean affects); + // Paper end + + /** + * Update the list of commands sent to the client. + *
+ * Generally useful to ensure the client has a complete list of commands + * after permission changes are done. + */ + public void updateCommands(); + + /** + * Gets the view distance for this player + * + * @return the player's view distance + */ + public int getViewDistance(); + + /** + * Sets the view distance for this player + * + * @param viewDistance the player's view distance + */ + public void setViewDistance(int viewDistance); + + // Paper start + /** + * Request that the player's client download and switch resource packs. + *

+ * The player's client will download the new resource pack asynchronously + * in the background, and will automatically switch to it once the + * download is complete. If the client has downloaded and cached the same + * resource pack in the past, it will perform a quick timestamp check + * over the network to determine if the resource pack has changed and + * needs to be downloaded again. When this request is sent for the very + * first time from a given server, the client will first display a + * confirmation GUI to the player before proceeding with the download. + *

+ * Notes: + *

    + *
  • Players can disable server resources on their client, in which + * case this method will have no affect on them. + *
  • There is no concept of resetting resource packs back to default + * within Minecraft, so players will have to relog to do so. + *
+ * + * @param url The URL from which the client will download the resource + * pack. The string must contain only US-ASCII characters and should + * be encoded as per RFC 1738. + * @param hash A 40 character hexadecimal and lowercase SHA-1 digest of + * the resource pack file. + * @throws IllegalArgumentException Thrown if the URL is null. + * @throws IllegalArgumentException Thrown if the URL is too long. The + * length restriction is an implementation specific arbitrary value. + */ + void setResourcePack(@NotNull String url, @NotNull String hash); + + /** + * @return the most recent resource pack status received from the player, + * or null if no status has ever been received from this player. + */ + @Nullable + org.bukkit.event.player.PlayerResourcePackStatusEvent.Status getResourcePackStatus(); + + /** + * @return the most recent resource pack hash received from the player, + * or null if no hash has ever been received from this player. + * + * @deprecated This is no longer sent from the client and will always be null + */ + @Nullable + @Deprecated + String getResourcePackHash(); + + /** + * @return true if the last resource pack status received from this player + * was {@link org.bukkit.event.player.PlayerResourcePackStatusEvent.Status#SUCCESSFULLY_LOADED} + */ + boolean hasResourcePack(); + + /** + * Gets a copy of this players profile + * @return The players profile object + */ + @NotNull + PlayerProfile getPlayerProfile(); + + /** + * Changes the PlayerProfile for this player. This will cause this player + * to be reregistered to all clients that can currently see this player + * @param profile The new profile to use + */ + void setPlayerProfile(@NotNull PlayerProfile profile); + + /** + * Returns the amount of ticks the current cooldown lasts + * + * @return Amount of ticks cooldown will last + */ + float getCooldownPeriod(); + + /** + * Returns the percentage of attack power available based on the cooldown (zero to one). + * + * @param adjustTicks Amount of ticks to add to cooldown counter for this calculation + * @return Percentage of attack power available + */ + float getCooledAttackStrength(float adjustTicks); + + /** + * Reset the cooldown counter to 0, effectively starting the cooldown period. + */ + void resetCooldown(); + // Paper end + + // Spigot start + public class Spigot extends Entity.Spigot + { + + /** + * Gets the connection address of this player, regardless of whether it + * has been spoofed or not. + * + * @return the player's connection address + */ + @NotNull + public InetSocketAddress getRawAddress() + { + throw new UnsupportedOperationException( "Not supported yet." ); + } + + /** + * Gets whether the player collides with entities + * + * @return the player's collision toggle state + * @deprecated see {@link LivingEntity#isCollidable()} + */ + @Deprecated + public boolean getCollidesWithEntities() + { + throw new UnsupportedOperationException( "Not supported yet." ); + } + + /** + * Sets whether the player collides with entities + * + * @param collides whether the player should collide with entities or + * not. + * @deprecated {@link LivingEntity#setCollidable(boolean)} + */ + @Deprecated + public void setCollidesWithEntities(boolean collides) + { + throw new UnsupportedOperationException( "Not supported yet." ); + } + + /** + * Respawns the player if dead. + */ + public void respawn() + { + throw new UnsupportedOperationException( "Not supported yet." ); + } + + /** + * Gets player locale language. + * + * @return the player's client language settings + * @deprecated Use {@link Player#getLocale()} + */ + @Deprecated + @NotNull + public String getLocale() + { + throw new UnsupportedOperationException( "Not supported yet." ); + } + + /** + * Gets all players hidden with {@link #hidePlayer(org.bukkit.entity.Player)}. + * + * @return a Set with all hidden players + */ + @NotNull + public java.util.Set getHiddenPlayers() + { + throw new UnsupportedOperationException( "Not supported yet." ); + } + + @Override + public void sendMessage(@NotNull net.md_5.bungee.api.chat.BaseComponent component) { + throw new UnsupportedOperationException("Not supported yet."); + } + + @Override + public void sendMessage(@NotNull net.md_5.bungee.api.chat.BaseComponent... components) { + throw new UnsupportedOperationException("Not supported yet."); + } + + /** + * Sends the component to the specified screen position of this player + * + * @deprecated This is unlikely the API you want to use. See {@link #sendActionBar(String)} for a more proper Action Bar API. This deprecated API may send unsafe items to the client. + * @param position the screen position + * @param component the components to send + */ + @Deprecated + public void sendMessage(@NotNull net.md_5.bungee.api.ChatMessageType position, @NotNull net.md_5.bungee.api.chat.BaseComponent component) { + throw new UnsupportedOperationException("Not supported yet."); + } + + /** + * Sends an array of components as a single message to the specified screen position of this player + * + * @deprecated This is unlikely the API you want to use. See {@link #sendActionBar(String)} for a more proper Action Bar API. This deprecated API may send unsafe items to the client. + * @param position the screen position + * @param components the components to send + */ + @Deprecated + public void sendMessage(@NotNull net.md_5.bungee.api.ChatMessageType position, @NotNull net.md_5.bungee.api.chat.BaseComponent... components) { + throw new UnsupportedOperationException("Not supported yet."); + } + + public int getPing() + { + throw new UnsupportedOperationException( "Not supported yet." ); + } + } + + @NotNull + @Override + Spigot spigot(); + // Spigot end +} diff --git a/api/src/main/java/org/bukkit/entity/PolarBear.java b/api/src/main/java/org/bukkit/entity/PolarBear.java new file mode 100644 index 000000000..479f7a7c5 --- /dev/null +++ b/api/src/main/java/org/bukkit/entity/PolarBear.java @@ -0,0 +1,6 @@ +package org.bukkit.entity; + +/** + * Represents a polar bear. + */ +public interface PolarBear extends Animals {} diff --git a/api/src/main/java/org/bukkit/entity/Projectile.java b/api/src/main/java/org/bukkit/entity/Projectile.java new file mode 100644 index 000000000..c854860c1 --- /dev/null +++ b/api/src/main/java/org/bukkit/entity/Projectile.java @@ -0,0 +1,42 @@ +package org.bukkit.entity; + +import org.bukkit.projectiles.ProjectileSource; +import org.jetbrains.annotations.Nullable; + +/** + * Represents a shootable entity. + */ +public interface Projectile extends Entity { + + /** + * Retrieve the shooter of this projectile. + * + * @return the {@link ProjectileSource} that shot this projectile + */ + @Nullable + public ProjectileSource getShooter(); + + /** + * Set the shooter of this projectile. + * + * @param source the {@link ProjectileSource} that shot this projectile + */ + public void setShooter(@Nullable ProjectileSource source); + + /** + * Determine if this projectile should bounce or not when it hits. + *

+ * If a small fireball does not bounce it will set the target on fire. + * + * @return true if it should bounce. + */ + public boolean doesBounce(); + + /** + * Set whether or not this projectile should bounce or not when it hits + * something. + * + * @param doesBounce whether or not it should bounce. + */ + public void setBounce(boolean doesBounce); +} diff --git a/api/src/main/java/org/bukkit/entity/PufferFish.java b/api/src/main/java/org/bukkit/entity/PufferFish.java new file mode 100644 index 000000000..2d26c19b5 --- /dev/null +++ b/api/src/main/java/org/bukkit/entity/PufferFish.java @@ -0,0 +1,21 @@ +package org.bukkit.entity; + +/** + * Represents a puffer fish. + */ +public interface PufferFish extends Fish { + + /** + * Returns the current puff state of this fish (i.e. how inflated it is). + * + * @return current puff state + */ + int getPuffState(); + + /** + * Sets the current puff state of this fish (i.e. how inflated it is). + * + * @param state new puff state + */ + void setPuffState(int state); +} diff --git a/api/src/main/java/org/bukkit/entity/Rabbit.java b/api/src/main/java/org/bukkit/entity/Rabbit.java new file mode 100644 index 000000000..e88154283 --- /dev/null +++ b/api/src/main/java/org/bukkit/entity/Rabbit.java @@ -0,0 +1,52 @@ +package org.bukkit.entity; + +import org.jetbrains.annotations.NotNull; + +public interface Rabbit extends Animals { + + /** + * @return The type of rabbit. + */ + @NotNull + public Type getRabbitType(); + + /** + * @param type Sets the type of rabbit for this entity. + */ + public void setRabbitType(@NotNull Type type); + + /** + * Represents the various types a Rabbit might be. + */ + public enum Type { + + /** + * Chocolate colored rabbit. + */ + BROWN, + /** + * Pure white rabbit. + */ + WHITE, + /** + * Black rabbit. + */ + BLACK, + /** + * Black with white patches, or white with black patches? + */ + BLACK_AND_WHITE, + /** + * Golden bunny. + */ + GOLD, + /** + * Salt and pepper colored, whatever that means. + */ + SALT_AND_PEPPER, + /** + * Rabbit with pure white fur, blood red horizontal eyes, and is hostile to players. + */ + THE_KILLER_BUNNY + } +} diff --git a/api/src/main/java/org/bukkit/entity/Salmon.java b/api/src/main/java/org/bukkit/entity/Salmon.java new file mode 100644 index 000000000..a52a7af21 --- /dev/null +++ b/api/src/main/java/org/bukkit/entity/Salmon.java @@ -0,0 +1,7 @@ + +package org.bukkit.entity; + +/** + * Represents a salmon fish. + */ +public interface Salmon extends Fish { } diff --git a/api/src/main/java/org/bukkit/entity/Sheep.java b/api/src/main/java/org/bukkit/entity/Sheep.java new file mode 100644 index 000000000..f4ce312cc --- /dev/null +++ b/api/src/main/java/org/bukkit/entity/Sheep.java @@ -0,0 +1,19 @@ +package org.bukkit.entity; + +import org.bukkit.material.Colorable; + +/** + * Represents a Sheep. + */ +public interface Sheep extends Animals, Colorable { + + /** + * @return Whether the sheep is sheared. + */ + public boolean isSheared(); + + /** + * @param flag Whether to shear the sheep + */ + public void setSheared(boolean flag); +} diff --git a/api/src/main/java/org/bukkit/entity/Shulker.java b/api/src/main/java/org/bukkit/entity/Shulker.java new file mode 100644 index 000000000..3441bdb7f --- /dev/null +++ b/api/src/main/java/org/bukkit/entity/Shulker.java @@ -0,0 +1,5 @@ +package org.bukkit.entity; + +import org.bukkit.material.Colorable; + +public interface Shulker extends Golem, Colorable {} diff --git a/api/src/main/java/org/bukkit/entity/ShulkerBullet.java b/api/src/main/java/org/bukkit/entity/ShulkerBullet.java new file mode 100644 index 000000000..4623e0d76 --- /dev/null +++ b/api/src/main/java/org/bukkit/entity/ShulkerBullet.java @@ -0,0 +1,21 @@ +package org.bukkit.entity; + +import org.jetbrains.annotations.Nullable; + +public interface ShulkerBullet extends Projectile { + + /** + * Retrieve the target of this bullet. + * + * @return the targeted entity + */ + @Nullable + Entity getTarget(); + + /** + * Sets the target of this bullet + * + * @param target the entity to target + */ + void setTarget(@Nullable Entity target); +} diff --git a/api/src/main/java/org/bukkit/entity/Silverfish.java b/api/src/main/java/org/bukkit/entity/Silverfish.java new file mode 100644 index 000000000..fe01007fb --- /dev/null +++ b/api/src/main/java/org/bukkit/entity/Silverfish.java @@ -0,0 +1,6 @@ +package org.bukkit.entity; + +/** + * Represents a Silverfish. + */ +public interface Silverfish extends Monster {} diff --git a/api/src/main/java/org/bukkit/entity/Sittable.java b/api/src/main/java/org/bukkit/entity/Sittable.java new file mode 100644 index 000000000..ea6ee26fc --- /dev/null +++ b/api/src/main/java/org/bukkit/entity/Sittable.java @@ -0,0 +1,23 @@ +package org.bukkit.entity; + +/** + * An animal that can sit still. + */ +public interface Sittable { + + /** + * Checks if this animal is sitting + * + * @return true if sitting + */ + boolean isSitting(); + + /** + * Sets if this animal is sitting. Will remove any path that the animal + * was following beforehand. + * + * @param sitting true if sitting + */ + void setSitting(boolean sitting); + +} diff --git a/api/src/main/java/org/bukkit/entity/Skeleton.java b/api/src/main/java/org/bukkit/entity/Skeleton.java new file mode 100644 index 000000000..d42a4e12c --- /dev/null +++ b/api/src/main/java/org/bukkit/entity/Skeleton.java @@ -0,0 +1,49 @@ +package org.bukkit.entity; + +import org.jetbrains.annotations.Contract; +import org.jetbrains.annotations.NotNull; +import com.destroystokyo.paper.entity.RangedEntity; + +/** + * Represents a Skeleton. + */ +public interface Skeleton extends Monster, RangedEntity { // Paper + + /** + * Gets the current type of this skeleton. + * + * @return Current type + * @deprecated should check what class instance this is + */ + @Deprecated + @NotNull + public SkeletonType getSkeletonType(); + + /** + * @param type Type to set + * @deprecated Must spawn a new subtype variant + */ + @Deprecated + @Contract("_ -> fail") + public void setSkeletonType(SkeletonType type); + + /* + * @deprecated classes are different types + */ + @Deprecated + public enum SkeletonType { + + /** + * Standard skeleton type. + */ + NORMAL, + /** + * Wither skeleton. Generally found in Nether fortresses. + */ + WITHER, + /** + * Stray skeleton. Generally found in ice biomes. Shoots tipped arrows. + */ + STRAY; + } +} diff --git a/api/src/main/java/org/bukkit/entity/SkeletonHorse.java b/api/src/main/java/org/bukkit/entity/SkeletonHorse.java new file mode 100644 index 000000000..ba9983463 --- /dev/null +++ b/api/src/main/java/org/bukkit/entity/SkeletonHorse.java @@ -0,0 +1,14 @@ +package org.bukkit.entity; + +/** + * Represents a SkeletonHorse - variant of {@link AbstractHorse}. + */ +public interface SkeletonHorse extends AbstractHorse { + // Paper start + int getTrapTime(); + + boolean isTrap(); + + void setTrap(boolean trap); + // Paper end +} diff --git a/api/src/main/java/org/bukkit/entity/Slime.java b/api/src/main/java/org/bukkit/entity/Slime.java new file mode 100644 index 000000000..c4791f95d --- /dev/null +++ b/api/src/main/java/org/bukkit/entity/Slime.java @@ -0,0 +1,33 @@ +package org.bukkit.entity; + +/** + * Represents a Slime. + */ +public interface Slime extends Mob { + + /** + * @return The size of the slime + */ + public int getSize(); + + /** + * @param sz The new size of the slime. + */ + public void setSize(int sz); + + // Paper start + /** + * Get whether this slime can randomly wander/jump around on its own + * + * @return true if can wander + */ + public boolean canWander(); + + /** + * Set whether this slime can randomly wander/jump around on its own + * + * @param canWander true if can wander + */ + public void setWander(boolean canWander); + // Paper end +} diff --git a/api/src/main/java/org/bukkit/entity/SmallFireball.java b/api/src/main/java/org/bukkit/entity/SmallFireball.java new file mode 100644 index 000000000..33f54d3ef --- /dev/null +++ b/api/src/main/java/org/bukkit/entity/SmallFireball.java @@ -0,0 +1,8 @@ +package org.bukkit.entity; + +/** + * Represents a small {@link Fireball} + */ +public interface SmallFireball extends Fireball { + +} diff --git a/api/src/main/java/org/bukkit/entity/Snowball.java b/api/src/main/java/org/bukkit/entity/Snowball.java new file mode 100644 index 000000000..8c6b43339 --- /dev/null +++ b/api/src/main/java/org/bukkit/entity/Snowball.java @@ -0,0 +1,6 @@ +package org.bukkit.entity; + +/** + * Represents a snowball. + */ +public interface Snowball extends Projectile {} diff --git a/api/src/main/java/org/bukkit/entity/Snowman.java b/api/src/main/java/org/bukkit/entity/Snowman.java new file mode 100644 index 000000000..10f8f6d45 --- /dev/null +++ b/api/src/main/java/org/bukkit/entity/Snowman.java @@ -0,0 +1,26 @@ +package org.bukkit.entity; + +import com.destroystokyo.paper.entity.RangedEntity; + +/** + * Represents a snowman entity + */ +public interface Snowman extends Golem, RangedEntity { // Paper + + /** + * Gets whether this snowman is in "derp mode", meaning it is not wearing a + * pumpkin. + * + * @return True if the snowman is bald, false if it is wearing a pumpkin + */ + boolean isDerp(); + + /** + * Sets whether this snowman is in "derp mode", meaning it is not wearing a + * pumpkin. NOTE: This value is not persisted to disk and will therefore + * reset when the chunk is reloaded. + * + * @param derpMode True to remove the pumpkin, false to add a pumpkin + */ + void setDerp(boolean derpMode); +} diff --git a/api/src/main/java/org/bukkit/entity/SpectralArrow.java b/api/src/main/java/org/bukkit/entity/SpectralArrow.java new file mode 100644 index 000000000..1a32341c3 --- /dev/null +++ b/api/src/main/java/org/bukkit/entity/SpectralArrow.java @@ -0,0 +1,22 @@ +package org.bukkit.entity; + +/** + * Represents a spectral arrow. + */ +public interface SpectralArrow extends Arrow { + + /** + * Returns the amount of time that this arrow will apply + * the glowing effect for. + * + * @return the glowing effect ticks + */ + int getGlowingTicks(); + + /** + * Sets the amount of time to apply the glowing effect for. + * + * @param duration the glowing effect ticks + */ + void setGlowingTicks(int duration); +} diff --git a/api/src/main/java/org/bukkit/entity/Spellcaster.java b/api/src/main/java/org/bukkit/entity/Spellcaster.java new file mode 100644 index 000000000..d5c107d4f --- /dev/null +++ b/api/src/main/java/org/bukkit/entity/Spellcaster.java @@ -0,0 +1,55 @@ +package org.bukkit.entity; + +import org.jetbrains.annotations.NotNull; + +/** + * Represents a spell casting "Illager". + */ +public interface Spellcaster extends Illager { + + /** + * Represents the current spell the entity is using. + */ + public enum Spell { + + /** + * No spell is being used.. + */ + NONE, + /** + * The spell that summons Vexes. + */ + SUMMON_VEX, + /** + * The spell that summons Fangs. + */ + FANGS, + /** + * The "wololo" spell. + */ + WOLOLO, + /** + * The spell that makes the casting entity invisible. + */ + DISAPPEAR, + /** + * The spell that makes the target blind. + */ + BLINDNESS; + } + + /** + * Gets the {@link Spell} the entity is currently using. + * + * @return the current spell + */ + @NotNull + Spell getSpell(); + + /** + * Sets the {@link Spell} the entity is currently using. + * + * @param spell the spell the entity should be using + */ + void setSpell(@NotNull Spell spell); +} diff --git a/api/src/main/java/org/bukkit/entity/Spider.java b/api/src/main/java/org/bukkit/entity/Spider.java new file mode 100644 index 000000000..f9ee8cc75 --- /dev/null +++ b/api/src/main/java/org/bukkit/entity/Spider.java @@ -0,0 +1,6 @@ +package org.bukkit.entity; + +/** + * Represents a Spider. + */ +public interface Spider extends Monster {} diff --git a/api/src/main/java/org/bukkit/entity/SplashPotion.java b/api/src/main/java/org/bukkit/entity/SplashPotion.java new file mode 100644 index 000000000..2a2102542 --- /dev/null +++ b/api/src/main/java/org/bukkit/entity/SplashPotion.java @@ -0,0 +1,6 @@ +package org.bukkit.entity; + +/** + * Represents a thrown splash potion bottle + */ +public interface SplashPotion extends ThrownPotion { } diff --git a/api/src/main/java/org/bukkit/entity/Squid.java b/api/src/main/java/org/bukkit/entity/Squid.java new file mode 100644 index 000000000..fb47968ef --- /dev/null +++ b/api/src/main/java/org/bukkit/entity/Squid.java @@ -0,0 +1,6 @@ +package org.bukkit.entity; + +/** + * Represents a Squid. + */ +public interface Squid extends WaterMob {} diff --git a/api/src/main/java/org/bukkit/entity/Stray.java b/api/src/main/java/org/bukkit/entity/Stray.java new file mode 100644 index 000000000..9c83f98e9 --- /dev/null +++ b/api/src/main/java/org/bukkit/entity/Stray.java @@ -0,0 +1,6 @@ +package org.bukkit.entity; + +/** + * Represents a Stray - variant of {@link Skeleton}. + */ +public interface Stray extends Skeleton { } diff --git a/api/src/main/java/org/bukkit/entity/TNTPrimed.java b/api/src/main/java/org/bukkit/entity/TNTPrimed.java new file mode 100644 index 000000000..3022b4a27 --- /dev/null +++ b/api/src/main/java/org/bukkit/entity/TNTPrimed.java @@ -0,0 +1,53 @@ +package org.bukkit.entity; + +import org.bukkit.Location; +import org.jetbrains.annotations.Nullable; + +/** + * Represents a Primed TNT. + */ +public interface TNTPrimed extends Explosive { + + /** + * Set the number of ticks until the TNT blows up after being primed. + * + * @param fuseTicks The fuse ticks + */ + public void setFuseTicks(int fuseTicks); + + /** + * Retrieve the number of ticks until the explosion of this TNTPrimed + * entity + * + * @return the number of ticks until this TNTPrimed explodes + */ + public int getFuseTicks(); + + /** + * Gets the source of this primed TNT. The source is the entity + * responsible for the creation of this primed TNT. (I.E. player ignites + * TNT with flint and steel.) Take note that this can be null if there is + * no suitable source. (created by the {@link + * org.bukkit.World#spawn(Location, Class)} method, for example.) + *

+ * The source will become null if the chunk this primed TNT is in is + * unloaded then reloaded. The source entity may be invalid if for example + * it has since died or been unloaded. Callers should check + * {@link Entity#isValid()}. + * + * @return the source of this primed TNT + */ + @Nullable + public Entity getSource(); + + /** + * Gets the source block location of the TNTPrimed + * + * @return the source block location the TNTPrimed was spawned from + * @deprecated replaced by {@link Entity#getOrigin()} + */ + @Deprecated + default org.bukkit.Location getSourceLoc() { + return this.getOrigin(); + } +} diff --git a/api/src/main/java/org/bukkit/entity/Tameable.java b/api/src/main/java/org/bukkit/entity/Tameable.java new file mode 100644 index 000000000..be436f8df --- /dev/null +++ b/api/src/main/java/org/bukkit/entity/Tameable.java @@ -0,0 +1,61 @@ +package org.bukkit.entity; + +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +public interface Tameable extends Entity { + + /** + * Check if this is tamed + *

+ * If something is tamed then a player can not tame it through normal + * methods, even if it does not belong to anyone in particular. + * + * @return true if this has been tamed + */ + public boolean isTamed(); + + /** + * Sets if this has been tamed. Not necessary if the method setOwner has + * been used, as it tames automatically. + *

+ * If something is tamed then a player can not tame it through normal + * methods, even if it does not belong to anyone in particular. + * + * @param tame true if tame + */ + public void setTamed(boolean tame); + + // Paper start + /** + * Gets the owners UUID + * + * @return the owners UUID, or null if not owned + */ + @NotNull + public java.util.UUID getOwnerUniqueId(); + // Paper end + + /** + * Gets the current owning AnimalTamer + * + * @see #getOwnerUniqueId() Recommended to use UUID version instead of this for performance. + * This method will cause OfflinePlayer to be loaded from disk if the owner is not online. + * + * @return the owning AnimalTamer, or null if not owned + */ + @Nullable + public AnimalTamer getOwner(); + + /** + * Set this to be owned by given AnimalTamer. + *

+ * If the owner is not null, this will be tamed and will have any current + * path it is following removed. If the owner is set to null, this will be + * untamed, and the current owner removed. + * + * @param tamer the AnimalTamer who should own this + */ + public void setOwner(@Nullable AnimalTamer tamer); + +} diff --git a/api/src/main/java/org/bukkit/entity/ThrownExpBottle.java b/api/src/main/java/org/bukkit/entity/ThrownExpBottle.java new file mode 100644 index 000000000..671282ed2 --- /dev/null +++ b/api/src/main/java/org/bukkit/entity/ThrownExpBottle.java @@ -0,0 +1,8 @@ +package org.bukkit.entity; + +/** + * Represents a thrown Experience bottle. + */ +public interface ThrownExpBottle extends Projectile { + +} diff --git a/api/src/main/java/org/bukkit/entity/ThrownPotion.java b/api/src/main/java/org/bukkit/entity/ThrownPotion.java new file mode 100644 index 000000000..02218457a --- /dev/null +++ b/api/src/main/java/org/bukkit/entity/ThrownPotion.java @@ -0,0 +1,44 @@ +package org.bukkit.entity; + +import java.util.Collection; + +import org.bukkit.inventory.ItemStack; +import org.bukkit.potion.PotionEffect; +import org.jetbrains.annotations.NotNull; + +/** + * Represents a thrown potion bottle + */ +public interface ThrownPotion extends Projectile { + + /** + * Returns the effects that are applied by this potion. + * + * @return The potion effects + */ + @NotNull + public Collection getEffects(); + + /** + * Returns a copy of the ItemStack for this thrown potion. + *

+ * Altering this copy will not alter the thrown potion directly. If you want + * to alter the thrown potion, you must use the {@link + * #setItem(ItemStack) setItemStack} method. + * + * @return A copy of the ItemStack for this thrown potion. + */ + @NotNull + public ItemStack getItem(); + + /** + * Set the ItemStack for this thrown potion. + *

+ * The ItemStack must be of type {@link org.bukkit.Material#SPLASH_POTION} + * or {@link org.bukkit.Material#LINGERING_POTION}, otherwise an exception + * is thrown. + * + * @param item New ItemStack + */ + public void setItem(@NotNull ItemStack item); +} diff --git a/api/src/main/java/org/bukkit/entity/TippedArrow.java b/api/src/main/java/org/bukkit/entity/TippedArrow.java new file mode 100644 index 000000000..d53b40d34 --- /dev/null +++ b/api/src/main/java/org/bukkit/entity/TippedArrow.java @@ -0,0 +1,98 @@ +package org.bukkit.entity; + +import java.util.List; + +import org.bukkit.Color; +import org.bukkit.potion.PotionData; +import org.bukkit.potion.PotionEffect; +import org.bukkit.potion.PotionEffectType; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +public interface TippedArrow extends Arrow { + + /** + * Sets the underlying potion data + * + * @param data PotionData to set the base potion state to + */ + void setBasePotionData(@NotNull PotionData data); + + /** + * Returns the potion data about the base potion + * + * @return a PotionData object + */ + @NotNull + PotionData getBasePotionData(); + + /** + * Gets the color of this arrow. + * + * @return arrow color + */ + @NotNull + Color getColor(); + + /** + * Sets the color of this arrow. Will be applied as a tint to its particles. + * + * @param color arrow color + */ + void setColor(@NotNull Color color); + + /** + * Checks for the presence of custom potion effects. + * + * @return true if custom potion effects are applied + */ + boolean hasCustomEffects(); + + /** + * Gets an immutable list containing all custom potion effects applied to + * this arrow. + *

+ * Plugins should check that hasCustomEffects() returns true before calling + * this method. + * + * @return the immutable list of custom potion effects + */ + @NotNull + List getCustomEffects(); + + /** + * Adds a custom potion effect to this arrow. + * + * @param effect the potion effect to add + * @param overwrite true if any existing effect of the same type should be + * overwritten + * @return true if the effect was added as a result of this call + */ + boolean addCustomEffect(@NotNull PotionEffect effect, boolean overwrite); + + /** + * Removes a custom potion effect from this arrow. + * + * @param type the potion effect type to remove + * @return true if the an effect was removed as a result of this call + * @throws IllegalArgumentException if this operation would leave the Arrow + * in a state with no Custom Effects and PotionType.UNCRAFTABLE + */ + boolean removeCustomEffect(@NotNull PotionEffectType type); + + /** + * Checks for a specific custom potion effect type on this arrow. + * + * @param type the potion effect type to check for + * @return true if the potion has this effect + */ + boolean hasCustomEffect(@Nullable PotionEffectType type); + + /** + * Removes all custom potion effects from this arrow. + * + * @throws IllegalArgumentException if this operation would leave the Arrow + * in a state with no Custom Effects and PotionType.UNCRAFTABLE + */ + void clearCustomEffects(); +} diff --git a/api/src/main/java/org/bukkit/entity/Trident.java b/api/src/main/java/org/bukkit/entity/Trident.java new file mode 100644 index 000000000..7af949eac --- /dev/null +++ b/api/src/main/java/org/bukkit/entity/Trident.java @@ -0,0 +1,6 @@ +package org.bukkit.entity; + +/** + * Represents a thrown trident. + */ +public interface Trident extends Arrow { } diff --git a/api/src/main/java/org/bukkit/entity/TropicalFish.java b/api/src/main/java/org/bukkit/entity/TropicalFish.java new file mode 100644 index 000000000..bc5055f2d --- /dev/null +++ b/api/src/main/java/org/bukkit/entity/TropicalFish.java @@ -0,0 +1,76 @@ +package org.bukkit.entity; + +import org.bukkit.DyeColor; +import org.jetbrains.annotations.NotNull; + +/** + * Tropical fish. + */ +public interface TropicalFish extends Fish { + + /** + * Gets the color of the fish's pattern. + * + * @return pattern color + */ + @NotNull + DyeColor getPatternColor(); + + /** + * Sets the color of the fish's pattern + * + * @param color pattern color + */ + void setPatternColor(@NotNull DyeColor color); + + /** + * Gets the color of the fish's body. + * + * @return pattern color + */ + @NotNull + DyeColor getBodyColor(); + + /** + * Sets the color of the fish's body + * + * @param color body color + */ + void setBodyColor(@NotNull DyeColor color); + + /** + * Gets the fish's pattern. + * + * @return pattern + */ + @NotNull + Pattern getPattern(); + + /** + * Sets the fish's pattern + * + * @param pattern new pattern + */ + void setPattern(@NotNull Pattern pattern); + + /** + * Enumeration of all different fish patterns. Refer to the + * Minecraft Wiki + * for pictures. + */ + public static enum Pattern { + + KOB, + SUNSTREAK, + SNOOPER, + DASHER, + BRINELY, + SPOTTY, + FLOPPER, + STRIPEY, + GLITTER, + BLOCKFISH, + BETTY, + CLAYFISH; + } +} diff --git a/api/src/main/java/org/bukkit/entity/Turtle.java b/api/src/main/java/org/bukkit/entity/Turtle.java new file mode 100644 index 000000000..5375ea140 --- /dev/null +++ b/api/src/main/java/org/bukkit/entity/Turtle.java @@ -0,0 +1,55 @@ +package org.bukkit.entity; + +import org.bukkit.Location; +import org.jetbrains.annotations.NotNull; + +/** + * Represents a turtle. + */ +public interface Turtle extends Animals { + // Paper start + + /** + * Get the turtle's home location + * + * @return Home location + */ + @NotNull + Location getHome(); + + /** + * Set the turtle's home location + * + * @param location Home location + */ + void setHome(@NotNull Location location); + + /** + * Check if turtle is currently pathfinding to it's home + * + * @return True if going home + */ + boolean isGoingHome(); + + /** + * Get if turtle is digging to lay eggs + * + * @return True if digging + */ + boolean isDigging(); + + /** + * Get if turtle is carrying egg + * + * @return True if carrying egg + */ + boolean hasEgg(); + + /** + * Set if turtle is carrying egg + * + * @param hasEgg True if carrying egg + */ + void setHasEgg(boolean hasEgg); + // Paper end +} diff --git a/api/src/main/java/org/bukkit/entity/Vehicle.java b/api/src/main/java/org/bukkit/entity/Vehicle.java new file mode 100644 index 000000000..c73ee6cbd --- /dev/null +++ b/api/src/main/java/org/bukkit/entity/Vehicle.java @@ -0,0 +1,25 @@ +package org.bukkit.entity; + +import org.bukkit.util.Vector; +import org.jetbrains.annotations.NotNull; + +/** + * Represents a vehicle entity. + */ +public interface Vehicle extends Entity { + + /** + * Gets the vehicle's velocity. + * + * @return velocity vector + */ + @NotNull + public Vector getVelocity(); + + /** + * Sets the vehicle's velocity. + * + * @param vel velocity vector + */ + public void setVelocity(@NotNull Vector vel); +} diff --git a/api/src/main/java/org/bukkit/entity/Vex.java b/api/src/main/java/org/bukkit/entity/Vex.java new file mode 100644 index 000000000..c34a3ea7b --- /dev/null +++ b/api/src/main/java/org/bukkit/entity/Vex.java @@ -0,0 +1,44 @@ +package org.bukkit.entity; + +import org.jetbrains.annotations.Nullable; + +/** + * Represents a Vex. + */ +public interface Vex extends Monster { + + /** + * Gets the charging state of this entity. + * + * When this entity is charging it will having a glowing red texture. + * + * @return charging state + */ + boolean isCharging(); + + /** + * Sets the charging state of this entity. + * + * When this entity is charging it will having a glowing red texture. + * + * @param charging new state + */ + void setCharging(boolean charging); + + // Paper start + /** + * Get the Mob that summoned this vex + * + * @return Mob that summoned this vex + */ + @Nullable + Mob getSummoner(); + + /** + * Set the summoner of this vex + * + * @param summoner New summoner + */ + void setSummoner(@Nullable Mob summoner); + // Paper end +} diff --git a/api/src/main/java/org/bukkit/entity/Villager.java b/api/src/main/java/org/bukkit/entity/Villager.java new file mode 100644 index 000000000..d8dfb4388 --- /dev/null +++ b/api/src/main/java/org/bukkit/entity/Villager.java @@ -0,0 +1,272 @@ +package org.bukkit.entity; + +import com.google.common.collect.ImmutableList; +import com.google.common.collect.LinkedListMultimap; +import com.google.common.collect.Multimap; +import java.util.List; +import org.bukkit.inventory.Inventory; +import org.bukkit.inventory.InventoryHolder; +import org.bukkit.inventory.Merchant; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +/** + * Represents a villager NPC + */ +public interface Villager extends Ageable, NPC, InventoryHolder, Merchant { + + /** + * Gets the current profession of this villager. + * + * @return Current profession. + */ + @NotNull + public Profession getProfession(); + + /** + * Sets the new profession of this villager. + * + * @param profession New profession. + */ + public void setProfession(@NotNull Profession profession); + + /** + * Get the current {@link Career} for this Villager. + * + * @return the {@link Career} + */ + @NotNull + public Career getCareer(); + + /** + * Set the new {@link Career} for this Villager. + * This method will reset the villager's trades to the new career. + * + * @param career the new career, or null to clear the career to a random one + * @throws IllegalArgumentException when the new {@link Career} cannot be + * used with this Villager's current {@link Profession}. + */ + public void setCareer(@Nullable Career career); + + /** + * Set the new {@link Career} for this Villager. + * + * @param career the new career, or null to clear the career to a random one + * @param resetTrades true to reset this Villager's trades to the new + * career's (if any) + * @throws IllegalArgumentException when the new {@link Career} cannot be + * used with this Villager's current {@link Profession}. + */ + public void setCareer(@Nullable Career career, boolean resetTrades); + + /** + * Gets this villager's inventory. + *
+ * Note that this inventory is not the Merchant inventory, rather, it is the + * items that a villager might have collected (from harvesting crops, etc.) + * + * {@inheritDoc} + */ + @NotNull + @Override + Inventory getInventory(); + + /** + * Gets this villager's riches, the number of emeralds this villager has + * been given. + * + * @return the villager's riches + */ + int getRiches(); + + /** + * Sets this villager's riches. + * + * @see Villager#getRiches() + * + * @param riches the new riches + */ + void setRiches(int riches); + + /** + * Represents the various different Villager professions there may be. + * Villagers have different trading options depending on their profession, + */ + public enum Profession { + /** + * Normal. Reserved for Zombies. + * @deprecated Unused + */ + @Deprecated + NORMAL(true), + /** + * Farmer profession. Wears a brown robe. + */ + FARMER(false), + /** + * Librarian profession. Wears a white robe. + */ + LIBRARIAN(false), + /** + * Priest profession. Wears a purple robe. + */ + PRIEST(false), + /** + * Blacksmith profession. Wears a black apron. + */ + BLACKSMITH(false), + /** + * Butcher profession. Wears a white apron. + */ + BUTCHER(false), + /** + * Nitwit profession. Wears a green apron, cannot trade. + */ + NITWIT(false), + /** + * Husk. Reserved for Zombies + * @deprecated Unused + */ + @Deprecated + HUSK(true); + private final boolean zombie; + + private Profession(boolean zombie) { + this.zombie = zombie; + } + + /** + * Returns if this profession can only be used by zombies. + * + * @return zombie profession status + * @deprecated Unused + */ + @Deprecated + public boolean isZombie() { + return zombie; + } + + /** + * Get an immutable list of {@link Career} belonging to this Profession. + * + * @return an immutable list of careers for this profession, or an empty + * map if this Profession has no careers. + */ + @NotNull + public List getCareers() { + return Career.getCareers(this); + } + } + + /** + * The Career of this Villager. + * Each {@link Profession} has a set of careers it is applicable to. Each + * career dictates the trading options that are generated. + */ + public enum Career { + /* + NOTE: The Career entries are order-specific. They should be maintained in the numerical order they are used in the CB implementation. + (e.g. Farmer careers are 1,2,3,4 so Career should reflect that numerical order in their ordinal status) + */ + // Farmer careers + /** + * Farmers primarily trade for food-related items. + */ + FARMER(Profession.FARMER), + /** + * Fisherman primarily trade for fish, as well as possibly selling + * string and/or coal. + */ + FISHERMAN(Profession.FARMER), + /** + * Shepherds primarily trade for wool items, and shears. + */ + SHEPHERD(Profession.FARMER), + /** + * Fletchers primarily trade for string, bows, and arrows. + */ + FLETCHER(Profession.FARMER), + // Librarian careers + /** + * Librarians primarily trade for paper, books, and enchanted books. + */ + LIBRARIAN(Profession.LIBRARIAN), + /** + * Cartographers primarily trade for explorer maps and some paper. + */ + CARTOGRAPHER(Profession.LIBRARIAN), + // Priest careers + /** + * Clerics primarily trade for rotten flesh, gold ingot, redstone, + * lapis, ender pearl, glowstone, and bottle o' enchanting. + */ + CLERIC(Profession.PRIEST), + // Blacksmith careers + /** + * Armorers primarily trade for iron armor, chainmail armor, and + * sometimes diamond armor. + */ + ARMORER(Profession.BLACKSMITH), + /** + * Weapon smiths primarily trade for iron and diamond weapons, sometimes + * enchanted. + */ + WEAPON_SMITH(Profession.BLACKSMITH), + /** + * Tool smiths primarily trade for iron and diamond tools. + */ + TOOL_SMITH(Profession.BLACKSMITH), + // Butcher careers + /** + * Butchers primarily trade for raw and cooked food. + */ + BUTCHER(Profession.BUTCHER), + /** + * Leatherworkers primarily trade for leather, and leather armor, as + * well as saddles. + */ + LEATHERWORKER(Profession.BUTCHER), + // Nitwit + /** + * Nitwit villagers do not do anything. They do not have any trades by + * default. + */ + NITWIT(Profession.NITWIT); + + private static final Multimap careerMap = LinkedListMultimap.create(); + private final Profession profession; + + private Career(/*@NotNull*/ Profession profession) { + this.profession = profession; + } + + /** + * Get the {@link Profession} this {@link Career} belongs to. + * + * @return the {@link Profession}. + */ + @NotNull + public Profession getProfession() { + return profession; + } + + /** + * Get an immutable list of {@link Career}s that can be used with a + * given {@link Profession} + * + * @param profession the profession to get careers for + * @return an immutable list of Careers that can be used by a + * profession, or an empty map if the profession was not found + */ + @NotNull + public static List getCareers(@NotNull Profession profession) { + return careerMap.containsKey(profession) ? ImmutableList.copyOf(careerMap.get(profession)) : ImmutableList.of(); + } + + static { + for (Career career : Career.values()) { + careerMap.put(career.profession, career); + } + } + } +} diff --git a/api/src/main/java/org/bukkit/entity/Vindicator.java b/api/src/main/java/org/bukkit/entity/Vindicator.java new file mode 100644 index 000000000..c5d9e76a6 --- /dev/null +++ b/api/src/main/java/org/bukkit/entity/Vindicator.java @@ -0,0 +1,32 @@ +package org.bukkit.entity; + +/** + * Represents a Vindicator. + */ +public interface Vindicator extends Illager { + // Paper start + /** + * Check if this Vindicator is set to Johnny mode. + *

+ * When in Johnny mode the Vindicator will be hostile to any kind of mob, except + * for evokers, ghasts, illusioners and other vindicators. It will even be hostile + * to vexes. All mobs, except for endermites, phantoms, guardians, slimes and + * magma cubes, will try to attack the vindicator in return. + * + * @return True if in Johnny mode + */ + boolean isJohnny(); + + /** + * Set this Vindicator's Johnny mode. + *

+ * When in Johnny mode the Vindicator will be hostile to any kind of mob, except + * for evokers, ghasts, illusioners and other vindicators. It will even be hostile + * to vexes. All mobs, except for endermites, phantoms, guardians, slimes and + * magma cubes, will try to attack the vindicator in return. + * + * @param johnny True to enable Johnny mode + */ + void setJohnny(boolean johnny); + // Paper end +} diff --git a/api/src/main/java/org/bukkit/entity/WaterMob.java b/api/src/main/java/org/bukkit/entity/WaterMob.java new file mode 100644 index 000000000..62b4e89d0 --- /dev/null +++ b/api/src/main/java/org/bukkit/entity/WaterMob.java @@ -0,0 +1,6 @@ +package org.bukkit.entity; + +/** + * Represents a Water Mob + */ +public interface WaterMob extends Creature {} diff --git a/api/src/main/java/org/bukkit/entity/Weather.java b/api/src/main/java/org/bukkit/entity/Weather.java new file mode 100644 index 000000000..6d77851f9 --- /dev/null +++ b/api/src/main/java/org/bukkit/entity/Weather.java @@ -0,0 +1,6 @@ +package org.bukkit.entity; + +/** + * Represents a Weather related entity, such as a storm + */ +public interface Weather extends Entity {} diff --git a/api/src/main/java/org/bukkit/entity/Witch.java b/api/src/main/java/org/bukkit/entity/Witch.java new file mode 100644 index 000000000..1828b2ced --- /dev/null +++ b/api/src/main/java/org/bukkit/entity/Witch.java @@ -0,0 +1,44 @@ +package org.bukkit.entity; + +import com.destroystokyo.paper.entity.RangedEntity; + +// Paper start +import org.bukkit.inventory.ItemStack; +import org.jetbrains.annotations.Nullable; +// Paper end + +/** + * Represents a Witch + */ +// Paper start +public interface Witch extends Monster, RangedEntity { + /** + * Check if Witch is drinking a potion + * + * @return True if drinking a potion + */ + boolean isDrinkingPotion(); + + /** + * Get time remaining (in ticks) the Witch is drinking a potion + * + * @return Time remaining (in ticks) + */ + int getPotionUseTimeLeft(); + + /** + * Get the potion the Witch is drinking + * + * @return The potion the witch is drinking + */ + @Nullable + ItemStack getDrinkingPotion(); + + /** + * Set the potion the Witch should drink + * + * @param potion Potion to drink + */ + void setDrinkingPotion(@Nullable ItemStack potion); + // Paper end +} diff --git a/api/src/main/java/org/bukkit/entity/Wither.java b/api/src/main/java/org/bukkit/entity/Wither.java new file mode 100644 index 000000000..426d36933 --- /dev/null +++ b/api/src/main/java/org/bukkit/entity/Wither.java @@ -0,0 +1,9 @@ +package org.bukkit.entity; + +import com.destroystokyo.paper.entity.RangedEntity; + +/** + * Represents a Wither boss + */ +public interface Wither extends Monster, Boss, RangedEntity { // Paper +} diff --git a/api/src/main/java/org/bukkit/entity/WitherSkeleton.java b/api/src/main/java/org/bukkit/entity/WitherSkeleton.java new file mode 100644 index 000000000..7045014e6 --- /dev/null +++ b/api/src/main/java/org/bukkit/entity/WitherSkeleton.java @@ -0,0 +1,6 @@ +package org.bukkit.entity; + +/** + * Represents a WitherSkeleton - variant of {@link Skeleton}. + */ +public interface WitherSkeleton extends Skeleton { } diff --git a/api/src/main/java/org/bukkit/entity/WitherSkull.java b/api/src/main/java/org/bukkit/entity/WitherSkull.java new file mode 100644 index 000000000..33d20abc0 --- /dev/null +++ b/api/src/main/java/org/bukkit/entity/WitherSkull.java @@ -0,0 +1,21 @@ +package org.bukkit.entity; + +/** + * Represents a wither skull {@link Fireball}. + */ +public interface WitherSkull extends Fireball { + + /** + * Sets the charged status of the wither skull. + * + * @param charged whether it should be charged + */ + public void setCharged(boolean charged); + + /** + * Gets whether or not the wither skull is charged. + * + * @return whether the wither skull is charged + */ + public boolean isCharged(); +} diff --git a/api/src/main/java/org/bukkit/entity/Wolf.java b/api/src/main/java/org/bukkit/entity/Wolf.java new file mode 100644 index 000000000..f07d51941 --- /dev/null +++ b/api/src/main/java/org/bukkit/entity/Wolf.java @@ -0,0 +1,44 @@ +package org.bukkit.entity; + +import org.bukkit.DyeColor; +import org.jetbrains.annotations.NotNull; + +/** + * Represents a Wolf + */ +public interface Wolf extends Animals, Tameable, Sittable { + + /** + * Checks if this wolf is angry + * + * @return Anger true if angry + */ + public boolean isAngry(); + + /** + * Sets the anger of this wolf. + *

+ * An angry wolf can not be fed or tamed, and must have a target to attack. + * If a target is not set the wolf will quickly revert to its non-angry + * state. + * + * @param angry true if angry + * @see #setTarget(org.bukkit.entity.LivingEntity) + */ + public void setAngry(boolean angry); + + /** + * Get the collar color of this wolf + * + * @return the color of the collar + */ + @NotNull + public DyeColor getCollarColor(); + + /** + * Set the collar color of this wolf + * + * @param color the color to apply + */ + public void setCollarColor(@NotNull DyeColor color); +} diff --git a/api/src/main/java/org/bukkit/entity/Zombie.java b/api/src/main/java/org/bukkit/entity/Zombie.java new file mode 100644 index 000000000..74d6529a1 --- /dev/null +++ b/api/src/main/java/org/bukkit/entity/Zombie.java @@ -0,0 +1,140 @@ +package org.bukkit.entity; + +import org.jetbrains.annotations.Contract; +import org.jetbrains.annotations.Nullable; + +/** + * Represents a Zombie. + */ +public interface Zombie extends Monster { + + /** + * Gets whether the zombie is a baby + * + * @return Whether the zombie is a baby + */ + public boolean isBaby(); + + /** + * Sets whether the zombie is a baby + * + * @param flag Whether the zombie is a baby + */ + public void setBaby(boolean flag); + + /** + * Gets whether the zombie is a villager + * + * @return Whether the zombie is a villager + * @deprecated check if instanceof {@link ZombieVillager}. + */ + @Deprecated + public boolean isVillager(); + + /** + * @param flag Sets whether the Zombie is a villager + * @deprecated must spawn {@link ZombieVillager}. + */ + @Deprecated + @Contract("_ -> fail") + public void setVillager(boolean flag); + + /** + * @param profession Sets the ZombieVillager's profession + * @see ZombieVillager#getVillagerProfession() + */ + @Deprecated + @Contract("_ -> fail") + public void setVillagerProfession(Villager.Profession profession); + + /** + * @return profession + * @see ZombieVillager#getVillagerProfession() + */ + @Deprecated + @Nullable + @Contract("-> null") + public Villager.Profession getVillagerProfession(); + + /** + * Get if this entity is in the process of converting to a Drowned as a + * result of being underwater. + * + * @return conversion status + */ + boolean isConverting(); + + /** + * Gets the amount of ticks until this entity will be converted to a Drowned + * as a result of being underwater. + * + * When this reaches 0, the entity will be converted. + * + * @return conversion time + * @throws IllegalStateException if {@link #isConverting()} is false. + */ + int getConversionTime(); + + /** + * Sets the amount of ticks until this entity will be converted to a Drowned + * as a result of being underwater. + * + * When this reaches 0, the entity will be converted. A value of less than 0 + * will stop the current conversion process without converting the current + * entity. + * + * @param time new conversion time + */ + void setConversionTime(int time); + // Paper start + /** + * Check if zombie is drowning + * + * @return True if zombie conversion process has begun + */ + boolean isDrowning(); + + /** + * Make zombie start drowning + * + * @param drownedConversionTime Amount of time until zombie converts from drowning + * + * @deprecated See {@link #setConversionTime(int)} + */ + @Deprecated + void startDrowning(int drownedConversionTime); + + /** + * Stop a zombie from starting the drowning conversion process + */ + void stopDrowning(); + + /** + * Set if zombie has its arms raised + * + * @param raised True to raise arms + */ + void setArmsRaised(boolean raised); + + /** + * Check if zombie has arms raised + * + * @return True if arms are raised + */ + boolean isArmsRaised(); + + /** + * Check if this zombie will burn in the sunlight + * + * @return True if zombie will burn in sunlight + */ + boolean shouldBurnInDay(); + + /** + * Set if this zombie should burn in the sunlight + * + * @param shouldBurnInDay True to burn in sunlight + */ + void setShouldBurnInDay(boolean shouldBurnInDay); + // Paper end +} diff --git a/api/src/main/java/org/bukkit/entity/ZombieHorse.java b/api/src/main/java/org/bukkit/entity/ZombieHorse.java new file mode 100644 index 000000000..4179b68bd --- /dev/null +++ b/api/src/main/java/org/bukkit/entity/ZombieHorse.java @@ -0,0 +1,6 @@ +package org.bukkit.entity; + +/** + * Represents a ZombieHorse - variant of {@link AbstractHorse}. + */ +public interface ZombieHorse extends AbstractHorse { } diff --git a/api/src/main/java/org/bukkit/entity/ZombieVillager.java b/api/src/main/java/org/bukkit/entity/ZombieVillager.java new file mode 100644 index 000000000..f8ff3b655 --- /dev/null +++ b/api/src/main/java/org/bukkit/entity/ZombieVillager.java @@ -0,0 +1,58 @@ +package org.bukkit.entity; + +import org.jetbrains.annotations.Nullable; + +/** + * Represents a {@link Zombie} which was once a {@link Villager}. + */ +public interface ZombieVillager extends Zombie { + + /** + * Sets the villager profession of this zombie. + */ + @Override + void setVillagerProfession(@Nullable Villager.Profession profession); + + /** + * Returns the villager profession of this zombie. + * + * @return the profession or null + */ + @Override + @Nullable + Villager.Profession getVillagerProfession(); + + /** + * Get if this entity is in the process of converting to a Villager as a + * result of being cured. + * + * @return conversion status + */ + @Override + boolean isConverting(); + + /** + * Gets the amount of ticks until this entity will be converted to a + * Villager as a result of being cured. + * + * When this reaches 0, the entity will be converted. + * + * @return conversion time + * @throws IllegalStateException if {@link #isConverting()} is false. + */ + @Override + int getConversionTime(); + + /** + * Sets the amount of ticks until this entity will be converted to a + * Villager as a result of being cured. + * + * When this reaches 0, the entity will be converted. A value of less than 0 + * will stop the current conversion process without converting the current + * entity. + * + * @param time new conversion time + */ + @Override + void setConversionTime(int time); +} diff --git a/api/src/main/java/org/bukkit/entity/minecart/CommandMinecart.java b/api/src/main/java/org/bukkit/entity/minecart/CommandMinecart.java new file mode 100644 index 000000000..63c80b4ee --- /dev/null +++ b/api/src/main/java/org/bukkit/entity/minecart/CommandMinecart.java @@ -0,0 +1,38 @@ +package org.bukkit.entity.minecart; + +import org.bukkit.entity.Minecart; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +public interface CommandMinecart extends Minecart { + + /** + * Gets the command that this CommandMinecart will run when activated. + * This will never return null. If the CommandMinecart does not have a + * command, an empty String will be returned instead. + * + * @return Command that this CommandMinecart will run when powered. + */ + @NotNull + public String getCommand(); + + /** + * Sets the command that this CommandMinecart will run when activated. + * Setting the command to null is the same as setting it to an empty + * String. + * + * @param command Command that this CommandMinecart will run when + * activated. + */ + public void setCommand(@Nullable String command); + + /** + * Sets the name of this CommandMinecart. The name is used with commands + * that this CommandMinecart executes. Setting the name to null is the + * same as setting it to "@". + * + * @param name New name for this CommandMinecart. + */ + public void setName(@Nullable String name); + +} diff --git a/api/src/main/java/org/bukkit/entity/minecart/ExplosiveMinecart.java b/api/src/main/java/org/bukkit/entity/minecart/ExplosiveMinecart.java new file mode 100644 index 000000000..a4411daca --- /dev/null +++ b/api/src/main/java/org/bukkit/entity/minecart/ExplosiveMinecart.java @@ -0,0 +1,9 @@ +package org.bukkit.entity.minecart; + +import org.bukkit.entity.Minecart; + +/** + * Represents a Minecart with TNT inside it that can explode when triggered. + */ +public interface ExplosiveMinecart extends Minecart { +} diff --git a/api/src/main/java/org/bukkit/entity/minecart/HopperMinecart.java b/api/src/main/java/org/bukkit/entity/minecart/HopperMinecart.java new file mode 100644 index 000000000..865885501 --- /dev/null +++ b/api/src/main/java/org/bukkit/entity/minecart/HopperMinecart.java @@ -0,0 +1,27 @@ +package org.bukkit.entity.minecart; + +import com.destroystokyo.paper.loottable.LootableEntityInventory; +import org.bukkit.entity.Minecart; +import org.bukkit.inventory.InventoryHolder; +import org.bukkit.loot.Lootable; + +/** + * Represents a Minecart with a Hopper inside it + */ +public interface HopperMinecart extends Minecart, InventoryHolder, LootableEntityInventory { + + /** + * Checks whether or not this Minecart will pick up + * items into its inventory. + * + * @return true if the Minecart will pick up items + */ + boolean isEnabled(); + + /** + * Sets whether this Minecart will pick up items. + * + * @param enabled new enabled state + */ + void setEnabled(boolean enabled); +} diff --git a/api/src/main/java/org/bukkit/entity/minecart/PoweredMinecart.java b/api/src/main/java/org/bukkit/entity/minecart/PoweredMinecart.java new file mode 100644 index 000000000..57e8b1d59 --- /dev/null +++ b/api/src/main/java/org/bukkit/entity/minecart/PoweredMinecart.java @@ -0,0 +1,10 @@ +package org.bukkit.entity.minecart; + +import org.bukkit.entity.Minecart; + +/** + * Represents a powered minecart. A powered minecart moves on its own when a + * player deposits {@link org.bukkit.Material#COAL fuel}. + */ +public interface PoweredMinecart extends Minecart { +} diff --git a/api/src/main/java/org/bukkit/entity/minecart/RideableMinecart.java b/api/src/main/java/org/bukkit/entity/minecart/RideableMinecart.java new file mode 100644 index 000000000..1b8264573 --- /dev/null +++ b/api/src/main/java/org/bukkit/entity/minecart/RideableMinecart.java @@ -0,0 +1,14 @@ +package org.bukkit.entity.minecart; + +import org.bukkit.entity.Minecart; + +/** + * Represents a minecart that can have certain {@link + * org.bukkit.entity.Entity entities} as passengers. Normal passengers + * include all {@link org.bukkit.entity.LivingEntity living entities} with + * the exception of {@link org.bukkit.entity.IronGolem iron golems}. + * Non-player entities that meet normal passenger criteria automatically + * mount these minecarts when close enough. + */ +public interface RideableMinecart extends Minecart { +} diff --git a/api/src/main/java/org/bukkit/entity/minecart/SpawnerMinecart.java b/api/src/main/java/org/bukkit/entity/minecart/SpawnerMinecart.java new file mode 100644 index 000000000..0ce3592ec --- /dev/null +++ b/api/src/main/java/org/bukkit/entity/minecart/SpawnerMinecart.java @@ -0,0 +1,10 @@ +package org.bukkit.entity.minecart; + +import org.bukkit.entity.Minecart; + +/** + * Represents a Minecart with an {@link org.bukkit.block.CreatureSpawner + * entity spawner} inside it. + */ +public interface SpawnerMinecart extends Minecart { +} diff --git a/api/src/main/java/org/bukkit/entity/minecart/StorageMinecart.java b/api/src/main/java/org/bukkit/entity/minecart/StorageMinecart.java new file mode 100644 index 000000000..238d118f7 --- /dev/null +++ b/api/src/main/java/org/bukkit/entity/minecart/StorageMinecart.java @@ -0,0 +1,14 @@ +package org.bukkit.entity.minecart; + +import com.destroystokyo.paper.loottable.LootableEntityInventory; +import org.bukkit.entity.Minecart; +import org.bukkit.inventory.InventoryHolder; +import org.bukkit.loot.Lootable; + +/** + * Represents a minecart with a chest. These types of {@link Minecart + * minecarts} have their own inventory that can be accessed using methods + * from the {@link InventoryHolder} interface. + */ +public interface StorageMinecart extends Minecart, InventoryHolder, LootableEntityInventory { // Paper +} diff --git a/api/src/main/java/org/bukkit/event/Cancellable.java b/api/src/main/java/org/bukkit/event/Cancellable.java new file mode 100644 index 000000000..799b0b0f3 --- /dev/null +++ b/api/src/main/java/org/bukkit/event/Cancellable.java @@ -0,0 +1,20 @@ +package org.bukkit.event; + +public interface Cancellable { + + /** + * Gets the cancellation state of this event. A cancelled event will not + * be executed in the server, but will still pass to other plugins + * + * @return true if this event is cancelled + */ + public boolean isCancelled(); + + /** + * Sets the cancellation state of this event. A cancelled event will not + * be executed in the server, but will still pass to other plugins. + * + * @param cancel true if you wish to cancel this event + */ + public void setCancelled(boolean cancel); +} diff --git a/api/src/main/java/org/bukkit/event/Event.java b/api/src/main/java/org/bukkit/event/Event.java new file mode 100644 index 000000000..8ec56cd6b --- /dev/null +++ b/api/src/main/java/org/bukkit/event/Event.java @@ -0,0 +1,118 @@ +package org.bukkit.event; + +import org.bukkit.plugin.Plugin; +import org.bukkit.plugin.PluginManager; +import org.jetbrains.annotations.NotNull; + +/** + * Represents an event. + * + * All events require a static method named getHandlerList() which returns the same {@link HandlerList} as {@link #getHandlers()}. + * + * @see PluginManager#callEvent(Event) + * @see PluginManager#registerEvents(Listener,Plugin) + */ +public abstract class Event { + private String name; + private final boolean async; + + /** + * The default constructor is defined for cleaner code. This constructor + * assumes the event is synchronous. + */ + public Event() { + this(false); + } + + /** + * This constructor is used to explicitly declare an event as synchronous + * or asynchronous. + * + * @param isAsync true indicates the event will fire asynchronously, false + * by default from default constructor + */ + public Event(boolean isAsync) { + this.async = isAsync; + } + + // Paper start + /** + * Calls the event and tests if cancelled. + * + * @return false if event was cancelled, if cancellable. otherwise true. + */ + public boolean callEvent() { + org.bukkit.Bukkit.getPluginManager().callEvent(this); + if (this instanceof Cancellable) { + return !((Cancellable) this).isCancelled(); + } else { + return true; + } + } + // Paper end + + /** + * Convenience method for providing a user-friendly identifier. By + * default, it is the event's class's {@linkplain Class#getSimpleName() + * simple name}. + * + * @return name of this event + */ + @NotNull + public String getEventName() { + if (name == null) { + name = getClass().getSimpleName(); + } + return name; + } + + @NotNull + public abstract HandlerList getHandlers(); + + /** + * Any custom event that should not by synchronized with other events must + * use the specific constructor. These are the caveats of using an + * asynchronous event: + *

    + *
  • The event is never fired from inside code triggered by a + * synchronous event. Attempting to do so results in an {@link + * java.lang.IllegalStateException}. + *
  • However, asynchronous event handlers may fire synchronous or + * asynchronous events + *
  • The event may be fired multiple times simultaneously and in any + * order. + *
  • Any newly registered or unregistered handler is ignored after an + * event starts execution. + *
  • The handlers for this event may block for any length of time. + *
  • Some implementations may selectively declare a specific event use + * as asynchronous. This behavior should be clearly defined. + *
  • Asynchronous calls are not calculated in the plugin timing system. + *
+ * + * @return false by default, true if the event fires asynchronously + */ + public final boolean isAsynchronous() { + return async; + } + + public enum Result { + + /** + * Deny the event. Depending on the event, the action indicated by the + * event will either not take place or will be reverted. Some actions + * may not be denied. + */ + DENY, + /** + * Neither deny nor allow the event. The server will proceed with its + * normal handling. + */ + DEFAULT, + /** + * Allow / Force the event. The action indicated by the event will + * take place if possible, even if the server would not normally allow + * the action. Some actions may not be allowed. + */ + ALLOW; + } +} diff --git a/api/src/main/java/org/bukkit/event/EventException.java b/api/src/main/java/org/bukkit/event/EventException.java new file mode 100644 index 000000000..84638e852 --- /dev/null +++ b/api/src/main/java/org/bukkit/event/EventException.java @@ -0,0 +1,53 @@ +package org.bukkit.event; + +public class EventException extends Exception { + private static final long serialVersionUID = 3532808232324183999L; + private final Throwable cause; + + /** + * Constructs a new EventException based on the given Exception + * + * @param throwable Exception that triggered this Exception + */ + public EventException(Throwable throwable) { + cause = throwable; + } + + /** + * Constructs a new EventException + */ + public EventException() { + cause = null; + } + + /** + * Constructs a new EventException with the given message + * + * @param cause The exception that caused this + * @param message The message + */ + public EventException(Throwable cause, String message) { + super(message); + this.cause = cause; + } + + /** + * Constructs a new EventException with the given message + * + * @param message The message + */ + public EventException(String message) { + super(message); + cause = null; + } + + /** + * If applicable, returns the Exception that triggered this Exception + * + * @return Inner exception, or null if one does not exist + */ + @Override + public Throwable getCause() { + return cause; + } +} diff --git a/api/src/main/java/org/bukkit/event/EventHandler.java b/api/src/main/java/org/bukkit/event/EventHandler.java new file mode 100644 index 000000000..4c9fb3cde --- /dev/null +++ b/api/src/main/java/org/bukkit/event/EventHandler.java @@ -0,0 +1,41 @@ +package org.bukkit.event; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +/** + * An annotation to mark methods as being event handler methods + */ +@Target(ElementType.METHOD) +@Retention(RetentionPolicy.RUNTIME) +public @interface EventHandler { + + /** + * Define the priority of the event. + *

+ * First priority to the last priority executed: + *

    + *
  1. LOWEST + *
  2. LOW + *
  3. NORMAL + *
  4. HIGH + *
  5. HIGHEST + *
  6. MONITOR + *
+ * + * @return the priority + */ + EventPriority priority() default EventPriority.NORMAL; + + /** + * Define if the handler ignores a cancelled event. + *

+ * If ignoreCancelled is true and the event is cancelled, the method is + * not called. Otherwise, the method is always called. + * + * @return whether cancelled events should be ignored + */ + boolean ignoreCancelled() default false; +} diff --git a/api/src/main/java/org/bukkit/event/EventPriority.java b/api/src/main/java/org/bukkit/event/EventPriority.java new file mode 100644 index 000000000..61ffa50f2 --- /dev/null +++ b/api/src/main/java/org/bukkit/event/EventPriority.java @@ -0,0 +1,47 @@ +package org.bukkit.event; + +/** + * Represents an event's priority in execution + */ +public enum EventPriority { + + /** + * Event call is of very low importance and should be ran first, to allow + * other plugins to further customise the outcome + */ + LOWEST(0), + /** + * Event call is of low importance + */ + LOW(1), + /** + * Event call is neither important nor unimportant, and may be ran + * normally + */ + NORMAL(2), + /** + * Event call is of high importance + */ + HIGH(3), + /** + * Event call is critical and must have the final say in what happens + * to the event + */ + HIGHEST(4), + /** + * Event is listened to purely for monitoring the outcome of an event. + *

+ * No modifications to the event should be made under this priority + */ + MONITOR(5); + + private final int slot; + + private EventPriority(int slot) { + this.slot = slot; + } + + public int getSlot() { + return slot; + } +} diff --git a/src/api/main/java/org/bukkit/event/HandlerList.java b/api/src/main/java/org/bukkit/event/HandlerList.java similarity index 100% rename from src/api/main/java/org/bukkit/event/HandlerList.java rename to api/src/main/java/org/bukkit/event/HandlerList.java diff --git a/api/src/main/java/org/bukkit/event/Listener.java b/api/src/main/java/org/bukkit/event/Listener.java new file mode 100644 index 000000000..ff083e62d --- /dev/null +++ b/api/src/main/java/org/bukkit/event/Listener.java @@ -0,0 +1,6 @@ +package org.bukkit.event; + +/** + * Simple interface for tagging all EventListeners + */ +public interface Listener {} diff --git a/api/src/main/java/org/bukkit/event/block/Action.java b/api/src/main/java/org/bukkit/event/block/Action.java new file mode 100644 index 000000000..25d26e3fe --- /dev/null +++ b/api/src/main/java/org/bukkit/event/block/Action.java @@ -0,0 +1,33 @@ +package org.bukkit.event.block; + +public enum Action { + + /** + * Left-clicking a block + */ + LEFT_CLICK_BLOCK, + /** + * Right-clicking a block + */ + RIGHT_CLICK_BLOCK, + /** + * Left-clicking the air + */ + LEFT_CLICK_AIR, + /** + * Right-clicking the air + */ + RIGHT_CLICK_AIR, + /** + * Stepping onto or into a block (Ass-pressure) + * + * Examples: + *

    + *
  • Jumping on soil + *
  • Standing on pressure plate + *
  • Triggering redstone ore + *
  • Triggering tripwire + *
+ */ + PHYSICAL, +} diff --git a/api/src/main/java/org/bukkit/event/block/BlockBreakEvent.java b/api/src/main/java/org/bukkit/event/block/BlockBreakEvent.java new file mode 100644 index 000000000..ef5048ef1 --- /dev/null +++ b/api/src/main/java/org/bukkit/event/block/BlockBreakEvent.java @@ -0,0 +1,76 @@ +package org.bukkit.event.block; + +import org.bukkit.block.Block; +import org.bukkit.entity.Player; +import org.bukkit.event.Cancellable; +import org.jetbrains.annotations.NotNull; + +/** + * Called when a block is broken by a player. + *

+ * If you wish to have the block drop experience, you must set the experience + * value above 0. By default, experience will be set in the event if: + *

    + *
  1. The player is not in creative or adventure mode + *
  2. The player can loot the block (ie: does not destroy it completely, by + * using the correct tool) + *
  3. The player does not have silk touch + *
  4. The block drops experience in vanilla Minecraft + *
+ *

+ * Note: + * Plugins wanting to simulate a traditional block drop should set the block + * to air and utilize their own methods for determining what the default drop + * for the block being broken is and what to do about it, if anything. + *

+ * If a Block Break event is cancelled, the block will not break and + * experience will not drop. + */ +public class BlockBreakEvent extends BlockExpEvent implements Cancellable { + private final Player player; + private boolean dropItems; + private boolean cancel; + + public BlockBreakEvent(@NotNull final Block theBlock, @NotNull final Player player) { + super(theBlock, 0); + + this.player = player; + this.dropItems = true; // Defaults to dropping items as it normally would + } + + /** + * Gets the Player that is breaking the block involved in this event. + * + * @return The Player that is breaking the block involved in this event + */ + @NotNull + public Player getPlayer() { + return player; + } + + /** + * Sets whether or not the block will drop items as it normally would. + * + * @param dropItems Whether or not the block will drop items + */ + public void setDropItems(boolean dropItems) { + this.dropItems = dropItems; + } + + /** + * Gets whether or not the block will drop items. + * + * @return Whether or not the block will drop items + */ + public boolean isDropItems() { + return this.dropItems; + } + + public boolean isCancelled() { + return cancel; + } + + public void setCancelled(boolean cancel) { + this.cancel = cancel; + } +} diff --git a/api/src/main/java/org/bukkit/event/block/BlockBurnEvent.java b/api/src/main/java/org/bukkit/event/block/BlockBurnEvent.java new file mode 100644 index 000000000..3fc9c5716 --- /dev/null +++ b/api/src/main/java/org/bukkit/event/block/BlockBurnEvent.java @@ -0,0 +1,59 @@ +package org.bukkit.event.block; + +import org.bukkit.block.Block; +import org.bukkit.event.Cancellable; +import org.bukkit.event.HandlerList; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +/** + * Called when a block is destroyed as a result of being burnt by fire. + *

+ * If a Block Burn event is cancelled, the block will not be destroyed as a + * result of being burnt by fire. + */ +public class BlockBurnEvent extends BlockEvent implements Cancellable { + private static final HandlerList handlers = new HandlerList(); + private boolean cancelled; + private final Block ignitingBlock; + + @Deprecated + public BlockBurnEvent(@NotNull final Block block) { + this(block, null); + } + + public BlockBurnEvent(@NotNull final Block block, @Nullable final Block ignitingBlock) { + super(block); + this.ignitingBlock = ignitingBlock; + } + + /** + * Gets the block which ignited this block. + * + * @return The Block that ignited and burned this block, or null if no + * source block exists + */ + @Nullable + public Block getIgnitingBlock() { + return ignitingBlock; + } + + public boolean isCancelled() { + return cancelled; + } + + public void setCancelled(boolean cancel) { + this.cancelled = cancel; + } + + @NotNull + @Override + public HandlerList getHandlers() { + return handlers; + } + + @NotNull + public static HandlerList getHandlerList() { + return handlers; + } +} diff --git a/api/src/main/java/org/bukkit/event/block/BlockCanBuildEvent.java b/api/src/main/java/org/bukkit/event/block/BlockCanBuildEvent.java new file mode 100644 index 000000000..3c9903c04 --- /dev/null +++ b/api/src/main/java/org/bukkit/event/block/BlockCanBuildEvent.java @@ -0,0 +1,112 @@ +package org.bukkit.event.block; + +import org.bukkit.block.Block; +import org.bukkit.Material; +import org.bukkit.block.data.BlockData; +import org.bukkit.entity.Player; +import org.bukkit.event.HandlerList; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +/** + * Called when we try to place a block, to see if we can build it here or not. + *

+ * Note: + *

    + *
  • The Block returned by getBlock() is the block we are trying to place + * on, not the block we are trying to place. + *
  • If you want to figure out what is being placed, use {@link + * #getMaterial()} instead. + *
+ */ +public class BlockCanBuildEvent extends BlockEvent { + private static final HandlerList handlers = new HandlerList(); + protected boolean buildable; + + protected BlockData blockData; + private final Player player; + + @Deprecated + public BlockCanBuildEvent(@NotNull final Block block, @NotNull final BlockData type, final boolean canBuild) { + this(block, null, type, canBuild); + } + + /** + * + * @param block the block involved in this event + * @param player the player placing the block + * @param type the id of the block to place + * @param canBuild whether we can build + */ + public BlockCanBuildEvent(@NotNull final Block block, @Nullable final Player player, @NotNull final BlockData type, final boolean canBuild) { + super(block); + this.player = player; + this.buildable = canBuild; + this.blockData = type; + } + + /** + * Gets whether or not the block can be built here. + *

+ * By default, returns Minecraft's answer on whether the block can be + * built here or not. + * + * @return boolean whether or not the block can be built + */ + public boolean isBuildable() { + return buildable; + } + + /** + * Sets whether the block can be built here or not. + * + * @param cancel true if you want to allow the block to be built here + * despite Minecraft's default behaviour + */ + public void setBuildable(boolean cancel) { + this.buildable = cancel; + } + + /** + * Gets the Material that we are trying to place. + * + * @return The Material that we are trying to place + */ + @NotNull + public Material getMaterial() { + return blockData.getMaterial(); + } + + /** + * Gets the BlockData that we are trying to place. + * + * @return The BlockData that we are trying to place + */ + @NotNull + public BlockData getBlockData() { + return blockData; + } + + /** + * Gets the player who placed the block involved in this event. + *
+ * May be null for legacy calls of the event. + * + * @return The Player who placed the block involved in this event + */ + @Nullable + public Player getPlayer() { + return player; + } + + @NotNull + @Override + public HandlerList getHandlers() { + return handlers; + } + + @NotNull + public static HandlerList getHandlerList() { + return handlers; + } +} diff --git a/api/src/main/java/org/bukkit/event/block/BlockDamageEvent.java b/api/src/main/java/org/bukkit/event/block/BlockDamageEvent.java new file mode 100644 index 000000000..9fbe23ab2 --- /dev/null +++ b/api/src/main/java/org/bukkit/event/block/BlockDamageEvent.java @@ -0,0 +1,88 @@ +package org.bukkit.event.block; + +import org.bukkit.block.Block; +import org.bukkit.entity.Player; +import org.bukkit.event.Cancellable; +import org.bukkit.event.HandlerList; +import org.bukkit.inventory.ItemStack; +import org.jetbrains.annotations.NotNull; + +/** + * Called when a block is damaged by a player. + *

+ * If a Block Damage event is cancelled, the block will not be damaged. + */ +public class BlockDamageEvent extends BlockEvent implements Cancellable { + private static final HandlerList handlers = new HandlerList(); + private final Player player; + private boolean instaBreak; + private boolean cancel; + private final ItemStack itemstack; + + public BlockDamageEvent(@NotNull final Player player, @NotNull final Block block, @NotNull final ItemStack itemInHand, final boolean instaBreak) { + super(block); + this.instaBreak = instaBreak; + this.player = player; + this.itemstack = itemInHand; + this.cancel = false; + } + + /** + * Gets the player damaging the block involved in this event. + * + * @return The player damaging the block involved in this event + */ + @NotNull + public Player getPlayer() { + return player; + } + + /** + * Gets if the block is set to instantly break when damaged by the player. + * + * @return true if the block should instantly break when damaged by the + * player + */ + public boolean getInstaBreak() { + return instaBreak; + } + + /** + * Sets if the block should instantly break when damaged by the player. + * + * @param bool true if you want the block to instantly break when damaged + * by the player + */ + public void setInstaBreak(boolean bool) { + this.instaBreak = bool; + } + + /** + * Gets the ItemStack for the item currently in the player's hand. + * + * @return The ItemStack for the item currently in the player's hand + */ + @NotNull + public ItemStack getItemInHand() { + return itemstack; + } + + public boolean isCancelled() { + return cancel; + } + + public void setCancelled(boolean cancel) { + this.cancel = cancel; + } + + @NotNull + @Override + public HandlerList getHandlers() { + return handlers; + } + + @NotNull + public static HandlerList getHandlerList() { + return handlers; + } +} diff --git a/api/src/main/java/org/bukkit/event/block/BlockDispenseArmorEvent.java b/api/src/main/java/org/bukkit/event/block/BlockDispenseArmorEvent.java new file mode 100644 index 000000000..57b831979 --- /dev/null +++ b/api/src/main/java/org/bukkit/event/block/BlockDispenseArmorEvent.java @@ -0,0 +1,34 @@ +package org.bukkit.event.block; + +import org.bukkit.block.Block; +import org.bukkit.entity.LivingEntity; +import org.bukkit.inventory.ItemStack; +import org.bukkit.util.Vector; +import org.jetbrains.annotations.NotNull; + +/** + * Called when an equippable item is dispensed from a block and equipped on a + * nearby entity. + *

+ * If a Block Dispense Armor event is cancelled, the equipment will not be + * equipped on the target entity. + */ +public class BlockDispenseArmorEvent extends BlockDispenseEvent { + + private final LivingEntity target; + + public BlockDispenseArmorEvent(@NotNull Block block, @NotNull ItemStack dispensed, @NotNull LivingEntity target) { + super(block, dispensed, new Vector(0, 0, 0)); + this.target = target; + } + + /** + * Get the living entity on which the armor was dispensed. + * + * @return the target entity + */ + @NotNull + public LivingEntity getTargetEntity() { + return target; + } +} diff --git a/api/src/main/java/org/bukkit/event/block/BlockDispenseEvent.java b/api/src/main/java/org/bukkit/event/block/BlockDispenseEvent.java new file mode 100644 index 000000000..23c428d2d --- /dev/null +++ b/api/src/main/java/org/bukkit/event/block/BlockDispenseEvent.java @@ -0,0 +1,89 @@ +package org.bukkit.event.block; + +import org.bukkit.block.Block; +import org.bukkit.event.Cancellable; +import org.bukkit.event.HandlerList; +import org.bukkit.inventory.ItemStack; +import org.bukkit.util.Vector; +import org.jetbrains.annotations.NotNull; + +/** + * Called when an item is dispensed from a block. + *

+ * If a Block Dispense event is cancelled, the block will not dispense the + * item. + */ +public class BlockDispenseEvent extends BlockEvent implements Cancellable { + private static final HandlerList handlers = new HandlerList(); + private boolean cancelled = false; + private ItemStack item; + private Vector velocity; + + public BlockDispenseEvent(@NotNull final Block block, @NotNull final ItemStack dispensed, @NotNull final Vector velocity) { + super(block); + this.item = dispensed; + this.velocity = velocity; + } + + /** + * Gets the item that is being dispensed. Modifying the returned item will + * have no effect, you must use {@link + * #setItem(org.bukkit.inventory.ItemStack)} instead. + * + * @return An ItemStack for the item being dispensed + */ + @NotNull + public ItemStack getItem() { + return item.clone(); + } + + /** + * Sets the item being dispensed. + * + * @param item the item being dispensed + */ + public void setItem(@NotNull ItemStack item) { + this.item = item; + } + + /** + * Gets the velocity. + *

+ * Note: Modifying the returned Vector will not change the velocity, you + * must use {@link #setVelocity(org.bukkit.util.Vector)} instead. + * + * @return A Vector for the dispensed item's velocity + */ + @NotNull + public Vector getVelocity() { + return velocity.clone(); + } + + /** + * Sets the velocity of the item being dispensed. + * + * @param vel the velocity of the item being dispensed + */ + public void setVelocity(@NotNull Vector vel) { + velocity = vel; + } + + public boolean isCancelled() { + return cancelled; + } + + public void setCancelled(boolean cancel) { + cancelled = cancel; + } + + @NotNull + @Override + public HandlerList getHandlers() { + return handlers; + } + + @NotNull + public static HandlerList getHandlerList() { + return handlers; + } +} diff --git a/api/src/main/java/org/bukkit/event/block/BlockDropItemEvent.java b/api/src/main/java/org/bukkit/event/block/BlockDropItemEvent.java new file mode 100644 index 000000000..6328e66bd --- /dev/null +++ b/api/src/main/java/org/bukkit/event/block/BlockDropItemEvent.java @@ -0,0 +1,110 @@ +package org.bukkit.event.block; + +import java.util.List; +import org.bukkit.Warning; +import org.bukkit.block.Block; +import org.bukkit.block.BlockState; +import org.bukkit.entity.Item; +import org.bukkit.entity.Player; +import org.bukkit.event.Cancellable; +import org.bukkit.event.HandlerList; +import org.jetbrains.annotations.NotNull; + +/** + * Called if a block broken by a player drops an item. + * + * If the block break is cancelled, this event won't be called. + * + * If isDropItems in BlockBreakEvent is set to false, this event won't be + * called. + * + * This event will also be called if the player breaks a multi block structure, + * for example a torch on top of a stone. Both items will have an event call. + * + * The Block is already broken as this event is called, so #getBlock() will be + * AIR in most cases. Use #getBlockState() for more Information about the broken + * block. + * + * @deprecated draft API + */ +@Deprecated +@Warning(false) +public class BlockDropItemEvent extends BlockEvent implements Cancellable { + + private static final HandlerList handlers = new HandlerList(); + private final Player player; + private boolean cancel; + private final BlockState blockState; + private final List items; + + public BlockDropItemEvent(@NotNull Block block, @NotNull BlockState blockState, @NotNull Player player, @NotNull List items) { + super(block); + this.blockState = blockState; + this.player = player; + this.items = items; + } + + /** + * Gets the Player that is breaking the block involved in this event. + * + * @return The Player that is breaking the block involved in this event + */ + @NotNull + public Player getPlayer() { + return player; + } + + /** + * Gets the BlockState of the block involved in this event before it was + * broken. + * + * @return The BlockState of the block involved in this event + */ + @NotNull + public BlockState getBlockState() { + return blockState; + } + + /** + * Gets list of the Item drops caused by the block break. + * + * This list is mutable - removing an item from it will cause it to not + * drop. It is not legal however to add new items to the list. + * + * @return The Item the block caused to drop + */ + @NotNull + public List getItems() { + return items; + } + + /** + * @deprecated very temporary compatibility measure + */ + @Deprecated + @NotNull + public Item getItem() { + return items.get(0); + } + + @Override + public boolean isCancelled() { + return cancel; + } + + @Override + public void setCancelled(boolean cancel) { + this.cancel = cancel; + } + + @NotNull + @Override + public HandlerList getHandlers() { + return handlers; + } + + @NotNull + public static HandlerList getHandlerList() { + return handlers; + } +} diff --git a/api/src/main/java/org/bukkit/event/block/BlockEvent.java b/api/src/main/java/org/bukkit/event/block/BlockEvent.java new file mode 100644 index 000000000..62a4d1345 --- /dev/null +++ b/api/src/main/java/org/bukkit/event/block/BlockEvent.java @@ -0,0 +1,26 @@ +package org.bukkit.event.block; + +import org.bukkit.block.Block; +import org.bukkit.event.Event; +import org.jetbrains.annotations.NotNull; + +/** + * Represents a block related event. + */ +public abstract class BlockEvent extends Event { + protected Block block; + + public BlockEvent(@NotNull final Block theBlock) { + block = theBlock; + } + + /** + * Gets the block involved in this event. + * + * @return The Block which block is involved in this event + */ + @NotNull + public final Block getBlock() { + return block; + } +} diff --git a/api/src/main/java/org/bukkit/event/block/BlockExpEvent.java b/api/src/main/java/org/bukkit/event/block/BlockExpEvent.java new file mode 100644 index 000000000..c5d9bf3c2 --- /dev/null +++ b/api/src/main/java/org/bukkit/event/block/BlockExpEvent.java @@ -0,0 +1,48 @@ +package org.bukkit.event.block; + +import org.bukkit.block.Block; +import org.bukkit.event.HandlerList; +import org.jetbrains.annotations.NotNull; + +/** + * An event that's called when a block yields experience. + */ +public class BlockExpEvent extends BlockEvent { + private static final HandlerList handlers = new HandlerList(); + private int exp; + + public BlockExpEvent(@NotNull Block block, int exp) { + super(block); + + this.exp = exp; + } + + /** + * Get the experience dropped by the block after the event has processed + * + * @return The experience to drop + */ + public int getExpToDrop() { + return exp; + } + + /** + * Set the amount of experience dropped by the block after the event has + * processed + * + * @param exp 1 or higher to drop experience, else nothing will drop + */ + public void setExpToDrop(int exp) { + this.exp = exp; + } + + @NotNull + public HandlerList getHandlers() { + return handlers; + } + + @NotNull + public static HandlerList getHandlerList() { + return handlers; + } +} diff --git a/api/src/main/java/org/bukkit/event/block/BlockExplodeEvent.java b/api/src/main/java/org/bukkit/event/block/BlockExplodeEvent.java new file mode 100644 index 000000000..635d4bc0e --- /dev/null +++ b/api/src/main/java/org/bukkit/event/block/BlockExplodeEvent.java @@ -0,0 +1,73 @@ +package org.bukkit.event.block; + +import org.bukkit.block.Block; +import org.bukkit.event.Cancellable; +import org.bukkit.event.HandlerList; +import org.jetbrains.annotations.NotNull; + +import java.util.List; + +/** + * Called when a block explodes + */ +public class BlockExplodeEvent extends BlockEvent implements Cancellable { + private static final HandlerList handlers = new HandlerList(); + private boolean cancel; + private final List blocks; + private float yield; + + public BlockExplodeEvent(@NotNull final Block what, @NotNull final List blocks, final float yield) { + super(what); + this.blocks = blocks; + this.yield = yield; + this.cancel = false; + } + + public boolean isCancelled() { + return cancel; + } + + public void setCancelled(boolean cancel) { + this.cancel = cancel; + } + + /** + * Returns the list of blocks that would have been removed or were removed + * from the explosion event. + * + * @return All blown-up blocks + */ + @NotNull + public List blockList() { + return blocks; + } + + /** + * Returns the percentage of blocks to drop from this explosion + * + * @return The yield. + */ + public float getYield() { + return yield; + } + + /** + * Sets the percentage of blocks to drop from this explosion + * + * @param yield The new yield percentage + */ + public void setYield(float yield) { + this.yield = yield; + } + + @NotNull + @Override + public HandlerList getHandlers() { + return handlers; + } + + @NotNull + public static HandlerList getHandlerList() { + return handlers; + } +} diff --git a/api/src/main/java/org/bukkit/event/block/BlockFadeEvent.java b/api/src/main/java/org/bukkit/event/block/BlockFadeEvent.java new file mode 100644 index 000000000..844dc823f --- /dev/null +++ b/api/src/main/java/org/bukkit/event/block/BlockFadeEvent.java @@ -0,0 +1,65 @@ +package org.bukkit.event.block; + +import org.bukkit.block.Block; +import org.bukkit.block.BlockState; +import org.bukkit.event.Cancellable; +import org.bukkit.event.HandlerList; +import org.jetbrains.annotations.NotNull; + +/** + * Called when a block fades, melts or disappears based on world conditions + *

+ * Examples: + *

    + *
  • Snow melting due to being near a light source. + *
  • Ice melting due to being near a light source. + *
  • Fire burning out after time, without destroying fuel block. + *
  • Coral fading to dead coral due to lack of water
  • + *
  • Turtle Egg bursting when a turtle hatches
  • + *
+ *

+ * If a Block Fade event is cancelled, the block will not fade, melt or + * disappear. + */ +public class BlockFadeEvent extends BlockEvent implements Cancellable { + private static final HandlerList handlers = new HandlerList(); + private boolean cancelled; + private final BlockState newState; + + public BlockFadeEvent(@NotNull final Block block, @NotNull final BlockState newState) { + super(block); + this.newState = newState; + this.cancelled = false; + } + + /** + * Gets the state of the block that will be fading, melting or + * disappearing. + * + * @return The block state of the block that will be fading, melting or + * disappearing + */ + @NotNull + public BlockState getNewState() { + return newState; + } + + public boolean isCancelled() { + return cancelled; + } + + public void setCancelled(boolean cancel) { + this.cancelled = cancel; + } + + @NotNull + @Override + public HandlerList getHandlers() { + return handlers; + } + + @NotNull + public static HandlerList getHandlerList() { + return handlers; + } +} diff --git a/api/src/main/java/org/bukkit/event/block/BlockFertilizeEvent.java b/api/src/main/java/org/bukkit/event/block/BlockFertilizeEvent.java new file mode 100644 index 000000000..1d12e68cc --- /dev/null +++ b/api/src/main/java/org/bukkit/event/block/BlockFertilizeEvent.java @@ -0,0 +1,73 @@ +package org.bukkit.event.block; + +import java.util.List; + +import org.bukkit.block.Block; +import org.bukkit.block.BlockState; +import org.bukkit.entity.Player; +import org.bukkit.event.Cancellable; +import org.bukkit.event.HandlerList; +import org.bukkit.event.world.StructureGrowEvent; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +/** + * Called with the block changes resulting from a player fertilizing a given + * block with bonemeal. Will be called after the applicable + * {@link StructureGrowEvent}. + */ +public class BlockFertilizeEvent extends BlockEvent implements Cancellable { + + private static final HandlerList handlers = new HandlerList(); + private boolean cancelled; + // + private final Player player; + private final List blocks; + + public BlockFertilizeEvent(@NotNull Block theBlock, @Nullable Player player, @NotNull List blocks) { + super(theBlock); + this.player = player; + this.blocks = blocks; + } + + /** + * Gets the player that triggered the fertilization. + * + * @return triggering player, or null if not applicable + */ + @Nullable + public Player getPlayer() { + return player; + } + + /** + * Gets a list of all blocks changed by the fertilization. + * + * @return list of all changed blocks + */ + @NotNull + public List getBlocks() { + return blocks; + } + + @Override + public boolean isCancelled() { + return cancelled; + } + + @Override + public void setCancelled(boolean cancelled) { + this.cancelled = cancelled; + } + + @NotNull + @Override + public HandlerList getHandlers() { + return handlers; + } + + @NotNull + public static HandlerList getHandlerList() { + return handlers; + } +} diff --git a/api/src/main/java/org/bukkit/event/block/BlockFormEvent.java b/api/src/main/java/org/bukkit/event/block/BlockFormEvent.java new file mode 100644 index 000000000..010678041 --- /dev/null +++ b/api/src/main/java/org/bukkit/event/block/BlockFormEvent.java @@ -0,0 +1,43 @@ +package org.bukkit.event.block; + +import org.bukkit.block.Block; +import org.bukkit.block.BlockState; +import org.bukkit.event.HandlerList; +import org.jetbrains.annotations.NotNull; + +/** + * Called when a block is formed or spreads based on world conditions. + *

+ * Use {@link BlockSpreadEvent} to catch blocks that actually spread and don't + * just "randomly" form. + *

+ * Examples: + *

    + *
  • Snow forming due to a snow storm. + *
  • Ice forming in a snowy Biome like Taiga or Tundra. + *
  • Obsidian / Cobblestone forming due to contact with water. + *
  • Concrete forming due to mixing of concrete powder and water. + *
+ *

+ * If a Block Form event is cancelled, the block will not be formed. + * + * @see BlockSpreadEvent + */ +public class BlockFormEvent extends BlockGrowEvent { + private static final HandlerList handlers = new HandlerList(); + + public BlockFormEvent(@NotNull final Block block, @NotNull final BlockState newState) { + super(block, newState); + } + + @NotNull + @Override + public HandlerList getHandlers() { + return handlers; + } + + @NotNull + public static HandlerList getHandlerList() { + return handlers; + } +} diff --git a/api/src/main/java/org/bukkit/event/block/BlockFromToEvent.java b/api/src/main/java/org/bukkit/event/block/BlockFromToEvent.java new file mode 100644 index 000000000..2fb4a214a --- /dev/null +++ b/api/src/main/java/org/bukkit/event/block/BlockFromToEvent.java @@ -0,0 +1,76 @@ +package org.bukkit.event.block; + +import org.bukkit.block.Block; +import org.bukkit.block.BlockFace; +import org.bukkit.event.Cancellable; +import org.bukkit.event.HandlerList; +import org.jetbrains.annotations.NotNull; + +/** + * Represents events with a source block and a destination block, currently + * only applies to liquid (lava and water) and teleporting dragon eggs. + *

+ * If a Block From To event is cancelled, the block will not move (the liquid + * will not flow). + */ +public class BlockFromToEvent extends BlockEvent implements Cancellable { + private static final HandlerList handlers = new HandlerList(); + protected Block to; + protected BlockFace face; + protected boolean cancel; + + public BlockFromToEvent(@NotNull final Block block, @NotNull final BlockFace face) { + super(block); + this.face = face; + this.cancel = false; + } + + public BlockFromToEvent(@NotNull final Block block, @NotNull final Block toBlock) { + super(block); + this.to = toBlock; + this.face = BlockFace.SELF; + this.cancel = false; + } + + /** + * Gets the BlockFace that the block is moving to. + * + * @return The BlockFace that the block is moving to + */ + @NotNull + public BlockFace getFace() { + return face; + } + + /** + * Convenience method for getting the faced Block. + * + * @return The faced Block + */ + @NotNull + public Block getToBlock() { + if (to == null) { + to = block.getRelative(face); + } + return to; + } + + public boolean isCancelled() { + return cancel; + } + + public void setCancelled(boolean cancel) { + this.cancel = cancel; + } + + @NotNull + @Override + public HandlerList getHandlers() { + return handlers; + } + + @NotNull + public static HandlerList getHandlerList() { + return handlers; + } +} diff --git a/api/src/main/java/org/bukkit/event/block/BlockGrowEvent.java b/api/src/main/java/org/bukkit/event/block/BlockGrowEvent.java new file mode 100644 index 000000000..d3679795a --- /dev/null +++ b/api/src/main/java/org/bukkit/event/block/BlockGrowEvent.java @@ -0,0 +1,61 @@ +package org.bukkit.event.block; + +import org.bukkit.block.Block; +import org.bukkit.block.BlockState; +import org.bukkit.event.Cancellable; +import org.bukkit.event.HandlerList; +import org.jetbrains.annotations.NotNull; + +/** + * Called when a block grows naturally in the world. + *

+ * Examples: + *

    + *
  • Wheat + *
  • Sugar Cane + *
  • Cactus + *
  • Watermelon + *
  • Pumpkin + *
  • Turtle Egg + *
+ *

+ * If a Block Grow event is cancelled, the block will not grow. + */ +public class BlockGrowEvent extends BlockEvent implements Cancellable { + private static final HandlerList handlers = new HandlerList(); + private final BlockState newState; + private boolean cancelled = false; + + public BlockGrowEvent(@NotNull final Block block, @NotNull final BlockState newState) { + super(block); + this.newState = newState; + } + + /** + * Gets the state of the block where it will form or spread to. + * + * @return The block state for this events block + */ + @NotNull + public BlockState getNewState() { + return newState; + } + + public boolean isCancelled() { + return cancelled; + } + + public void setCancelled(boolean cancel) { + this.cancelled = cancel; + } + + @NotNull + public HandlerList getHandlers() { + return handlers; + } + + @NotNull + public static HandlerList getHandlerList() { + return handlers; + } +} diff --git a/api/src/main/java/org/bukkit/event/block/BlockIgniteEvent.java b/api/src/main/java/org/bukkit/event/block/BlockIgniteEvent.java new file mode 100644 index 000000000..5dc2bc1f5 --- /dev/null +++ b/api/src/main/java/org/bukkit/event/block/BlockIgniteEvent.java @@ -0,0 +1,137 @@ +package org.bukkit.event.block; + +import org.bukkit.block.Block; +import org.bukkit.entity.Entity; +import org.bukkit.entity.Player; +import org.bukkit.event.Cancellable; +import org.bukkit.event.HandlerList; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +/** + * Called when a block is ignited. If you want to catch when a Player places + * fire, you need to use {@link BlockPlaceEvent}. + *

+ * If a Block Ignite event is cancelled, the block will not be ignited. + */ +public class BlockIgniteEvent extends BlockEvent implements Cancellable { + private static final HandlerList handlers = new HandlerList(); + private final IgniteCause cause; + private final Entity ignitingEntity; + private final Block ignitingBlock; + private boolean cancel; + + public BlockIgniteEvent(@NotNull final Block theBlock, @NotNull final IgniteCause cause, @NotNull final Entity ignitingEntity) { + this(theBlock, cause, ignitingEntity, null); + } + + public BlockIgniteEvent(@NotNull final Block theBlock, @NotNull final IgniteCause cause, @NotNull final Block ignitingBlock) { + this(theBlock, cause, null, ignitingBlock); + } + + public BlockIgniteEvent(@NotNull final Block theBlock, @NotNull final IgniteCause cause, @Nullable final Entity ignitingEntity, @Nullable final Block ignitingBlock) { + super(theBlock); + this.cause = cause; + this.ignitingEntity = ignitingEntity; + this.ignitingBlock = ignitingBlock; + this.cancel = false; + } + + public boolean isCancelled() { + return cancel; + } + + public void setCancelled(boolean cancel) { + this.cancel = cancel; + } + + /** + * Gets the cause of block ignite. + * + * @return An IgniteCause value detailing the cause of block ignition + */ + @NotNull + public IgniteCause getCause() { + return cause; + } + + /** + * Gets the player who ignited this block + * + * @return The Player that placed/ignited the fire block, or null if not ignited by a Player. + */ + @Nullable + public Player getPlayer() { + if (ignitingEntity instanceof Player) { + return (Player) ignitingEntity; + } + + return null; + } + + /** + * Gets the entity who ignited this block + * + * @return The Entity that placed/ignited the fire block, or null if not ignited by a Entity. + */ + @Nullable + public Entity getIgnitingEntity() { + return ignitingEntity; + } + + /** + * Gets the block which ignited this block + * + * @return The Block that placed/ignited the fire block, or null if not ignited by a Block. + */ + @Nullable + public Block getIgnitingBlock() { + return ignitingBlock; + } + + /** + * An enum to specify the cause of the ignite + */ + public enum IgniteCause { + + /** + * Block ignition caused by lava. + */ + LAVA, + /** + * Block ignition caused by a player or dispenser using flint-and-steel. + */ + FLINT_AND_STEEL, + /** + * Block ignition caused by dynamic spreading of fire. + */ + SPREAD, + /** + * Block ignition caused by lightning. + */ + LIGHTNING, + /** + * Block ignition caused by an entity using a fireball. + */ + FIREBALL, + /** + * Block ignition caused by an Ender Crystal. + */ + ENDER_CRYSTAL, + /** + * Block ignition caused by explosion. + */ + EXPLOSION, + } + + @NotNull + @Override + public HandlerList getHandlers() { + return handlers; + } + + @NotNull + public static HandlerList getHandlerList() { + return handlers; + } +} diff --git a/api/src/main/java/org/bukkit/event/block/BlockMultiPlaceEvent.java b/api/src/main/java/org/bukkit/event/block/BlockMultiPlaceEvent.java new file mode 100644 index 000000000..e4f7ca66d --- /dev/null +++ b/api/src/main/java/org/bukkit/event/block/BlockMultiPlaceEvent.java @@ -0,0 +1,38 @@ +package org.bukkit.event.block; + +import com.google.common.collect.ImmutableList; +import org.bukkit.block.Block; +import org.bukkit.block.BlockState; +import org.bukkit.entity.Player; +import org.bukkit.inventory.ItemStack; +import org.jetbrains.annotations.NotNull; + +import java.util.List; + +/** + * Fired when a single block placement action of a player triggers the + * creation of multiple blocks(e.g. placing a bed block). The block returned + * by {@link #getBlockPlaced()} and its related methods is the block where + * the placed block would exist if the placement only affected a single + * block. + */ +public class BlockMultiPlaceEvent extends BlockPlaceEvent { + private final List states; + + public BlockMultiPlaceEvent(@NotNull List states, @NotNull Block clicked, @NotNull ItemStack itemInHand, @NotNull Player thePlayer, boolean canBuild) { + super(states.get(0).getBlock(), states.get(0), clicked, itemInHand, thePlayer, canBuild); + this.states = ImmutableList.copyOf(states); + } + + /** + * Gets a list of blockstates for all blocks which were replaced by the + * placement of the new blocks. Most of these blocks will just have a + * Material type of AIR. + * + * @return immutable list of replaced BlockStates + */ + @NotNull + public List getReplacedBlockStates() { + return states; + } +} diff --git a/api/src/main/java/org/bukkit/event/block/BlockPhysicsEvent.java b/api/src/main/java/org/bukkit/event/block/BlockPhysicsEvent.java new file mode 100644 index 000000000..a28731dc2 --- /dev/null +++ b/api/src/main/java/org/bukkit/event/block/BlockPhysicsEvent.java @@ -0,0 +1,92 @@ +package org.bukkit.event.block; + +import org.bukkit.block.Block; +import org.bukkit.Material; +import org.bukkit.block.data.BlockData; +import org.bukkit.event.Cancellable; +import org.bukkit.event.HandlerList; +import org.jetbrains.annotations.NotNull; + +/** + * Thrown when a block physics check is called. + *
+ * This event is a high frequency event, it may be called thousands of times per + * a second on a busy server. Plugins are advised to listen to the event with + * caution and only perform lightweight checks when using it. + *
+ * In addition to this, cancelling the event is liable to leave the world in an + * inconsistent state. For example if you use the event to leave a block + * floating in mid air when that block has a requirement to be attached to + * something, there is no guarantee that the floating block will persist across + * server restarts or map upgrades. + *
+ * Plugins should also note that where possible this event may only called for + * the "root" block of physics updates in order to limit event spam. Physics + * updates that cause other blocks to change their state may not result in an + * event for each of those blocks (usually adjacent). If you are concerned about + * monitoring these changes then you should check adjacent blocks yourself. + */ +public class BlockPhysicsEvent extends BlockEvent implements Cancellable { + private static final HandlerList handlers = new HandlerList(); + private final BlockData changed; + private final Block sourceBlock; + private boolean cancel = false; + + // Paper start - Legacy constructor, use #BlockPhysicsEvent(Block, BlockData, Block) + @Deprecated + public BlockPhysicsEvent(final Block block, final BlockData changed, final int sourceX, final int sourceY, final int sourceZ) { + this(block, changed, block.getWorld().getBlockAt(sourceX, sourceY, sourceZ)); + } + // Paper end + + public BlockPhysicsEvent(@NotNull final Block block, @NotNull final BlockData changed) { + this(block, changed, block); + } + + public BlockPhysicsEvent(@NotNull final Block block, @NotNull final BlockData changed, @NotNull final Block sourceBlock) { + super(block); + this.changed = changed; + this.sourceBlock = sourceBlock; + } + + /** + * Gets the source block that triggered this event. + * + * Note: This will default to block if not set. + * + * @return The source block + */ + @NotNull + public Block getSourceBlock() { + return sourceBlock; + } + + /** + * Gets the type of block that changed, causing this event + * + * @return Changed block's type + */ + @NotNull + public Material getChangedType() { + return changed.getMaterial(); + } + + public boolean isCancelled() { + return cancel; + } + + public void setCancelled(boolean cancel) { + this.cancel = cancel; + } + + @NotNull + @Override + public HandlerList getHandlers() { + return handlers; + } + + @NotNull + public static HandlerList getHandlerList() { + return handlers; + } +} diff --git a/api/src/main/java/org/bukkit/event/block/BlockPistonEvent.java b/api/src/main/java/org/bukkit/event/block/BlockPistonEvent.java new file mode 100644 index 000000000..47191c8f2 --- /dev/null +++ b/api/src/main/java/org/bukkit/event/block/BlockPistonEvent.java @@ -0,0 +1,50 @@ +package org.bukkit.event.block; + +import org.bukkit.Material; +import org.bukkit.block.Block; +import org.bukkit.block.BlockFace; +import org.bukkit.event.Cancellable; +import org.jetbrains.annotations.NotNull; + +/** + * Called when a piston block is triggered + */ +public abstract class BlockPistonEvent extends BlockEvent implements Cancellable { + private boolean cancelled; + private final BlockFace direction; + + public BlockPistonEvent(@NotNull final Block block, @NotNull final BlockFace direction) { + super(block); + this.direction = direction; + } + + public boolean isCancelled() { + return this.cancelled; + } + + public void setCancelled(boolean cancelled) { + this.cancelled = cancelled; + } + + /** + * Returns true if the Piston in the event is sticky. + * + * @return stickiness of the piston + */ + public boolean isSticky() { + return block.getType() == Material.STICKY_PISTON || block.getType() == Material.MOVING_PISTON; + } + + /** + * Return the direction in which the piston will operate. + * + * @return direction of the piston + */ + @NotNull + public BlockFace getDirection() { + // Both are meh! + // return ((PistonBaseMaterial) block.getType().getNewData(block.getData())).getFacing(); + // return ((PistonBaseMaterial) block.getState().getData()).getFacing(); + return direction; + } +} diff --git a/api/src/main/java/org/bukkit/event/block/BlockPistonExtendEvent.java b/api/src/main/java/org/bukkit/event/block/BlockPistonExtendEvent.java new file mode 100644 index 000000000..51bca6515 --- /dev/null +++ b/api/src/main/java/org/bukkit/event/block/BlockPistonExtendEvent.java @@ -0,0 +1,74 @@ +package org.bukkit.event.block; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; + +import org.bukkit.block.Block; +import org.bukkit.block.BlockFace; +import org.bukkit.event.HandlerList; +import org.jetbrains.annotations.NotNull; + +/** + * Called when a piston extends + */ +public class BlockPistonExtendEvent extends BlockPistonEvent { + private static final HandlerList handlers = new HandlerList(); + private final int length; + private List blocks; + + @Deprecated + public BlockPistonExtendEvent(@NotNull final Block block, final int length, @NotNull final BlockFace direction) { + super(block, direction); + + this.length = length; + } + + public BlockPistonExtendEvent(@NotNull final Block block, @NotNull final List blocks, @NotNull final BlockFace direction) { + super(block, direction); + + this.length = blocks.size(); + this.blocks = blocks; + } + + /** + * Get the amount of blocks which will be moved while extending. + * + * @return the amount of moving blocks + * @deprecated slime blocks make the value of this method + * inaccurate due to blocks being pushed at the side + */ + @Deprecated + public int getLength() { + return this.length; + } + + /** + * Get an immutable list of the blocks which will be moved by the + * extending. + * + * @return Immutable list of the moved blocks. + */ + @NotNull + public List getBlocks() { + if (blocks == null) { + ArrayList tmp = new ArrayList(); + for (int i = 0; i < this.getLength(); i++) { + tmp.add(block.getRelative(getDirection(), i + 1)); + } + blocks = Collections.unmodifiableList(tmp); + } + return blocks; + } + + @NotNull + @Override + public HandlerList getHandlers() { + return handlers; + } + + @NotNull + public static HandlerList getHandlerList() { + return handlers; + } +} diff --git a/api/src/main/java/org/bukkit/event/block/BlockPistonRetractEvent.java b/api/src/main/java/org/bukkit/event/block/BlockPistonRetractEvent.java new file mode 100644 index 000000000..4744e0fa5 --- /dev/null +++ b/api/src/main/java/org/bukkit/event/block/BlockPistonRetractEvent.java @@ -0,0 +1,56 @@ +package org.bukkit.event.block; + +import java.util.List; +import org.bukkit.Location; +import org.bukkit.block.Block; +import org.bukkit.block.BlockFace; +import org.bukkit.event.HandlerList; +import org.jetbrains.annotations.NotNull; + +/** + * Called when a piston retracts + */ +public class BlockPistonRetractEvent extends BlockPistonEvent { + private static final HandlerList handlers = new HandlerList(); + private List blocks; + + public BlockPistonRetractEvent(@NotNull final Block block, @NotNull final List blocks, @NotNull final BlockFace direction) { + super(block, direction); + + this.blocks = blocks; + } + + /** + * Gets the location where the possible moving block might be if the + * retracting piston is sticky. + * + * @return The possible location of the possibly moving block. + */ + @Deprecated + @NotNull + public Location getRetractLocation() { + return getBlock().getRelative(getDirection(), 2).getLocation(); + } + + /** + * Get an immutable list of the blocks which will be moved by the + * extending. + * + * @return Immutable list of the moved blocks. + */ + @NotNull + public List getBlocks() { + return blocks; + } + + @NotNull + @Override + public HandlerList getHandlers() { + return handlers; + } + + @NotNull + public static HandlerList getHandlerList() { + return handlers; + } +} diff --git a/api/src/main/java/org/bukkit/event/block/BlockPlaceEvent.java b/api/src/main/java/org/bukkit/event/block/BlockPlaceEvent.java new file mode 100644 index 000000000..586e3c8ed --- /dev/null +++ b/api/src/main/java/org/bukkit/event/block/BlockPlaceEvent.java @@ -0,0 +1,146 @@ +package org.bukkit.event.block; + +import org.bukkit.block.Block; +import org.bukkit.block.BlockState; +import org.bukkit.entity.Player; +import org.bukkit.event.Cancellable; +import org.bukkit.event.HandlerList; +import org.bukkit.inventory.EquipmentSlot; +import org.bukkit.inventory.ItemStack; +import org.jetbrains.annotations.NotNull; + +/** + * Called when a block is placed by a player. + *

+ * If a Block Place event is cancelled, the block will not be placed. + */ +public class BlockPlaceEvent extends BlockEvent implements Cancellable { + private static final HandlerList handlers = new HandlerList(); + protected boolean cancel; + protected boolean canBuild; + protected Block placedAgainst; + protected BlockState replacedBlockState; + protected ItemStack itemInHand; + protected Player player; + protected EquipmentSlot hand; + + @Deprecated + public BlockPlaceEvent(@NotNull final Block placedBlock, @NotNull final BlockState replacedBlockState, @NotNull final Block placedAgainst, @NotNull final ItemStack itemInHand, @NotNull final Player thePlayer, final boolean canBuild) { + this(placedBlock, replacedBlockState, placedAgainst, itemInHand, thePlayer, canBuild, EquipmentSlot.HAND); + } + + public BlockPlaceEvent(@NotNull final Block placedBlock, @NotNull final BlockState replacedBlockState, @NotNull final Block placedAgainst, @NotNull final ItemStack itemInHand, @NotNull final Player thePlayer, final boolean canBuild, @NotNull final EquipmentSlot hand) { + super(placedBlock); + this.placedAgainst = placedAgainst; + this.itemInHand = itemInHand; + this.player = thePlayer; + this.replacedBlockState = replacedBlockState; + this.canBuild = canBuild; + this.hand = hand; + cancel = false; + } + + public boolean isCancelled() { + return cancel; + } + + public void setCancelled(boolean cancel) { + this.cancel = cancel; + } + + /** + * Gets the player who placed the block involved in this event. + * + * @return The Player who placed the block involved in this event + */ + @NotNull + public Player getPlayer() { + return player; + } + + /** + * Clarity method for getting the placed block. Not really needed except + * for reasons of clarity. + * + * @return The Block that was placed + */ + @NotNull + public Block getBlockPlaced() { + return getBlock(); + } + + /** + * Gets the BlockState for the block which was replaced. Material type air + * mostly. + * + * @return The BlockState for the block which was replaced. + */ + @NotNull + public BlockState getBlockReplacedState() { + return this.replacedBlockState; + } + + /** + * Gets the block that this block was placed against + * + * @return Block the block that the new block was placed against + */ + @NotNull + public Block getBlockAgainst() { + return placedAgainst; + } + + /** + * Gets the item in the player's hand when they placed the block. + * + * @return The ItemStack for the item in the player's hand when they + * placed the block + */ + @NotNull + public ItemStack getItemInHand() { + return itemInHand; + } + + /** + * Gets the hand which placed the block + * @return Main or off-hand, depending on which hand was used to place the block + */ + @NotNull + public EquipmentSlot getHand() { + return this.hand; + } + + /** + * Gets the value whether the player would be allowed to build here. + * Defaults to spawn if the server was going to stop them (such as, the + * player is in Spawn). Note that this is an entirely different check + * than BLOCK_CANBUILD, as this refers to a player, not universe-physics + * rule like cactus on dirt. + * + * @return boolean whether the server would allow a player to build here + */ + public boolean canBuild() { + return this.canBuild; + } + + /** + * Sets the canBuild state of this event. Set to true if you want the + * player to be able to build. + * + * @param canBuild true if you want the player to be able to build + */ + public void setBuild(boolean canBuild) { + this.canBuild = canBuild; + } + + @NotNull + @Override + public HandlerList getHandlers() { + return handlers; + } + + @NotNull + public static HandlerList getHandlerList() { + return handlers; + } +} diff --git a/api/src/main/java/org/bukkit/event/block/BlockRedstoneEvent.java b/api/src/main/java/org/bukkit/event/block/BlockRedstoneEvent.java new file mode 100644 index 000000000..e46419b8c --- /dev/null +++ b/api/src/main/java/org/bukkit/event/block/BlockRedstoneEvent.java @@ -0,0 +1,58 @@ +package org.bukkit.event.block; + +import org.bukkit.block.Block; +import org.bukkit.event.HandlerList; +import org.jetbrains.annotations.NotNull; + +/** + * Called when a redstone current changes + */ +public class BlockRedstoneEvent extends BlockEvent { + private static final HandlerList handlers = new HandlerList(); + private final int oldCurrent; + private int newCurrent; + + public BlockRedstoneEvent(@NotNull final Block block, final int oldCurrent, final int newCurrent) { + super(block); + this.oldCurrent = oldCurrent; + this.newCurrent = newCurrent; + } + + /** + * Gets the old current of this block + * + * @return The previous current + */ + public int getOldCurrent() { + return oldCurrent; + } + + /** + * Gets the new current of this block + * + * @return The new current + */ + public int getNewCurrent() { + return newCurrent; + } + + /** + * Sets the new current of this block + * + * @param newCurrent The new current to set + */ + public void setNewCurrent(int newCurrent) { + this.newCurrent = newCurrent; + } + + @NotNull + @Override + public HandlerList getHandlers() { + return handlers; + } + + @NotNull + public static HandlerList getHandlerList() { + return handlers; + } +} diff --git a/api/src/main/java/org/bukkit/event/block/BlockSpreadEvent.java b/api/src/main/java/org/bukkit/event/block/BlockSpreadEvent.java new file mode 100644 index 000000000..e9239caec --- /dev/null +++ b/api/src/main/java/org/bukkit/event/block/BlockSpreadEvent.java @@ -0,0 +1,53 @@ +package org.bukkit.event.block; + +import org.bukkit.block.Block; +import org.bukkit.block.BlockState; +import org.bukkit.event.HandlerList; +import org.jetbrains.annotations.NotNull; + +/** + * Called when a block spreads based on world conditions. + *

+ * Use {@link BlockFormEvent} to catch blocks that "randomly" form instead of + * actually spread. + *

+ * Examples: + *

    + *
  • Mushrooms spreading. + *
  • Fire spreading. + *
+ *

+ * If a Block Spread event is cancelled, the block will not spread. + * + * @see BlockFormEvent + */ +public class BlockSpreadEvent extends BlockFormEvent { + private static final HandlerList handlers = new HandlerList(); + private final Block source; + + public BlockSpreadEvent(@NotNull final Block block, @NotNull final Block source, @NotNull final BlockState newState) { + super(block, newState); + this.source = source; + } + + /** + * Gets the source block involved in this event. + * + * @return the Block for the source block involved in this event. + */ + @NotNull + public Block getSource() { + return source; + } + + @NotNull + @Override + public HandlerList getHandlers() { + return handlers; + } + + @NotNull + public static HandlerList getHandlerList() { + return handlers; + } +} diff --git a/api/src/main/java/org/bukkit/event/block/CauldronLevelChangeEvent.java b/api/src/main/java/org/bukkit/event/block/CauldronLevelChangeEvent.java new file mode 100644 index 000000000..4aaa78afd --- /dev/null +++ b/api/src/main/java/org/bukkit/event/block/CauldronLevelChangeEvent.java @@ -0,0 +1,116 @@ +package org.bukkit.event.block; + +import com.google.common.base.Preconditions; +import org.bukkit.block.Block; +import org.bukkit.entity.Entity; +import org.bukkit.event.Cancellable; +import org.bukkit.event.HandlerList; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +public class CauldronLevelChangeEvent extends BlockEvent implements Cancellable { + + private static final HandlerList handlers = new HandlerList(); + private boolean cancelled; + // + private final Entity entity; + private final ChangeReason reason; + private final int oldLevel; + private int newLevel; + + public CauldronLevelChangeEvent(@NotNull Block block, @Nullable Entity entity, @NotNull ChangeReason reason, int oldLevel, int newLevel) { + super(block); + this.entity = entity; + this.reason = reason; + this.oldLevel = oldLevel; + this.newLevel = newLevel; + } + + /** + * Get entity which did this. May be null. + * + * @return acting entity + */ + @Nullable + public Entity getEntity() { + return entity; + } + + @NotNull + public ChangeReason getReason() { + return reason; + } + + public int getOldLevel() { + return oldLevel; + } + + public int getNewLevel() { + return newLevel; + } + + public void setNewLevel(int newLevel) { + Preconditions.checkArgument(0 <= newLevel && newLevel <= 3, "Cauldron level out of bounds 0 <= %s <= 3", newLevel); + this.newLevel = newLevel; + } + + @Override + public boolean isCancelled() { + return cancelled; + } + + @Override + public void setCancelled(boolean cancelled) { + this.cancelled = cancelled; + } + + @NotNull + @Override + public HandlerList getHandlers() { + return handlers; + } + + @NotNull + public static HandlerList getHandlerList() { + return handlers; + } + + public enum ChangeReason { + /** + * Player emptying the cauldron by filling their bucket. + */ + BUCKET_FILL, + /** + * Player filling the cauldron by emptying their bucket. + */ + BUCKET_EMPTY, + /** + * Player emptying the cauldron by filling their bottle. + */ + BOTTLE_FILL, + /** + * Player filling the cauldron by emptying their bottle. + */ + BOTTLE_EMPTY, + /** + * Player cleaning their banner. + */ + BANNER_WASH, + /** + * Player cleaning their armor. + */ + ARMOR_WASH, + /** + * Entity being extinguished. + */ + EXTINGUISH, + /** + * Evaporating due to biome dryness. + */ + EVAPORATE, + /** + * Unknown. + */ + UNKNOWN + } +} diff --git a/api/src/main/java/org/bukkit/event/block/EntityBlockFormEvent.java b/api/src/main/java/org/bukkit/event/block/EntityBlockFormEvent.java new file mode 100644 index 000000000..741aead8b --- /dev/null +++ b/api/src/main/java/org/bukkit/event/block/EntityBlockFormEvent.java @@ -0,0 +1,35 @@ +package org.bukkit.event.block; + +import org.bukkit.block.Block; +import org.bukkit.block.BlockState; +import org.bukkit.entity.Entity; +import org.jetbrains.annotations.NotNull; + +/** + * Called when a block is formed by entities. + *

+ * Examples: + *

    + *
  • Snow formed by a {@link org.bukkit.entity.Snowman}. + *
  • Frosted Ice formed by the Frost Walker enchantment. + *
+ */ +public class EntityBlockFormEvent extends BlockFormEvent { + private final Entity entity; + + public EntityBlockFormEvent(@NotNull final Entity entity, @NotNull final Block block, @NotNull final BlockState blockstate) { + super(block, blockstate); + + this.entity = entity; + } + + /** + * Get the entity that formed the block. + * + * @return Entity involved in event + */ + @NotNull + public Entity getEntity() { + return entity; + } +} \ No newline at end of file diff --git a/api/src/main/java/org/bukkit/event/block/FluidLevelChangeEvent.java b/api/src/main/java/org/bukkit/event/block/FluidLevelChangeEvent.java new file mode 100644 index 000000000..9bd0440c3 --- /dev/null +++ b/api/src/main/java/org/bukkit/event/block/FluidLevelChangeEvent.java @@ -0,0 +1,69 @@ +package org.bukkit.event.block; + +import com.google.common.base.Preconditions; +import org.bukkit.block.Block; +import org.bukkit.block.data.BlockData; +import org.bukkit.event.Cancellable; +import org.bukkit.event.HandlerList; +import org.jetbrains.annotations.NotNull; + +/** + * Called when the fluid level of a block changes due to changes in adjacent + * blocks. + */ +public class FluidLevelChangeEvent extends BlockEvent implements Cancellable { + + private static final HandlerList handlers = new HandlerList(); + private boolean cancelled; + // + private BlockData newData; + + public FluidLevelChangeEvent(@NotNull Block theBlock, @NotNull BlockData newData) { + super(theBlock); + this.newData = newData; + } + + /** + * Gets the new data of the changed block. + * + * @return new data + */ + @NotNull + public BlockData getNewData() { + return newData; + } + + /** + * Sets the new data of the changed block. Must be of the same Material as + * the old one. + * + * @param newData the new data + */ + public void setNewData(@NotNull BlockData newData) { + Preconditions.checkArgument(newData != null, "newData null"); + Preconditions.checkArgument(this.newData.getMaterial().equals(newData.getMaterial()), "Cannot change fluid type"); + + this.newData = newData; + } + + @Override + public boolean isCancelled() { + return cancelled; + } + + @Override + public void setCancelled(boolean cancelled) { + this.cancelled = cancelled; + } + + @NotNull + @Override + public HandlerList getHandlers() { + return handlers; + } + + @NotNull + public static HandlerList getHandlerList() { + return handlers; + } +} diff --git a/api/src/main/java/org/bukkit/event/block/LeavesDecayEvent.java b/api/src/main/java/org/bukkit/event/block/LeavesDecayEvent.java new file mode 100644 index 000000000..538e4246c --- /dev/null +++ b/api/src/main/java/org/bukkit/event/block/LeavesDecayEvent.java @@ -0,0 +1,39 @@ +package org.bukkit.event.block; + +import org.bukkit.block.Block; +import org.bukkit.event.Cancellable; +import org.bukkit.event.HandlerList; +import org.jetbrains.annotations.NotNull; + +/** + * Called when leaves are decaying naturally. + *

+ * If a Leaves Decay event is cancelled, the leaves will not decay. + */ +public class LeavesDecayEvent extends BlockEvent implements Cancellable { + private static final HandlerList handlers = new HandlerList(); + private boolean cancel = false; + + public LeavesDecayEvent(@NotNull final Block block) { + super(block); + } + + public boolean isCancelled() { + return cancel; + } + + public void setCancelled(boolean cancel) { + this.cancel = cancel; + } + + @NotNull + @Override + public HandlerList getHandlers() { + return handlers; + } + + @NotNull + public static HandlerList getHandlerList() { + return handlers; + } +} diff --git a/api/src/main/java/org/bukkit/event/block/MoistureChangeEvent.java b/api/src/main/java/org/bukkit/event/block/MoistureChangeEvent.java new file mode 100644 index 000000000..bf5de52e7 --- /dev/null +++ b/api/src/main/java/org/bukkit/event/block/MoistureChangeEvent.java @@ -0,0 +1,54 @@ +package org.bukkit.event.block; + +import org.bukkit.block.Block; +import org.bukkit.block.BlockState; +import org.bukkit.event.Cancellable; +import org.bukkit.event.HandlerList; +import org.jetbrains.annotations.NotNull; + +/** + * Called when the moisture level of a soil block changes. + */ +public class MoistureChangeEvent extends BlockEvent implements Cancellable { + + private static final HandlerList handlers = new HandlerList(); + private boolean cancelled; + private final BlockState newState; + + public MoistureChangeEvent(@NotNull final Block block, @NotNull final BlockState newState) { + super(block); + this.newState = newState; + this.cancelled = false; + } + + /** + * Gets the new state of the affected block. + * + * @return new block state + */ + @NotNull + public BlockState getNewState() { + return newState; + } + + @Override + public boolean isCancelled() { + return cancelled; + } + + @Override + public void setCancelled(boolean cancel) { + this.cancelled = cancel; + } + + @NotNull + @Override + public HandlerList getHandlers() { + return handlers; + } + + @NotNull + public static HandlerList getHandlerList() { + return handlers; + } +} diff --git a/api/src/main/java/org/bukkit/event/block/NotePlayEvent.java b/api/src/main/java/org/bukkit/event/block/NotePlayEvent.java new file mode 100644 index 000000000..ad8b89e2b --- /dev/null +++ b/api/src/main/java/org/bukkit/event/block/NotePlayEvent.java @@ -0,0 +1,91 @@ +package org.bukkit.event.block; + +import org.bukkit.Instrument; +import org.bukkit.Note; +import org.bukkit.block.Block; +import org.bukkit.event.Cancellable; +import org.bukkit.event.HandlerList; +import org.jetbrains.annotations.NotNull; + +/** + * Called when a note block is being played through player interaction or a + * redstone current. + */ +public class NotePlayEvent extends BlockEvent implements Cancellable { + + private static HandlerList handlers = new HandlerList(); + private Instrument instrument; + private Note note; + private boolean cancelled = false; + + public NotePlayEvent(@NotNull Block block, @NotNull Instrument instrument, @NotNull Note note) { + super(block); + this.instrument = instrument; + this.note = note; + } + + public boolean isCancelled() { + return cancelled; + } + + public void setCancelled(boolean cancel) { + this.cancelled = cancel; + } + + /** + * Gets the {@link Instrument} to be used. + * + * @return the Instrument + */ + @NotNull + public Instrument getInstrument() { + return instrument; + } + + /** + * Gets the {@link Note} to be played. + * + * @return the Note + */ + @NotNull + public Note getNote() { + return note; + } + + /** + * Overrides the {@link Instrument} to be used. + * + * @param instrument the Instrument. Has no effect if null. + * @deprecated no effect on newer Minecraft versions + */ + @Deprecated + public void setInstrument(@NotNull Instrument instrument) { + if (instrument != null) { + this.instrument = instrument; + } + } + + /** + * Overrides the {@link Note} to be played. + * + * @param note the Note. Has no effect if null. + * @deprecated no effect on newer Minecraft versions + */ + @Deprecated + public void setNote(@NotNull Note note) { + if (note != null) { + this.note = note; + } + } + + @NotNull + @Override + public HandlerList getHandlers() { + return handlers; + } + + @NotNull + public static HandlerList getHandlerList() { + return handlers; + } +} diff --git a/api/src/main/java/org/bukkit/event/block/SignChangeEvent.java b/api/src/main/java/org/bukkit/event/block/SignChangeEvent.java new file mode 100644 index 000000000..8b78d4de5 --- /dev/null +++ b/api/src/main/java/org/bukkit/event/block/SignChangeEvent.java @@ -0,0 +1,91 @@ +package org.bukkit.event.block; + +import org.bukkit.block.Block; +import org.bukkit.entity.Player; +import org.bukkit.event.Cancellable; +import org.bukkit.event.HandlerList; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +/** + * Called when a sign is changed by a player. + *

+ * If a Sign Change event is cancelled, the sign will not be changed. + */ +public class SignChangeEvent extends BlockEvent implements Cancellable { + private static final HandlerList handlers = new HandlerList(); + private boolean cancel = false; + private final Player player; + private final String[] lines; + + public SignChangeEvent(@NotNull final Block theBlock, @NotNull final Player thePlayer, @NotNull final String[] theLines) { + super(theBlock); + this.player = thePlayer; + this.lines = theLines; + } + + /** + * Gets the player changing the sign involved in this event. + * + * @return the Player involved in this event + */ + @NotNull + public Player getPlayer() { + return player; + } + + /** + * Gets all of the lines of text from the sign involved in this event. + * + * @return the String array for the sign's lines new text + */ + @NotNull + public String[] getLines() { + return lines; + } + + /** + * Gets a single line of text from the sign involved in this event. + * + * @param index index of the line to get + * @return the String containing the line of text associated with the + * provided index + * @throws IndexOutOfBoundsException thrown when the provided index is {@literal > 3 + * or < 0} + */ + @Nullable + public String getLine(int index) throws IndexOutOfBoundsException { + return lines[index]; + } + + /** + * Sets a single line for the sign involved in this event + * + * @param index index of the line to set + * @param line text to set + * @throws IndexOutOfBoundsException thrown when the provided index is {@literal > 3 + * or < 0} + */ + public void setLine(int index, @Nullable String line) throws IndexOutOfBoundsException { + lines[index] = line; + } + + public boolean isCancelled() { + return cancel; + } + + public void setCancelled(boolean cancel) { + this.cancel = cancel; + } + + @NotNull + @Override + public HandlerList getHandlers() { + return handlers; + } + + @NotNull + public static HandlerList getHandlerList() { + return handlers; + } +} diff --git a/api/src/main/java/org/bukkit/event/block/SpongeAbsorbEvent.java b/api/src/main/java/org/bukkit/event/block/SpongeAbsorbEvent.java new file mode 100644 index 000000000..ab98bba61 --- /dev/null +++ b/api/src/main/java/org/bukkit/event/block/SpongeAbsorbEvent.java @@ -0,0 +1,64 @@ +package org.bukkit.event.block; + +import org.bukkit.block.Block; +import org.bukkit.block.BlockState; +import org.bukkit.event.Cancellable; +import org.bukkit.event.HandlerList; +import java.util.List; +import org.bukkit.Material; +import org.jetbrains.annotations.NotNull; + +/** + * Called when a sponge absorbs water from the world. + *
+ * The world will be in its previous state, and {@link #getBlocks()} will + * represent the changes to be made to the world, if the event is not cancelled. + *
+ * As this is a physics based event it may be called multiple times for "the + * same" changes. + */ +public class SpongeAbsorbEvent extends BlockEvent implements Cancellable { + + private static final HandlerList handlers = new HandlerList(); + private boolean cancelled; + private final List blocks; + + public SpongeAbsorbEvent(@NotNull Block block, @NotNull List waterblocks) { + super(block); + this.blocks = waterblocks; + } + + /** + * Get a list of all blocks to be removed by the sponge. + *
+ * This list is mutable and contains the blocks in their removed state, i.e. + * having a type of {@link Material#AIR}. + * + * @return list of the to be removed blocks. + */ + @NotNull + public List getBlocks() { + return blocks; + } + + @Override + public boolean isCancelled() { + return cancelled; + } + + @Override + public void setCancelled(boolean cancel) { + this.cancelled = cancel; + } + + @NotNull + @Override + public HandlerList getHandlers() { + return handlers; + } + + @NotNull + public static HandlerList getHandlerList() { + return handlers; + } +} diff --git a/api/src/main/java/org/bukkit/event/command/UnknownCommandEvent.java b/api/src/main/java/org/bukkit/event/command/UnknownCommandEvent.java new file mode 100644 index 000000000..251342c3c --- /dev/null +++ b/api/src/main/java/org/bukkit/event/command/UnknownCommandEvent.java @@ -0,0 +1,82 @@ +package org.bukkit.event.command; + +import org.bukkit.command.CommandSender; +import org.bukkit.event.HandlerList; +import org.bukkit.event.Event; + +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +/** + * Thrown when a player executes a command that is not defined + */ +public class UnknownCommandEvent extends Event { + private static final HandlerList handlers = new HandlerList(); + @NotNull private CommandSender sender; + @NotNull private String commandLine; + @Nullable private String message; + + public UnknownCommandEvent(@NotNull final CommandSender sender, @NotNull final String commandLine, @Nullable final String message) { + super(false); + this.sender = sender; + this.commandLine = commandLine; + this.message = message; + } + + /** + * Gets the CommandSender or ConsoleCommandSender + *

+ * + * @return Sender of the command + */ + @NotNull + public CommandSender getSender() { + return sender; + } + + /** + * Gets the command that was send + *

+ * + * @return Command sent + */ + @NotNull + public String getCommandLine() { + return commandLine; + } + + /** + * Gets message that will be returned + *

+ * + * @return Unknown command message + */ + @Nullable + public String getMessage() { + return message; + } + + + /** + * Sets message that will be returned + *

+ * Set to null to avoid any message being sent + * + * @param message the message to be returned, or null + */ + public void setMessage(@Nullable String message) { + this.message = message; + } + + @NotNull + @Override + public HandlerList getHandlers() { + return handlers; + } + + @NotNull + public static HandlerList getHandlerList() { + return handlers; + } +} + diff --git a/api/src/main/java/org/bukkit/event/enchantment/EnchantItemEvent.java b/api/src/main/java/org/bukkit/event/enchantment/EnchantItemEvent.java new file mode 100644 index 000000000..ac1b1cb9a --- /dev/null +++ b/api/src/main/java/org/bukkit/event/enchantment/EnchantItemEvent.java @@ -0,0 +1,128 @@ +package org.bukkit.event.enchantment; + +import java.util.HashMap; +import java.util.Map; + +import org.bukkit.block.Block; +import org.bukkit.enchantments.Enchantment; +import org.bukkit.entity.Player; +import org.bukkit.event.Cancellable; +import org.bukkit.event.HandlerList; +import org.bukkit.event.inventory.InventoryEvent; +import org.bukkit.inventory.InventoryView; +import org.bukkit.inventory.ItemStack; +import org.jetbrains.annotations.NotNull; + +/** + * Called when an ItemStack is successfully enchanted (currently at + * enchantment table) + */ +public class EnchantItemEvent extends InventoryEvent implements Cancellable { + private static final HandlerList handlers = new HandlerList(); + private final Block table; + private final ItemStack item; + private int level; + private boolean cancelled; + private final Map enchants; + private final Player enchanter; + private int button; + + public EnchantItemEvent(@NotNull final Player enchanter, @NotNull final InventoryView view, @NotNull final Block table, @NotNull final ItemStack item, final int level, @NotNull final Map enchants, final int i) { + super(view); + this.enchanter = enchanter; + this.table = table; + this.item = item; + this.level = level; + this.enchants = new HashMap(enchants); + this.cancelled = false; + this.button = i; + } + + /** + * Gets the player enchanting the item + * + * @return enchanting player + */ + @NotNull + public Player getEnchanter() { + return enchanter; + } + + /** + * Gets the block being used to enchant the item + * + * @return the block used for enchanting + */ + @NotNull + public Block getEnchantBlock() { + return table; + } + + /** + * Gets the item to be enchanted (can be modified) + * + * @return ItemStack of item + */ + @NotNull + public ItemStack getItem() { + return item; + } + + /** + * Get cost in exp levels of the enchantment + * + * @return experience level cost + */ + public int getExpLevelCost() { + return level; + } + + /** + * Set cost in exp levels of the enchantment + * + * @param level - cost in levels + */ + public void setExpLevelCost(int level) { + this.level = level; + } + + /** + * Get map of enchantment (levels, keyed by type) to be added to item + * (modify map returned to change values). Note: Any enchantments not + * allowed for the item will be ignored + * + * @return map of enchantment levels, keyed by enchantment + */ + @NotNull + public Map getEnchantsToAdd() { + return enchants; + } + + /** + * Which button was pressed to initiate the enchanting. + * + * @return The button index (0, 1, or 2). + */ + public int whichButton() { + return button; + } + + public boolean isCancelled() { + return cancelled; + } + + public void setCancelled(boolean cancel) { + this.cancelled = cancel; + } + + @NotNull + @Override + public HandlerList getHandlers() { + return handlers; + } + + @NotNull + public static HandlerList getHandlerList() { + return handlers; + } +} diff --git a/api/src/main/java/org/bukkit/event/enchantment/PrepareItemEnchantEvent.java b/api/src/main/java/org/bukkit/event/enchantment/PrepareItemEnchantEvent.java new file mode 100644 index 000000000..2ff1b1308 --- /dev/null +++ b/api/src/main/java/org/bukkit/event/enchantment/PrepareItemEnchantEvent.java @@ -0,0 +1,122 @@ +package org.bukkit.event.enchantment; + +import org.bukkit.block.Block; +import org.bukkit.enchantments.EnchantmentOffer; +import org.bukkit.entity.Player; +import org.bukkit.event.Cancellable; +import org.bukkit.event.HandlerList; +import org.bukkit.event.inventory.InventoryEvent; +import org.bukkit.inventory.InventoryView; +import org.bukkit.inventory.ItemStack; +import org.jetbrains.annotations.NotNull; + +/** + * Called when an ItemStack is inserted in an enchantment table - can be + * called multiple times + */ +public class PrepareItemEnchantEvent extends InventoryEvent implements Cancellable { + private static final HandlerList handlers = new HandlerList(); + private final Block table; + private final ItemStack item; + private final EnchantmentOffer[] offers; + private final int bonus; + private boolean cancelled; + private final Player enchanter; + + public PrepareItemEnchantEvent(@NotNull final Player enchanter, @NotNull InventoryView view, @NotNull final Block table, @NotNull final ItemStack item, @NotNull final EnchantmentOffer[] offers, final int bonus) { + super(view); + this.enchanter = enchanter; + this.table = table; + this.item = item; + this.offers = offers; + this.bonus = bonus; + } + + /** + * Gets the player enchanting the item + * + * @return enchanting player + */ + @NotNull + public Player getEnchanter() { + return enchanter; + } + + /** + * Gets the block being used to enchant the item + * + * @return the block used for enchanting + */ + @NotNull + public Block getEnchantBlock() { + return table; + } + + /** + * Gets the item to be enchanted. + * + * @return ItemStack of item + */ + @NotNull + public ItemStack getItem() { + return item; + } + + /** + * Get a list of offered experience level costs of the enchantment. + * + * @return experience level costs offered + * @deprecated Use {@link #getOffers()} instead of this method + */ + @NotNull + public int[] getExpLevelCostsOffered() { + int[] levelOffers = new int[offers.length]; + for (int i = 0; i < offers.length; i++) { + levelOffers[i] = offers[i] != null ? offers[i].getCost() : 0; + } + return levelOffers; + } + + /** + * Get a list of available {@link EnchantmentOffer} for the player. You can + * modify the values to change the available offers for the player. An offer + * may be null, if there isn't a enchantment offer at a specific slot. There + * are 3 slots in the enchantment table available to modify. + * + * @return list of available enchantment offers + */ + @NotNull + public EnchantmentOffer[] getOffers() { + return offers; + } + + /** + * Get enchantment bonus in effect - corresponds to number of bookshelves + * + * @return enchantment bonus + */ + public int getEnchantmentBonus() { + return bonus; + } + + @Override + public boolean isCancelled() { + return cancelled; + } + + @Override + public void setCancelled(boolean cancel) { + this.cancelled = cancel; + } + + @NotNull + @Override + public HandlerList getHandlers() { + return handlers; + } + + @NotNull + public static HandlerList getHandlerList() { + return handlers; + } +} diff --git a/api/src/main/java/org/bukkit/event/entity/AreaEffectCloudApplyEvent.java b/api/src/main/java/org/bukkit/event/entity/AreaEffectCloudApplyEvent.java new file mode 100644 index 000000000..618db8ed3 --- /dev/null +++ b/api/src/main/java/org/bukkit/event/entity/AreaEffectCloudApplyEvent.java @@ -0,0 +1,66 @@ +package org.bukkit.event.entity; + +import java.util.List; + +import org.bukkit.entity.AreaEffectCloud; +import org.bukkit.entity.LivingEntity; +import org.bukkit.event.Cancellable; +import org.bukkit.event.HandlerList; +import org.jetbrains.annotations.NotNull; + +/** + * Called when a lingering potion applies it's effects. Happens + * once every 5 ticks + */ +public class AreaEffectCloudApplyEvent extends EntityEvent implements Cancellable { + private static final HandlerList handlers = new HandlerList(); + private final List affectedEntities; + private boolean cancelled = false; + + public AreaEffectCloudApplyEvent(@NotNull final AreaEffectCloud entity, @NotNull final List affectedEntities) { + super(entity); + this.affectedEntities = affectedEntities; + } + + @Override + public boolean isCancelled() { + return this.cancelled; + } + + @Override + public void setCancelled(boolean cancel) { + this.cancelled = cancel; + } + + @Override + @NotNull + public AreaEffectCloud getEntity() { + return (AreaEffectCloud) entity; + } + + /** + * Retrieves a mutable list of the effected entities + *

+ * It is important to note that not every entity in this list + * is guaranteed to be effected. The cloud may die during the + * application of its effects due to the depletion of {@link AreaEffectCloud#getDurationOnUse()} + * or {@link AreaEffectCloud#getRadiusOnUse()} + * + * @return the affected entity list + */ + @NotNull + public List getAffectedEntities() { + return affectedEntities; + } + + @NotNull + @Override + public HandlerList getHandlers() { + return handlers; + } + + @NotNull + public static HandlerList getHandlerList() { + return handlers; + } +} diff --git a/api/src/main/java/org/bukkit/event/entity/BatToggleSleepEvent.java b/api/src/main/java/org/bukkit/event/entity/BatToggleSleepEvent.java new file mode 100644 index 000000000..5a5615d17 --- /dev/null +++ b/api/src/main/java/org/bukkit/event/entity/BatToggleSleepEvent.java @@ -0,0 +1,55 @@ +package org.bukkit.event.entity; + +import org.bukkit.entity.Bat; +import org.bukkit.event.Cancellable; +import org.bukkit.event.HandlerList; +import org.jetbrains.annotations.NotNull; + +/** + * Called when a bat attempts to sleep or wake up from its slumber. + *

+ * If a Bat Toggle Sleep event is cancelled, the Bat will not toggle its sleep + * state. + */ +public class BatToggleSleepEvent extends EntityEvent implements Cancellable { + + private static final HandlerList handlers = new HandlerList(); + + private boolean cancel = false; + private final boolean awake; + + public BatToggleSleepEvent(@NotNull Bat what, boolean awake) { + super(what); + this.awake = awake; + } + + /** + * Get whether or not the bat is attempting to awaken. + * + * @return true if trying to awaken, false otherwise + */ + public boolean isAwake() { + return awake; + } + + @Override + public void setCancelled(boolean cancel) { + this.cancel = cancel; + } + + @Override + public boolean isCancelled() { + return cancel; + } + + @NotNull + @Override + public HandlerList getHandlers() { + return handlers; + } + + @NotNull + public static HandlerList getHandlerList() { + return handlers; + } +} diff --git a/api/src/main/java/org/bukkit/event/entity/CreatureSpawnEvent.java b/api/src/main/java/org/bukkit/event/entity/CreatureSpawnEvent.java new file mode 100644 index 000000000..3c48c2a3d --- /dev/null +++ b/api/src/main/java/org/bukkit/event/entity/CreatureSpawnEvent.java @@ -0,0 +1,161 @@ +package org.bukkit.event.entity; + +import org.bukkit.entity.LivingEntity; +import org.jetbrains.annotations.NotNull; + +/** + * Called when a creature is spawned into a world. + *

+ * If a Creature Spawn event is cancelled, the creature will not spawn. + */ +public class CreatureSpawnEvent extends EntitySpawnEvent { + private final SpawnReason spawnReason; + + public CreatureSpawnEvent(@NotNull final LivingEntity spawnee, @NotNull final SpawnReason spawnReason) { + super(spawnee); + this.spawnReason = spawnReason; + } + + @NotNull + @Override + public LivingEntity getEntity() { + return (LivingEntity) entity; + } + + /** + * Gets the reason for why the creature is being spawned. + * + * @return A SpawnReason value detailing the reason for the creature being + * spawned + */ + @NotNull + public SpawnReason getSpawnReason() { + return spawnReason; + } + + /** + * An enum to specify the type of spawning + */ + public enum SpawnReason { + + /** + * When something spawns from natural means + */ + NATURAL, + /** + * When an entity spawns as a jockey of another entity (mostly spider + * jockeys) + */ + JOCKEY, + /** + * When a creature spawns due to chunk generation + */ + CHUNK_GEN, + /** + * When a creature spawns from a spawner + */ + SPAWNER, + /** + * When a creature spawns from an egg + */ + EGG, + /** + * When a creature spawns from a Spawner Egg + */ + SPAWNER_EGG, + /** + * When a creature spawns because of a lightning strike + */ + LIGHTNING, + /** + * When a snowman is spawned by being built + */ + BUILD_SNOWMAN, + /** + * When an iron golem is spawned by being built + */ + BUILD_IRONGOLEM, + /** + * When a wither boss is spawned by being built + */ + BUILD_WITHER, + /** + * When an iron golem is spawned to defend a village + */ + VILLAGE_DEFENSE, + /** + * When a zombie is spawned to invade a village + */ + VILLAGE_INVASION, + /** + * When an animal breeds to create a child + */ + BREEDING, + /** + * When a slime splits + */ + SLIME_SPLIT, + /** + * When an entity calls for reinforcements + */ + REINFORCEMENTS, + /** + * When a creature is spawned by nether portal + */ + NETHER_PORTAL, + /** + * When a creature is spawned by a dispenser dispensing an egg + */ + DISPENSE_EGG, + /** + * When a zombie infects a villager + */ + INFECTION, + /** + * When a villager is cured from infection + */ + CURED, + /** + * When an ocelot has a baby spawned along with them + */ + OCELOT_BABY, + /** + * When a silverfish spawns from a block + */ + SILVERFISH_BLOCK, + /** + * When an entity spawns as a mount of another entity (mostly chicken + * jockeys) + */ + MOUNT, + /** + * When an entity spawns as a trap for players approaching + */ + TRAP, + /** + * When an entity is spawned as a result of ender pearl usage + */ + ENDER_PEARL, + /** + * When an entity is spawned as a result of the entity it is being + * perched on jumping or being damaged + */ + SHOULDER_ENTITY, + /** + * When a creature is spawned by another entity drowning + */ + DROWNED, + /** + * When an cow is spawned by shearing a mushroom cow + */ + SHEARED, + /** + * When a creature is spawned by plugins + */ + CUSTOM, + /** + * When an entity is missing a SpawnReason + */ + DEFAULT + } +} diff --git a/api/src/main/java/org/bukkit/event/entity/CreeperPowerEvent.java b/api/src/main/java/org/bukkit/event/entity/CreeperPowerEvent.java new file mode 100644 index 000000000..5828a52b1 --- /dev/null +++ b/api/src/main/java/org/bukkit/event/entity/CreeperPowerEvent.java @@ -0,0 +1,100 @@ +package org.bukkit.event.entity; + +import org.bukkit.entity.Creeper; +import org.bukkit.entity.LightningStrike; +import org.bukkit.event.Cancellable; +import org.bukkit.event.HandlerList; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +/** + * Called when a Creeper is struck by lightning. + *

+ * If a Creeper Power event is cancelled, the Creeper will not be powered. + */ +public class CreeperPowerEvent extends EntityEvent implements Cancellable { + private static final HandlerList handlers = new HandlerList(); + private boolean canceled; + private final PowerCause cause; + private LightningStrike bolt; + + public CreeperPowerEvent(@NotNull final Creeper creeper, @NotNull final LightningStrike bolt, @NotNull final PowerCause cause) { + this(creeper, cause); + this.bolt = bolt; + } + + public CreeperPowerEvent(@NotNull final Creeper creeper, @NotNull final PowerCause cause) { + super(creeper); + this.cause = cause; + } + + public boolean isCancelled() { + return canceled; + } + + public void setCancelled(boolean cancel) { + canceled = cancel; + } + + @NotNull + @Override + public Creeper getEntity() { + return (Creeper) entity; + } + + /** + * Gets the lightning bolt which is striking the Creeper. + * + * @return The Entity for the lightning bolt which is striking the Creeper + */ + @Nullable + public LightningStrike getLightning() { + return bolt; + } + + /** + * Gets the cause of the creeper being (un)powered. + * + * @return A PowerCause value detailing the cause of change in power. + */ + @NotNull + public PowerCause getCause() { + return cause; + } + + @NotNull + @Override + public HandlerList getHandlers() { + return handlers; + } + + @NotNull + public static HandlerList getHandlerList() { + return handlers; + } + + /** + * An enum to specify the cause of the change in power + */ + public enum PowerCause { + + /** + * Power change caused by a lightning bolt + *

+ * Powered state: true + */ + LIGHTNING, + /** + * Power change caused by something else (probably a plugin) + *

+ * Powered state: true + */ + SET_ON, + /** + * Power change caused by something else (probably a plugin) + *

+ * Powered state: false + */ + SET_OFF + } +} diff --git a/api/src/main/java/org/bukkit/event/entity/EnderDragonChangePhaseEvent.java b/api/src/main/java/org/bukkit/event/entity/EnderDragonChangePhaseEvent.java new file mode 100644 index 000000000..1b654fa99 --- /dev/null +++ b/api/src/main/java/org/bukkit/event/entity/EnderDragonChangePhaseEvent.java @@ -0,0 +1,83 @@ +package org.bukkit.event.entity; + +import org.apache.commons.lang.Validate; +import org.bukkit.entity.EnderDragon; +import org.bukkit.event.Cancellable; +import org.bukkit.event.HandlerList; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +/** + * Called when an EnderDragon switches controller phase. + */ +public class EnderDragonChangePhaseEvent extends EntityEvent implements Cancellable { + + private static final HandlerList handlers = new HandlerList(); + private boolean cancel; + private final EnderDragon.Phase currentPhase; + private EnderDragon.Phase newPhase; + + public EnderDragonChangePhaseEvent(@NotNull EnderDragon enderDragon, @Nullable EnderDragon.Phase currentPhase, @NotNull EnderDragon.Phase newPhase) { + super(enderDragon); + this.currentPhase = currentPhase; + this.setNewPhase(newPhase); + } + + @NotNull + @Override + public EnderDragon getEntity() { + return (EnderDragon) entity; + } + + /** + * Gets the current phase that the dragon is in. This method will return null + * when a dragon is first spawned and hasn't yet been assigned a phase. + * + * @return the current dragon phase + */ + @Nullable + public EnderDragon.Phase getCurrentPhase() { + return currentPhase; + } + + /** + * Gets the new phase that the dragon will switch to. + * + * @return the new dragon phase + */ + @NotNull + public EnderDragon.Phase getNewPhase() { + return newPhase; + } + + /** + * Sets the new phase for the ender dragon. + * + * @param newPhase the new dragon phase + */ + public void setNewPhase(@NotNull EnderDragon.Phase newPhase) { + Validate.notNull(newPhase, "New dragon phase cannot be null"); + this.newPhase = newPhase; + } + + @Override + public boolean isCancelled() { + return cancel; + } + + @Override + public void setCancelled(boolean cancel) { + this.cancel = cancel; + } + + @NotNull + @Override + public HandlerList getHandlers() { + return handlers; + } + + @NotNull + public static HandlerList getHandlerList() { + return handlers; + } +} diff --git a/api/src/main/java/org/bukkit/event/entity/EntityAirChangeEvent.java b/api/src/main/java/org/bukkit/event/entity/EntityAirChangeEvent.java new file mode 100644 index 000000000..cd20a4aa4 --- /dev/null +++ b/api/src/main/java/org/bukkit/event/entity/EntityAirChangeEvent.java @@ -0,0 +1,62 @@ +package org.bukkit.event.entity; + +import org.bukkit.entity.Entity; +import org.bukkit.event.Cancellable; +import org.bukkit.event.HandlerList; +import org.jetbrains.annotations.NotNull; + +/** + * Called when the amount of air an entity has remaining changes. + */ +public class EntityAirChangeEvent extends EntityEvent implements Cancellable { + + private static final HandlerList handlers = new HandlerList(); + // + private int amount; + // + private boolean cancelled; + + public EntityAirChangeEvent(@NotNull Entity what, int amount) { + super(what); + this.amount = amount; + } + + /** + * Gets the amount of air the entity has left (measured in ticks). + * + * @return amount of air remaining + */ + public int getAmount() { + return amount; + } + + /** + * Sets the amount of air remaining for the entity (measured in ticks. + * + * @param amount amount of air remaining + */ + public void setAmount(int amount) { + this.amount = amount; + } + + @Override + public boolean isCancelled() { + return cancelled; + } + + @Override + public void setCancelled(boolean cancelled) { + this.cancelled = cancelled; + } + + @NotNull + @Override + public HandlerList getHandlers() { + return handlers; + } + + @NotNull + public static HandlerList getHandlerList() { + return handlers; + } +} diff --git a/api/src/main/java/org/bukkit/event/entity/EntityBreakDoorEvent.java b/api/src/main/java/org/bukkit/event/entity/EntityBreakDoorEvent.java new file mode 100644 index 000000000..30b9bdca8 --- /dev/null +++ b/api/src/main/java/org/bukkit/event/entity/EntityBreakDoorEvent.java @@ -0,0 +1,24 @@ +package org.bukkit.event.entity; + +import org.bukkit.Material; +import org.bukkit.block.Block; +import org.bukkit.entity.Entity; +import org.bukkit.entity.LivingEntity; +import org.jetbrains.annotations.NotNull; + +/** + * Called when an {@link Entity} breaks a door + *

+ * Cancelling the event will cause the event to be delayed + */ +public class EntityBreakDoorEvent extends EntityChangeBlockEvent { + public EntityBreakDoorEvent(@NotNull final LivingEntity entity, @NotNull final Block targetBlock) { + super(entity, targetBlock, Material.AIR.createBlockData()); + } + + @NotNull + @Override + public LivingEntity getEntity() { + return (LivingEntity) entity; + } +} diff --git a/api/src/main/java/org/bukkit/event/entity/EntityBreedEvent.java b/api/src/main/java/org/bukkit/event/entity/EntityBreedEvent.java new file mode 100644 index 000000000..ded0693f9 --- /dev/null +++ b/api/src/main/java/org/bukkit/event/entity/EntityBreedEvent.java @@ -0,0 +1,128 @@ +package org.bukkit.event.entity; + +import org.apache.commons.lang.Validate; +import org.bukkit.entity.LivingEntity; +import org.bukkit.event.Cancellable; +import org.bukkit.event.HandlerList; +import org.bukkit.inventory.ItemStack; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +/** + * Called when one Entity breeds with another Entity. + */ +public class EntityBreedEvent extends EntityEvent implements Cancellable { + + private static final HandlerList handlers = new HandlerList(); + // + private final LivingEntity mother; + private final LivingEntity father; + private final LivingEntity breeder; + private final ItemStack bredWith; + private int experience; + // + private boolean cancel; + + public EntityBreedEvent(@NotNull LivingEntity child, @NotNull LivingEntity mother, @NotNull LivingEntity father, @Nullable LivingEntity breeder, @Nullable ItemStack bredWith, int experience) { + super(child); + + Validate.notNull(child, "Cannot have null child"); + Validate.notNull(mother, "Cannot have null mother"); + Validate.notNull(father, "Cannot have null father"); + + // Breeder can be null in the case of spontaneous conception + this.mother = mother; + this.father = father; + this.breeder = breeder; + this.bredWith = bredWith; + + setExperience(experience); + } + + @NotNull + @Override + public LivingEntity getEntity() { + return (LivingEntity) entity; + } + + /** + * Gets the parent creating this entity. + * + * @return The "birth" parent + */ + @NotNull + public LivingEntity getMother() { + return mother; + } + + /** + * Gets the other parent of the newly born entity. + * + * @return the other parent + */ + @NotNull + public LivingEntity getFather() { + return father; + } + + /** + * Gets the Entity responsible for breeding. Breeder is null for spontaneous + * conception. + * + * @return The Entity who initiated breeding. + */ + @Nullable + public LivingEntity getBreeder() { + return breeder; + } + + /** + * The ItemStack that was used to initiate breeding, if present. + * + * @return ItemStack used to initiate breeding. + */ + @Nullable + public ItemStack getBredWith() { + return bredWith; + } + + /** + * Get the amount of experience granted by breeding. + * + * @return experience amount + */ + public int getExperience() { + return experience; + } + + /** + * Set the amount of experience granted by breeding. + * + * @param experience experience amount + */ + public void setExperience(int experience) { + Validate.isTrue(experience >= 0, "Experience cannot be negative"); + this.experience = experience; + } + + @Override + public boolean isCancelled() { + return cancel; + } + + @Override + public void setCancelled(boolean cancel) { + this.cancel = cancel; + } + + @NotNull + @Override + public HandlerList getHandlers() { + return handlers; + } + + @NotNull + public static HandlerList getHandlerList() { + return handlers; + } +} diff --git a/api/src/main/java/org/bukkit/event/entity/EntityChangeBlockEvent.java b/api/src/main/java/org/bukkit/event/entity/EntityChangeBlockEvent.java new file mode 100644 index 000000000..62fba99a0 --- /dev/null +++ b/api/src/main/java/org/bukkit/event/entity/EntityChangeBlockEvent.java @@ -0,0 +1,75 @@ +package org.bukkit.event.entity; + +import org.bukkit.Material; +import org.bukkit.block.Block; +import org.bukkit.block.data.BlockData; +import org.bukkit.entity.Entity; +import org.bukkit.event.Cancellable; +import org.bukkit.event.HandlerList; +import org.jetbrains.annotations.NotNull; + +/** + * Called when any Entity, excluding players, changes a block. + */ +public class EntityChangeBlockEvent extends EntityEvent implements Cancellable { + private static final HandlerList handlers = new HandlerList(); + private final Block block; + private boolean cancel; + private final BlockData to; + + public EntityChangeBlockEvent(@NotNull final Entity what, @NotNull final Block block, @NotNull final BlockData to) { + super(what); + this.block = block; + this.cancel = false; + this.to = to; + } + + /** + * Gets the block the entity is changing + * + * @return the block that is changing + */ + @NotNull + public Block getBlock() { + return block; + } + + public boolean isCancelled() { + return cancel; + } + + public void setCancelled(boolean cancel) { + this.cancel = cancel; + } + + /** + * Gets the Material that the block is changing into + * + * @return the material that the block is changing into + */ + @NotNull + public Material getTo() { + return to.getMaterial(); + } + + /** + * Gets the data for the block that would be changed into + * + * @return the data for the block that would be changed into + */ + @NotNull + public BlockData getBlockData() { + return to; + } + + @NotNull + @Override + public HandlerList getHandlers() { + return handlers; + } + + @NotNull + public static HandlerList getHandlerList() { + return handlers; + } +} diff --git a/api/src/main/java/org/bukkit/event/entity/EntityCombustByBlockEvent.java b/api/src/main/java/org/bukkit/event/entity/EntityCombustByBlockEvent.java new file mode 100644 index 000000000..5886ee448 --- /dev/null +++ b/api/src/main/java/org/bukkit/event/entity/EntityCombustByBlockEvent.java @@ -0,0 +1,30 @@ +package org.bukkit.event.entity; + +import org.bukkit.block.Block; +import org.bukkit.entity.Entity; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +/** + * Called when a block causes an entity to combust. + */ +public class EntityCombustByBlockEvent extends EntityCombustEvent { + private final Block combuster; + + public EntityCombustByBlockEvent(@Nullable final Block combuster, @NotNull final Entity combustee, final int duration) { + super(combustee, duration); + this.combuster = combuster; + } + + /** + * The combuster can be lava or a block that is on fire. + *

+ * WARNING: block may be null. + * + * @return the Block that set the combustee alight. + */ + @Nullable + public Block getCombuster() { + return combuster; + } +} diff --git a/api/src/main/java/org/bukkit/event/entity/EntityCombustByEntityEvent.java b/api/src/main/java/org/bukkit/event/entity/EntityCombustByEntityEvent.java new file mode 100644 index 000000000..0b67364e1 --- /dev/null +++ b/api/src/main/java/org/bukkit/event/entity/EntityCombustByEntityEvent.java @@ -0,0 +1,26 @@ +package org.bukkit.event.entity; + +import org.bukkit.entity.Entity; +import org.jetbrains.annotations.NotNull; + +/** + * Called when an entity causes another entity to combust. + */ +public class EntityCombustByEntityEvent extends EntityCombustEvent { + private final Entity combuster; + + public EntityCombustByEntityEvent(@NotNull final Entity combuster, @NotNull final Entity combustee, final int duration) { + super(combustee, duration); + this.combuster = combuster; + } + + /** + * Get the entity that caused the combustion event. + * + * @return the Entity that set the combustee alight. + */ + @NotNull + public Entity getCombuster() { + return combuster; + } +} diff --git a/api/src/main/java/org/bukkit/event/entity/EntityCombustEvent.java b/api/src/main/java/org/bukkit/event/entity/EntityCombustEvent.java new file mode 100644 index 000000000..4b4c7bb52 --- /dev/null +++ b/api/src/main/java/org/bukkit/event/entity/EntityCombustEvent.java @@ -0,0 +1,62 @@ +package org.bukkit.event.entity; + +import org.bukkit.entity.Entity; +import org.bukkit.event.Cancellable; +import org.bukkit.event.HandlerList; +import org.jetbrains.annotations.NotNull; + +/** + * Called when an entity combusts. + *

+ * If an Entity Combust event is cancelled, the entity will not combust. + */ +public class EntityCombustEvent extends EntityEvent implements Cancellable { + private static final HandlerList handlers = new HandlerList(); + private int duration; + private boolean cancel; + + public EntityCombustEvent(@NotNull final Entity combustee, final int duration) { + super(combustee); + this.duration = duration; + this.cancel = false; + } + + public boolean isCancelled() { + return cancel; + } + + public void setCancelled(boolean cancel) { + this.cancel = cancel; + } + + /** + * @return the amount of time (in seconds) the combustee should be alight + * for + */ + public int getDuration() { + return duration; + } + + /** + * The number of seconds the combustee should be alight for. + *

+ * This value will only ever increase the combustion time, not decrease + * existing combustion times. + * + * @param duration the time in seconds to be alight for. + */ + public void setDuration(int duration) { + this.duration = duration; + } + + @NotNull + @Override + public HandlerList getHandlers() { + return handlers; + } + + @NotNull + public static HandlerList getHandlerList() { + return handlers; + } +} diff --git a/api/src/main/java/org/bukkit/event/entity/EntityCreatePortalEvent.java b/api/src/main/java/org/bukkit/event/entity/EntityCreatePortalEvent.java new file mode 100644 index 000000000..4de098341 --- /dev/null +++ b/api/src/main/java/org/bukkit/event/entity/EntityCreatePortalEvent.java @@ -0,0 +1,71 @@ +package org.bukkit.event.entity; + +import java.util.List; +import org.bukkit.PortalType; +import org.bukkit.block.BlockState; +import org.bukkit.entity.LivingEntity; +import org.bukkit.event.Cancellable; +import org.bukkit.event.HandlerList; +import org.jetbrains.annotations.NotNull; + +/** + * Thrown when a Living Entity creates a portal in a world. + */ +public class EntityCreatePortalEvent extends EntityEvent implements Cancellable { + private static final HandlerList handlers = new HandlerList(); + private final List blocks; + private boolean cancelled = false; + private PortalType type = PortalType.CUSTOM; + + public EntityCreatePortalEvent(@NotNull final LivingEntity what, @NotNull final List blocks, @NotNull final PortalType type) { + super(what); + + this.blocks = blocks; + this.type = type; + } + + @NotNull + @Override + public LivingEntity getEntity() { + return (LivingEntity) entity; + } + + /** + * Gets a list of all blocks associated with the portal. + * + * @return List of blocks that will be changed. + */ + @NotNull + public List getBlocks() { + return blocks; + } + + public boolean isCancelled() { + return cancelled; + } + + public void setCancelled(boolean cancel) { + this.cancelled = cancel; + } + + /** + * Gets the type of portal that is trying to be created. + * + * @return Type of portal. + */ + @NotNull + public PortalType getPortalType() { + return type; + } + + @NotNull + @Override + public HandlerList getHandlers() { + return handlers; + } + + @NotNull + public static HandlerList getHandlerList() { + return handlers; + } +} diff --git a/api/src/main/java/org/bukkit/event/entity/EntityDamageByBlockEvent.java b/api/src/main/java/org/bukkit/event/entity/EntityDamageByBlockEvent.java new file mode 100644 index 000000000..4c519eba8 --- /dev/null +++ b/api/src/main/java/org/bukkit/event/entity/EntityDamageByBlockEvent.java @@ -0,0 +1,36 @@ +package org.bukkit.event.entity; + +import java.util.Map; + +import com.google.common.base.Function; +import org.bukkit.block.Block; +import org.bukkit.entity.Entity; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +/** + * Called when an entity is damaged by a block + */ +public class EntityDamageByBlockEvent extends EntityDamageEvent { + private final Block damager; + + public EntityDamageByBlockEvent(@Nullable final Block damager, @NotNull final Entity damagee, @NotNull final DamageCause cause, final double damage) { + super(damagee, cause, damage); + this.damager = damager; + } + + public EntityDamageByBlockEvent(@Nullable final Block damager, @NotNull final Entity damagee, @NotNull final DamageCause cause, @NotNull final Map modifiers, @NotNull final Map> modifierFunctions) { + super(damagee, cause, modifiers, modifierFunctions); + this.damager = damager; + } + + /** + * Returns the block that damaged the player. + * + * @return Block that damaged the player + */ + @Nullable + public Block getDamager() { + return damager; + } +} diff --git a/api/src/main/java/org/bukkit/event/entity/EntityDamageByEntityEvent.java b/api/src/main/java/org/bukkit/event/entity/EntityDamageByEntityEvent.java new file mode 100644 index 000000000..b6e866962 --- /dev/null +++ b/api/src/main/java/org/bukkit/event/entity/EntityDamageByEntityEvent.java @@ -0,0 +1,34 @@ +package org.bukkit.event.entity; + +import java.util.Map; + +import com.google.common.base.Function; +import org.bukkit.entity.Entity; +import org.jetbrains.annotations.NotNull; + +/** + * Called when an entity is damaged by an entity + */ +public class EntityDamageByEntityEvent extends EntityDamageEvent { + private final Entity damager; + + public EntityDamageByEntityEvent(@NotNull final Entity damager, @NotNull final Entity damagee, @NotNull final DamageCause cause, final double damage) { + super(damagee, cause, damage); + this.damager = damager; + } + + public EntityDamageByEntityEvent(@NotNull final Entity damager, @NotNull final Entity damagee, @NotNull final DamageCause cause, @NotNull final Map modifiers, @NotNull final Map> modifierFunctions) { + super(damagee, cause, modifiers, modifierFunctions); + this.damager = damager; + } + + /** + * Returns the entity that damaged the defender. + * + * @return Entity that damaged the defender. + */ + @NotNull + public Entity getDamager() { + return damager; + } +} diff --git a/api/src/main/java/org/bukkit/event/entity/EntityDamageEvent.java b/api/src/main/java/org/bukkit/event/entity/EntityDamageEvent.java new file mode 100644 index 000000000..477ecfbc8 --- /dev/null +++ b/api/src/main/java/org/bukkit/event/entity/EntityDamageEvent.java @@ -0,0 +1,434 @@ +package org.bukkit.event.entity; + +import java.util.EnumMap; +import java.util.Map; + +import org.apache.commons.lang.Validate; +import org.bukkit.Material; +import org.bukkit.entity.Entity; +import org.bukkit.entity.Player; +import org.bukkit.event.Cancellable; +import org.bukkit.event.HandlerList; + +import com.google.common.base.Function; +import com.google.common.base.Functions; +import com.google.common.collect.ImmutableMap; +import org.jetbrains.annotations.NotNull; + +/** + * Stores data for damage events + */ +public class EntityDamageEvent extends EntityEvent implements Cancellable { + private static final HandlerList handlers = new HandlerList(); + private static final DamageModifier[] MODIFIERS = DamageModifier.values(); + private static final Function ZERO = Functions.constant(-0.0); + private final Map modifiers; + private final Map> modifierFunctions; + private final Map originals; + private boolean cancelled; + private final DamageCause cause; + + public EntityDamageEvent(@NotNull final Entity damagee, @NotNull final DamageCause cause, final double damage) { + this(damagee, cause, new EnumMap(ImmutableMap.of(DamageModifier.BASE, damage)), new EnumMap>(ImmutableMap.of(DamageModifier.BASE, ZERO))); + } + + public EntityDamageEvent(@NotNull final Entity damagee, @NotNull final DamageCause cause, @NotNull final Map modifiers, @NotNull final Map> modifierFunctions) { + super(damagee); + Validate.isTrue(modifiers.containsKey(DamageModifier.BASE), "BASE DamageModifier missing"); + Validate.isTrue(!modifiers.containsKey(null), "Cannot have null DamageModifier"); + Validate.noNullElements(modifiers.values(), "Cannot have null modifier values"); + Validate.isTrue(modifiers.keySet().equals(modifierFunctions.keySet()), "Must have a modifier function for each DamageModifier"); + Validate.noNullElements(modifierFunctions.values(), "Cannot have null modifier function"); + this.originals = new EnumMap(modifiers); + this.cause = cause; + this.modifiers = modifiers; + this.modifierFunctions = modifierFunctions; + } + + public boolean isCancelled() { + return cancelled; + } + + public void setCancelled(boolean cancel) { + cancelled = cancel; + } + + /** + * Gets the original damage for the specified modifier, as defined at this + * event's construction. + * + * @param type the modifier + * @return the original damage + * @throws IllegalArgumentException if type is null + */ + public double getOriginalDamage(@NotNull DamageModifier type) throws IllegalArgumentException { + final Double damage = originals.get(type); + if (damage != null) { + return damage; + } + if (type == null) { + throw new IllegalArgumentException("Cannot have null DamageModifier"); + } + return 0; + } + + /** + * Sets the damage for the specified modifier. + * + * @param type the damage modifier + * @param damage the scalar value of the damage's modifier + * @see #getFinalDamage() + * @throws IllegalArgumentException if type is null + * @throws UnsupportedOperationException if the caller does not support + * the particular DamageModifier, or to rephrase, when {@link + * #isApplicable(DamageModifier)} returns false + */ + public void setDamage(@NotNull DamageModifier type, double damage) throws IllegalArgumentException, UnsupportedOperationException { + if (!modifiers.containsKey(type)) { + throw type == null ? new IllegalArgumentException("Cannot have null DamageModifier") : new UnsupportedOperationException(type + " is not applicable to " + getEntity()); + } + modifiers.put(type, damage); + } + + /** + * Gets the damage change for some modifier + * + * @param type the damage modifier + * @return The raw amount of damage caused by the event + * @throws IllegalArgumentException if type is null + * @see DamageModifier#BASE + */ + public double getDamage(@NotNull DamageModifier type) throws IllegalArgumentException { + Validate.notNull(type, "Cannot have null DamageModifier"); + final Double damage = modifiers.get(type); + return damage == null ? 0 : damage; + } + + /** + * This checks to see if a particular modifier is valid for this event's + * caller, such that, {@link #setDamage(DamageModifier, double)} will not + * throw an {@link UnsupportedOperationException}. + *

+ * {@link DamageModifier#BASE} is always applicable. + * + * @param type the modifier + * @return true if the modifier is supported by the caller, false otherwise + * @throws IllegalArgumentException if type is null + */ + public boolean isApplicable(@NotNull DamageModifier type) throws IllegalArgumentException { + Validate.notNull(type, "Cannot have null DamageModifier"); + return modifiers.containsKey(type); + } + + /** + * Gets the raw amount of damage caused by the event + * + * @return The raw amount of damage caused by the event + * @see DamageModifier#BASE + */ + public double getDamage() { + return getDamage(DamageModifier.BASE); + } + + /** + * Gets the amount of damage caused by the event after all damage + * reduction is applied. + * + * @return the amount of damage caused by the event + */ + public final double getFinalDamage() { + double damage = 0; + for (DamageModifier modifier : MODIFIERS) { + damage += getDamage(modifier); + } + return damage; + } + + /** + * Sets the raw amount of damage caused by the event. + *

+ * For compatibility this also recalculates the modifiers and scales + * them by the difference between the modifier for the previous damage + * value and the new one. + * + * @param damage The raw amount of damage caused by the event + */ + public void setDamage(double damage) { + // These have to happen in the same order as the server calculates them, keep the enum sorted + double remaining = damage; + double oldRemaining = getDamage(DamageModifier.BASE); + for (DamageModifier modifier : MODIFIERS) { + if (!isApplicable(modifier)) { + continue; + } + + Function modifierFunction = modifierFunctions.get(modifier); + double newVanilla = modifierFunction.apply(remaining); + double oldVanilla = modifierFunction.apply(oldRemaining); + double difference = oldVanilla - newVanilla; + + // Don't allow value to cross zero, assume zero values should be negative + double old = getDamage(modifier); + if (old > 0) { + setDamage(modifier, Math.max(0, old - difference)); + } else { + setDamage(modifier, Math.min(0, old - difference)); + } + remaining += newVanilla; + oldRemaining += oldVanilla; + } + + setDamage(DamageModifier.BASE, damage); + } + + /** + * Gets the cause of the damage. + * + * @return A DamageCause value detailing the cause of the damage. + */ + @NotNull + public DamageCause getCause() { + return cause; + } + + @NotNull + @Override + public HandlerList getHandlers() { + return handlers; + } + + @NotNull + public static HandlerList getHandlerList() { + return handlers; + } + + /** + * An enum to specify the types of modifier + * + * @deprecated This API is responsible for a large number of implementation + * problems and is in general unsustainable to maintain. It is likely to be + * removed very soon in a subsequent release. Please see + * https://www.spigotmc.org/threads/194446/ for more information. + */ + @Deprecated + public enum DamageModifier { + /** + * This represents the amount of damage being done, also known as the + * raw {@link EntityDamageEvent#getDamage()}. + */ + BASE, + /** + * This represents the damage reduced by a wearing a helmet when hit + * by a falling block. + */ + HARD_HAT, + /** + * This represents the damage reduction caused by blocking, only present for + * {@link Player Players}. + */ + BLOCKING, + /** + * This represents the damage reduction caused by wearing armor. + */ + ARMOR, + /** + * This represents the damage reduction caused by the Resistance potion effect. + */ + RESISTANCE, + /** + * This represents the damage reduction caused by the combination of: + *

    + *
  • + * Armor enchantments + *
  • + * Witch's potion resistance + *
  • + *
+ */ + MAGIC, + /** + * This represents the damage reduction caused by the absorption potion + * effect. + */ + ABSORPTION, + ; + } + + /** + * An enum to specify the cause of the damage + */ + public enum DamageCause { + + /** + * Damage caused when an entity contacts a block such as a Cactus. + *

+ * Damage: 1 (Cactus) + */ + CONTACT, + /** + * Damage caused when an entity attacks another entity. + *

+ * Damage: variable + */ + ENTITY_ATTACK, + /** + * Damage caused when an entity attacks another entity in a sweep attack. + *

+ * Damage: variable + */ + ENTITY_SWEEP_ATTACK, + /** + * Damage caused when attacked by a projectile. + *

+ * Damage: variable + */ + PROJECTILE, + /** + * Damage caused by being put in a block + *

+ * Damage: 1 + */ + SUFFOCATION, + /** + * Damage caused when an entity falls a distance greater than 3 blocks + *

+ * Damage: fall height - 3.0 + */ + FALL, + /** + * Damage caused by direct exposure to fire + *

+ * Damage: 1 + */ + FIRE, + /** + * Damage caused due to burns caused by fire + *

+ * Damage: 1 + */ + FIRE_TICK, + /** + * Damage caused due to a snowman melting + *

+ * Damage: 1 + */ + MELTING, + /** + * Damage caused by direct exposure to lava + *

+ * Damage: 4 + */ + LAVA, + /** + * Damage caused by running out of air while in water + *

+ * Damage: 2 + */ + DROWNING, + /** + * Damage caused by being in the area when a block explodes. + *

+ * Damage: variable + */ + BLOCK_EXPLOSION, + /** + * Damage caused by being in the area when an entity, such as a + * Creeper, explodes. + *

+ * Damage: variable + */ + ENTITY_EXPLOSION, + /** + * Damage caused by falling into the void + *

+ * Damage: 4 for players + */ + VOID, + /** + * Damage caused by being struck by lightning + *

+ * Damage: 5 + */ + LIGHTNING, + /** + * Damage caused by committing suicide using the command "/kill" + *

+ * Damage: 1000 + */ + SUICIDE, + /** + * Damage caused by starving due to having an empty hunger bar + *

+ * Damage: 1 + */ + STARVATION, + /** + * Damage caused due to an ongoing poison effect + *

+ * Damage: 1 + */ + POISON, + /** + * Damage caused by being hit by a damage potion or spell + *

+ * Damage: variable + */ + MAGIC, + /** + * Damage caused by Wither potion effect + */ + WITHER, + /** + * Damage caused by being hit by a falling block which deals damage + *

+ * Note: Not every block deals damage + *

+ * Damage: variable + */ + FALLING_BLOCK, + /** + * Damage caused in retaliation to another attack by the Thorns + * enchantment. + *

+ * Damage: 1-4 (Thorns) + */ + THORNS, + /** + * Damage caused by a dragon breathing fire. + *

+ * Damage: variable + */ + DRAGON_BREATH, + /** + * Custom damage. + *

+ * Damage: variable + */ + CUSTOM, + /** + * Damage caused when an entity runs into a wall. + *

+ * Damage: variable + */ + FLY_INTO_WALL, + /** + * Damage caused when an entity steps on {@link Material#MAGMA_BLOCK}. + *

+ * Damage: 1 + */ + HOT_FLOOR, + /** + * Damage caused when an entity is colliding with too many entities due + * to the maxEntityCramming game rule. + *

+ * Damage: 6 + */ + CRAMMING, + /** + * Damage caused when an entity that should be in water is not. + *

+ * Damage: 1 + */ + DRYOUT + } +} diff --git a/api/src/main/java/org/bukkit/event/entity/EntityDeathEvent.java b/api/src/main/java/org/bukkit/event/entity/EntityDeathEvent.java new file mode 100644 index 000000000..e19a3df9a --- /dev/null +++ b/api/src/main/java/org/bukkit/event/entity/EntityDeathEvent.java @@ -0,0 +1,217 @@ +package org.bukkit.event.entity; + +import java.util.List; +import org.bukkit.entity.LivingEntity; +import org.bukkit.event.HandlerList; +import org.bukkit.inventory.ItemStack; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +/** + * Thrown whenever a LivingEntity dies + */ +public class EntityDeathEvent extends EntityEvent implements org.bukkit.event.Cancellable { // Paper - make cancellable + private static final HandlerList handlers = new HandlerList(); + private final List drops; + private int dropExp = 0; + // Paper start - make cancellable + private boolean cancelled; + private double reviveHealth = 0; + private boolean shouldPlayDeathSound; + @Nullable private org.bukkit.Sound deathSound; + @Nullable private org.bukkit.SoundCategory deathSoundCategory; + private float deathSoundVolume; + private float deathSoundPitch; + // Paper end + + public EntityDeathEvent(@NotNull final LivingEntity entity, @NotNull final List drops) { + this(entity, drops, 0); + } + + public EntityDeathEvent(@NotNull final LivingEntity what, @NotNull final List drops, final int droppedExp) { + super(what); + this.drops = drops; + this.dropExp = droppedExp; + } + + @NotNull + @Override + public LivingEntity getEntity() { + return (LivingEntity) entity; + } + + /** + * Gets how much EXP should be dropped from this death. + *

+ * This does not indicate how much EXP should be taken from the entity in + * question, merely how much should be created after its death. + * + * @return Amount of EXP to drop. + */ + public int getDroppedExp() { + return dropExp; + } + + /** + * Sets how much EXP should be dropped from this death. + *

+ * This does not indicate how much EXP should be taken from the entity in + * question, merely how much should be created after its death. + * + * @param exp Amount of EXP to drop. + */ + public void setDroppedExp(int exp) { + this.dropExp = exp; + } + + /** + * Gets all the items which will drop when the entity dies + * + * @return Items to drop when the entity dies + */ + @NotNull + public List getDrops() { + return drops; + } + + @NotNull + @Override + public HandlerList getHandlers() { + return handlers; + } + + @NotNull + public static HandlerList getHandlerList() { + return handlers; + } + + // Paper start - make cancellable + @Override + public boolean isCancelled() { + return cancelled; + } + + @Override + public void setCancelled(boolean cancel) { + cancelled = cancel; + } + + /** + * Get the amount of health that the entity should revive with after cancelling the event. + * Set to the entity's max health by default. + * + * @return The amount of health + */ + public double getReviveHealth() { + return reviveHealth; + } + + /** + * Set the amount of health that the entity should revive with after cancelling the event. + * Revive health value must be between 0 (exclusive) and the entity's max health (inclusive). + * + * @param reviveHealth The amount of health + * @throws IllegalArgumentException Thrown if the health is {@literal <= 0 or >} max health + */ + public void setReviveHealth(double reviveHealth) throws IllegalArgumentException { + double maxHealth = ((LivingEntity) entity).getAttribute(org.bukkit.attribute.Attribute.GENERIC_MAX_HEALTH).getValue(); + if ((maxHealth != 0 && reviveHealth <= 0) || (reviveHealth > maxHealth)) { + throw new IllegalArgumentException("Health must be between 0 (exclusive) and " + maxHealth + " (inclusive), but was " + reviveHealth); + } + this.reviveHealth = reviveHealth; + } + + + /** + * Whether or not the death sound should play when the entity dies. If the event is cancelled it does not play! + * + * @return Whether or not the death sound should play. Event is called with this set to false if the entity is silent. + */ + public boolean shouldPlayDeathSound() { + return shouldPlayDeathSound; + } + + /** + * Set whether or not the death sound should play when the entity dies. If the event is cancelled it does not play! + * + * @param playDeathSound Enable or disable the death sound + */ + public void setShouldPlayDeathSound(boolean playDeathSound) { + this.shouldPlayDeathSound = playDeathSound; + } + + /** + * Get the sound that the entity makes when dying + * + * @return The sound that the entity makes + */ + @Nullable + public org.bukkit.Sound getDeathSound() { + return deathSound; + } + + /** + * Set the sound that the entity makes when dying + * + * @param sound The sound that the entity should make when dying + */ + public void setDeathSound(@Nullable org.bukkit.Sound sound) { + deathSound = sound; + } + + /** + * Get the sound category that the death sound should play in + * + * @return The sound category + */ + @Nullable + public org.bukkit.SoundCategory getDeathSoundCategory() { + return deathSoundCategory; + } + + /** + * Set the sound category that the death sound should play in. + * + * @param soundCategory The sound category + */ + public void setDeathSoundCategory(@Nullable org.bukkit.SoundCategory soundCategory) { + this.deathSoundCategory = soundCategory; + } + + /** + * Get the volume that the death sound will play at. + * + * @return The volume the death sound will play at + */ + public float getDeathSoundVolume() { + return deathSoundVolume; + } + + /** + * Set the volume the death sound should play at. If the event is cancelled this will not play the sound! + * + * @param volume The volume the death sound should play at + */ + public void setDeathSoundVolume(float volume) { + this.deathSoundVolume = volume; + } + + /** + * Get the pitch that the death sound will play with. + * + * @return The pitch the death sound will play with + */ + public float getDeathSoundPitch() { + return deathSoundPitch; + } + + /** + * GSetet the pitch that the death sound should play with. + * + * @param pitch The pitch the death sound should play with + */ + public void setDeathSoundPitch(float pitch) { + this.deathSoundPitch = pitch; + } + // Paper end +} diff --git a/api/src/main/java/org/bukkit/event/entity/EntityDropItemEvent.java b/api/src/main/java/org/bukkit/event/entity/EntityDropItemEvent.java new file mode 100644 index 000000000..3ccdcc755 --- /dev/null +++ b/api/src/main/java/org/bukkit/event/entity/EntityDropItemEvent.java @@ -0,0 +1,53 @@ +package org.bukkit.event.entity; + +import org.bukkit.entity.Entity; +import org.bukkit.entity.Item; +import org.bukkit.event.Cancellable; +import org.bukkit.event.HandlerList; +import org.jetbrains.annotations.NotNull; + +/** + * Thrown when an entity creates an item drop. + */ +public class EntityDropItemEvent extends EntityEvent implements Cancellable { + + private static final HandlerList handlers = new HandlerList(); + private final Item drop; + private boolean cancel = false; + + public EntityDropItemEvent(@NotNull final Entity entity, @NotNull final Item drop) { + super(entity); + this.drop = drop; + } + + /** + * Gets the Item created by the entity + * + * @return Item created by the entity + */ + @NotNull + public Item getItemDrop() { + return drop; + } + + @Override + public boolean isCancelled() { + return cancel; + } + + @Override + public void setCancelled(boolean cancel) { + this.cancel = cancel; + } + + @NotNull + @Override + public HandlerList getHandlers() { + return handlers; + } + + @NotNull + public static HandlerList getHandlerList() { + return handlers; + } +} diff --git a/api/src/main/java/org/bukkit/event/entity/EntityEvent.java b/api/src/main/java/org/bukkit/event/entity/EntityEvent.java new file mode 100644 index 000000000..e3a9e3293 --- /dev/null +++ b/api/src/main/java/org/bukkit/event/entity/EntityEvent.java @@ -0,0 +1,37 @@ +package org.bukkit.event.entity; + +import org.bukkit.entity.Entity; +import org.bukkit.entity.EntityType; +import org.bukkit.event.Event; +import org.jetbrains.annotations.NotNull; + +/** + * Represents an Entity-related event + */ +public abstract class EntityEvent extends Event { + protected Entity entity; + + public EntityEvent(@NotNull final Entity what) { + entity = what; + } + + /** + * Returns the Entity involved in this event + * + * @return Entity who is involved in this event + */ + @NotNull + public Entity getEntity() { + return entity; + } + + /** + * Gets the EntityType of the Entity involved in this event. + * + * @return EntityType of the Entity involved in this event + */ + @NotNull + public EntityType getEntityType() { + return entity.getType(); + } +} diff --git a/api/src/main/java/org/bukkit/event/entity/EntityExplodeEvent.java b/api/src/main/java/org/bukkit/event/entity/EntityExplodeEvent.java new file mode 100644 index 000000000..21eb09293 --- /dev/null +++ b/api/src/main/java/org/bukkit/event/entity/EntityExplodeEvent.java @@ -0,0 +1,91 @@ +package org.bukkit.event.entity; + +import org.bukkit.Location; +import org.bukkit.block.Block; +import org.bukkit.entity.Entity; +import org.bukkit.event.Cancellable; +import org.bukkit.event.HandlerList; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +import java.util.List; + +/** + * Called when an entity explodes + */ +public class EntityExplodeEvent extends EntityEvent implements Cancellable { + private static final HandlerList handlers = new HandlerList(); + private boolean cancel; + private final Location location; + private final List blocks; + private float yield; + + public EntityExplodeEvent(@NotNull final Entity what, @NotNull final Location location, @NotNull final List blocks, final float yield) { + super(what); + this.location = location; + this.blocks = blocks; + this.yield = yield; + this.cancel = false; + } + + public boolean isCancelled() { + return cancel; + } + + public void setCancelled(boolean cancel) { + this.cancel = cancel; + } + + /** + * Returns the list of blocks that would have been removed or were removed + * from the explosion event. + * + * @return All blown-up blocks + */ + @NotNull + public List blockList() { + return blocks; + } + + /** + * Returns the location where the explosion happened. + *

+ * It is not possible to get this value from the Entity as the Entity no + * longer exists in the world. + * + * @return The location of the explosion + */ + @NotNull + public Location getLocation() { + return location; + } + + /** + * Returns the percentage of blocks to drop from this explosion + * + * @return The yield. + */ + public float getYield() { + return yield; + } + + /** + * Sets the percentage of blocks to drop from this explosion + * + * @param yield The new yield percentage + */ + public void setYield(float yield) { + this.yield = yield; + } + + @NotNull + @Override + public HandlerList getHandlers() { + return handlers; + } + + @NotNull + public static HandlerList getHandlerList() { + return handlers; + } +} diff --git a/api/src/main/java/org/bukkit/event/entity/EntityInteractEvent.java b/api/src/main/java/org/bukkit/event/entity/EntityInteractEvent.java new file mode 100644 index 000000000..97ef2481d --- /dev/null +++ b/api/src/main/java/org/bukkit/event/entity/EntityInteractEvent.java @@ -0,0 +1,50 @@ +package org.bukkit.event.entity; + +import org.bukkit.block.Block; +import org.bukkit.entity.Entity; +import org.bukkit.event.Cancellable; +import org.bukkit.event.HandlerList; +import org.jetbrains.annotations.NotNull; + +/** + * Called when an entity interacts with an object + */ +public class EntityInteractEvent extends EntityEvent implements Cancellable { + private static final HandlerList handlers = new HandlerList(); + protected Block block; + private boolean cancelled; + + public EntityInteractEvent(@NotNull final Entity entity, @NotNull final Block block) { + super(entity); + this.block = block; + } + + public boolean isCancelled() { + return cancelled; + } + + public void setCancelled(boolean cancel) { + cancelled = cancel; + } + + /** + * Returns the involved block + * + * @return the block clicked with this item. + */ + @NotNull + public Block getBlock() { + return block; + } + + @NotNull + @Override + public HandlerList getHandlers() { + return handlers; + } + + @NotNull + public static HandlerList getHandlerList() { + return handlers; + } +} diff --git a/api/src/main/java/org/bukkit/event/entity/EntityPickupItemEvent.java b/api/src/main/java/org/bukkit/event/entity/EntityPickupItemEvent.java new file mode 100644 index 000000000..b583accbb --- /dev/null +++ b/api/src/main/java/org/bukkit/event/entity/EntityPickupItemEvent.java @@ -0,0 +1,67 @@ +package org.bukkit.event.entity; + +import org.bukkit.entity.Item; +import org.bukkit.entity.LivingEntity; +import org.bukkit.event.Cancellable; +import org.bukkit.event.HandlerList; +import org.jetbrains.annotations.NotNull; + +/** + * Thrown when a entity picks an item up from the ground + */ +public class EntityPickupItemEvent extends EntityEvent implements Cancellable { + private static final HandlerList handlers = new HandlerList(); + private final Item item; + private boolean cancel = false; + private final int remaining; + + public EntityPickupItemEvent(@NotNull final LivingEntity entity, @NotNull final Item item, final int remaining) { + super(entity); + this.item = item; + this.remaining = remaining; + } + + @NotNull + @Override + public LivingEntity getEntity() { + return (LivingEntity) entity; + } + + /** + * Gets the Item picked up by the entity. + * + * @return Item + */ + @NotNull + public Item getItem() { + return item; + } + + /** + * Gets the amount remaining on the ground, if any + * + * @return amount remaining on the ground + */ + public int getRemaining() { + return remaining; + } + + public boolean isCancelled() { + return cancel; + } + + public void setCancelled(boolean cancel) { + this.cancel = cancel; + } + + @NotNull + @Override + public HandlerList getHandlers() { + return handlers; + } + + @NotNull + public static HandlerList getHandlerList() { + return handlers; + } +} diff --git a/api/src/main/java/org/bukkit/event/entity/EntityPlaceEvent.java b/api/src/main/java/org/bukkit/event/entity/EntityPlaceEvent.java new file mode 100644 index 000000000..ea21069b9 --- /dev/null +++ b/api/src/main/java/org/bukkit/event/entity/EntityPlaceEvent.java @@ -0,0 +1,89 @@ +package org.bukkit.event.entity; + +import org.bukkit.Warning; +import org.bukkit.block.Block; +import org.bukkit.block.BlockFace; +import org.bukkit.entity.Entity; +import org.bukkit.entity.Player; +import org.bukkit.event.Cancellable; +import org.bukkit.event.HandlerList; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +/** + * Triggered when a entity is created in the world by a player "placing" an item + * on a block. + *
+ * Note that this event is currently only fired for three specific placements: + * armor stands, minecarts, and end crystals. + * + * @deprecated draft API + */ +@Deprecated +@Warning(false) +public class EntityPlaceEvent extends EntityEvent implements Cancellable { + + private static final HandlerList handlers = new HandlerList(); + private boolean cancelled; + private final Player player; + private final Block block; + private final BlockFace blockFace; + + public EntityPlaceEvent(@NotNull final Entity entity, @Nullable final Player player, @NotNull final Block block, @NotNull final BlockFace blockFace) { + super(entity); + this.player = player; + this.block = block; + this.blockFace = blockFace; + } + + /** + * Returns the player placing the entity + * + * @return the player placing the entity + */ + @Nullable + public Player getPlayer() { + return player; + } + + /** + * Returns the block that the entity was placed on + * + * @return the block that the entity was placed on + */ + @NotNull + public Block getBlock() { + return block; + } + + /** + * Returns the face of the block that the entity was placed on + * + * @return the face of the block that the entity was placed on + */ + @NotNull + public BlockFace getBlockFace() { + return blockFace; + } + + @Override + public boolean isCancelled() { + return cancelled; + } + + @Override + public void setCancelled(boolean cancel) { + this.cancelled = cancel; + } + + @NotNull + @Override + public HandlerList getHandlers() { + return handlers; + } + + @NotNull + public static HandlerList getHandlerList() { + return handlers; + } +} diff --git a/api/src/main/java/org/bukkit/event/entity/EntityPortalEnterEvent.java b/api/src/main/java/org/bukkit/event/entity/EntityPortalEnterEvent.java new file mode 100644 index 000000000..e949072ba --- /dev/null +++ b/api/src/main/java/org/bukkit/event/entity/EntityPortalEnterEvent.java @@ -0,0 +1,40 @@ +package org.bukkit.event.entity; + +import org.bukkit.entity.Entity; +import org.bukkit.Location; +import org.bukkit.event.HandlerList; +import org.jetbrains.annotations.NotNull; + +/** + * Called when an entity comes into contact with a portal + */ +public class EntityPortalEnterEvent extends EntityEvent { + private static final HandlerList handlers = new HandlerList(); + private final Location location; + + public EntityPortalEnterEvent(@NotNull final Entity entity, @NotNull final Location location) { + super(entity); + this.location = location; + } + + /** + * Gets the portal block the entity is touching + * + * @return The portal block the entity is touching + */ + @NotNull + public Location getLocation() { + return location; + } + + @NotNull + @Override + public HandlerList getHandlers() { + return handlers; + } + + @NotNull + public static HandlerList getHandlerList() { + return handlers; + } +} diff --git a/api/src/main/java/org/bukkit/event/entity/EntityPortalEvent.java b/api/src/main/java/org/bukkit/event/entity/EntityPortalEvent.java new file mode 100644 index 000000000..ec759948f --- /dev/null +++ b/api/src/main/java/org/bukkit/event/entity/EntityPortalEvent.java @@ -0,0 +1,87 @@ +package org.bukkit.event.entity; + +import org.bukkit.Location; +import org.bukkit.TravelAgent; +import org.bukkit.entity.Entity; +import org.bukkit.event.HandlerList; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +/** + * Called when a non-player entity is about to teleport because it is in + * contact with a portal. + *

+ * For players see {@link org.bukkit.event.player.PlayerPortalEvent} + */ +public class EntityPortalEvent extends EntityTeleportEvent { + private static final HandlerList handlers = new HandlerList(); + protected boolean useTravelAgent = true; + protected TravelAgent travelAgent; + + public EntityPortalEvent(@NotNull final Entity entity, @NotNull final Location from, @Nullable final Location to, @NotNull final TravelAgent pta) { + super(entity, from, to); + this.travelAgent = pta; + } + + /** + * Sets whether or not the Travel Agent will be used. + *

+ * If this is set to true, the TravelAgent will try to find a Portal at + * the {@link #getTo()} Location, and will try to create one if there is + * none. + *

+ * If this is set to false, the {@link #getEntity()} will only be + * teleported to the {@link #getTo()} Location. + * + * @param useTravelAgent whether to use the Travel Agent + */ + public void useTravelAgent(boolean useTravelAgent) { + this.useTravelAgent = useTravelAgent; + } + + /** + * Gets whether or not the Travel Agent will be used. + *

+ * If this is set to true, the TravelAgent will try to find a Portal at + * the {@link #getTo()} Location, and will try to create one if there is + * none. + *

+ * If this is set to false, the {@link #getEntity()} will only be + * teleported to the {@link #getTo()} Location. + * + * @return whether to use the Travel Agent + */ + public boolean useTravelAgent() { + return useTravelAgent; + } + + /** + * Gets the Travel Agent used (or not) in this event. + * + * @return the Travel Agent used (or not) in this event + */ + @NotNull + public TravelAgent getPortalTravelAgent() { + return this.travelAgent; + } + + /** + * Sets the Travel Agent used (or not) in this event. + * + * @param travelAgent the Travel Agent used (or not) in this event + */ + public void setPortalTravelAgent(@NotNull TravelAgent travelAgent) { + this.travelAgent = travelAgent; + } + + @NotNull + @Override + public HandlerList getHandlers() { + return handlers; + } + + @NotNull + public static HandlerList getHandlerList() { + return handlers; + } +} \ No newline at end of file diff --git a/api/src/main/java/org/bukkit/event/entity/EntityPortalExitEvent.java b/api/src/main/java/org/bukkit/event/entity/EntityPortalExitEvent.java new file mode 100644 index 000000000..f124d43f0 --- /dev/null +++ b/api/src/main/java/org/bukkit/event/entity/EntityPortalExitEvent.java @@ -0,0 +1,67 @@ +package org.bukkit.event.entity; + +import org.bukkit.Location; +import org.bukkit.entity.Entity; +import org.bukkit.event.HandlerList; +import org.bukkit.util.Vector; +import org.jetbrains.annotations.NotNull; + +/** + * Called before an entity exits a portal. + *

+ * This event allows you to modify the velocity of the entity after they have + * successfully exited the portal. + */ +public class EntityPortalExitEvent extends EntityTeleportEvent { + private static final HandlerList handlers = new HandlerList(); + private Vector before; + private Vector after; + + public EntityPortalExitEvent(@NotNull final Entity entity, @NotNull final Location from, @NotNull final Location to, @NotNull final Vector before, @NotNull final Vector after) { + super(entity, from, to); + this.before = before; + this.after = after; + } + + /** + * Gets a copy of the velocity that the entity has before entering the + * portal. + * + * @return velocity of entity before entering the portal + */ + @NotNull + public Vector getBefore() { + return this.before.clone(); + } + + /** + * Gets a copy of the velocity that the entity will have after exiting the + * portal. + * + * @return velocity of entity after exiting the portal + */ + @NotNull + public Vector getAfter() { + return this.after.clone(); + } + + /** + * Sets the velocity that the entity will have after exiting the portal. + * + * @param after the velocity after exiting the portal + */ + public void setAfter(@NotNull Vector after) { + this.after = after.clone(); + } + + @NotNull + @Override + public HandlerList getHandlers() { + return handlers; + } + + @NotNull + public static HandlerList getHandlerList() { + return handlers; + } +} \ No newline at end of file diff --git a/api/src/main/java/org/bukkit/event/entity/EntityPotionEffectEvent.java b/api/src/main/java/org/bukkit/event/entity/EntityPotionEffectEvent.java new file mode 100644 index 000000000..d5f8bde3b --- /dev/null +++ b/api/src/main/java/org/bukkit/event/entity/EntityPotionEffectEvent.java @@ -0,0 +1,249 @@ +package org.bukkit.event.entity; + +import org.bukkit.entity.LivingEntity; +import org.bukkit.event.Cancellable; +import org.bukkit.event.HandlerList; +import org.bukkit.potion.PotionEffect; +import org.bukkit.potion.PotionEffectType; +import org.jetbrains.annotations.Contract; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +/** + * Called when a potion effect is modified on an entity. + *

+ * If the event is cancelled, no change will be made on the entity. + */ +public class EntityPotionEffectEvent extends EntityEvent implements Cancellable { + + private static final HandlerList handlers = new HandlerList(); + private boolean cancel; + private final PotionEffect oldEffect; + private final PotionEffect newEffect; + private final Cause cause; + private final Action action; + private boolean override; + + @Contract("_, null, null, _, _, _ -> fail") + public EntityPotionEffectEvent(@NotNull LivingEntity livingEntity, @Nullable PotionEffect oldEffect, @Nullable PotionEffect newEffect, @NotNull Cause cause, @NotNull Action action, boolean override) { + super(livingEntity); + this.oldEffect = oldEffect; + this.newEffect = newEffect; + this.cause = cause; + this.action = action; + this.override = override; + } + + /** + * Gets the old potion effect of the changed type, which will be removed. + * + * @return The old potion effect or null if the entity did not have the + * changed effect type. + */ + @Nullable + public PotionEffect getOldEffect() { + return oldEffect; + } + + /** + * Gets new potion effect of the changed type to be applied. + * + * @return The new potion effect or null if the effect of the changed type + * will be removed. + */ + @Nullable + public PotionEffect getNewEffect() { + return newEffect; + } + + /** + * Gets the cause why the effect has changed. + * + * @return A Cause value why the effect has changed. + */ + @NotNull + public Cause getCause() { + return cause; + } + + /** + * Gets the action which will be performed on the potion effect type. + * + * @return An action to be performed on the potion effect type. + */ + @NotNull + public Action getAction() { + return action; + } + + /** + * Gets the modified potion effect type. + * + * @return The effect type which will be modified on the entity. + */ + @NotNull + public PotionEffectType getModifiedType() { + return (oldEffect == null) ? ((newEffect == null) ? null : newEffect.getType()) : oldEffect.getType(); + } + + /** + * Returns if the new potion effect will override the old potion effect + * (Only applicable for the CHANGED Action). + * + * @return If the new effect will override the old one. + */ + public boolean isOverride() { + return override; + } + + /** + * Sets if the new potion effect will override the old potion effect (Only + * applicable for the CHANGED action). + * + * @param override If the new effect will override the old one. + */ + public void setOverride(boolean override) { + this.override = override; + } + + @Override + public boolean isCancelled() { + return cancel; + } + + @Override + public void setCancelled(boolean cancel) { + this.cancel = cancel; + } + + @NotNull + @Override + public HandlerList getHandlers() { + return handlers; + } + + @NotNull + public static HandlerList getHandlerList() { + return handlers; + } + + /** + * An enum to specify the action to be performed. + */ + public enum Action { + + /** + * When the potion effect is added because the entity didn't have it's + * type. + */ + ADDED, + /** + * When the entity already had the potion effect type, but the effect is + * changed. + */ + CHANGED, + /** + * When the effect is removed due to all effects being removed. + */ + CLEARED, + /** + * When the potion effect type is completely removed. + */ + REMOVED + } + + /** + * An enum to specify the cause why an effect was changed. + */ + public enum Cause { + + /** + * When the entity stands inside an area effect cloud. + */ + AREA_EFFECT_CLOUD, + /** + * When the entity is hit by an spectral or tipped arrow. + */ + ARROW, + /** + * When the entity is inflicted with a potion effect due to an entity + * attack (e.g. a cave spider or a shulker bullet). + */ + ATTACK, + /** + * When beacon effects get applied due to the entity being nearby. + */ + BEACON, + /** + * When a potion effect is changed due to the /effect command. + */ + COMMAND, + /** + * When the entity gets the effect from a conduit. + */ + CONDUIT, + /** + * When a conversion from a villager zombie to a villager is started or + * finished. + */ + CONVERSION, + /** + * When all effects are removed due to death (Note: This is called on + * respawn, so it's player only!) + */ + DEATH, + /** + * When the entity gets the effect from a dolphin. + */ + DOLPHIN, + /** + * When the effect was removed due to expiration. + */ + EXPIRATION, + /** + * When an effect is inflicted due to food (e.g. when a player eats or a + * cookie is given to a parrot). + */ + FOOD, + /** + * When an illusion illager makes himself disappear. + */ + ILLUSION, + /** + * When all effects are removed due to a bucket of milk. + */ + MILK, + /** + * When a potion effect is modified through the plugin methods. + */ + PLUGIN, + /** + * When the entity drinks a potion. + */ + POTION_DRINK, + /** + * When the entity is inflicted with an effect due to a splash potion. + */ + POTION_SPLASH, + /** + * When a spider gets effects when spawning on hard difficulty. + */ + SPIDER_SPAWN, + /** + * When the entity gets effects from a totem item saving it's life. + */ + TOTEM, + /** + * When the entity gets water breathing by wearing a turtle helmet. + */ + TURTLE_HELMET, + /** + * When the Cause is missing. + */ + UNKNOWN, + /** + * When a villager gets regeneration after a trade. + */ + VILLAGER_TRADE + } +} diff --git a/api/src/main/java/org/bukkit/event/entity/EntityRegainHealthEvent.java b/api/src/main/java/org/bukkit/event/entity/EntityRegainHealthEvent.java new file mode 100644 index 000000000..d51d2ec1d --- /dev/null +++ b/api/src/main/java/org/bukkit/event/entity/EntityRegainHealthEvent.java @@ -0,0 +1,136 @@ +package org.bukkit.event.entity; + +import org.bukkit.entity.Entity; +import org.bukkit.event.Cancellable; +import org.bukkit.event.HandlerList; +import org.jetbrains.annotations.NotNull; + +/** + * Stores data for health-regain events + */ +public class EntityRegainHealthEvent extends EntityEvent implements Cancellable { + private static final HandlerList handlers = new HandlerList(); + private boolean cancelled; + private double amount; + private final RegainReason regainReason; + private final boolean isFastRegen; // Paper + + public EntityRegainHealthEvent(@NotNull final Entity entity, final double amount, @NotNull final RegainReason regainReason) { + // Paper start - Forward + this(entity, amount, regainReason, false); + } + + public EntityRegainHealthEvent(@NotNull final Entity entity, final double amount, @NotNull final RegainReason regainReason, boolean isFastRegen) { + // Paper end + super(entity); + this.amount = amount; + this.regainReason = regainReason; + this.isFastRegen = isFastRegen; // Paper + } + + // Paper start - Add getter for isFastRegen + /** + * Is this event a result of the fast regeneration mechanic + * + * @return Whether the event is the result of a fast regeneration mechanic + */ + public boolean isFastRegen() { + return isFastRegen; + } + // Paper end + + /** + * Gets the amount of regained health + * + * @return The amount of health regained + */ + public double getAmount() { + return amount; + } + + /** + * Sets the amount of regained health + * + * @param amount the amount of health the entity will regain + */ + public void setAmount(double amount) { + this.amount = amount; + } + + @Override + public boolean isCancelled() { + return cancelled; + } + + @Override + public void setCancelled(boolean cancel) { + cancelled = cancel; + } + + /** + * Gets the reason for why the entity is regaining health + * + * @return A RegainReason detailing the reason for the entity regaining + * health + */ + @NotNull + public RegainReason getRegainReason() { + return regainReason; + } + + @NotNull + @Override + public HandlerList getHandlers() { + return handlers; + } + + @NotNull + public static HandlerList getHandlerList() { + return handlers; + } + + /** + * An enum to specify the type of health regaining that is occurring + */ + public enum RegainReason { + + /** + * When a player regains health from regenerating due to Peaceful mode + * (difficulty=0) + */ + REGEN, + /** + * When a player regains health from regenerating due to their hunger + * being satisfied + */ + SATIATED, + /** + * When a player regains health from eating consumables + */ + EATING, + /** + * When an ender dragon regains health from an ender crystal + */ + ENDER_CRYSTAL, + /** + * When a player is healed by a potion or spell + */ + MAGIC, + /** + * When a player is healed over time by a potion or spell + */ + MAGIC_REGEN, + /** + * When a wither is filling its health during spawning + */ + WITHER_SPAWN, + /** + * When an entity is damaged by the Wither potion effect + */ + WITHER, + /** + * Any other reason not covered by the reasons above + */ + CUSTOM + } +} diff --git a/api/src/main/java/org/bukkit/event/entity/EntityResurrectEvent.java b/api/src/main/java/org/bukkit/event/entity/EntityResurrectEvent.java new file mode 100644 index 000000000..fa6b6c3b0 --- /dev/null +++ b/api/src/main/java/org/bukkit/event/entity/EntityResurrectEvent.java @@ -0,0 +1,49 @@ +package org.bukkit.event.entity; + +import org.bukkit.entity.LivingEntity; +import org.bukkit.event.Cancellable; +import org.bukkit.event.HandlerList; +import org.jetbrains.annotations.NotNull; + +/** + * Called when an entity dies and may have the opportunity to be resurrected. + * Will be called in a cancelled state if the entity does not have a totem + * equipped. + */ +public class EntityResurrectEvent extends EntityEvent implements Cancellable { + + private static final HandlerList handlers = new HandlerList(); + // + private boolean cancelled; + + public EntityResurrectEvent(@NotNull LivingEntity what) { + super(what); + } + + @NotNull + @Override + public LivingEntity getEntity() { + return (LivingEntity) entity; + } + + @Override + public boolean isCancelled() { + return cancelled; + } + + @Override + public void setCancelled(boolean cancelled) { + this.cancelled = cancelled; + } + + @NotNull + @Override + public HandlerList getHandlers() { + return handlers; + } + + @NotNull + public static HandlerList getHandlerList() { + return handlers; + } +} diff --git a/api/src/main/java/org/bukkit/event/entity/EntityShootBowEvent.java b/api/src/main/java/org/bukkit/event/entity/EntityShootBowEvent.java new file mode 100644 index 000000000..efde16174 --- /dev/null +++ b/api/src/main/java/org/bukkit/event/entity/EntityShootBowEvent.java @@ -0,0 +1,112 @@ +package org.bukkit.event.entity; + +import org.bukkit.entity.Entity; +import org.bukkit.entity.LivingEntity; +import org.bukkit.entity.Projectile; +import org.bukkit.event.Cancellable; +import org.bukkit.event.HandlerList; +import org.bukkit.inventory.ItemStack; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +/** + * Called when a LivingEntity shoots a bow firing an arrow + */ +public class EntityShootBowEvent extends EntityEvent implements Cancellable { + private static final HandlerList handlers = new HandlerList(); + private final ItemStack bow; + private Entity projectile; + private final float force; + private boolean cancelled; + // Paper start + private boolean consumeArrow = true; + private final ItemStack arrowItem; + public void setConsumeArrow(boolean consumeArrow) { + this.consumeArrow = consumeArrow; + } + public boolean getConsumeArrow() { + return consumeArrow; + } + + @NotNull + public ItemStack getArrowItem() { + return arrowItem; + } + + @Deprecated + public EntityShootBowEvent(@NotNull final LivingEntity shooter, @Nullable final ItemStack bow, @NotNull final Projectile projectile, final float force) { + this(shooter, bow, new ItemStack(org.bukkit.Material.AIR), projectile, force); + } + + public EntityShootBowEvent(@NotNull final LivingEntity shooter, @Nullable final ItemStack bow, @NotNull ItemStack arrowItem, @NotNull final Projectile projectile, final float force) { + super(shooter); + this.arrowItem = arrowItem; + // Paper end + this.bow = bow; + this.projectile = projectile; + this.force = force; + } + + @NotNull + @Override + public LivingEntity getEntity() { + return (LivingEntity) entity; + } + + /** + * Gets the bow ItemStack used to fire the arrow. + * + * @return the bow involved in this event + */ + @Nullable + public ItemStack getBow() { + return bow; + } + + /** + * Gets the projectile which will be launched by this event + * + * @return the launched projectile + */ + @NotNull + public Entity getProjectile() { + return projectile; + } + + /** + * Replaces the projectile which will be launched + * + * @param projectile the new projectile + */ + public void setProjectile(@NotNull Entity projectile) { + this.projectile = projectile; + } + + /** + * Gets the force the arrow was launched with + * + * @return bow shooting force, up to 1.0 + */ + public float getForce() { + return force; + } + + public boolean isCancelled() { + return cancelled; + } + + public void setCancelled(boolean cancel) { + cancelled = cancel; + } + + @NotNull + @Override + public HandlerList getHandlers() { + return handlers; + } + + @NotNull + public static HandlerList getHandlerList() { + return handlers; + } +} diff --git a/api/src/main/java/org/bukkit/event/entity/EntitySpawnEvent.java b/api/src/main/java/org/bukkit/event/entity/EntitySpawnEvent.java new file mode 100644 index 000000000..961ee5113 --- /dev/null +++ b/api/src/main/java/org/bukkit/event/entity/EntitySpawnEvent.java @@ -0,0 +1,53 @@ +package org.bukkit.event.entity; + +import org.bukkit.Location; +import org.bukkit.entity.Entity; +import org.bukkit.event.Cancellable; +import org.bukkit.event.HandlerList; +import org.jetbrains.annotations.NotNull; + +/** + * Called when an entity is spawned into a world. + *

+ * If an Entity Spawn event is cancelled, the entity will not spawn. + */ +public class EntitySpawnEvent extends EntityEvent implements Cancellable { + + private static final HandlerList handlers = new HandlerList(); + private boolean canceled; + + public EntitySpawnEvent(@NotNull final Entity spawnee) { + super(spawnee); + } + + @Override + public boolean isCancelled() { + return canceled; + } + + @Override + public void setCancelled(boolean cancel) { + canceled = cancel; + } + + /** + * Gets the location at which the entity is spawning. + * + * @return The location at which the entity is spawning + */ + @NotNull + public Location getLocation() { + return getEntity().getLocation(); + } + + @NotNull + @Override + public HandlerList getHandlers() { + return handlers; + } + + @NotNull + public static HandlerList getHandlerList() { + return handlers; + } +} diff --git a/api/src/main/java/org/bukkit/event/entity/EntityTameEvent.java b/api/src/main/java/org/bukkit/event/entity/EntityTameEvent.java new file mode 100644 index 000000000..0698fa693 --- /dev/null +++ b/api/src/main/java/org/bukkit/event/entity/EntityTameEvent.java @@ -0,0 +1,56 @@ +package org.bukkit.event.entity; + +import org.bukkit.entity.AnimalTamer; +import org.bukkit.entity.LivingEntity; +import org.bukkit.event.Cancellable; +import org.bukkit.event.HandlerList; +import org.jetbrains.annotations.NotNull; + +/** + * Thrown when a LivingEntity is tamed + */ +public class EntityTameEvent extends EntityEvent implements Cancellable { + private static final HandlerList handlers = new HandlerList(); + private boolean cancelled; + private final AnimalTamer owner; + + public EntityTameEvent(@NotNull final LivingEntity entity, @NotNull final AnimalTamer owner) { + super(entity); + this.owner = owner; + } + + @NotNull + @Override + public LivingEntity getEntity() { + return (LivingEntity) entity; + } + + public boolean isCancelled() { + return cancelled; + } + + public void setCancelled(boolean cancel) { + cancelled = cancel; + } + + /** + * Gets the owning AnimalTamer + * + * @return the owning AnimalTamer + */ + @NotNull + public AnimalTamer getOwner() { + return owner; + } + + @NotNull + @Override + public HandlerList getHandlers() { + return handlers; + } + + @NotNull + public static HandlerList getHandlerList() { + return handlers; + } +} diff --git a/api/src/main/java/org/bukkit/event/entity/EntityTargetEvent.java b/api/src/main/java/org/bukkit/event/entity/EntityTargetEvent.java new file mode 100644 index 000000000..37d0ffa1b --- /dev/null +++ b/api/src/main/java/org/bukkit/event/entity/EntityTargetEvent.java @@ -0,0 +1,164 @@ +package org.bukkit.event.entity; + +import org.bukkit.entity.Entity; +import org.bukkit.event.Cancellable; +import org.bukkit.event.HandlerList; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +/** + * Called when a creature targets or untargets another entity + */ +public class EntityTargetEvent extends EntityEvent implements Cancellable { + private static final HandlerList handlers = new HandlerList(); + private boolean cancel = false; + private Entity target; + private final TargetReason reason; + + public EntityTargetEvent(@NotNull final Entity entity, @Nullable final Entity target, @NotNull final TargetReason reason) { + super(entity); + this.target = target; + this.reason = reason; + } + + public boolean isCancelled() { + return cancel; + } + + public void setCancelled(boolean cancel) { + this.cancel = cancel; + } + + /** + * Returns the reason for the targeting + * + * @return The reason + */ + @NotNull + public TargetReason getReason() { + return reason; + } + + /** + * Get the entity that this is targeting. + *

+ * This will be null in the case that the event is called when the mob + * forgets its target. + * + * @return The entity + */ + @Nullable + public Entity getTarget() { + return target; + } + + /** + * Set the entity that you want the mob to target instead. + *

+ * It is possible to be null, null will cause the entity to be + * target-less. + *

+ * This is different from cancelling the event. Cancelling the event will + * cause the entity to keep an original target, while setting to be null + * will cause the entity to be reset. + * + * @param target The entity to target + */ + public void setTarget(@Nullable Entity target) { + this.target = target; + } + + @NotNull + @Override + public HandlerList getHandlers() { + return handlers; + } + + @NotNull + public static HandlerList getHandlerList() { + return handlers; + } + + /** + * An enum to specify the reason for the targeting + */ + public enum TargetReason { + + /** + * When the entity's target has died, and so it no longer targets it + */ + TARGET_DIED, + /** + * When the entity doesn't have a target, so it attacks the nearest + * player + */ + CLOSEST_PLAYER, + /** + * When the target attacks the entity, so entity targets it + */ + TARGET_ATTACKED_ENTITY, + /** + * When the target attacks a fellow pig zombie, so the whole group + * will target him with this reason. + * + * @deprecated obsoleted by {@link #TARGET_ATTACKED_NEARBY_ENTITY} + */ + @Deprecated + PIG_ZOMBIE_TARGET, + /** + * When the target is forgotten for whatever reason. + *

+ * Currently only occurs in with spiders when there is a high + * brightness. + */ + FORGOT_TARGET, + /** + * When the target attacks the owner of the entity, so the entity + * targets it. + */ + TARGET_ATTACKED_OWNER, + /** + * When the owner of the entity attacks the target attacks, so the + * entity targets it. + */ + OWNER_ATTACKED_TARGET, + /** + * When the entity has no target, so the entity randomly chooses one. + */ + RANDOM_TARGET, + /** + * When an entity selects a target while defending a village. + */ + DEFEND_VILLAGE, + /** + * When the target attacks a nearby entity of the same type, so the entity targets it + */ + TARGET_ATTACKED_NEARBY_ENTITY, + /** + * When a zombie targeting an entity summons reinforcements, so the reinforcements target the same entity + */ + REINFORCEMENT_TARGET, + /** + * When an entity targets another entity after colliding with it. + */ + COLLISION, + /** + * For custom calls to the event. + */ + CUSTOM, + /** + * When the entity doesn't have a target, so it attacks the nearest + * entity + */ + CLOSEST_ENTITY, + /** + * When another entity tempts this entity by having a desired item such + * as wheat in it's hand. + */ + TEMPT, + /** + * A currently unknown reason for the entity changing target. + */ + UNKNOWN; + } +} diff --git a/api/src/main/java/org/bukkit/event/entity/EntityTargetLivingEntityEvent.java b/api/src/main/java/org/bukkit/event/entity/EntityTargetLivingEntityEvent.java new file mode 100644 index 000000000..f309986f3 --- /dev/null +++ b/api/src/main/java/org/bukkit/event/entity/EntityTargetLivingEntityEvent.java @@ -0,0 +1,37 @@ +package org.bukkit.event.entity; + +import org.bukkit.entity.Entity; +import org.bukkit.entity.LivingEntity; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +/** + * Called when an Entity targets a {@link LivingEntity} and can only target + * LivingEntity's. + */ +public class EntityTargetLivingEntityEvent extends EntityTargetEvent{ + public EntityTargetLivingEntityEvent(@NotNull final Entity entity, @Nullable final LivingEntity target, @Nullable final TargetReason reason) { + super(entity, target, reason); + } + + @Nullable + public LivingEntity getTarget() { + return (LivingEntity) super.getTarget(); + } + + /** + * Set the Entity that you want the mob to target. + *

+ * It is possible to be null, null will cause the entity to be + * target-less. + *

+ * Must be a LivingEntity, or null. + * + * @param target The entity to target + */ + public void setTarget(@Nullable Entity target) { + if (target == null || target instanceof LivingEntity) { + super.setTarget(target); + } + } +} diff --git a/api/src/main/java/org/bukkit/event/entity/EntityTeleportEvent.java b/api/src/main/java/org/bukkit/event/entity/EntityTeleportEvent.java new file mode 100644 index 000000000..310259fe8 --- /dev/null +++ b/api/src/main/java/org/bukkit/event/entity/EntityTeleportEvent.java @@ -0,0 +1,85 @@ +package org.bukkit.event.entity; + +import org.bukkit.Location; +import org.bukkit.entity.Entity; +import org.bukkit.event.Cancellable; +import org.bukkit.event.HandlerList; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +/** + * Thrown when a non-player entity is teleported from one location to another. + *
+ * This may be as a result of natural causes (Enderman, Shulker), pathfinding + * (Wolf), or commands (/teleport). + */ +public class EntityTeleportEvent extends EntityEvent implements Cancellable { + private static final HandlerList handlers = new HandlerList(); + private boolean cancel; + private Location from; + private Location to; + + public EntityTeleportEvent(@NotNull Entity what, @NotNull Location from, @Nullable Location to) { + super(what); + this.from = from; + this.to = to; + this.cancel = false; + } + + public boolean isCancelled() { + return cancel; + } + + public void setCancelled(boolean cancel) { + this.cancel = cancel; + } + + /** + * Gets the location that this entity moved from + * + * @return Location this entity moved from + */ + @NotNull + public Location getFrom() { + return from; + } + + /** + * Sets the location that this entity moved from + * + * @param from New location this entity moved from + */ + public void setFrom(@NotNull Location from) { + this.from = from; + } + + /** + * Gets the location that this entity moved to + * + * @return Location the entity moved to + */ + @Nullable + public Location getTo() { + return to; + } + + /** + * Sets the location that this entity moved to + * + * @param to New Location this entity moved to + */ + public void setTo(@Nullable Location to) { + this.to = to; + } + + @NotNull + @Override + public HandlerList getHandlers() { + return handlers; + } + + @NotNull + public static HandlerList getHandlerList() { + return handlers; + } +} \ No newline at end of file diff --git a/api/src/main/java/org/bukkit/event/entity/EntityToggleGlideEvent.java b/api/src/main/java/org/bukkit/event/entity/EntityToggleGlideEvent.java new file mode 100644 index 000000000..1478cdbb4 --- /dev/null +++ b/api/src/main/java/org/bukkit/event/entity/EntityToggleGlideEvent.java @@ -0,0 +1,53 @@ +package org.bukkit.event.entity; + +import org.bukkit.entity.LivingEntity; +import org.bukkit.event.Cancellable; +import org.bukkit.event.HandlerList; +import org.jetbrains.annotations.NotNull; + +/** + * Sent when an entity's gliding status is toggled with an Elytra. + * Examples of when this event would be called: + *

    + *
  • Player presses the jump key while in midair and using an Elytra
  • + *
  • Player lands on ground while they are gliding (with an Elytra)
  • + *
+ * This can be visually estimated by the animation in which a player turns horizontal. + */ +public class EntityToggleGlideEvent extends EntityEvent implements Cancellable { + private static final HandlerList handlers = new HandlerList(); + + private boolean cancel = false; + private final boolean isGliding; + + public EntityToggleGlideEvent(@NotNull LivingEntity who, final boolean isGliding) { + super(who); + this.isGliding = isGliding; + } + + @Override + public boolean isCancelled() { + return cancel; + } + + @Override + public void setCancelled(boolean cancel) { + this.cancel = cancel; + } + + public boolean isGliding() { + return isGliding; + } + + @NotNull + @Override + public HandlerList getHandlers() { + return handlers; + } + + @NotNull + public static HandlerList getHandlerList() { + return handlers; + } + +} diff --git a/api/src/main/java/org/bukkit/event/entity/EntityToggleSwimEvent.java b/api/src/main/java/org/bukkit/event/entity/EntityToggleSwimEvent.java new file mode 100644 index 000000000..7b3fc4cad --- /dev/null +++ b/api/src/main/java/org/bukkit/event/entity/EntityToggleSwimEvent.java @@ -0,0 +1,46 @@ +package org.bukkit.event.entity; + +import org.bukkit.entity.LivingEntity; +import org.bukkit.event.Cancellable; +import org.bukkit.event.HandlerList; +import org.jetbrains.annotations.NotNull; + +/** + * Sent when an entity's swimming status is toggled. + */ +public class EntityToggleSwimEvent extends EntityEvent implements Cancellable { + private static final HandlerList handlers = new HandlerList(); + + private boolean cancel = false; + private final boolean isSwimming; + + public EntityToggleSwimEvent(@NotNull LivingEntity who, final boolean isSwimming) { + super(who); + this.isSwimming = isSwimming; + } + + @Override + public boolean isCancelled() { + return cancel; + } + + @Override + public void setCancelled(boolean cancel) { + this.cancel = cancel; + } + + public boolean isSwimming() { + return isSwimming; + } + + @NotNull + @Override + public HandlerList getHandlers() { + return handlers; + } + + @NotNull + public static HandlerList getHandlerList() { + return handlers; + } +} diff --git a/api/src/main/java/org/bukkit/event/entity/EntityTransformEvent.java b/api/src/main/java/org/bukkit/event/entity/EntityTransformEvent.java new file mode 100644 index 000000000..1370380ae --- /dev/null +++ b/api/src/main/java/org/bukkit/event/entity/EntityTransformEvent.java @@ -0,0 +1,108 @@ +package org.bukkit.event.entity; + +import java.util.Collections; +import java.util.List; +import org.bukkit.entity.Entity; +import org.bukkit.event.Cancellable; +import org.bukkit.event.HandlerList; +import org.jetbrains.annotations.NotNull; + +/** + * Called when an entity is about to be replaced by another entity. + */ +public class EntityTransformEvent extends EntityEvent implements Cancellable { + + private static final HandlerList handlers = new HandlerList(); + private boolean cancelled; + private final Entity converted; + private final List convertedList; + private final TransformReason transformReason; + + public EntityTransformEvent(@NotNull Entity original, @NotNull List convertedList, @NotNull TransformReason transformReason) { + super(original); + this.convertedList = Collections.unmodifiableList(convertedList); + this.converted = convertedList.get(0); + this.transformReason = transformReason; + } + + /** + * Gets the entity that the original entity was transformed to. + * + * This returns the first entity in the transformed entity list. + * + * @return The transformed entity. + * @see #getTransformedEntities() + */ + @NotNull + public Entity getTransformedEntity() { + return converted; + } + + /** + * Gets the entities that the original entity was transformed to. + * + * @return The transformed entities. + */ + @NotNull + public List getTransformedEntities() { + return convertedList; + } + + /** + * Gets the reason for the conversion that has occurred. + * + * @return The reason for conversion that has occurred. + */ + @NotNull + public TransformReason getTransformReason() { + return transformReason; + } + + @Override + public boolean isCancelled() { + return cancelled; + } + + @Override + public void setCancelled(boolean cancel) { + cancelled = cancel; + } + + @NotNull + @Override + public HandlerList getHandlers() { + return handlers; + } + + @NotNull + public static HandlerList getHandlerList() { + return handlers; + } + + public enum TransformReason { + /** + * When a zombie gets cured and a villager is spawned. + */ + CURED, + /** + * When a villager gets infected and a zombie villager spawns. + */ + INFECTION, + /** + * When a entity drowns in water and a new entity spawns. + */ + DROWNED, + /** + * When a mooshroom (or MUSHROOM_COW) is sheared and a cow spawns. + */ + SHEARED, + /** + * When lightning strikes a entity. + */ + LIGHTNING, + /** + * When a slime splits into multiple smaller slimes. + */ + SPLIT + } +} diff --git a/api/src/main/java/org/bukkit/event/entity/EntityUnleashEvent.java b/api/src/main/java/org/bukkit/event/entity/EntityUnleashEvent.java new file mode 100644 index 000000000..a33986a0c --- /dev/null +++ b/api/src/main/java/org/bukkit/event/entity/EntityUnleashEvent.java @@ -0,0 +1,56 @@ +package org.bukkit.event.entity; + +import org.bukkit.entity.Entity; +import org.bukkit.event.HandlerList; +import org.jetbrains.annotations.NotNull; + +/** + * Called immediately prior to an entity being unleashed. + */ +public class EntityUnleashEvent extends EntityEvent { + private static final HandlerList handlers = new HandlerList(); + private final UnleashReason reason; + + public EntityUnleashEvent(@NotNull Entity entity, @NotNull UnleashReason reason) { + super(entity); + this.reason = reason; + } + + /** + * Returns the reason for the unleashing. + * + * @return The reason + */ + @NotNull + public UnleashReason getReason() { + return reason; + } + + @NotNull + @Override + public HandlerList getHandlers() { + return handlers; + } + + @NotNull + public static HandlerList getHandlerList() { + return handlers; + } + + public enum UnleashReason { + /** + * When the entity's leashholder has died or logged out, and so is + * unleashed + */ + HOLDER_GONE, + /** + * When the entity's leashholder attempts to unleash it + */ + PLAYER_UNLEASH, + /** + * When the entity's leashholder is more than 10 blocks away + */ + DISTANCE, + UNKNOWN; + } +} diff --git a/api/src/main/java/org/bukkit/event/entity/ExpBottleEvent.java b/api/src/main/java/org/bukkit/event/entity/ExpBottleEvent.java new file mode 100644 index 000000000..6417c2fd4 --- /dev/null +++ b/api/src/main/java/org/bukkit/event/entity/ExpBottleEvent.java @@ -0,0 +1,79 @@ +package org.bukkit.event.entity; + +import org.bukkit.entity.ThrownExpBottle; +import org.bukkit.event.HandlerList; +import org.jetbrains.annotations.NotNull; + +/** + * Called when a ThrownExpBottle hits and releases experience. + */ +public class ExpBottleEvent extends ProjectileHitEvent { + private static final HandlerList handlers = new HandlerList(); + private int exp; + private boolean showEffect = true; + + public ExpBottleEvent(@NotNull final ThrownExpBottle bottle, final int exp) { + super(bottle); + this.exp = exp; + } + + @NotNull + @Override + public ThrownExpBottle getEntity() { + return (ThrownExpBottle) entity; + } + + /** + * This method indicates if the particle effect should be shown. + * + * @return true if the effect will be shown, false otherwise + */ + public boolean getShowEffect() { + return this.showEffect; + } + + /** + * This method sets if the particle effect will be shown. + *

+ * This does not change the experience created. + * + * @param showEffect true indicates the effect will be shown, false + * indicates no effect will be shown + */ + public void setShowEffect(final boolean showEffect) { + this.showEffect = showEffect; + } + + /** + * This method retrieves the amount of experience to be created. + *

+ * The number indicates a total amount to be divided into orbs. + * + * @return the total amount of experience to be created + */ + public int getExperience() { + return exp; + } + + /** + * This method sets the amount of experience to be created. + *

+ * The number indicates a total amount to be divided into orbs. + * + * @param exp the total amount of experience to be created + */ + public void setExperience(final int exp) { + this.exp = exp; + } + + @NotNull + @Override + public HandlerList getHandlers() { + return handlers; + } + + @NotNull + public static HandlerList getHandlerList() { + return handlers; + } +} diff --git a/api/src/main/java/org/bukkit/event/entity/ExplosionPrimeEvent.java b/api/src/main/java/org/bukkit/event/entity/ExplosionPrimeEvent.java new file mode 100644 index 000000000..31fdb3014 --- /dev/null +++ b/api/src/main/java/org/bukkit/event/entity/ExplosionPrimeEvent.java @@ -0,0 +1,83 @@ +package org.bukkit.event.entity; + +import org.bukkit.entity.Entity; +import org.bukkit.entity.Explosive; +import org.bukkit.event.Cancellable; +import org.bukkit.event.HandlerList; +import org.jetbrains.annotations.NotNull; + +/** + * Called when an entity has made a decision to explode. + */ +public class ExplosionPrimeEvent extends EntityEvent implements Cancellable { + private static final HandlerList handlers = new HandlerList(); + private boolean cancel; + private float radius; + private boolean fire; + + public ExplosionPrimeEvent(@NotNull final Entity what, final float radius, final boolean fire) { + super(what); + this.cancel = false; + this.radius = radius; + this.fire = fire; + } + + public ExplosionPrimeEvent(@NotNull final Explosive explosive) { + this(explosive, explosive.getYield(), explosive.isIncendiary()); + } + + public boolean isCancelled() { + return cancel; + } + + public void setCancelled(boolean cancel) { + this.cancel = cancel; + } + + /** + * Gets the radius of the explosion + * + * @return returns the radius of the explosion + */ + public float getRadius() { + return radius; + } + + /** + * Sets the radius of the explosion + * + * @param radius the radius of the explosion + */ + public void setRadius(float radius) { + this.radius = radius; + } + + /** + * Gets whether this explosion will create fire or not + * + * @return true if this explosion will create fire + */ + public boolean getFire() { + return fire; + } + + /** + * Sets whether this explosion will create fire or not + * + * @param fire true if you want this explosion to create fire + */ + public void setFire(boolean fire) { + this.fire = fire; + } + + @NotNull + @Override + public HandlerList getHandlers() { + return handlers; + } + + @NotNull + public static HandlerList getHandlerList() { + return handlers; + } +} diff --git a/api/src/main/java/org/bukkit/event/entity/FireworkExplodeEvent.java b/api/src/main/java/org/bukkit/event/entity/FireworkExplodeEvent.java new file mode 100644 index 000000000..51ad0e6bf --- /dev/null +++ b/api/src/main/java/org/bukkit/event/entity/FireworkExplodeEvent.java @@ -0,0 +1,53 @@ +package org.bukkit.event.entity; + +import org.bukkit.entity.Firework; +import org.bukkit.event.Cancellable; +import org.bukkit.event.HandlerList; +import org.jetbrains.annotations.NotNull; + +/** + * Called when a firework explodes. + */ +public class FireworkExplodeEvent extends EntityEvent implements Cancellable { + + private static final HandlerList handlers = new HandlerList(); + private boolean cancel; + + public FireworkExplodeEvent(@NotNull final Firework what) { + super(what); + } + + @Override + public boolean isCancelled() { + return cancel; + } + + /** + * Set the cancelled state of this event. If the firework explosion is + * cancelled, the firework will still be removed, but no particles will be + * displayed. + * + * @param cancel whether to cancel or not. + */ + @Override + public void setCancelled(boolean cancel) { + this.cancel = cancel; + } + + @NotNull + @Override + public Firework getEntity() { + return (Firework) super.getEntity(); + } + + @NotNull + @Override + public HandlerList getHandlers() { + return handlers; + } + + @NotNull + public static HandlerList getHandlerList() { + return handlers; + } +} diff --git a/api/src/main/java/org/bukkit/event/entity/FoodLevelChangeEvent.java b/api/src/main/java/org/bukkit/event/entity/FoodLevelChangeEvent.java new file mode 100644 index 000000000..f5b990612 --- /dev/null +++ b/api/src/main/java/org/bukkit/event/entity/FoodLevelChangeEvent.java @@ -0,0 +1,70 @@ +package org.bukkit.event.entity; + +import org.bukkit.entity.HumanEntity; +import org.bukkit.event.Cancellable; +import org.bukkit.event.HandlerList; +import org.jetbrains.annotations.NotNull; + +/** + * Called when a human entity's food level changes + */ +public class FoodLevelChangeEvent extends EntityEvent implements Cancellable { + private static final HandlerList handlers = new HandlerList(); + private boolean cancel = false; + private int level; + + public FoodLevelChangeEvent(@NotNull final HumanEntity what, final int level) { + super(what); + this.level = level; + } + + @NotNull + @Override + public HumanEntity getEntity() { + return (HumanEntity) entity; + } + + /** + * Gets the resultant food level that the entity involved in this event + * should be set to. + *

+ * Where 20 is a full food bar and 0 is an empty one. + * + * @return The resultant food level + */ + public int getFoodLevel() { + return level; + } + + /** + * Sets the resultant food level that the entity involved in this event + * should be set to + * + * @param level the resultant food level that the entity involved in this + * event should be set to + */ + public void setFoodLevel(int level) { + if (level < 0) level = 0; + + this.level = level; + } + + public boolean isCancelled() { + return cancel; + } + + public void setCancelled(boolean cancel) { + this.cancel = cancel; + } + + @NotNull + @Override + public HandlerList getHandlers() { + return handlers; + } + + @NotNull + public static HandlerList getHandlerList() { + return handlers; + } +} diff --git a/api/src/main/java/org/bukkit/event/entity/HorseJumpEvent.java b/api/src/main/java/org/bukkit/event/entity/HorseJumpEvent.java new file mode 100644 index 000000000..74588fc8f --- /dev/null +++ b/api/src/main/java/org/bukkit/event/entity/HorseJumpEvent.java @@ -0,0 +1,88 @@ +package org.bukkit.event.entity; + +import org.bukkit.event.Cancellable; +import org.bukkit.event.HandlerList; +import org.bukkit.entity.AbstractHorse; +import org.jetbrains.annotations.NotNull; + +/** + * Called when a horse jumps. + */ +public class HorseJumpEvent extends EntityEvent implements Cancellable { + private static final HandlerList handlers = new HandlerList(); + private boolean cancelled; + private float power; + + public HorseJumpEvent(@NotNull final AbstractHorse horse, final float power) { + super(horse); + this.power = power; + } + + public boolean isCancelled() { + return cancelled; + } + + /** + * @deprecated horse jumping was moved client side. + */ + @Deprecated + public void setCancelled(boolean cancel) { + cancelled = cancel; + } + + @NotNull + @Override + public AbstractHorse getEntity() { + return (AbstractHorse) entity; + } + + /** + * Gets the power of the jump. + *

+ * Power is a value that defines how much of the horse's jump strength + * should be used for the jump. Power is effectively multiplied times + * the horse's jump strength to determine how high the jump is; 0 + * represents no jump strength while 1 represents full jump strength. + * Setting power to a value above 1 will use additional jump strength + * that the horse does not usually have. + *

+ * Power does not affect how high the horse is capable of jumping, only + * how much of its jumping capability will be used in this jump. To set + * the horse's overall jump strength, see {@link + * AbstractHorse#setJumpStrength(double)}. + * + * @return jump strength + */ + public float getPower() { + return power; + } + + /** + * Sets the power of the jump. + *

+ * Jump power can be set to a value above 1.0 which will increase the + * strength of this jump above the horse's actual jump strength. + *

+ * Setting the jump power to 0 will result in the jump animation still + * playing, but the horse not leaving the ground. Only canceling this + * event will result in no jump animation at all. + * + * @param power power of the jump + * @deprecated horse jumping was moved client side. + */ + @Deprecated + public void setPower(float power) { + this.power = power; + } + + @NotNull + @Override + public HandlerList getHandlers() { + return handlers; + } + + @NotNull + public static HandlerList getHandlerList() { + return handlers; + } +} diff --git a/api/src/main/java/org/bukkit/event/entity/ItemDespawnEvent.java b/api/src/main/java/org/bukkit/event/entity/ItemDespawnEvent.java new file mode 100644 index 000000000..93e6f5ee7 --- /dev/null +++ b/api/src/main/java/org/bukkit/event/entity/ItemDespawnEvent.java @@ -0,0 +1,60 @@ +package org.bukkit.event.entity; + +import org.bukkit.Location; +import org.bukkit.entity.Item; +import org.bukkit.event.Cancellable; +import org.bukkit.event.HandlerList; +import org.jetbrains.annotations.NotNull; + +/** + * This event is called when a {@link org.bukkit.entity.Item} is removed from + * the world because it has existed for 5 minutes. + *

+ * Cancelling the event results in the item being allowed to exist for 5 more + * minutes. This behavior is not guaranteed and may change in future versions. + */ +public class ItemDespawnEvent extends EntityEvent implements Cancellable { + private static final HandlerList handlers = new HandlerList(); + private boolean canceled; + private final Location location; + + public ItemDespawnEvent(@NotNull final Item despawnee, @NotNull final Location loc) { + super(despawnee); + location = loc; + } + + public boolean isCancelled() { + return canceled; + } + + public void setCancelled(boolean cancel) { + canceled = cancel; + } + + @NotNull + @Override + public Item getEntity() { + return (Item) entity; + } + + /** + * Gets the location at which the item is despawning. + * + * @return The location at which the item is despawning + */ + @NotNull + public Location getLocation() { + return location; + } + + @NotNull + @Override + public HandlerList getHandlers() { + return handlers; + } + + @NotNull + public static HandlerList getHandlerList() { + return handlers; + } +} diff --git a/api/src/main/java/org/bukkit/event/entity/ItemMergeEvent.java b/api/src/main/java/org/bukkit/event/entity/ItemMergeEvent.java new file mode 100644 index 000000000..e378cc29b --- /dev/null +++ b/api/src/main/java/org/bukkit/event/entity/ItemMergeEvent.java @@ -0,0 +1,55 @@ +package org.bukkit.event.entity; + +import org.bukkit.entity.Item; +import org.bukkit.event.Cancellable; +import org.bukkit.event.HandlerList; +import org.jetbrains.annotations.NotNull; + +public class ItemMergeEvent extends EntityEvent implements Cancellable { + + private static final HandlerList handlers = new HandlerList(); + private boolean cancelled; + private final Item target; + + public ItemMergeEvent(@NotNull Item item, @NotNull Item target) { + super(item); + this.target = target; + } + + @Override + public boolean isCancelled() { + return cancelled; + } + + @Override + public void setCancelled(boolean cancelled) { + this.cancelled = cancelled; + } + + @NotNull + @Override + public Item getEntity() { + return (Item) entity; + } + + /** + * Gets the Item entity the main Item is being merged into. + * + * @return The Item being merged with + */ + @NotNull + public Item getTarget() { + return target; + } + + @NotNull + @Override + public HandlerList getHandlers() { + return handlers; + } + + @NotNull + public static HandlerList getHandlerList() { + return handlers; + } +} diff --git a/api/src/main/java/org/bukkit/event/entity/ItemSpawnEvent.java b/api/src/main/java/org/bukkit/event/entity/ItemSpawnEvent.java new file mode 100644 index 000000000..657589da8 --- /dev/null +++ b/api/src/main/java/org/bukkit/event/entity/ItemSpawnEvent.java @@ -0,0 +1,27 @@ +package org.bukkit.event.entity; + +import org.bukkit.Location; +import org.bukkit.UndefinedNullability; +import org.bukkit.entity.Item; +import org.jetbrains.annotations.NotNull; + +/** + * Called when an item is spawned into a world + */ +public class ItemSpawnEvent extends EntitySpawnEvent { + + @Deprecated + public ItemSpawnEvent(@NotNull final Item spawnee, final Location loc) { + this(spawnee); + } + + public ItemSpawnEvent(@NotNull final Item spawnee) { + super(spawnee); + } + + @NotNull + @Override + public Item getEntity() { + return (Item) entity; + } +} diff --git a/api/src/main/java/org/bukkit/event/entity/LingeringPotionSplashEvent.java b/api/src/main/java/org/bukkit/event/entity/LingeringPotionSplashEvent.java new file mode 100644 index 000000000..31606399a --- /dev/null +++ b/api/src/main/java/org/bukkit/event/entity/LingeringPotionSplashEvent.java @@ -0,0 +1,57 @@ +package org.bukkit.event.entity; + +import org.bukkit.entity.AreaEffectCloud; +import org.bukkit.entity.LingeringPotion; +import org.bukkit.entity.ThrownPotion; +import org.bukkit.event.Cancellable; +import org.bukkit.event.HandlerList; +import org.jetbrains.annotations.NotNull; + +/** + * Called when a splash potion hits an area + */ +public class LingeringPotionSplashEvent extends ProjectileHitEvent implements Cancellable { + private static final HandlerList handlers = new HandlerList(); + private boolean cancelled; + private final AreaEffectCloud entity; + + public LingeringPotionSplashEvent(@NotNull final ThrownPotion potion, @NotNull final AreaEffectCloud entity) { + super(potion); + this.entity = entity; + } + + @NotNull + @Override + public LingeringPotion getEntity() { + return (LingeringPotion) super.getEntity(); + } + + /** + * Gets the AreaEffectCloud spawned + * + * @return The spawned AreaEffectCloud + */ + @NotNull + public AreaEffectCloud getAreaEffectCloud() { + return entity; + } + + public boolean isCancelled() { + return cancelled; + } + + public void setCancelled(boolean cancel) { + cancelled = cancel; + } + + @NotNull + @Override + public HandlerList getHandlers() { + return handlers; + } + + @NotNull + public static HandlerList getHandlerList() { + return handlers; + } +} diff --git a/api/src/main/java/org/bukkit/event/entity/PigZapEvent.java b/api/src/main/java/org/bukkit/event/entity/PigZapEvent.java new file mode 100644 index 000000000..0eaa8df94 --- /dev/null +++ b/api/src/main/java/org/bukkit/event/entity/PigZapEvent.java @@ -0,0 +1,79 @@ +package org.bukkit.event.entity; + +import java.util.Collections; +import org.bukkit.entity.Entity; +import com.destroystokyo.paper.event.entity.EntityZapEvent; +import org.bukkit.entity.LightningStrike; +import org.bukkit.entity.Pig; +import org.bukkit.entity.PigZombie; +import org.bukkit.event.Cancellable; +import org.bukkit.event.HandlerList; +import org.jetbrains.annotations.NotNull; + +/** + * Stores data for pigs being zapped + */ +public class PigZapEvent extends EntityZapEvent implements Cancellable { + //private static final HandlerList handlers = new HandlerList(); + private boolean canceled; + private final PigZombie pigzombie; + private final LightningStrike bolt; + + public PigZapEvent(@NotNull final Pig pig, @NotNull final LightningStrike bolt, @NotNull final PigZombie pigzombie) { + super(pig, bolt, pigzombie); + this.bolt = bolt; + this.pigzombie = pigzombie; + } + + public boolean isCancelled() { + return canceled; + } + + public void setCancelled(boolean cancel) { + canceled = cancel; + } + + @NotNull + @Override + public Pig getEntity() { + return (Pig) entity; + } + + /** + * Gets the bolt which is striking the pig. + * + * @return lightning entity + */ + @NotNull + public LightningStrike getLightning() { + return bolt; + } + + /** + * Gets the zombie pig that will replace the pig, provided the event is + * not cancelled first. + * + * @return resulting entity + * @deprecated use {@link EntityTransformEvent#getTransformedEntity()} + */ + @NotNull + @Deprecated + public PigZombie getPigZombie() { + return pigzombie; + } + + // Paper start + /* + @NotNull + @Override + public HandlerList getHandlers() { + return handlers; + } + + @NotNull + public static HandlerList getHandlerList() { + return handlers; + } + */ + // Paper end +} diff --git a/api/src/main/java/org/bukkit/event/entity/PigZombieAngerEvent.java b/api/src/main/java/org/bukkit/event/entity/PigZombieAngerEvent.java new file mode 100644 index 000000000..bb4f13bbb --- /dev/null +++ b/api/src/main/java/org/bukkit/event/entity/PigZombieAngerEvent.java @@ -0,0 +1,84 @@ +package org.bukkit.event.entity; + +import org.bukkit.entity.Entity; +import org.bukkit.entity.PigZombie; +import org.bukkit.event.Cancellable; +import org.bukkit.event.HandlerList; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +/** + * Called when a Pig Zombie is angered by another entity. + *

+ * If the event is cancelled, the pig zombie will not be angered. + */ +public class PigZombieAngerEvent extends EntityEvent implements Cancellable { + + private static final HandlerList handlers = new HandlerList(); + private boolean canceled; + private final Entity target; + private int newAnger; + + public PigZombieAngerEvent(@NotNull final PigZombie pigZombie, @Nullable final Entity target, final int newAnger) { + super(pigZombie); + this.target = target; + this.newAnger = newAnger; + } + + /** + * Gets the entity (if any) which triggered this anger update. + * + * @return triggering entity, or null + */ + @Nullable + public Entity getTarget() { + return target; + } + + /** + * Gets the new anger resulting from this event. + * + * @return new anger + * @see PigZombie#getAnger() + */ + public int getNewAnger() { + return newAnger; + } + + /** + * Sets the new anger resulting from this event. + * + * @param newAnger the new anger + * @see PigZombie#setAnger(int) + */ + public void setNewAnger(int newAnger) { + this.newAnger = newAnger; + } + + @NotNull + @Override + public PigZombie getEntity() { + return (PigZombie) entity; + } + + @Override + public boolean isCancelled() { + return canceled; + } + + @Override + public void setCancelled(boolean cancel) { + canceled = cancel; + } + + @NotNull + @Override + public HandlerList getHandlers() { + return handlers; + } + + @NotNull + public static HandlerList getHandlerList() { + return handlers; + } +} diff --git a/api/src/main/java/org/bukkit/event/entity/PlayerDeathEvent.java b/api/src/main/java/org/bukkit/event/entity/PlayerDeathEvent.java new file mode 100644 index 000000000..16f015500 --- /dev/null +++ b/api/src/main/java/org/bukkit/event/entity/PlayerDeathEvent.java @@ -0,0 +1,199 @@ +package org.bukkit.event.entity; + +import java.util.List; + +import org.bukkit.entity.Player; +import org.bukkit.inventory.ItemStack; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +/** + * Thrown whenever a {@link Player} dies + */ +public class PlayerDeathEvent extends EntityDeathEvent { + private int newExp = 0; + private String deathMessage = ""; + private int newLevel = 0; + private int newTotalExp = 0; + private boolean keepLevel = false; + private boolean keepInventory = false; + + // Paper start + private List itemsToKeep = new java.util.ArrayList<>(); + + /** + * A mutable collection to add items that the player should retain in their inventory on death (Similar to KeepInventory game rule) + * + * You MUST remove the item from the .getDrops() collection too or it will duplicate! + *

{@code
+     *    {@literal @EventHandler(ignoreCancelled = true)}
+     *     public void onPlayerDeath(PlayerDeathEvent event) {
+     *         for (Iterator iterator = event.getDrops().iterator(); iterator.hasNext(); ) {
+     *             ItemStack drop = iterator.next();
+     *             List lore = drop.getLore();
+     *             if (lore != null && !lore.isEmpty()) {
+     *                 if (lore.get(0).contains("(SOULBOUND)")) {
+     *                     iterator.remove();
+     *                     event.getItemsToKeep().add(drop);
+     *                 }
+     *             }
+     *         }
+     *     }
+     * }
+ * + * Adding an item to this list that the player did not previously have will give them the item on death. + * An example case could be a "Note" that "You died at X/Y/Z coordinates" + * + * @return The list to hold items to keep + */ + @NotNull + public List getItemsToKeep() { + return itemsToKeep; + } + // Paper end + + public PlayerDeathEvent(@NotNull final Player player, @NotNull final List drops, final int droppedExp, @Nullable final String deathMessage) { + this(player, drops, droppedExp, 0, deathMessage); + } + + public PlayerDeathEvent(@NotNull final Player player, @NotNull final List drops, final int droppedExp, final int newExp, @Nullable final String deathMessage) { + this(player, drops, droppedExp, newExp, 0, 0, deathMessage); + } + + public PlayerDeathEvent(@NotNull final Player player, @NotNull final List drops, final int droppedExp, final int newExp, final int newTotalExp, final int newLevel, @Nullable final String deathMessage) { + super(player, drops, droppedExp); + this.newExp = newExp; + this.newTotalExp = newTotalExp; + this.newLevel = newLevel; + this.deathMessage = deathMessage; + } + + @NotNull + @Override + public Player getEntity() { + return (Player) entity; + } + + /** + * Set the death message that will appear to everyone on the server. + * + * @param deathMessage Message to appear to other players on the server. + */ + public void setDeathMessage(@Nullable String deathMessage) { + this.deathMessage = deathMessage; + } + + /** + * Get the death message that will appear to everyone on the server. + * + * @return Message to appear to other players on the server. + */ + @Nullable + public String getDeathMessage() { + return deathMessage; + } + + /** + * Gets how much EXP the Player should have at respawn. + *

+ * This does not indicate how much EXP should be dropped, please see + * {@link #getDroppedExp()} for that. + * + * @return New EXP of the respawned player + */ + public int getNewExp() { + return newExp; + } + + /** + * Sets how much EXP the Player should have at respawn. + *

+ * This does not indicate how much EXP should be dropped, please see + * {@link #setDroppedExp(int)} for that. + * + * @param exp New EXP of the respawned player + */ + public void setNewExp(int exp) { + newExp = exp; + } + + /** + * Gets the Level the Player should have at respawn. + * + * @return New Level of the respawned player + */ + public int getNewLevel() { + return newLevel; + } + + /** + * Sets the Level the Player should have at respawn. + * + * @param level New Level of the respawned player + */ + public void setNewLevel(int level) { + newLevel = level; + } + + /** + * Gets the Total EXP the Player should have at respawn. + * + * @return New Total EXP of the respawned player + */ + public int getNewTotalExp() { + return newTotalExp; + } + + /** + * Sets the Total EXP the Player should have at respawn. + * + * @param totalExp New Total EXP of the respawned player + */ + public void setNewTotalExp(int totalExp) { + newTotalExp = totalExp; + } + + /** + * Gets if the Player should keep all EXP at respawn. + *

+ * This flag overrides other EXP settings + * + * @return True if Player should keep all pre-death exp + */ + public boolean getKeepLevel() { + return keepLevel; + } + + /** + * Sets if the Player should keep all EXP at respawn. + *

+ * This overrides all other EXP settings + *

+ * This doesn't prevent prevent the EXP from dropping. + * {@link #setDroppedExp(int)} should be used stop the + * EXP from dropping. + * + * @param keepLevel True to keep all current value levels + */ + public void setKeepLevel(boolean keepLevel) { + this.keepLevel = keepLevel; + } + + /** + * Sets if the Player keeps inventory on death. + * + * @param keepInventory True to keep the inventory + */ + public void setKeepInventory(boolean keepInventory) { + this.keepInventory = keepInventory; + } + + /** + * Gets if the Player keeps inventory on death. + * + * @return True if the player keeps inventory on death + */ + public boolean getKeepInventory() { + return keepInventory; + } +} diff --git a/api/src/main/java/org/bukkit/event/entity/PlayerLeashEntityEvent.java b/api/src/main/java/org/bukkit/event/entity/PlayerLeashEntityEvent.java new file mode 100644 index 000000000..e087e1418 --- /dev/null +++ b/api/src/main/java/org/bukkit/event/entity/PlayerLeashEntityEvent.java @@ -0,0 +1,74 @@ +package org.bukkit.event.entity; + +import org.bukkit.entity.Entity; +import org.bukkit.entity.Player; +import org.bukkit.event.Cancellable; +import org.bukkit.event.Event; +import org.bukkit.event.HandlerList; +import org.jetbrains.annotations.NotNull; + +/** + * Called immediately prior to a creature being leashed by a player. + */ +public class PlayerLeashEntityEvent extends Event implements Cancellable { + private static final HandlerList handlers = new HandlerList(); + private final Entity leashHolder; + private final Entity entity; + private boolean cancelled = false; + private final Player player; + + public PlayerLeashEntityEvent(@NotNull Entity what, @NotNull Entity leashHolder, @NotNull Player leasher) { + this.leashHolder = leashHolder; + this.entity = what; + this.player = leasher; + } + + /** + * Returns the entity that is holding the leash. + * + * @return The leash holder + */ + @NotNull + public Entity getLeashHolder() { + return leashHolder; + } + + /** + * Returns the entity being leashed. + * + * @return The entity + */ + @NotNull + public Entity getEntity() { + return entity; + } + + /** + * Returns the player involved in this event + * + * @return Player who is involved in this event + */ + @NotNull + public final Player getPlayer() { + return player; + } + + @NotNull + @Override + public HandlerList getHandlers() { + return handlers; + } + + @NotNull + public static HandlerList getHandlerList() { + return handlers; + } + + public boolean isCancelled() { + return this.cancelled; + } + + public void setCancelled(boolean cancel) { + this.cancelled = cancel; + } +} diff --git a/api/src/main/java/org/bukkit/event/entity/PotionSplashEvent.java b/api/src/main/java/org/bukkit/event/entity/PotionSplashEvent.java new file mode 100644 index 000000000..45d224ec9 --- /dev/null +++ b/api/src/main/java/org/bukkit/event/entity/PotionSplashEvent.java @@ -0,0 +1,100 @@ +package org.bukkit.event.entity; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.Map; + +import org.apache.commons.lang.Validate; +import org.bukkit.entity.LivingEntity; +import org.bukkit.entity.ThrownPotion; +import org.bukkit.event.Cancellable; +import org.bukkit.event.HandlerList; +import org.jetbrains.annotations.NotNull; + +/** + * Called when a splash potion hits an area + */ +public class PotionSplashEvent extends ProjectileHitEvent implements Cancellable { + private static final HandlerList handlers = new HandlerList(); + private boolean cancelled; + private final Map affectedEntities; + + public PotionSplashEvent(@NotNull final ThrownPotion potion, @NotNull final Map affectedEntities) { + super(potion); + + this.affectedEntities = affectedEntities; + } + + @NotNull + @Override + public ThrownPotion getEntity() { + return (ThrownPotion) entity; + } + + /** + * Gets the potion which caused this event + * + * @return The thrown potion entity + */ + @NotNull + public ThrownPotion getPotion() { + return (ThrownPotion) getEntity(); + } + + /** + * Retrieves a list of all effected entities + * + * @return A fresh copy of the affected entity list + */ + @NotNull + public Collection getAffectedEntities() { + return new ArrayList(affectedEntities.keySet()); + } + + /** + * Gets the intensity of the potion's effects for given entity; This + * depends on the distance to the impact center + * + * @param entity Which entity to get intensity for + * @return intensity relative to maximum effect; 0.0: not affected; 1.0: + * fully hit by potion effects + */ + public double getIntensity(@NotNull LivingEntity entity) { + Double intensity = affectedEntities.get(entity); + return intensity != null ? intensity : 0.0; + } + + /** + * Overwrites the intensity for a given entity + * + * @param entity For which entity to define a new intensity + * @param intensity relative to maximum effect + */ + public void setIntensity(@NotNull LivingEntity entity, double intensity) { + Validate.notNull(entity, "You must specify a valid entity."); + if (intensity <= 0.0) { + affectedEntities.remove(entity); + } else { + affectedEntities.put(entity, Math.min(intensity, 1.0)); + } + } + + public boolean isCancelled() { + return cancelled; + } + + public void setCancelled(boolean cancel) { + cancelled = cancel; + } + + @NotNull + @Override + public HandlerList getHandlers() { + return handlers; + } + + @NotNull + public static HandlerList getHandlerList() { + return handlers; + } +} diff --git a/api/src/main/java/org/bukkit/event/entity/ProjectileHitEvent.java b/api/src/main/java/org/bukkit/event/entity/ProjectileHitEvent.java new file mode 100644 index 000000000..809c1098c --- /dev/null +++ b/api/src/main/java/org/bukkit/event/entity/ProjectileHitEvent.java @@ -0,0 +1,91 @@ +package org.bukkit.event.entity; + +import org.bukkit.block.Block; +import org.bukkit.block.BlockFace; +import org.bukkit.entity.Entity; +import org.bukkit.entity.Projectile; +import org.bukkit.event.HandlerList; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +/** + * Called when a projectile hits an object + */ +public class ProjectileHitEvent extends EntityEvent { + private static final HandlerList handlers = new HandlerList(); + private final Entity hitEntity; + private final Block hitBlock; + private final BlockFace hitFace; + + public ProjectileHitEvent(@NotNull final Projectile projectile) { + this(projectile, null, null); + } + + public ProjectileHitEvent(@NotNull final Projectile projectile, @Nullable Entity hitEntity) { + this(projectile, hitEntity, null); + } + + public ProjectileHitEvent(@NotNull final Projectile projectile, @Nullable Block hitBlock) { + this(projectile, null, hitBlock); + } + + public ProjectileHitEvent(@NotNull final Projectile projectile, @Nullable Entity hitEntity, @Nullable Block hitBlock) { + this(projectile, hitEntity, hitBlock, null); + } + + public ProjectileHitEvent(@NotNull final Projectile projectile, @Nullable Entity hitEntity, @Nullable Block hitBlock, @Nullable BlockFace hitFace) { + super(projectile); + this.hitEntity = hitEntity; + this.hitBlock = hitBlock; + this.hitFace = hitFace; + } + + @NotNull + @Override + public Projectile getEntity() { + return (Projectile) entity; + } + + /** + * Gets the block that was hit, if it was a block that was hit. + * + * @return hit block or else null + */ + @Nullable + public Block getHitBlock() { + return hitBlock; + } + + /** + * Gets the block face that was hit, if it was a block that was hit and the + * face was provided in the vent. + * + * @return hit face or else null + */ + @Nullable + public BlockFace getHitBlockFace() { + return hitFace; + } + + /** + * Gets the entity that was hit, if it was an entity that was hit. + * + * @return hit entity or else null + */ + @Nullable + public Entity getHitEntity() { + return hitEntity; + } + + @NotNull + @Override + public HandlerList getHandlers() { + return handlers; + } + + @NotNull + public static HandlerList getHandlerList() { + return handlers; + } + +} diff --git a/api/src/main/java/org/bukkit/event/entity/ProjectileLaunchEvent.java b/api/src/main/java/org/bukkit/event/entity/ProjectileLaunchEvent.java new file mode 100644 index 000000000..f7a4bc4bd --- /dev/null +++ b/api/src/main/java/org/bukkit/event/entity/ProjectileLaunchEvent.java @@ -0,0 +1,31 @@ +package org.bukkit.event.entity; + +import org.bukkit.entity.Entity; +import org.bukkit.entity.Projectile; +import org.bukkit.event.Cancellable; +import org.jetbrains.annotations.NotNull; + +/** + * Called when a projectile is launched. + */ +public class ProjectileLaunchEvent extends EntitySpawnEvent implements Cancellable { + private boolean cancelled; + + public ProjectileLaunchEvent(@NotNull Entity what) { + super(what); + } + + public boolean isCancelled() { + return cancelled; + } + + public void setCancelled(boolean cancel) { + cancelled = cancel; + } + + @NotNull + @Override + public Projectile getEntity() { + return (Projectile) entity; + } +} diff --git a/api/src/main/java/org/bukkit/event/entity/SheepDyeWoolEvent.java b/api/src/main/java/org/bukkit/event/entity/SheepDyeWoolEvent.java new file mode 100644 index 000000000..60c648ee6 --- /dev/null +++ b/api/src/main/java/org/bukkit/event/entity/SheepDyeWoolEvent.java @@ -0,0 +1,67 @@ +package org.bukkit.event.entity; + +import org.bukkit.DyeColor; +import org.bukkit.entity.Sheep; +import org.bukkit.event.Cancellable; +import org.bukkit.event.HandlerList; +import org.jetbrains.annotations.NotNull; + +/** + * Called when a sheep's wool is dyed + */ +public class SheepDyeWoolEvent extends EntityEvent implements Cancellable { + private static final HandlerList handlers = new HandlerList(); + private boolean cancel; + private DyeColor color; + + public SheepDyeWoolEvent(@NotNull final Sheep sheep, @NotNull final DyeColor color) { + super(sheep); + this.cancel = false; + this.color = color; + } + + public boolean isCancelled() { + return cancel; + } + + public void setCancelled(boolean cancel) { + this.cancel = cancel; + } + + @NotNull + @Override + public Sheep getEntity() { + return (Sheep) entity; + } + + /** + * Gets the DyeColor the sheep is being dyed + * + * @return the DyeColor the sheep is being dyed + */ + @NotNull + public DyeColor getColor() { + return color; + } + + /** + * Sets the DyeColor the sheep is being dyed + * + * @param color the DyeColor the sheep will be dyed + */ + public void setColor(@NotNull DyeColor color) { + this.color = color; + } + + @NotNull + @Override + public HandlerList getHandlers() { + return handlers; + } + + @NotNull + public static HandlerList getHandlerList() { + return handlers; + } + +} diff --git a/api/src/main/java/org/bukkit/event/entity/SheepRegrowWoolEvent.java b/api/src/main/java/org/bukkit/event/entity/SheepRegrowWoolEvent.java new file mode 100644 index 000000000..428ca4c9c --- /dev/null +++ b/api/src/main/java/org/bukkit/event/entity/SheepRegrowWoolEvent.java @@ -0,0 +1,45 @@ +package org.bukkit.event.entity; + +import org.bukkit.entity.Sheep; +import org.bukkit.event.Cancellable; +import org.bukkit.event.HandlerList; +import org.jetbrains.annotations.NotNull; + +/** + * Called when a sheep regrows its wool + */ +public class SheepRegrowWoolEvent extends EntityEvent implements Cancellable { + private static final HandlerList handlers = new HandlerList(); + private boolean cancel; + + public SheepRegrowWoolEvent(@NotNull final Sheep sheep) { + super(sheep); + this.cancel = false; + } + + public boolean isCancelled() { + return cancel; + } + + public void setCancelled(boolean cancel) { + this.cancel = cancel; + } + + @NotNull + @Override + public Sheep getEntity() { + return (Sheep) entity; + } + + @NotNull + @Override + public HandlerList getHandlers() { + return handlers; + } + + @NotNull + public static HandlerList getHandlerList() { + return handlers; + } + +} diff --git a/api/src/main/java/org/bukkit/event/entity/SlimeSplitEvent.java b/api/src/main/java/org/bukkit/event/entity/SlimeSplitEvent.java new file mode 100644 index 000000000..6e634dd7f --- /dev/null +++ b/api/src/main/java/org/bukkit/event/entity/SlimeSplitEvent.java @@ -0,0 +1,63 @@ +package org.bukkit.event.entity; + +import org.bukkit.entity.Slime; +import org.bukkit.event.Cancellable; +import org.bukkit.event.HandlerList; +import org.jetbrains.annotations.NotNull; + +/** + * Called when a Slime splits into smaller Slimes upon death + */ +public class SlimeSplitEvent extends EntityEvent implements Cancellable { + private static final HandlerList handlers = new HandlerList(); + private boolean cancel = false; + private int count; + + public SlimeSplitEvent(@NotNull final Slime slime, final int count) { + super(slime); + this.count = count; + } + + public boolean isCancelled() { + return cancel; + } + + public void setCancelled(boolean cancel) { + this.cancel = cancel; + } + + @NotNull + @Override + public Slime getEntity() { + return (Slime) entity; + } + + /** + * Gets the amount of smaller slimes to spawn + * + * @return the amount of slimes to spawn + */ + public int getCount() { + return count; + } + + /** + * Sets how many smaller slimes will spawn on the split + * + * @param count the amount of slimes to spawn + */ + public void setCount(int count) { + this.count = count; + } + + @NotNull + @Override + public HandlerList getHandlers() { + return handlers; + } + + @NotNull + public static HandlerList getHandlerList() { + return handlers; + } +} \ No newline at end of file diff --git a/api/src/main/java/org/bukkit/event/entity/SpawnerSpawnEvent.java b/api/src/main/java/org/bukkit/event/entity/SpawnerSpawnEvent.java new file mode 100644 index 000000000..9353f0d09 --- /dev/null +++ b/api/src/main/java/org/bukkit/event/entity/SpawnerSpawnEvent.java @@ -0,0 +1,24 @@ +package org.bukkit.event.entity; + +import org.bukkit.block.CreatureSpawner; +import org.bukkit.entity.Entity; +import org.jetbrains.annotations.NotNull; + +/** + * Called when an entity is spawned into a world by a spawner. + *

+ * If a Spawner Spawn event is cancelled, the entity will not spawn. + */ +public class SpawnerSpawnEvent extends EntitySpawnEvent { + private final CreatureSpawner spawner; + + public SpawnerSpawnEvent(@NotNull final Entity spawnee, @NotNull final CreatureSpawner spawner) { + super(spawnee); + this.spawner = spawner; + } + + @NotNull + public CreatureSpawner getSpawner() { + return spawner; + } +} diff --git a/api/src/main/java/org/bukkit/event/entity/VillagerAcquireTradeEvent.java b/api/src/main/java/org/bukkit/event/entity/VillagerAcquireTradeEvent.java new file mode 100644 index 000000000..95740eb7f --- /dev/null +++ b/api/src/main/java/org/bukkit/event/entity/VillagerAcquireTradeEvent.java @@ -0,0 +1,69 @@ +package org.bukkit.event.entity; + +import org.bukkit.entity.Villager; +import org.bukkit.event.Cancellable; +import org.bukkit.event.HandlerList; +import org.bukkit.inventory.MerchantRecipe; +import org.jetbrains.annotations.NotNull; + +/** + * Called whenever a villager acquires a new trade. + */ +public class VillagerAcquireTradeEvent extends EntityEvent implements Cancellable { + + private static final HandlerList handlers = new HandlerList(); + private boolean cancelled; + // + private MerchantRecipe recipe; + + public VillagerAcquireTradeEvent(@NotNull Villager what, @NotNull MerchantRecipe recipe) { + super(what); + this.recipe = recipe; + } + + /** + * Get the recipe to be acquired. + * + * @return the new recipe + */ + @NotNull + public MerchantRecipe getRecipe() { + return recipe; + } + + /** + * Set the recipe to be acquired. + * + * @param recipe the new recipe + */ + public void setRecipe(@NotNull MerchantRecipe recipe) { + this.recipe = recipe; + } + + @Override + public boolean isCancelled() { + return cancelled; + } + + @Override + public void setCancelled(boolean cancel) { + this.cancelled = cancel; + } + + @NotNull + @Override + public Villager getEntity() { + return (Villager) super.getEntity(); + } + + @NotNull + @Override + public HandlerList getHandlers() { + return handlers; + } + + @NotNull + public static HandlerList getHandlerList() { + return handlers; + } +} diff --git a/api/src/main/java/org/bukkit/event/entity/VillagerReplenishTradeEvent.java b/api/src/main/java/org/bukkit/event/entity/VillagerReplenishTradeEvent.java new file mode 100644 index 000000000..738469dd3 --- /dev/null +++ b/api/src/main/java/org/bukkit/event/entity/VillagerReplenishTradeEvent.java @@ -0,0 +1,94 @@ +package org.bukkit.event.entity; + +import org.bukkit.entity.Villager; +import org.bukkit.event.Cancellable; +import org.bukkit.event.HandlerList; +import org.bukkit.inventory.MerchantRecipe; +import org.jetbrains.annotations.NotNull; + +/** + * Called when a villager's trade's maximum uses is increased, due to a player's + * trade. + * + * @see MerchantRecipe#getMaxUses() + */ +public class VillagerReplenishTradeEvent extends EntityEvent implements Cancellable { + + private static final HandlerList handlers = new HandlerList(); + private boolean cancelled; + // + private MerchantRecipe recipe; + private int bonus; + + public VillagerReplenishTradeEvent(@NotNull Villager what, @NotNull MerchantRecipe recipe, int bonus) { + super(what); + this.recipe = recipe; + this.bonus = bonus; + } + + /** + * Get the recipe to replenish. + * + * @return the replenished recipe + */ + @NotNull + public MerchantRecipe getRecipe() { + return recipe; + } + + /** + * Set the recipe to replenish. + * + * @param recipe the replenished recipe + */ + public void setRecipe(@NotNull MerchantRecipe recipe) { + this.recipe = recipe; + } + + /** + * Get the bonus uses added. The maximum uses of the recipe will be + * increased by this number. + * + * @return the extra uses added + */ + public int getBonus() { + return bonus; + } + + /** + * Set the bonus uses added. + * + * @see VillagerReplenishTradeEvent#getBonus() + * @param bonus the extra uses added + */ + public void setBonus(int bonus) { + this.bonus = bonus; + } + + @Override + public boolean isCancelled() { + return cancelled; + } + + @Override + public void setCancelled(boolean cancel) { + this.cancelled = cancel; + } + + @NotNull + @Override + public Villager getEntity() { + return (Villager) super.getEntity(); + } + + @NotNull + @Override + public HandlerList getHandlers() { + return handlers; + } + + @NotNull + public static HandlerList getHandlerList() { + return handlers; + } +} diff --git a/api/src/main/java/org/bukkit/event/hanging/HangingBreakByEntityEvent.java b/api/src/main/java/org/bukkit/event/hanging/HangingBreakByEntityEvent.java new file mode 100644 index 000000000..68517811f --- /dev/null +++ b/api/src/main/java/org/bukkit/event/hanging/HangingBreakByEntityEvent.java @@ -0,0 +1,33 @@ +package org.bukkit.event.hanging; + +import org.bukkit.entity.Entity; +import org.bukkit.entity.Hanging; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +/** + * Triggered when a hanging entity is removed by an entity + */ +public class HangingBreakByEntityEvent extends HangingBreakEvent { + private final Entity remover; + + public HangingBreakByEntityEvent(@NotNull final Hanging hanging, @Nullable final Entity remover) { + this(hanging, remover, HangingBreakEvent.RemoveCause.ENTITY); + } + + public HangingBreakByEntityEvent(@NotNull final Hanging hanging, @Nullable final Entity remover, @NotNull final HangingBreakEvent.RemoveCause cause) { + super(hanging, cause); + this.remover = remover; + } + + /** + * Gets the entity that removed the hanging entity. + * May be null, for example when broken by an explosion. + * + * @return the entity that removed the hanging entity + */ + @Nullable + public Entity getRemover() { + return remover; + } +} diff --git a/api/src/main/java/org/bukkit/event/hanging/HangingBreakEvent.java b/api/src/main/java/org/bukkit/event/hanging/HangingBreakEvent.java new file mode 100644 index 000000000..8c43c890e --- /dev/null +++ b/api/src/main/java/org/bukkit/event/hanging/HangingBreakEvent.java @@ -0,0 +1,75 @@ +package org.bukkit.event.hanging; + +import org.bukkit.entity.Hanging; +import org.bukkit.event.Cancellable; +import org.bukkit.event.HandlerList; +import org.jetbrains.annotations.NotNull; + +/** + * Triggered when a hanging entity is removed + */ +public class HangingBreakEvent extends HangingEvent implements Cancellable { + private static final HandlerList handlers = new HandlerList(); + private boolean cancelled; + private final HangingBreakEvent.RemoveCause cause; + + public HangingBreakEvent(@NotNull final Hanging hanging, @NotNull final HangingBreakEvent.RemoveCause cause) { + super(hanging); + this.cause = cause; + } + + /** + * Gets the cause for the hanging entity's removal + * + * @return the RemoveCause for the hanging entity's removal + */ + @NotNull + public HangingBreakEvent.RemoveCause getCause() { + return cause; + } + + public boolean isCancelled() { + return cancelled; + } + + public void setCancelled(boolean cancel) { + this.cancelled = cancel; + } + + /** + * An enum to specify the cause of the removal + */ + public enum RemoveCause { + /** + * Removed by an entity + */ + ENTITY, + /** + * Removed by an explosion + */ + EXPLOSION, + /** + * Removed by placing a block on it + */ + OBSTRUCTION, + /** + * Removed by destroying the block behind it, etc + */ + PHYSICS, + /** + * Removed by an uncategorised cause + */ + DEFAULT, + } + + @NotNull + @Override + public HandlerList getHandlers() { + return handlers; + } + + @NotNull + public static HandlerList getHandlerList() { + return handlers; + } +} diff --git a/api/src/main/java/org/bukkit/event/hanging/HangingEvent.java b/api/src/main/java/org/bukkit/event/hanging/HangingEvent.java new file mode 100644 index 000000000..f01a1a0f0 --- /dev/null +++ b/api/src/main/java/org/bukkit/event/hanging/HangingEvent.java @@ -0,0 +1,26 @@ +package org.bukkit.event.hanging; + +import org.bukkit.entity.Hanging; +import org.bukkit.event.Event; +import org.jetbrains.annotations.NotNull; + +/** + * Represents a hanging entity-related event. + */ +public abstract class HangingEvent extends Event { + protected Hanging hanging; + + protected HangingEvent(@NotNull final Hanging painting) { + this.hanging = painting; + } + + /** + * Gets the hanging entity involved in this event. + * + * @return the hanging entity + */ + @NotNull + public Hanging getEntity() { + return hanging; + } +} diff --git a/api/src/main/java/org/bukkit/event/hanging/HangingPlaceEvent.java b/api/src/main/java/org/bukkit/event/hanging/HangingPlaceEvent.java new file mode 100644 index 000000000..8ce33024f --- /dev/null +++ b/api/src/main/java/org/bukkit/event/hanging/HangingPlaceEvent.java @@ -0,0 +1,77 @@ +package org.bukkit.event.hanging; + +import org.bukkit.block.Block; +import org.bukkit.block.BlockFace; +import org.bukkit.entity.Hanging; +import org.bukkit.entity.Player; +import org.bukkit.event.Cancellable; +import org.bukkit.event.HandlerList; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +/** + * Triggered when a hanging entity is created in the world + */ +public class HangingPlaceEvent extends HangingEvent implements Cancellable { + private static final HandlerList handlers = new HandlerList(); + private boolean cancelled; + private final Player player; + private final Block block; + private final BlockFace blockFace; + + public HangingPlaceEvent(@NotNull final Hanging hanging, @Nullable final Player player, @NotNull final Block block, @NotNull final BlockFace blockFace) { + super(hanging); + this.player = player; + this.block = block; + this.blockFace = blockFace; + } + + /** + * Returns the player placing the hanging entity + * + * @return the player placing the hanging entity + */ + @Nullable + public Player getPlayer() { + return player; + } + + /** + * Returns the block that the hanging entity was placed on + * + * @return the block that the hanging entity was placed on + */ + @NotNull + public Block getBlock() { + return block; + } + + /** + * Returns the face of the block that the hanging entity was placed on + * + * @return the face of the block that the hanging entity was placed on + */ + @NotNull + public BlockFace getBlockFace() { + return blockFace; + } + + public boolean isCancelled() { + return cancelled; + } + + public void setCancelled(boolean cancel) { + this.cancelled = cancel; + } + + @NotNull + @Override + public HandlerList getHandlers() { + return handlers; + } + + @NotNull + public static HandlerList getHandlerList() { + return handlers; + } +} diff --git a/api/src/main/java/org/bukkit/event/inventory/BrewEvent.java b/api/src/main/java/org/bukkit/event/inventory/BrewEvent.java new file mode 100644 index 000000000..140fc9208 --- /dev/null +++ b/api/src/main/java/org/bukkit/event/inventory/BrewEvent.java @@ -0,0 +1,63 @@ +package org.bukkit.event.inventory; + +import org.bukkit.block.Block; +import org.bukkit.event.Cancellable; +import org.bukkit.event.HandlerList; +import org.bukkit.event.block.BlockEvent; +import org.bukkit.inventory.BrewerInventory; +import org.jetbrains.annotations.NotNull; + +/** + * Called when the brewing of the contents inside the Brewing Stand is + * complete. + */ +public class BrewEvent extends BlockEvent implements Cancellable { + private static final HandlerList handlers = new HandlerList(); + private BrewerInventory contents; + private int fuelLevel; + private boolean cancelled; + + public BrewEvent(@NotNull Block brewer, @NotNull BrewerInventory contents, int fuelLevel) { + super(brewer); + this.contents = contents; + this.fuelLevel = fuelLevel; + } + + /** + * Gets the contents of the Brewing Stand. + * + * @return the contents + */ + @NotNull + public BrewerInventory getContents() { + return contents; + } + + /** + * Gets the remaining fuel level. + * + * @return the remaining fuel + */ + public int getFuelLevel() { + return fuelLevel; + } + + public boolean isCancelled() { + return cancelled; + } + + public void setCancelled(boolean cancel) { + cancelled = cancel; + } + + @NotNull + @Override + public HandlerList getHandlers() { + return handlers; + } + + @NotNull + public static HandlerList getHandlerList() { + return handlers; + } +} diff --git a/api/src/main/java/org/bukkit/event/inventory/BrewingStandFuelEvent.java b/api/src/main/java/org/bukkit/event/inventory/BrewingStandFuelEvent.java new file mode 100644 index 000000000..633ec5187 --- /dev/null +++ b/api/src/main/java/org/bukkit/event/inventory/BrewingStandFuelEvent.java @@ -0,0 +1,96 @@ +package org.bukkit.event.inventory; + +import org.bukkit.block.Block; +import org.bukkit.event.Cancellable; +import org.bukkit.event.HandlerList; +import org.bukkit.event.block.BlockEvent; +import org.bukkit.inventory.ItemStack; +import org.jetbrains.annotations.NotNull; + +/** + * Called when an ItemStack is about to increase the fuel level of a brewing + * stand. + */ +public class BrewingStandFuelEvent extends BlockEvent implements Cancellable { + + private static final HandlerList handlers = new HandlerList(); + private final ItemStack fuel; + private int fuelPower; + private boolean cancelled; + private boolean consuming = true; + + public BrewingStandFuelEvent(@NotNull Block brewingStand, @NotNull ItemStack fuel, int fuelPower) { + super(brewingStand); + this.fuel = fuel; + this.fuelPower = fuelPower; + } + + /** + * Gets the ItemStack of the fuel before the amount was subtracted. + * + * @return the fuel ItemStack + */ + @NotNull + public ItemStack getFuel() { + return fuel; + } + + /** + * Gets the fuel power for this fuel. Each unit of power can fuel one + * brewing operation. + * + * @return the fuel power for this fuel + */ + public int getFuelPower() { + return fuelPower; + } + + /** + * Sets the fuel power for this fuel. Each unit of power can fuel one + * brewing operation. + * + * @param fuelPower the fuel power for this fuel + */ + public void setFuelPower(int fuelPower) { + this.fuelPower = fuelPower; + } + + /** + * Gets whether the brewing stand's fuel will be reduced / consumed or not. + * + * @return whether the fuel will be reduced or not + */ + public boolean isConsuming() { + return consuming; + } + + /** + * Sets whether the brewing stand's fuel will be reduced / consumed or not. + * + * @param consuming whether the fuel will be reduced or not + */ + public void setConsuming(boolean consuming) { + this.consuming = consuming; + } + + @Override + public boolean isCancelled() { + return cancelled; + } + + @Override + public void setCancelled(boolean cancel) { + this.cancelled = cancel; + } + + @NotNull + @Override + public HandlerList getHandlers() { + return handlers; + } + + @NotNull + public static HandlerList getHandlerList() { + return handlers; + } +} diff --git a/api/src/main/java/org/bukkit/event/inventory/ClickType.java b/api/src/main/java/org/bukkit/event/inventory/ClickType.java new file mode 100644 index 000000000..a7440aac9 --- /dev/null +++ b/api/src/main/java/org/bukkit/event/inventory/ClickType.java @@ -0,0 +1,115 @@ +package org.bukkit.event.inventory; + +/** + * What the client did to trigger this action (not the result). + */ +public enum ClickType { + + /** + * The left (or primary) mouse button. + */ + LEFT, + /** + * Holding shift while pressing the left mouse button. + */ + SHIFT_LEFT, + /** + * The right mouse button. + */ + RIGHT, + /** + * Holding shift while pressing the right mouse button. + */ + SHIFT_RIGHT, + /** + * Clicking the left mouse button on the grey area around the inventory. + */ + WINDOW_BORDER_LEFT, + /** + * Clicking the right mouse button on the grey area around the inventory. + */ + WINDOW_BORDER_RIGHT, + /** + * The middle mouse button, or a "scrollwheel click". + */ + MIDDLE, + /** + * One of the number keys 1-9, correspond to slots on the hotbar. + */ + NUMBER_KEY, + /** + * Pressing the left mouse button twice in quick succession. + */ + DOUBLE_CLICK, + /** + * The "Drop" key (defaults to Q). + */ + DROP, + /** + * Holding Ctrl while pressing the "Drop" key (defaults to Q). + */ + CONTROL_DROP, + /** + * Any action done with the Creative inventory open. + */ + CREATIVE, + /** + * A type of inventory manipulation not yet recognized by Bukkit. + *

+ * This is only for transitional purposes on a new Minecraft update, and + * should never be relied upon. + *

+ * Any ClickType.UNKNOWN is called on a best-effort basis. + */ + UNKNOWN, + ; + + /** + * Gets whether this ClickType represents the pressing of a key on a + * keyboard. + * + * @return true if this ClickType represents the pressing of a key + */ + public boolean isKeyboardClick() { + return (this == ClickType.NUMBER_KEY) || (this == ClickType.DROP) || (this == ClickType.CONTROL_DROP); + } + + /** + * Gets whether this ClickType represents an action that can only be + * performed by a Player in creative mode. + * + * @return true if this action requires Creative mode + */ + public boolean isCreativeAction() { + // Why use middle click? + return (this == ClickType.MIDDLE) || (this == ClickType.CREATIVE); + } + + /** + * Gets whether this ClickType represents a right click. + * + * @return true if this ClickType represents a right click + */ + public boolean isRightClick() { + return (this == ClickType.RIGHT) || (this == ClickType.SHIFT_RIGHT); + } + + /** + * Gets whether this ClickType represents a left click. + * + * @return true if this ClickType represents a left click + */ + public boolean isLeftClick() { + return (this == ClickType.LEFT) || (this == ClickType.SHIFT_LEFT) || (this == ClickType.DOUBLE_CLICK) || (this == ClickType.CREATIVE); + } + + /** + * Gets whether this ClickType indicates that the shift key was pressed + * down when the click was made. + * + * @return true if the action uses Shift. + */ + public boolean isShiftClick() { + return (this == ClickType.SHIFT_LEFT) || (this == ClickType.SHIFT_RIGHT) || (this == ClickType.CONTROL_DROP); + } +} diff --git a/api/src/main/java/org/bukkit/event/inventory/CraftItemEvent.java b/api/src/main/java/org/bukkit/event/inventory/CraftItemEvent.java new file mode 100644 index 000000000..cab13877f --- /dev/null +++ b/api/src/main/java/org/bukkit/event/inventory/CraftItemEvent.java @@ -0,0 +1,38 @@ +package org.bukkit.event.inventory; + +import org.bukkit.event.inventory.InventoryType.SlotType; +import org.bukkit.inventory.CraftingInventory; +import org.bukkit.inventory.InventoryView; +import org.bukkit.inventory.Recipe; +import org.jetbrains.annotations.NotNull; + +/** + * Called when the recipe of an Item is completed inside a crafting matrix. + */ +public class CraftItemEvent extends InventoryClickEvent { + private Recipe recipe; + + public CraftItemEvent(@NotNull Recipe recipe, @NotNull InventoryView what, @NotNull SlotType type, int slot, @NotNull ClickType click, @NotNull InventoryAction action) { + super(what, type, slot, click, action); + this.recipe = recipe; + } + + public CraftItemEvent(@NotNull Recipe recipe, @NotNull InventoryView what, @NotNull SlotType type, int slot, @NotNull ClickType click, @NotNull InventoryAction action, int key) { + super(what, type, slot, click, action, key); + this.recipe = recipe; + } + + /** + * @return A copy of the current recipe on the crafting matrix. + */ + @NotNull + public Recipe getRecipe() { + return recipe; + } + + @NotNull + @Override + public CraftingInventory getInventory() { + return (CraftingInventory) super.getInventory(); + } +} diff --git a/api/src/main/java/org/bukkit/event/inventory/DragType.java b/api/src/main/java/org/bukkit/event/inventory/DragType.java new file mode 100644 index 000000000..72c2bed95 --- /dev/null +++ b/api/src/main/java/org/bukkit/event/inventory/DragType.java @@ -0,0 +1,17 @@ +package org.bukkit.event.inventory; + +/** + * Represents the effect of a drag that will be applied to an Inventory in an + * InventoryDragEvent. + */ +public enum DragType { + /** + * One item from the cursor is placed in each selected slot. + */ + SINGLE, + /** + * The cursor is split evenly across all selected slots, not to exceed the + * Material's max stack size, with the remainder going to the cursor. + */ + EVEN, +} diff --git a/api/src/main/java/org/bukkit/event/inventory/FurnaceBurnEvent.java b/api/src/main/java/org/bukkit/event/inventory/FurnaceBurnEvent.java new file mode 100644 index 000000000..46a67e671 --- /dev/null +++ b/api/src/main/java/org/bukkit/event/inventory/FurnaceBurnEvent.java @@ -0,0 +1,92 @@ +package org.bukkit.event.inventory; + +import org.bukkit.block.Block; +import org.bukkit.event.Cancellable; +import org.bukkit.event.HandlerList; +import org.bukkit.event.block.BlockEvent; +import org.bukkit.inventory.ItemStack; +import org.jetbrains.annotations.NotNull; + +/** + * Called when an ItemStack is successfully burned as fuel in a furnace. + */ +public class FurnaceBurnEvent extends BlockEvent implements Cancellable { + private static final HandlerList handlers = new HandlerList(); + private final ItemStack fuel; + private int burnTime; + private boolean cancelled; + private boolean burning; + + public FurnaceBurnEvent(@NotNull final Block furnace, @NotNull final ItemStack fuel, final int burnTime) { + super(furnace); + this.fuel = fuel; + this.burnTime = burnTime; + this.cancelled = false; + this.burning = true; + } + + /** + * Gets the fuel ItemStack for this event + * + * @return the fuel ItemStack + */ + @NotNull + public ItemStack getFuel() { + return fuel; + } + + /** + * Gets the burn time for this fuel + * + * @return the burn time for this fuel + */ + public int getBurnTime() { + return burnTime; + } + + /** + * Sets the burn time for this fuel + * + * @param burnTime the burn time for this fuel + */ + public void setBurnTime(int burnTime) { + this.burnTime = burnTime; + } + + /** + * Gets whether the furnace's fuel is burning or not. + * + * @return whether the furnace's fuel is burning or not. + */ + public boolean isBurning() { + return this.burning; + } + + /** + * Sets whether the furnace's fuel is burning or not. + * + * @param burning true if the furnace's fuel is burning + */ + public void setBurning(boolean burning) { + this.burning = burning; + } + + public boolean isCancelled() { + return cancelled; + } + + public void setCancelled(boolean cancel) { + this.cancelled = cancel; + } + + @NotNull + @Override + public HandlerList getHandlers() { + return handlers; + } + + @NotNull + public static HandlerList getHandlerList() { + return handlers; + } +} diff --git a/api/src/main/java/org/bukkit/event/inventory/FurnaceExtractEvent.java b/api/src/main/java/org/bukkit/event/inventory/FurnaceExtractEvent.java new file mode 100644 index 000000000..020739697 --- /dev/null +++ b/api/src/main/java/org/bukkit/event/inventory/FurnaceExtractEvent.java @@ -0,0 +1,52 @@ +package org.bukkit.event.inventory; + +import org.bukkit.Material; +import org.bukkit.block.Block; +import org.bukkit.entity.Player; +import org.bukkit.event.block.BlockExpEvent; +import org.jetbrains.annotations.NotNull; + +/** + * This event is called when a player takes items out of the furnace + */ +public class FurnaceExtractEvent extends BlockExpEvent { + private final Player player; + private final Material itemType; + private final int itemAmount; + + public FurnaceExtractEvent(@NotNull Player player, @NotNull Block block, @NotNull Material itemType, int itemAmount, int exp) { + super(block, exp); + this.player = player; + this.itemType = itemType; + this.itemAmount = itemAmount; + } + + /** + * Get the player that triggered the event + * + * @return the relevant player + */ + @NotNull + public Player getPlayer() { + return player; + } + + /** + * Get the Material of the item being retrieved + * + * @return the material of the item + */ + @NotNull + public Material getItemType() { + return itemType; + } + + /** + * Get the item count being retrieved + * + * @return the amount of the item + */ + public int getItemAmount() { + return itemAmount; + } +} diff --git a/api/src/main/java/org/bukkit/event/inventory/FurnaceSmeltEvent.java b/api/src/main/java/org/bukkit/event/inventory/FurnaceSmeltEvent.java new file mode 100644 index 000000000..98196837b --- /dev/null +++ b/api/src/main/java/org/bukkit/event/inventory/FurnaceSmeltEvent.java @@ -0,0 +1,73 @@ +package org.bukkit.event.inventory; + +import org.bukkit.block.Block; +import org.bukkit.event.Cancellable; +import org.bukkit.event.HandlerList; +import org.bukkit.event.block.BlockEvent; +import org.bukkit.inventory.ItemStack; +import org.jetbrains.annotations.NotNull; + +/** + * Called when an ItemStack is successfully smelted in a furnace. + */ +public class FurnaceSmeltEvent extends BlockEvent implements Cancellable { + private static final HandlerList handlers = new HandlerList(); + private final ItemStack source; + private ItemStack result; + private boolean cancelled; + + public FurnaceSmeltEvent(@NotNull final Block furnace, @NotNull final ItemStack source, @NotNull final ItemStack result) { + super(furnace); + this.source = source; + this.result = result; + this.cancelled = false; + } + + /** + * Gets the smelted ItemStack for this event + * + * @return smelting source ItemStack + */ + @NotNull + public ItemStack getSource() { + return source; + } + + /** + * Gets the resultant ItemStack for this event + * + * @return smelting result ItemStack + */ + @NotNull + public ItemStack getResult() { + return result; + } + + /** + * Sets the resultant ItemStack for this event + * + * @param result new result ItemStack + */ + public void setResult(@NotNull ItemStack result) { + this.result = result; + } + + public boolean isCancelled() { + return cancelled; + } + + public void setCancelled(boolean cancel) { + this.cancelled = cancel; + } + + @NotNull + @Override + public HandlerList getHandlers() { + return handlers; + } + + @NotNull + public static HandlerList getHandlerList() { + return handlers; + } +} diff --git a/api/src/main/java/org/bukkit/event/inventory/InventoryAction.java b/api/src/main/java/org/bukkit/event/inventory/InventoryAction.java new file mode 100644 index 000000000..a7bc694bd --- /dev/null +++ b/api/src/main/java/org/bukkit/event/inventory/InventoryAction.java @@ -0,0 +1,91 @@ +package org.bukkit.event.inventory; + +/** + * An estimation of what the result will be. + */ +public enum InventoryAction { + + /** + * Nothing will happen from the click. + *

+ * There may be cases where nothing will happen and this is value is not + * provided, but it is guaranteed that this value is accurate when given. + */ + NOTHING, + /** + * All of the items on the clicked slot are moved to the cursor. + */ + PICKUP_ALL, + /** + * Some of the items on the clicked slot are moved to the cursor. + */ + PICKUP_SOME, + /** + * Half of the items on the clicked slot are moved to the cursor. + */ + PICKUP_HALF, + /** + * One of the items on the clicked slot are moved to the cursor. + */ + PICKUP_ONE, + /** + * All of the items on the cursor are moved to the clicked slot. + */ + PLACE_ALL, + /** + * Some of the items from the cursor are moved to the clicked slot + * (usually up to the max stack size). + */ + PLACE_SOME, + /** + * A single item from the cursor is moved to the clicked slot. + */ + PLACE_ONE, + /** + * The clicked item and the cursor are exchanged. + */ + SWAP_WITH_CURSOR, + /** + * The entire cursor item is dropped. + */ + DROP_ALL_CURSOR, + /** + * One item is dropped from the cursor. + */ + DROP_ONE_CURSOR, + /** + * The entire clicked slot is dropped. + */ + DROP_ALL_SLOT, + /** + * One item is dropped from the clicked slot. + */ + DROP_ONE_SLOT, + /** + * The item is moved to the opposite inventory if a space is found. + */ + MOVE_TO_OTHER_INVENTORY, + /** + * The clicked item is moved to the hotbar, and the item currently there + * is re-added to the player's inventory. + */ + HOTBAR_MOVE_AND_READD, + /** + * The clicked slot and the picked hotbar slot are swapped. + */ + HOTBAR_SWAP, + /** + * A max-size stack of the clicked item is put on the cursor. + */ + CLONE_STACK, + /** + * The inventory is searched for the same material, and they are put on + * the cursor up to {@link org.bukkit.Material#getMaxStackSize()}. + */ + COLLECT_TO_CURSOR, + /** + * An unrecognized ClickType. + */ + UNKNOWN, + ; +} diff --git a/api/src/main/java/org/bukkit/event/inventory/InventoryClickEvent.java b/api/src/main/java/org/bukkit/event/inventory/InventoryClickEvent.java new file mode 100644 index 000000000..5aec15a90 --- /dev/null +++ b/api/src/main/java/org/bukkit/event/inventory/InventoryClickEvent.java @@ -0,0 +1,244 @@ +package org.bukkit.event.inventory; + +import org.bukkit.inventory.Inventory; +import org.bukkit.inventory.InventoryView; +import org.bukkit.entity.HumanEntity; +import org.bukkit.entity.Player; +import org.bukkit.event.HandlerList; +import org.bukkit.event.inventory.InventoryType.SlotType; +import org.bukkit.Location; +import org.bukkit.inventory.ItemStack; +import org.bukkit.scheduler.BukkitScheduler; +import org.bukkit.plugin.Plugin; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +/** + * This event is called when a player clicks a slot in an inventory. + *

+ * Because InventoryClickEvent occurs within a modification of the Inventory, + * not all Inventory related methods are safe to use. + *

+ * The following should never be invoked by an EventHandler for + * InventoryClickEvent using the HumanEntity or InventoryView associated with + * this event: + *

    + *
  • {@link HumanEntity#closeInventory()} + *
  • {@link HumanEntity#openInventory(Inventory)} + *
  • {@link HumanEntity#openWorkbench(Location, boolean)} + *
  • {@link HumanEntity#openEnchanting(Location, boolean)} + *
  • {@link InventoryView#close()} + *
+ * To invoke one of these methods, schedule a task using + * {@link BukkitScheduler#runTask(Plugin, Runnable)}, which will run the task + * on the next tick. Also be aware that this is not an exhaustive list, and + * other methods could potentially create issues as well. + *

+ * Assuming the EntityHuman associated with this event is an instance of a + * Player, manipulating the MaxStackSize or contents of an Inventory will + * require an Invocation of {@link Player#updateInventory()}. + *

+ * Modifications to slots that are modified by the results of this + * InventoryClickEvent can be overwritten. To change these slots, this event + * should be cancelled and all desired changes to the inventory applied. + * Alternatively, scheduling a task using {@link BukkitScheduler#runTask( + * Plugin, Runnable)}, which would execute the task on the next tick, would + * work as well. + */ +public class InventoryClickEvent extends InventoryInteractEvent { + private static final HandlerList handlers = new HandlerList(); + private final ClickType click; + private final InventoryAction action; + private SlotType slot_type; + private int whichSlot; + private int rawSlot; + private ItemStack current = null; + private int hotbarKey = -1; + + public InventoryClickEvent(@NotNull InventoryView view, @NotNull SlotType type, int slot, @NotNull ClickType click, @NotNull InventoryAction action) { + super(view); + this.slot_type = type; + this.rawSlot = slot; + this.whichSlot = view.convertSlot(slot); + this.click = click; + this.action = action; + } + + public InventoryClickEvent(@NotNull InventoryView view, @NotNull SlotType type, int slot, @NotNull ClickType click, @NotNull InventoryAction action, int key) { + this(view, type, slot, click, action); + this.hotbarKey = key; + } + + /** + * Gets the type of slot that was clicked. + * + * @return the slot type + */ + @NotNull + public SlotType getSlotType() { + return slot_type; + } + + /** + * Gets the current ItemStack on the cursor. + * + * @return the cursor ItemStack + */ + @Nullable + public ItemStack getCursor() { + return getView().getCursor(); + } + + /** + * Gets the ItemStack currently in the clicked slot. + * + * @return the item in the clicked + */ + @Nullable + public ItemStack getCurrentItem() { + if (slot_type == SlotType.OUTSIDE) { + return current; + } + return getView().getItem(rawSlot); + } + + /** + * Gets whether or not the ClickType for this event represents a right + * click. + * + * @return true if the ClickType uses the right mouse button. + * @see ClickType#isRightClick() + */ + public boolean isRightClick() { + return click.isRightClick(); + } + + /** + * Gets whether or not the ClickType for this event represents a left + * click. + * + * @return true if the ClickType uses the left mouse button. + * @see ClickType#isLeftClick() + */ + public boolean isLeftClick() { + return click.isLeftClick(); + } + + /** + * Gets whether the ClickType for this event indicates that the key was + * pressed down when the click was made. + * + * @return true if the ClickType uses Shift or Ctrl. + * @see ClickType#isShiftClick() + */ + public boolean isShiftClick() { + return click.isShiftClick(); + } + + /** + * Sets the item on the cursor. + * + * @param stack the new cursor item + * @deprecated This changes the ItemStack in their hand before any + * calculations are applied to the Inventory, which has a tendency to + * create inconsistencies between the Player and the server, and to + * make unexpected changes in the behavior of the clicked Inventory. + */ + @Deprecated + public void setCursor(@Nullable ItemStack stack) { + getView().setCursor(stack); + } + + /** + * Sets the ItemStack currently in the clicked slot. + * + * @param stack the item to be placed in the current slot + */ + public void setCurrentItem(@Nullable ItemStack stack) { + if (slot_type == SlotType.OUTSIDE) { + current = stack; + } else { + getView().setItem(rawSlot, stack); + } + } + + /** + * Gets the inventory corresponding to the clicked slot. + * + * @see InventoryView#getInventory(int) + * @return inventory, or null if clicked outside + */ + @Nullable + public Inventory getClickedInventory() { + return getView().getInventory(rawSlot); + } + + /** + * The slot number that was clicked, ready for passing to + * {@link Inventory#getItem(int)}. Note that there may be two slots with + * the same slot number, since a view links two different inventories. + * + * @return The slot number. + */ + public int getSlot() { + return whichSlot; + } + + /** + * The raw slot number clicked, ready for passing to {@link InventoryView + * #getItem(int)} This slot number is unique for the view. + * + * @return the slot number + */ + public int getRawSlot() { + return rawSlot; + } + + /** + * If the ClickType is NUMBER_KEY, this method will return the index of + * the pressed key (0-8). + * + * @return the number on the key minus 1 (range 0-8); or -1 if not + * a NUMBER_KEY action + */ + public int getHotbarButton() { + return hotbarKey; + } + + /** + * Gets the InventoryAction that triggered this event. + *

+ * This action cannot be changed, and represents what the normal outcome + * of the event will be. To change the behavior of this + * InventoryClickEvent, changes must be manually applied. + * + * @return the InventoryAction that triggered this event. + */ + @NotNull + public InventoryAction getAction() { + return action; + } + + /** + * Gets the ClickType for this event. + *

+ * This is insulated against changes to the inventory by other plugins. + * + * @return the type of inventory click + */ + @NotNull + public ClickType getClick() { + return click; + } + + @NotNull + @Override + public HandlerList getHandlers() { + return handlers; + } + + @NotNull + public static HandlerList getHandlerList() { + return handlers; + } +} diff --git a/api/src/main/java/org/bukkit/event/inventory/InventoryCloseEvent.java b/api/src/main/java/org/bukkit/event/inventory/InventoryCloseEvent.java new file mode 100644 index 000000000..21ad8888c --- /dev/null +++ b/api/src/main/java/org/bukkit/event/inventory/InventoryCloseEvent.java @@ -0,0 +1,90 @@ + +package org.bukkit.event.inventory; + +import org.bukkit.entity.HumanEntity; +import org.bukkit.event.HandlerList; +import org.bukkit.inventory.InventoryView; +import org.jetbrains.annotations.NotNull; + +/** + * Represents a player related inventory event + */ +public class InventoryCloseEvent extends InventoryEvent { + private static final HandlerList handlers = new HandlerList(); + // Paper start + private final Reason reason; + @NotNull + public Reason getReason() { + return reason; + } + + public enum Reason { + /** + * Unknown reason + */ + UNKNOWN, + /** + * Player is teleporting + */ + TELEPORT, + /** + * Player is no longer permitted to use this inventory + */ + CANT_USE, + /** + * The chunk the inventory was in was unloaded + */ + UNLOADED, + /** + * Opening new inventory instead + */ + OPEN_NEW, + /** + * Closed + */ + PLAYER, + /** + * Closed due to disconnect + */ + DISCONNECT, + /** + * The player died + */ + DEATH, + /** + * Closed by Bukkit API + */ + PLUGIN, + } + + public InventoryCloseEvent(@NotNull InventoryView transaction) { + this(transaction, Reason.UNKNOWN); + } + + public InventoryCloseEvent(@NotNull InventoryView transaction, @NotNull Reason reason) { + super(transaction); + this.reason = reason; + // Paper end + } + + /** + * Returns the player involved in this event + * + * @return Player who is involved in this event + */ + @NotNull + public final HumanEntity getPlayer() { + return transaction.getPlayer(); + } + + @NotNull + @Override + public HandlerList getHandlers() { + return handlers; + } + + @NotNull + public static HandlerList getHandlerList() { + return handlers; + } +} diff --git a/api/src/main/java/org/bukkit/event/inventory/InventoryCreativeEvent.java b/api/src/main/java/org/bukkit/event/inventory/InventoryCreativeEvent.java new file mode 100644 index 000000000..bc1e13b76 --- /dev/null +++ b/api/src/main/java/org/bukkit/event/inventory/InventoryCreativeEvent.java @@ -0,0 +1,29 @@ +package org.bukkit.event.inventory; + +import org.bukkit.event.inventory.InventoryType.SlotType; +import org.bukkit.inventory.InventoryView; +import org.bukkit.inventory.ItemStack; +import org.jetbrains.annotations.NotNull; + +/** + * This event is called when a player in creative mode puts down or picks up + * an item in their inventory / hotbar and when they drop items from their + * Inventory while in creative mode. + */ +public class InventoryCreativeEvent extends InventoryClickEvent { + private ItemStack item; + + public InventoryCreativeEvent(@NotNull InventoryView what, @NotNull SlotType type, int slot, @NotNull ItemStack newItem) { + super(what, type, slot, ClickType.CREATIVE, InventoryAction.PLACE_ALL); + this.item = newItem; + } + + @NotNull + public ItemStack getCursor() { + return item; + } + + public void setCursor(@NotNull ItemStack item) { + this.item = item; + } +} diff --git a/api/src/main/java/org/bukkit/event/inventory/InventoryDragEvent.java b/api/src/main/java/org/bukkit/event/inventory/InventoryDragEvent.java new file mode 100644 index 000000000..1716369a7 --- /dev/null +++ b/api/src/main/java/org/bukkit/event/inventory/InventoryDragEvent.java @@ -0,0 +1,174 @@ +package org.bukkit.event.inventory; + +import java.util.Collections; +import java.util.Map; +import java.util.Set; + +import org.apache.commons.lang.Validate; +import org.bukkit.Location; +import org.bukkit.entity.HumanEntity; +import org.bukkit.entity.Player; +import org.bukkit.event.HandlerList; +import org.bukkit.inventory.Inventory; +import org.bukkit.inventory.InventoryView; +import org.bukkit.inventory.ItemStack; +import org.bukkit.plugin.Plugin; +import org.bukkit.scheduler.BukkitScheduler; + +import com.google.common.collect.ImmutableSet; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +/** + * This event is called when the player drags an item in their cursor across + * the inventory. The ItemStack is distributed across the slots the + * HumanEntity dragged over. The method of distribution is described by the + * DragType returned by {@link #getType()}. + *

+ * Canceling this event will result in none of the changes described in + * {@link #getNewItems()} being applied to the Inventory. + *

+ * Because InventoryDragEvent occurs within a modification of the Inventory, + * not all Inventory related methods are safe to use. + *

+ * The following should never be invoked by an EventHandler for + * InventoryDragEvent using the HumanEntity or InventoryView associated with + * this event. + *

    + *
  • {@link HumanEntity#closeInventory()} + *
  • {@link HumanEntity#openInventory(Inventory)} + *
  • {@link HumanEntity#openWorkbench(Location, boolean)} + *
  • {@link HumanEntity#openEnchanting(Location, boolean)} + *
  • {@link InventoryView#close()} + *
+ * To invoke one of these methods, schedule a task using + * {@link BukkitScheduler#runTask(Plugin, Runnable)}, which will run the task + * on the next tick. Also be aware that this is not an exhaustive list, and + * other methods could potentially create issues as well. + *

+ * Assuming the EntityHuman associated with this event is an instance of a + * Player, manipulating the MaxStackSize or contents of an Inventory will + * require an Invocation of {@link Player#updateInventory()}. + *

+ * Any modifications to slots that are modified by the results of this + * InventoryDragEvent will be overwritten. To change these slots, this event + * should be cancelled and the changes applied. Alternatively, scheduling a + * task using {@link BukkitScheduler#runTask(Plugin, Runnable)}, which would + * execute the task on the next tick, would work as well. + */ +public class InventoryDragEvent extends InventoryInteractEvent { + private static final HandlerList handlers = new HandlerList(); + private final DragType type; + private final Map addedItems; + private final Set containerSlots; + private final ItemStack oldCursor; + private ItemStack newCursor; + + public InventoryDragEvent(@NotNull InventoryView what, @Nullable ItemStack newCursor, @NotNull ItemStack oldCursor, boolean right, @NotNull Map slots) { + super(what); + + Validate.notNull(oldCursor); + Validate.notNull(slots); + + type = right ? DragType.SINGLE : DragType.EVEN; + this.newCursor = newCursor; + this.oldCursor = oldCursor; + this.addedItems = slots; + ImmutableSet.Builder b = ImmutableSet.builder(); + for (Integer slot : slots.keySet()) { + b.add(what.convertSlot(slot)); + } + this.containerSlots = b.build(); + } + + /** + * Gets all items to be added to the inventory in this drag. + * + * @return map from raw slot id to new ItemStack + */ + @NotNull + public Map getNewItems() { + return Collections.unmodifiableMap(addedItems); + } + + /** + * Gets the raw slot ids to be changed in this drag. + * + * @return list of raw slot ids, suitable for getView().getItem(int) + */ + @NotNull + public Set getRawSlots() { + return addedItems.keySet(); + } + + /** + * Gets the slots to be changed in this drag. + * + * @return list of converted slot ids, suitable for {@link + * org.bukkit.inventory.Inventory#getItem(int)}. + */ + @NotNull + public Set getInventorySlots() { + return containerSlots; + } + + /** + * Gets the result cursor after the drag is done. The returned value is + * mutable. + * + * @return the result cursor + */ + @Nullable + public ItemStack getCursor() { + return newCursor; + } + + /** + * Sets the result cursor after the drag is done. + *

+ * Changing this item stack changes the cursor item. Note that changing + * the affected "dragged" slots does not change this ItemStack, nor does + * changing this ItemStack affect the "dragged" slots. + * + * @param newCursor the new cursor ItemStack + */ + public void setCursor(@Nullable ItemStack newCursor) { + this.newCursor = newCursor; + } + + /** + * Gets an ItemStack representing the cursor prior to any modifications + * as a result of this drag. + * + * @return the original cursor + */ + @NotNull + public ItemStack getOldCursor() { + return oldCursor.clone(); + } + + /** + * Gets the DragType that describes the behavior of ItemStacks placed + * after this InventoryDragEvent. + *

+ * The ItemStacks and the raw slots that they're being applied to can be + * found using {@link #getNewItems()}. + * + * @return the DragType of this InventoryDragEvent + */ + @NotNull + public DragType getType() { + return type; + } + + @NotNull + @Override + public HandlerList getHandlers() { + return handlers; + } + + @NotNull + public static HandlerList getHandlerList() { + return handlers; + } +} diff --git a/api/src/main/java/org/bukkit/event/inventory/InventoryEvent.java b/api/src/main/java/org/bukkit/event/inventory/InventoryEvent.java new file mode 100644 index 000000000..cc534420d --- /dev/null +++ b/api/src/main/java/org/bukkit/event/inventory/InventoryEvent.java @@ -0,0 +1,65 @@ + +package org.bukkit.event.inventory; + +import java.util.List; + +import org.bukkit.event.HandlerList; +import org.bukkit.entity.HumanEntity; +import org.bukkit.event.Event; +import org.bukkit.inventory.Inventory; +import org.bukkit.inventory.InventoryView; +import org.jetbrains.annotations.NotNull; + +/** + * Represents a player related inventory event + */ +public class InventoryEvent extends Event { + private static final HandlerList handlers = new HandlerList(); + protected InventoryView transaction; + + public InventoryEvent(@NotNull InventoryView transaction) { + this.transaction = transaction; + } + + /** + * Gets the primary Inventory involved in this transaction + * + * @return The upper inventory. + */ + @NotNull + public Inventory getInventory() { + return transaction.getTopInventory(); + } + + /** + * Gets the list of players viewing the primary (upper) inventory involved + * in this event + * + * @return A list of people viewing. + */ + @NotNull + public List getViewers() { + return transaction.getTopInventory().getViewers(); + } + + /** + * Gets the view object itself + * + * @return InventoryView + */ + @NotNull + public InventoryView getView() { + return transaction; + } + + @NotNull + @Override + public HandlerList getHandlers() { + return handlers; + } + + @NotNull + public static HandlerList getHandlerList() { + return handlers; + } +} diff --git a/api/src/main/java/org/bukkit/event/inventory/InventoryInteractEvent.java b/api/src/main/java/org/bukkit/event/inventory/InventoryInteractEvent.java new file mode 100644 index 000000000..3b1d14365 --- /dev/null +++ b/api/src/main/java/org/bukkit/event/inventory/InventoryInteractEvent.java @@ -0,0 +1,79 @@ +package org.bukkit.event.inventory; + +import org.bukkit.entity.HumanEntity; +import org.bukkit.event.Cancellable; +import org.bukkit.inventory.InventoryView; +import org.jetbrains.annotations.NotNull; + +/** + * An abstract base class for events that describe an interaction between a + * HumanEntity and the contents of an Inventory. + */ +public abstract class InventoryInteractEvent extends InventoryEvent implements Cancellable { + private Result result = Result.DEFAULT; + + public InventoryInteractEvent(@NotNull InventoryView transaction) { + super(transaction); + } + + /** + * Gets the player who performed the click. + * + * @return The clicking player. + */ + @NotNull + public HumanEntity getWhoClicked() { + return getView().getPlayer(); + } + + /** + * Sets the result of this event. This will change whether or not this + * event is considered cancelled. + * + * @see #isCancelled() + * @param newResult the new {@link org.bukkit.event.Event.Result} for this event + */ + public void setResult(@NotNull Result newResult) { + result = newResult; + } + + /** + * Gets the {@link org.bukkit.event.Event.Result} of this event. The Result describes the + * behavior that will be applied to the inventory in relation to this + * event. + * + * @return the Result of this event. + */ + @NotNull + public Result getResult() { + return result; + } + + /** + * Gets whether or not this event is cancelled. This is based off of the + * Result value returned by {@link #getResult()}. Result.ALLOW and + * Result.DEFAULT will result in a returned value of false, but + * Result.DENY will result in a returned value of true. + *

+ * {@inheritDoc} + * + * @return whether the event is cancelled + */ + public boolean isCancelled() { + return getResult() == Result.DENY; + } + + /** + * Proxy method to {@link #setResult(Event.Result)} for the Cancellable + * interface. {@link #setResult(Event.Result)} is preferred, as it allows + * you to specify the Result beyond Result.DENY and Result.ALLOW. + *

+ * {@inheritDoc} + * + * @param toCancel result becomes DENY if true, ALLOW if false + */ + public void setCancelled(boolean toCancel) { + setResult(toCancel ? Result.DENY : Result.ALLOW); + } + +} diff --git a/api/src/main/java/org/bukkit/event/inventory/InventoryMoveItemEvent.java b/api/src/main/java/org/bukkit/event/inventory/InventoryMoveItemEvent.java new file mode 100644 index 000000000..f0a16d3c2 --- /dev/null +++ b/api/src/main/java/org/bukkit/event/inventory/InventoryMoveItemEvent.java @@ -0,0 +1,119 @@ +package org.bukkit.event.inventory; + +import org.apache.commons.lang.Validate; +import org.bukkit.event.Cancellable; +import org.bukkit.event.Event; +import org.bukkit.event.HandlerList; +import org.bukkit.inventory.Inventory; +import org.bukkit.inventory.ItemStack; +import org.jetbrains.annotations.NotNull; + +/** + * Called when some entity or block (e.g. hopper) tries to move items directly + * from one inventory to another. + *

+ * When this event is called, the initiator may already have removed the item + * from the source inventory and is ready to move it into the destination + * inventory. + *

+ * If this event is cancelled, the items will be returned to the source + * inventory, if needed. + *

+ * If this event is not cancelled, the initiator will try to put the ItemStack + * into the destination inventory. If this is not possible and the ItemStack + * has not been modified, the source inventory slot will be restored to its + * former state. Otherwise any additional items will be discarded. + */ +public class InventoryMoveItemEvent extends Event implements Cancellable { + private static final HandlerList handlers = new HandlerList(); + private boolean cancelled; + private final Inventory sourceInventory; + private final Inventory destinationInventory; + private ItemStack itemStack; + private final boolean didSourceInitiate; + public boolean calledGetItem; // Paper + public boolean calledSetItem; // Paper + + public InventoryMoveItemEvent(@NotNull final Inventory sourceInventory, @NotNull final ItemStack itemStack, @NotNull final Inventory destinationInventory, final boolean didSourceInitiate) { + Validate.notNull(itemStack, "ItemStack cannot be null"); + this.sourceInventory = sourceInventory; + this.itemStack = itemStack; + this.destinationInventory = destinationInventory; + this.didSourceInitiate = didSourceInitiate; + } + + /** + * Gets the Inventory that the ItemStack is being taken from + * + * @return Inventory that the ItemStack is being taken from + */ + @NotNull + public Inventory getSource() { + return sourceInventory; + } + + /** + * Gets the ItemStack being moved; if modified, the original item will not + * be removed from the source inventory. + * + * @return ItemStack + */ + @NotNull + public ItemStack getItem() { + calledGetItem = true; // Paper - record this method was used for auto detection of mode + return itemStack; // Paper - Removed clone, handled better in Server + } + + /** + * Sets the ItemStack being moved; if this is different from the original + * ItemStack, the original item will not be removed from the source + * inventory. + * + * @param itemStack The ItemStack + */ + public void setItem(@NotNull ItemStack itemStack) { + Validate.notNull(itemStack, "ItemStack cannot be null. Cancel the event if you want nothing to be transferred."); + calledSetItem = true; // Paper - record this method was used for auto detection of mode + this.itemStack = itemStack.clone(); + } + + /** + * Gets the Inventory that the ItemStack is being put into + * + * @return Inventory that the ItemStack is being put into + */ + @NotNull + public Inventory getDestination() { + return destinationInventory; + } + + /** + * Gets the Inventory that initiated the transfer. This will always be + * either the destination or source Inventory. + * + * @return Inventory that initiated the transfer + */ + @NotNull + public Inventory getInitiator() { + return didSourceInitiate ? sourceInventory : destinationInventory; + } + + public boolean isCancelled() { + return cancelled; + } + + public void setCancelled(boolean cancel) { + this.cancelled = cancel; + } + + @NotNull + @Override + public HandlerList getHandlers() { + return handlers; + } + + @NotNull + public static HandlerList getHandlerList() { + return handlers; + } +} diff --git a/api/src/main/java/org/bukkit/event/inventory/InventoryOpenEvent.java b/api/src/main/java/org/bukkit/event/inventory/InventoryOpenEvent.java new file mode 100644 index 000000000..331a1bd73 --- /dev/null +++ b/api/src/main/java/org/bukkit/event/inventory/InventoryOpenEvent.java @@ -0,0 +1,67 @@ +package org.bukkit.event.inventory; + +import org.bukkit.inventory.InventoryView; +import org.bukkit.entity.HumanEntity; +import org.bukkit.event.Cancellable; +import org.bukkit.event.HandlerList; +import org.jetbrains.annotations.NotNull; + +/** + * Represents a player related inventory event + */ +public class InventoryOpenEvent extends InventoryEvent implements Cancellable { + private static final HandlerList handlers = new HandlerList(); + private boolean cancelled; + + public InventoryOpenEvent(@NotNull InventoryView transaction) { + super(transaction); + this.cancelled = false; + } + + /** + * Returns the player involved in this event + * + * @return Player who is involved in this event + */ + @NotNull + public final HumanEntity getPlayer() { + return transaction.getPlayer(); + } + + /** + * Gets the cancellation state of this event. A cancelled event will not + * be executed in the server, but will still pass to other plugins. + *

+ * If an inventory open event is cancelled, the inventory screen will not + * show. + * + * @return true if this event is cancelled + */ + public boolean isCancelled() { + return cancelled; + } + + /** + * Sets the cancellation state of this event. A cancelled event will not + * be executed in the server, but will still pass to other plugins. + *

+ * If an inventory open event is cancelled, the inventory screen will not + * show. + * + * @param cancel true if you wish to cancel this event + */ + public void setCancelled(boolean cancel) { + cancelled = cancel; + } + + @NotNull + @Override + public HandlerList getHandlers() { + return handlers; + } + + @NotNull + public static HandlerList getHandlerList() { + return handlers; + } +} diff --git a/api/src/main/java/org/bukkit/event/inventory/InventoryPickupItemEvent.java b/api/src/main/java/org/bukkit/event/inventory/InventoryPickupItemEvent.java new file mode 100644 index 000000000..b761c0224 --- /dev/null +++ b/api/src/main/java/org/bukkit/event/inventory/InventoryPickupItemEvent.java @@ -0,0 +1,63 @@ +package org.bukkit.event.inventory; + +import org.bukkit.entity.Item; +import org.bukkit.event.Cancellable; +import org.bukkit.event.Event; +import org.bukkit.event.HandlerList; +import org.bukkit.inventory.Inventory; +import org.jetbrains.annotations.NotNull; + +/** + * Called when a hopper or hopper minecart picks up a dropped item. + */ +public class InventoryPickupItemEvent extends Event implements Cancellable { + private static final HandlerList handlers = new HandlerList(); + private boolean cancelled; + private final Inventory inventory; + private final Item item; + + public InventoryPickupItemEvent(@NotNull final Inventory inventory, @NotNull final Item item) { + super(); + this.inventory = inventory; + this.item = item; + } + + /** + * Gets the Inventory that picked up the item + * + * @return Inventory + */ + @NotNull + public Inventory getInventory() { + return inventory; + } + + /** + * Gets the Item entity that was picked up + * + * @return Item + */ + @NotNull + public Item getItem() { + return item; + } + + public boolean isCancelled() { + return cancelled; + } + + public void setCancelled(boolean cancel) { + this.cancelled = cancel; + } + + @NotNull + @Override + public HandlerList getHandlers() { + return handlers; + } + + @NotNull + public static HandlerList getHandlerList() { + return handlers; + } +} diff --git a/api/src/main/java/org/bukkit/event/inventory/InventoryType.java b/api/src/main/java/org/bukkit/event/inventory/InventoryType.java new file mode 100644 index 000000000..d69ce3ec7 --- /dev/null +++ b/api/src/main/java/org/bukkit/event/inventory/InventoryType.java @@ -0,0 +1,164 @@ +package org.bukkit.event.inventory; + +import org.bukkit.inventory.InventoryHolder; +import org.jetbrains.annotations.NotNull; + +/** + * Represents the different kinds of inventories available in Bukkit. + *
+ * Only InventoryTypes marked {@link #isCreatable()} can be created. + *
+ * The current list of inventories that cannot be created via + * {@link org.bukkit.Bukkit#createInventory} are:
+ *

+ * {@link InventoryType#CREATIVE} and {@link InventoryType#CRAFTING} + *
+ * + * See {@link org.bukkit.Bukkit#createInventory} for more information. + * + * @see org.bukkit.Bukkit#createInventory(InventoryHolder, InventoryType) + */ +public enum InventoryType { + + /** + * A chest inventory, with 0, 9, 18, 27, 36, 45, or 54 slots of type + * CONTAINER. + */ + CHEST(27,"Chest"), + /** + * A dispenser inventory, with 9 slots of type CONTAINER. + */ + DISPENSER(9,"Dispenser"), + /** + * A dropper inventory, with 9 slots of type CONTAINER. + */ + DROPPER(9, "Dropper"), + /** + * A furnace inventory, with a RESULT slot, a CRAFTING slot, and a FUEL + * slot. + */ + FURNACE(3,"Furnace"), + /** + * A workbench inventory, with 9 CRAFTING slots and a RESULT slot. + */ + WORKBENCH(10,"Crafting"), + /** + * A player's crafting inventory, with 4 CRAFTING slots and a RESULT slot. + * Also implies that the 4 ARMOR slots are accessible. + */ + CRAFTING(5,"Crafting", false), + /** + * An enchantment table inventory, with two CRAFTING slots and three + * enchanting buttons. + */ + ENCHANTING(2,"Enchanting"), + /** + * A brewing stand inventory, with one FUEL slot and three CRAFTING slots. + */ + BREWING(5,"Brewing"), + /** + * A player's inventory, with 9 QUICKBAR slots, 27 CONTAINER slots, 4 ARMOR + * slots and 1 offhand slot. The ARMOR and offhand slots may not be visible + * to the player, though. + */ + PLAYER(41,"Player"), + /** + * The creative mode inventory, with only 9 QUICKBAR slots and nothing + * else. (The actual creative interface with the items is client-side and + * cannot be altered by the server.) + */ + CREATIVE(9,"Creative", false), + /** + * The merchant inventory, with 2 TRADE-IN slots, and 1 RESULT slot. + */ + MERCHANT(3,"Villager", false), + /** + * The ender chest inventory, with 27 slots. + */ + ENDER_CHEST(27,"Ender Chest"), + /** + * An anvil inventory, with 2 CRAFTING slots and 1 RESULT slot + */ + ANVIL(3, "Repairing"), + /** + * A beacon inventory, with 1 CRAFTING slot + */ + BEACON(1, "container.beacon"), + /** + * A hopper inventory, with 5 slots of type CONTAINER. + */ + HOPPER(5, "Item Hopper"), + /** + * A shulker box inventory, with 27 slots of type CONTAINER. + */ + SHULKER_BOX(27, "Shulker Box"), + ; + + private final int size; + private final String title; + private final boolean isCreatable; + + private InventoryType(int defaultSize, /*@NotNull*/ String defaultTitle) { + this(defaultSize, defaultTitle, true); + } + + private InventoryType(int defaultSize, /*@NotNull*/ String defaultTitle, boolean isCreatable) { + size = defaultSize; + title = defaultTitle; + this.isCreatable = isCreatable; + } + + public int getDefaultSize() { + return size; + } + + @NotNull + public String getDefaultTitle() { + return title; + } + + /** + * Denotes that this InventoryType can be created via the normal + * {@link org.bukkit.Bukkit#createInventory} methods. + * + * @return if this InventoryType can be created and shown to a player + */ + public boolean isCreatable() { + return isCreatable; + } + + public enum SlotType { + /** + * A result slot in a furnace or crafting inventory. + */ + RESULT, + /** + * A slot in the crafting matrix, or the input slot in a furnace + * inventory, the potion slot in the brewing stand, or the enchanting + * slot. + */ + CRAFTING, + /** + * An armour slot in the player's inventory. + */ + ARMOR, + /** + * A regular slot in the container or the player's inventory; anything + * not covered by the other enum values. + */ + CONTAINER, + /** + * A slot in the bottom row or quickbar. + */ + QUICKBAR, + /** + * A pseudo-slot representing the area outside the inventory window. + */ + OUTSIDE, + /** + * The fuel slot in a furnace inventory, or the ingredient slot in a + * brewing stand inventory. + */ + FUEL; + } +} diff --git a/api/src/main/java/org/bukkit/event/inventory/PrepareAnvilEvent.java b/api/src/main/java/org/bukkit/event/inventory/PrepareAnvilEvent.java new file mode 100644 index 000000000..77109a07f --- /dev/null +++ b/api/src/main/java/org/bukkit/event/inventory/PrepareAnvilEvent.java @@ -0,0 +1,53 @@ +package org.bukkit.event.inventory; + +import org.bukkit.event.HandlerList; +import org.bukkit.inventory.AnvilInventory; +import org.bukkit.inventory.InventoryView; +import org.bukkit.inventory.ItemStack; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +/** + * Called when an item is put in a slot for repair by an anvil. + */ +public class PrepareAnvilEvent extends InventoryEvent { + + private static final HandlerList handlers = new HandlerList(); + private ItemStack result; + + public PrepareAnvilEvent(@NotNull InventoryView inventory, @Nullable ItemStack result) { + super(inventory); + this.result = result; + } + + @NotNull + @Override + public AnvilInventory getInventory() { + return (AnvilInventory) super.getInventory(); + } + + /** + * Get result item, may be null. + * + * @return result item + */ + @Nullable + public ItemStack getResult() { + return result; + } + + public void setResult(@Nullable ItemStack result) { + this.result = result; + } + + @NotNull + @Override + public HandlerList getHandlers() { + return handlers; + } + + @NotNull + public static HandlerList getHandlerList() { + return handlers; + } +} diff --git a/api/src/main/java/org/bukkit/event/inventory/PrepareItemCraftEvent.java b/api/src/main/java/org/bukkit/event/inventory/PrepareItemCraftEvent.java new file mode 100644 index 000000000..efd29d198 --- /dev/null +++ b/api/src/main/java/org/bukkit/event/inventory/PrepareItemCraftEvent.java @@ -0,0 +1,62 @@ +package org.bukkit.event.inventory; + +import org.bukkit.event.HandlerList; +import org.bukkit.inventory.CraftingInventory; +import org.bukkit.inventory.InventoryView; +import org.bukkit.inventory.Recipe; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +public class PrepareItemCraftEvent extends InventoryEvent { + private static final HandlerList handlers = new HandlerList(); + private boolean repair; + private CraftingInventory matrix; + + public PrepareItemCraftEvent(@NotNull CraftingInventory what, @NotNull InventoryView view, boolean isRepair) { + super(view); + this.matrix = what; + this.repair = isRepair; + } + + /** + * Get the recipe that has been formed. If this event was triggered by a + * tool repair, this will be a temporary shapeless recipe representing the + * repair. + * + * @return The recipe being crafted. + */ + @Nullable + public Recipe getRecipe() { + return matrix.getRecipe(); + } + + /** + * @return The crafting inventory on which the recipe was formed. + */ + @NotNull + @Override + public CraftingInventory getInventory() { + return matrix; + } + + /** + * Check if this event was triggered by a tool repair operation rather + * than a crafting recipe. + * + * @return True if this is a repair. + */ + public boolean isRepair() { + return repair; + } + + @NotNull + @Override + public HandlerList getHandlers() { + return handlers; + } + + @NotNull + public static HandlerList getHandlerList() { + return handlers; + } +} diff --git a/api/src/main/java/org/bukkit/event/player/AsyncPlayerChatEvent.java b/api/src/main/java/org/bukkit/event/player/AsyncPlayerChatEvent.java new file mode 100644 index 000000000..cc3049e82 --- /dev/null +++ b/api/src/main/java/org/bukkit/event/player/AsyncPlayerChatEvent.java @@ -0,0 +1,146 @@ +package org.bukkit.event.player; + +import java.util.IllegalFormatException; +import java.util.Set; + +import org.bukkit.entity.Player; +import org.bukkit.event.Cancellable; +import org.bukkit.event.HandlerList; +import org.jetbrains.annotations.NotNull; + +/** + * This event will sometimes fire synchronously, depending on how it was + * triggered. + *

+ * The constructor provides a boolean to indicate if the event was fired + * synchronously or asynchronously. When asynchronous, this event can be + * called from any thread, sans the main thread, and has limited access to the + * API. + *

+ * If a player is the direct cause of this event by an incoming packet, this + * event will be asynchronous. If a plugin triggers this event by compelling a + * player to chat, this event will be synchronous. + *

+ * Care should be taken to check {@link #isAsynchronous()} and treat the event + * appropriately. + */ +public class AsyncPlayerChatEvent extends PlayerEvent implements Cancellable { + private static final HandlerList handlers = new HandlerList(); + private boolean cancel = false; + private String message; + private String format = "<%1$s> %2$s"; + private final Set recipients; + + /** + * + * @param async This changes the event to a synchronous state. + * @param who the chat sender + * @param message the message sent + * @param players the players to receive the message. This may be a lazy + * or unmodifiable collection. + */ + public AsyncPlayerChatEvent(final boolean async, @NotNull final Player who, @NotNull final String message, @NotNull final Set players) { + super(who, async); + this.message = message; + recipients = players; + } + + /** + * Gets the message that the player is attempting to send. This message + * will be used with {@link #getFormat()}. + * + * @return Message the player is attempting to send + */ + @NotNull + public String getMessage() { + return message; + } + + /** + * Sets the message that the player will send. This message will be used + * with {@link #getFormat()}. + * + * @param message New message that the player will send + */ + public void setMessage(@NotNull String message) { + this.message = message; + } + + /** + * Gets the format to use to display this chat message. + *

+ * When this event finishes execution, the first format parameter is the + * {@link Player#getDisplayName()} and the second parameter is {@link + * #getMessage()} + * + * @return {@link String#format(String, Object...)} compatible format + * string + */ + @NotNull + public String getFormat() { + return format; + } + + /** + * Sets the format to use to display this chat message. + *

+ * When this event finishes execution, the first format parameter is the + * {@link Player#getDisplayName()} and the second parameter is {@link + * #getMessage()} + * + * @param format {@link String#format(String, Object...)} compatible + * format string + * @throws IllegalFormatException if the underlying API throws the + * exception + * @throws NullPointerException if format is null + * @see String#format(String, Object...) + */ + public void setFormat(@NotNull final String format) throws IllegalFormatException, NullPointerException { + // Oh for a better way to do this! + try { + String.format(format, player, message); + } catch (RuntimeException ex) { + ex.fillInStackTrace(); + throw ex; + } + + this.format = format; + } + + /** + * Gets a set of recipients that this chat message will be displayed to. + *

+ * The set returned is not guaranteed to be mutable and may auto-populate + * on access. Any listener accessing the returned set should be aware that + * it may reduce performance for a lazy set implementation. + *

+ * Listeners should be aware that modifying the list may throw {@link + * UnsupportedOperationException} if the event caller provides an + * unmodifiable set. + * + * @return All Players who will see this chat message + */ + @NotNull + public Set getRecipients() { + return recipients; + } + + public boolean isCancelled() { + return cancel; + } + + public void setCancelled(boolean cancel) { + this.cancel = cancel; + } + + @NotNull + @Override + public HandlerList getHandlers() { + return handlers; + } + + @NotNull + public static HandlerList getHandlerList() { + return handlers; + } +} diff --git a/api/src/main/java/org/bukkit/event/player/AsyncPlayerPreLoginEvent.java b/api/src/main/java/org/bukkit/event/player/AsyncPlayerPreLoginEvent.java new file mode 100644 index 000000000..6c09ea6c6 --- /dev/null +++ b/api/src/main/java/org/bukkit/event/player/AsyncPlayerPreLoginEvent.java @@ -0,0 +1,238 @@ +package org.bukkit.event.player; + +import java.net.InetAddress; +import java.util.UUID; + +import com.destroystokyo.paper.profile.PlayerProfile; +import org.bukkit.Bukkit; +import org.bukkit.event.Event; +import org.bukkit.event.HandlerList; +import org.jetbrains.annotations.NotNull; + +/** + * Stores details for players attempting to log in. + *

+ * This event is asynchronous, and not run using main thread. + */ +public class AsyncPlayerPreLoginEvent extends Event { + private static final HandlerList handlers = new HandlerList(); + private Result result; + private String message; + private final String name; + private final InetAddress ipAddress; + private final UUID uniqueId; + + @Deprecated + public AsyncPlayerPreLoginEvent(@NotNull final String name, @NotNull final InetAddress ipAddress) { + this(name, ipAddress, null); + } + + public AsyncPlayerPreLoginEvent(@NotNull final String name, @NotNull final InetAddress ipAddress, @NotNull final UUID uniqueId) { + // Paper start + this(name, ipAddress, uniqueId, Bukkit.createProfile(uniqueId, name)); + } + private PlayerProfile profile; + + /** + * Gets the PlayerProfile of the player logging in + * @return The Profile + */ + @NotNull + public PlayerProfile getPlayerProfile() { + return profile; + } + + /** + * Changes the PlayerProfile the player will login as + * @param profile The profile to use + */ + public void setPlayerProfile(@NotNull PlayerProfile profile) { + this.profile = profile; + } + + public AsyncPlayerPreLoginEvent(@NotNull final String name, @NotNull final InetAddress ipAddress, @NotNull final UUID uniqueId, @NotNull PlayerProfile profile) { + super(true); + this.profile = profile; + // Paper end + this.result = Result.ALLOWED; + this.message = ""; + this.name = name; + this.ipAddress = ipAddress; + this.uniqueId = uniqueId; + } + + /** + * Gets the current result of the login, as an enum + * + * @return Current Result of the login + */ + @NotNull + public Result getLoginResult() { + return result; + } + + /** + * Gets the current result of the login, as an enum + * + * @return Current Result of the login + * @deprecated This method uses a deprecated enum from {@link + * PlayerPreLoginEvent} + * @see #getLoginResult() + */ + @Deprecated + @NotNull + public PlayerPreLoginEvent.Result getResult() { + return result == null ? null : result.old(); + } + + /** + * Sets the new result of the login, as an enum + * + * @param result New result to set + */ + public void setLoginResult(@NotNull final Result result) { + this.result = result; + } + + /** + * Sets the new result of the login, as an enum + * + * @param result New result to set + * @deprecated This method uses a deprecated enum from {@link + * PlayerPreLoginEvent} + * @see #setLoginResult(Result) + */ + @Deprecated + public void setResult(@NotNull final PlayerPreLoginEvent.Result result) { + this.result = result == null ? null : Result.valueOf(result.name()); + } + + /** + * Gets the current kick message that will be used if getResult() != + * Result.ALLOWED + * + * @return Current kick message + */ + @NotNull + public String getKickMessage() { + return message; + } + + /** + * Sets the kick message to display if getResult() != Result.ALLOWED + * + * @param message New kick message + */ + public void setKickMessage(@NotNull final String message) { + this.message = message; + } + + /** + * Allows the player to log in + */ + public void allow() { + result = Result.ALLOWED; + message = ""; + } + + /** + * Disallows the player from logging in, with the given reason + * + * @param result New result for disallowing the player + * @param message Kick message to display to the user + */ + public void disallow(@NotNull final Result result, @NotNull final String message) { + this.result = result; + this.message = message; + } + + /** + * Disallows the player from logging in, with the given reason + * + * @param result New result for disallowing the player + * @param message Kick message to display to the user + * @deprecated This method uses a deprecated enum from {@link + * PlayerPreLoginEvent} + * @see #disallow(Result, String) + */ + @Deprecated + public void disallow(@NotNull final PlayerPreLoginEvent.Result result, @NotNull final String message) { + this.result = result == null ? null : Result.valueOf(result.name()); + this.message = message; + } + + /** + * Gets the player's name. + * + * @return the player's name + */ + @NotNull + public String getName() { + return name; + } + + /** + * Gets the player IP address. + * + * @return The IP address + */ + @NotNull + public InetAddress getAddress() { + return ipAddress; + } + + /** + * Gets the player's unique ID. + * + * @return The unique ID + */ + @NotNull + public UUID getUniqueId() { + return uniqueId; + } + + @NotNull + @Override + public HandlerList getHandlers() { + return handlers; + } + + @NotNull + public static HandlerList getHandlerList() { + return handlers; + } + + /** + * Basic kick reasons for communicating to plugins + */ + public enum Result { + + /** + * The player is allowed to log in + */ + ALLOWED, + /** + * The player is not allowed to log in, due to the server being full + */ + KICK_FULL, + /** + * The player is not allowed to log in, due to them being banned + */ + KICK_BANNED, + /** + * The player is not allowed to log in, due to them not being on the + * white list + */ + KICK_WHITELIST, + /** + * The player is not allowed to log in, for reasons undefined + */ + KICK_OTHER; + + @Deprecated + @NotNull + private PlayerPreLoginEvent.Result old() { + return PlayerPreLoginEvent.Result.valueOf(name()); + } + } +} diff --git a/api/src/main/java/org/bukkit/event/player/PlayerAchievementAwardedEvent.java b/api/src/main/java/org/bukkit/event/player/PlayerAchievementAwardedEvent.java new file mode 100644 index 000000000..fae6a901b --- /dev/null +++ b/api/src/main/java/org/bukkit/event/player/PlayerAchievementAwardedEvent.java @@ -0,0 +1,50 @@ +package org.bukkit.event.player; + +import org.bukkit.Achievement; +import org.bukkit.entity.Player; +import org.bukkit.event.Cancellable; +import org.bukkit.event.HandlerList; +import org.jetbrains.annotations.NotNull; + +/** + * Called when a player earns an achievement. + * @deprecated future versions of Minecraft do not have achievements + */ +@Deprecated +public class PlayerAchievementAwardedEvent extends PlayerEvent implements Cancellable { + private static final HandlerList handlers = new HandlerList(); + private final Achievement achievement; + private boolean isCancelled = false; + + public PlayerAchievementAwardedEvent(Player player, Achievement achievement) { + super(player); + this.achievement = achievement; + } + + /** + * Gets the Achievement being awarded. + * + * @return the achievement being awarded + */ + public Achievement getAchievement() { + return achievement; + } + + public boolean isCancelled() { + return isCancelled; + } + + public void setCancelled(boolean cancel) { + this.isCancelled = cancel; + } + + @NotNull + @Override + public HandlerList getHandlers() { + return handlers; + } + + public static HandlerList getHandlerList() { + return handlers; + } +} diff --git a/api/src/main/java/org/bukkit/event/player/PlayerAdvancementDoneEvent.java b/api/src/main/java/org/bukkit/event/player/PlayerAdvancementDoneEvent.java new file mode 100644 index 000000000..21ff095af --- /dev/null +++ b/api/src/main/java/org/bukkit/event/player/PlayerAdvancementDoneEvent.java @@ -0,0 +1,42 @@ +package org.bukkit.event.player; + +import org.bukkit.advancement.Advancement; +import org.bukkit.entity.Player; +import org.bukkit.event.HandlerList; +import org.jetbrains.annotations.NotNull; + +/** + * Called when a player has completed all criteria in an advancement. + */ +public class PlayerAdvancementDoneEvent extends PlayerEvent { + + private static final HandlerList handlers = new HandlerList(); + // + private final Advancement advancement; + + public PlayerAdvancementDoneEvent(@NotNull Player who, @NotNull Advancement advancement) { + super(who); + this.advancement = advancement; + } + + /** + * Get the advancement which has been completed. + * + * @return completed advancement + */ + @NotNull + public Advancement getAdvancement() { + return advancement; + } + + @NotNull + @Override + public HandlerList getHandlers() { + return handlers; + } + + @NotNull + public static HandlerList getHandlerList() { + return handlers; + } +} diff --git a/api/src/main/java/org/bukkit/event/player/PlayerAnimationEvent.java b/api/src/main/java/org/bukkit/event/player/PlayerAnimationEvent.java new file mode 100644 index 000000000..4affca127 --- /dev/null +++ b/api/src/main/java/org/bukkit/event/player/PlayerAnimationEvent.java @@ -0,0 +1,56 @@ +package org.bukkit.event.player; + +import org.bukkit.entity.Player; +import org.bukkit.event.Cancellable; +import org.bukkit.event.HandlerList; +import org.jetbrains.annotations.NotNull; + +/** + * Represents a player animation event + */ +public class PlayerAnimationEvent extends PlayerEvent implements Cancellable { + private static final HandlerList handlers = new HandlerList(); + private final PlayerAnimationType animationType; + private boolean isCancelled = false; + + /** + * Construct a new PlayerAnimation event + * + * @param player The player instance + */ + public PlayerAnimationEvent(@NotNull final Player player) { + super(player); + + // Only supported animation type for now: + animationType = PlayerAnimationType.ARM_SWING; + } + + /** + * Get the type of this animation event + * + * @return the animation type + */ + @NotNull + public PlayerAnimationType getAnimationType() { + return animationType; + } + + public boolean isCancelled() { + return this.isCancelled; + } + + public void setCancelled(boolean cancel) { + this.isCancelled = cancel; + } + + @NotNull + @Override + public HandlerList getHandlers() { + return handlers; + } + + @NotNull + public static HandlerList getHandlerList() { + return handlers; + } +} diff --git a/api/src/main/java/org/bukkit/event/player/PlayerAnimationType.java b/api/src/main/java/org/bukkit/event/player/PlayerAnimationType.java new file mode 100644 index 000000000..ea4bf26f0 --- /dev/null +++ b/api/src/main/java/org/bukkit/event/player/PlayerAnimationType.java @@ -0,0 +1,8 @@ +package org.bukkit.event.player; + +/** + * Different types of player animations + */ +public enum PlayerAnimationType { + ARM_SWING +} diff --git a/api/src/main/java/org/bukkit/event/player/PlayerArmorStandManipulateEvent.java b/api/src/main/java/org/bukkit/event/player/PlayerArmorStandManipulateEvent.java new file mode 100644 index 000000000..1263dffbe --- /dev/null +++ b/api/src/main/java/org/bukkit/event/player/PlayerArmorStandManipulateEvent.java @@ -0,0 +1,82 @@ +package org.bukkit.event.player; + +import org.bukkit.inventory.EquipmentSlot; +import org.bukkit.entity.Player; +import org.bukkit.entity.ArmorStand; +import org.bukkit.event.HandlerList; +import org.bukkit.inventory.ItemStack; +import org.jetbrains.annotations.NotNull; + +/** + * Called when a player interacts with an armor stand and will either swap, retrieve or place an item. + */ +public class PlayerArmorStandManipulateEvent extends PlayerInteractEntityEvent { + + private static final HandlerList handlers = new HandlerList(); + + private final ItemStack playerItem; + private final ItemStack armorStandItem; + private final EquipmentSlot slot; + + public PlayerArmorStandManipulateEvent(@NotNull final Player who, @NotNull final ArmorStand clickedEntity, @NotNull final ItemStack playerItem, @NotNull final ItemStack armorStandItem, @NotNull final EquipmentSlot slot) { + super(who, clickedEntity); + this.playerItem = playerItem; + this.armorStandItem = armorStandItem; + this.slot = slot; + } + + /** + * Returns the item held by the player. If this Item is null and the armor stand Item is also null, + * there will be no transaction between the player and the armor stand. + * If the Player's item is null, but the armor stand item is not then the player will obtain the armor stand item. + * In the case that the Player's item is not null, but the armor stand item is null, the players item will be placed on the armor stand. + * If both items are not null, the items will be swapped. + * In the case that the event is cancelled the original items will remain the same. + * @return the item held by the player. + */ + @NotNull + public ItemStack getPlayerItem() { + return this.playerItem; + } + + /** + * Returns the item held by the armor stand. + * If this Item is null and the player's Item is also null, there will be no transaction between the player and the armor stand. + * If the Player's item is null, but the armor stand item is not then the player will obtain the armor stand item. + * In the case that the Player's item is not null, but the armor stand item is null, the players item will be placed on the armor stand. + * If both items are not null, the items will be swapped. + * In the case that the event is cancelled the original items will remain the same. + * @return the item held by the armor stand. + */ + @NotNull + public ItemStack getArmorStandItem() { + return this.armorStandItem; + } + + /** + * Returns the raw item slot of the armor stand in this event. + * + * @return the index of the item obtained or placed of the armor stand. + */ + @NotNull + public EquipmentSlot getSlot() { + return this.slot; + } + + @NotNull + @Override + public ArmorStand getRightClicked() { + return (ArmorStand) this.clickedEntity; + } + + @NotNull + @Override + public HandlerList getHandlers() { + return handlers; + } + + @NotNull + public static HandlerList getHandlerList() { + return handlers; + } +} diff --git a/api/src/main/java/org/bukkit/event/player/PlayerAttemptPickupItemEvent.java b/api/src/main/java/org/bukkit/event/player/PlayerAttemptPickupItemEvent.java new file mode 100644 index 000000000..fb5cb3dc4 --- /dev/null +++ b/api/src/main/java/org/bukkit/event/player/PlayerAttemptPickupItemEvent.java @@ -0,0 +1,89 @@ +package org.bukkit.event.player; + +import org.bukkit.entity.Item; +import org.bukkit.entity.Player; +import org.bukkit.event.Cancellable; +import org.bukkit.event.HandlerList; +import org.jetbrains.annotations.NotNull; + +/** + * Thrown when a player attempts to pick an item up from the ground + */ +public class PlayerAttemptPickupItemEvent extends PlayerEvent implements Cancellable { + private static final HandlerList handlers = new HandlerList(); + @NotNull private final Item item; + private final int remaining; + private boolean flyAtPlayer = true; + private boolean isCancelled = false; + + @Deprecated // Remove in 1.13 // Remove in 1.14? + public PlayerAttemptPickupItemEvent(@NotNull final Player player, @NotNull final Item item) { + this(player, item, 0); + } + + public PlayerAttemptPickupItemEvent(@NotNull final Player player, @NotNull final Item item, final int remaining) { + super(player); + this.item = item; + this.remaining = remaining; + } + + /** + * Gets the Item attempted by the player. + * + * @return Item + */ + @NotNull + public Item getItem() { + return item; + } + + /** + * Gets the amount that will remain on the ground, if any + * + * @return amount that will remain on the ground + */ + public int getRemaining() { + return remaining; + } + + /** + * Set if the item will fly at the player + *

Cancelling the event will set this value to false.

+ * + * @param flyAtPlayer True for item to fly at player + */ + public void setFlyAtPlayer(boolean flyAtPlayer) { + this.flyAtPlayer = flyAtPlayer; + } + + /** + * Gets if the item will fly at the player + * + * @return True if the item will fly at the player + */ + public boolean getFlyAtPlayer() { + return this.flyAtPlayer; + } + + + @Override + public boolean isCancelled() { + return this.isCancelled; + } + + @Override + public void setCancelled(boolean cancel) { + this.isCancelled = cancel; + } + + @NotNull + @Override + public HandlerList getHandlers() { + return handlers; + } + + @NotNull + public static HandlerList getHandlerList() { + return handlers; + } +} diff --git a/api/src/main/java/org/bukkit/event/player/PlayerBedEnterEvent.java b/api/src/main/java/org/bukkit/event/player/PlayerBedEnterEvent.java new file mode 100644 index 000000000..c7d57e286 --- /dev/null +++ b/api/src/main/java/org/bukkit/event/player/PlayerBedEnterEvent.java @@ -0,0 +1,160 @@ +package org.bukkit.event.player; + +import org.bukkit.block.Block; +import org.bukkit.entity.Player; +import org.bukkit.event.Cancellable; +import org.bukkit.event.HandlerList; +import org.jetbrains.annotations.NotNull; + +/** + * This event is fired when the player is almost about to enter the bed. + */ +public class PlayerBedEnterEvent extends PlayerEvent implements Cancellable { + + /** + * Represents the default possible outcomes of this event. + */ + public enum BedEnterResult { + /** + * The player will enter the bed. + */ + OK, + /** + * The world doesn't allow sleeping (ex. Nether or The End). Entering + * the bed is prevented and the bed explodes. + */ + NOT_POSSIBLE_HERE, + /** + * Entering the bed is prevented due to it not being night nor + * thundering currently. + *

+ * If the event is forcefully allowed during daytime, the player will + * enter the bed (and set its bed location), but might get immediately + * thrown out again. + */ + NOT_POSSIBLE_NOW, + /** + * Entering the bed is prevented due to the player being too far away. + */ + TOO_FAR_AWAY, + /** + * Entering the bed is prevented due to there being monsters nearby. + */ + NOT_SAFE, + /** + * Entering the bed is prevented due to there being some other problem. + */ + OTHER_PROBLEM; + } + + private static final HandlerList handlers = new HandlerList(); + private final Block bed; + private final BedEnterResult bedEnterResult; + private Result useBed = Result.DEFAULT; + + public PlayerBedEnterEvent(@NotNull Player who, @NotNull Block bed, @NotNull BedEnterResult bedEnterResult) { + super(who); + this.bed = bed; + this.bedEnterResult = bedEnterResult; + } + + @Deprecated + public PlayerBedEnterEvent(@NotNull Player who, @NotNull Block bed) { + this(who, bed, BedEnterResult.OK); + } + + /** + * This describes the default outcome of this event. + * + * @return the bed enter result representing the default outcome of this event + */ + @NotNull + public BedEnterResult getBedEnterResult() { + return bedEnterResult; + } + + /** + * This controls the action to take with the bed that was clicked on. + *

+ * In case of {@link org.bukkit.event.Event.Result#DEFAULT}, the default outcome is described by + * {@link #getBedEnterResult()}. + * + * @return the action to take with the interacted bed + * @see #setUseBed(org.bukkit.event.Event.Result) + */ + @NotNull + public Result useBed() { + return useBed; + } + + /** + * Sets the action to take with the interacted bed. + *

+ * {@link org.bukkit.event.Event.Result#ALLOW} will result in the player sleeping, regardless of + * the default outcome described by {@link #getBedEnterResult()}. + *
+ * {@link org.bukkit.event.Event.Result#DENY} will prevent the player from sleeping. This has the + * same effect as canceling the event via {@link #setCancelled(boolean)}. + *
+ * {@link org.bukkit.event.Event.Result#DEFAULT} will result in the outcome described by + * {@link #getBedEnterResult()}. + * + * @param useBed the action to take with the interacted bed + * @see #useBed() + */ + public void setUseBed(@NotNull Result useBed) { + this.useBed = useBed; + } + + /** + * Gets the cancellation state of this event. Set to true if you want to + * prevent the player from sleeping. + *

+ * Canceling the event has the same effect as setting {@link #useBed()} to + * {@link org.bukkit.event.Event.Result#DENY}. + *

+ * For backwards compatibility reasons this also returns true if + * {@link #useBed()} is {@link org.bukkit.event.Event.Result#DEFAULT} and the + * {@link #getBedEnterResult() default action} is to prevent bed entering. + * + * @return boolean cancellation state + */ + @Override + public boolean isCancelled() { + return (useBed == Result.DENY || useBed == Result.DEFAULT && bedEnterResult != BedEnterResult.OK); + } + + /** + * Sets the cancellation state of this event. A canceled event will not be + * executed in the server, but will still pass to other plugins. + *

+ * Canceling this event will prevent use of the bed. + * + * @param cancel true if you wish to cancel this event + */ + @Override + public void setCancelled(boolean cancel) { + setUseBed(cancel ? Result.DENY : useBed() == Result.DENY ? Result.DEFAULT : useBed()); + } + + /** + * Returns the bed block involved in this event. + * + * @return the bed block involved in this event + */ + @NotNull + public Block getBed() { + return bed; + } + + @NotNull + @Override + public HandlerList getHandlers() { + return handlers; + } + + @NotNull + public static HandlerList getHandlerList() { + return handlers; + } +} diff --git a/api/src/main/java/org/bukkit/event/player/PlayerBedLeaveEvent.java b/api/src/main/java/org/bukkit/event/player/PlayerBedLeaveEvent.java new file mode 100644 index 000000000..8244bf09c --- /dev/null +++ b/api/src/main/java/org/bukkit/event/player/PlayerBedLeaveEvent.java @@ -0,0 +1,75 @@ +package org.bukkit.event.player; + +import org.bukkit.Location; +import org.bukkit.block.Block; +import org.bukkit.entity.Player; +import org.bukkit.event.HandlerList; +import org.jetbrains.annotations.NotNull; + +/** + * This event is fired when the player is leaving a bed. + */ +public class PlayerBedLeaveEvent extends PlayerEvent { + private static final HandlerList handlers = new HandlerList(); + private final Block bed; + private boolean setBedSpawn; + + public PlayerBedLeaveEvent(@NotNull final Player who, @NotNull final Block bed, boolean setBedSpawn) { + super(who); + this.bed = bed; + this.setBedSpawn = setBedSpawn; + } + + /** + * Returns the bed block involved in this event. + * + * @return the bed block involved in this event + */ + @NotNull + public Block getBed() { + return bed; + } + + /** + * Get if this event should set the new spawn location for the + * {@link Player}. + *
+ * This does not remove any existing spawn location, only prevent it from + * being changed (if true). + *
+ * To change a {@link Player}'s spawn location, use + * {@link Player#setBedSpawnLocation(Location)}. + * + * @return true if the spawn location will be changed + */ + public boolean shouldSetSpawnLocation() { + return setBedSpawn; + } + + /** + * Set if this event should set the new spawn location for the + * {@link Player}. + *
+ * This will not remove any existing spawn location, only prevent it from + * being changed (if true). + *
+ * To change a {@link Player}'s spawn location, use + * {@link Player#setBedSpawnLocation(Location)}. + * + * @param setBedSpawn true to change the new spawn location + */ + public void setSpawnLocation(boolean setBedSpawn) { + this.setBedSpawn = setBedSpawn; + } + + @NotNull + @Override + public HandlerList getHandlers() { + return handlers; + } + + @NotNull + public static HandlerList getHandlerList() { + return handlers; + } +} diff --git a/api/src/main/java/org/bukkit/event/player/PlayerBucketEmptyEvent.java b/api/src/main/java/org/bukkit/event/player/PlayerBucketEmptyEvent.java new file mode 100644 index 000000000..5e93f1bbb --- /dev/null +++ b/api/src/main/java/org/bukkit/event/player/PlayerBucketEmptyEvent.java @@ -0,0 +1,38 @@ +package org.bukkit.event.player; + +import org.bukkit.Material; +import org.bukkit.block.Block; +import org.bukkit.block.BlockFace; +import org.bukkit.entity.Player; +import org.bukkit.event.HandlerList; +import org.bukkit.inventory.EquipmentSlot; +import org.bukkit.inventory.ItemStack; +import org.jetbrains.annotations.NotNull; + +/** + * Called when a player empties a bucket + */ +public class PlayerBucketEmptyEvent extends PlayerBucketEvent { + private static final HandlerList handlers = new HandlerList(); + + public PlayerBucketEmptyEvent(@NotNull final Player who, @NotNull final Block blockClicked, @NotNull final BlockFace blockFace, @NotNull final Material bucket, @NotNull final ItemStack itemInHand) { + super(who, blockClicked, blockFace, bucket, itemInHand); + } + + // Paper start - add EquipmentSlot + public PlayerBucketEmptyEvent(@NotNull final Player who, @NotNull final Block blockClicked, @NotNull final BlockFace blockFace, @NotNull final Material bucket, @NotNull final ItemStack itemInHand, @org.jetbrains.annotations.Nullable final EquipmentSlot hand) { + super(who, blockClicked, blockFace, bucket, itemInHand, hand); + } + // Paper end + + @NotNull + @Override + public HandlerList getHandlers() { + return handlers; + } + + @NotNull + public static HandlerList getHandlerList() { + return handlers; + } +} diff --git a/api/src/main/java/org/bukkit/event/player/PlayerBucketEvent.java b/api/src/main/java/org/bukkit/event/player/PlayerBucketEvent.java new file mode 100644 index 000000000..280ca87bb --- /dev/null +++ b/api/src/main/java/org/bukkit/event/player/PlayerBucketEvent.java @@ -0,0 +1,107 @@ +package org.bukkit.event.player; + +import org.bukkit.Material; +import org.bukkit.block.Block; +import org.bukkit.block.BlockFace; +import org.bukkit.entity.Player; +import org.bukkit.event.Cancellable; +import org.bukkit.inventory.EquipmentSlot; +import org.bukkit.inventory.ItemStack; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +/** + * Called when a player interacts with a Bucket + */ +public abstract class PlayerBucketEvent extends PlayerEvent implements Cancellable { + private ItemStack itemStack; + private boolean cancelled = false; + private final Block blockClicked; + private final BlockFace blockFace; + private final Material bucket; + private final EquipmentSlot hand; // Paper - add EquipmentSlot + + public PlayerBucketEvent(@NotNull final Player who, @NotNull final Block blockClicked, @NotNull final BlockFace blockFace, @NotNull final Material bucket, @NotNull final ItemStack itemInHand) { + // Paper start - add EquipmentSlot + this(who, blockClicked, blockFace, bucket, itemInHand, null); + } + + public PlayerBucketEvent(@NotNull final Player who, @NotNull final Block blockClicked, @NotNull final BlockFace blockFace, @NotNull final Material bucket, @NotNull final ItemStack itemInHand, @Nullable final EquipmentSlot hand) { + // Paper end + super(who); + this.blockClicked = blockClicked; + this.blockFace = blockFace; + this.itemStack = itemInHand; + this.bucket = bucket; + this.hand = hand == null ? player.getInventory().getItemInMainHand().equals(itemInHand) ? EquipmentSlot.HAND : EquipmentSlot.OFF_HAND : hand; // Paper - add EquipmentSlot + } + + /** + * Returns the bucket used in this event + * + * @return the used bucket + */ + @NotNull + public Material getBucket() { + return bucket; + } + + /** + * Get the resulting item in hand after the bucket event + * + * @return ItemStack hold in hand after the event. + */ + @Nullable + public ItemStack getItemStack() { + return itemStack; + } + + /** + * Set the item in hand after the event + * + * @param itemStack the new held ItemStack after the bucket event. + */ + public void setItemStack(@Nullable ItemStack itemStack) { + this.itemStack = itemStack; + } + + /** + * Return the block clicked + * + * @return the clicked block + */ + @NotNull + public Block getBlockClicked() { + return blockClicked; + } + + /** + * Get the face on the clicked block + * + * @return the clicked face + */ + @NotNull + public BlockFace getBlockFace() { + return blockFace; + } + + // Paper start + /** + * The hand used to perform this action. + * + * @return the hand used + */ + @NotNull + public EquipmentSlot getHand() { + return hand; + } + // Paper end + + public boolean isCancelled() { + return cancelled; + } + + public void setCancelled(boolean cancel) { + this.cancelled = cancel; + } +} diff --git a/api/src/main/java/org/bukkit/event/player/PlayerBucketFillEvent.java b/api/src/main/java/org/bukkit/event/player/PlayerBucketFillEvent.java new file mode 100644 index 000000000..15be7128b --- /dev/null +++ b/api/src/main/java/org/bukkit/event/player/PlayerBucketFillEvent.java @@ -0,0 +1,39 @@ +package org.bukkit.event.player; + +import org.bukkit.Material; +import org.bukkit.block.Block; +import org.bukkit.block.BlockFace; +import org.bukkit.entity.Player; +import org.bukkit.event.HandlerList; +import org.bukkit.inventory.EquipmentSlot; +import org.bukkit.inventory.ItemStack; +import org.jetbrains.annotations.NotNull; + +/** + * Called when a player fills a bucket + */ +public class PlayerBucketFillEvent extends PlayerBucketEvent { + private static final HandlerList handlers = new HandlerList(); + + public PlayerBucketFillEvent(@NotNull final Player who, @NotNull final Block blockClicked, @NotNull final BlockFace blockFace, @NotNull final Material bucket, @NotNull final ItemStack itemInHand) { + super(who, blockClicked, blockFace, bucket, itemInHand); + } + + + // Paper start - add EquipmentSlot + public PlayerBucketFillEvent(@NotNull final Player who, @NotNull final Block blockClicked, @NotNull final BlockFace blockFace, @NotNull final Material bucket, @NotNull final ItemStack itemInHand, @org.jetbrains.annotations.Nullable final EquipmentSlot hand) { + super(who, blockClicked, blockFace, bucket, itemInHand, hand); + } + // Paper end + + @NotNull + @Override + public HandlerList getHandlers() { + return handlers; + } + + @NotNull + public static HandlerList getHandlerList() { + return handlers; + } +} diff --git a/api/src/main/java/org/bukkit/event/player/PlayerChangedMainHandEvent.java b/api/src/main/java/org/bukkit/event/player/PlayerChangedMainHandEvent.java new file mode 100644 index 000000000..6070b1323 --- /dev/null +++ b/api/src/main/java/org/bukkit/event/player/PlayerChangedMainHandEvent.java @@ -0,0 +1,43 @@ +package org.bukkit.event.player; + +import org.bukkit.entity.Player; +import org.bukkit.event.HandlerList; +import org.bukkit.inventory.MainHand; +import org.jetbrains.annotations.NotNull; + +/** + * Called when a player changes their main hand in the client settings. + */ +public class PlayerChangedMainHandEvent extends PlayerEvent { + + private static final HandlerList handlers = new HandlerList(); + // + private final MainHand mainHand; + + public PlayerChangedMainHandEvent(@NotNull Player who, @NotNull MainHand mainHand) { + super(who); + this.mainHand = mainHand; + } + + /** + * Gets the new main hand of the player. The old hand is still momentarily + * available via {@link Player#getMainHand()}. + * + * @return the new {@link MainHand} of the player + */ + @NotNull + public MainHand getMainHand() { + return mainHand; + } + + @NotNull + @Override + public HandlerList getHandlers() { + return handlers; + } + + @NotNull + public static HandlerList getHandlerList() { + return handlers; + } +} diff --git a/api/src/main/java/org/bukkit/event/player/PlayerChangedWorldEvent.java b/api/src/main/java/org/bukkit/event/player/PlayerChangedWorldEvent.java new file mode 100644 index 000000000..f1d1d1e9b --- /dev/null +++ b/api/src/main/java/org/bukkit/event/player/PlayerChangedWorldEvent.java @@ -0,0 +1,40 @@ +package org.bukkit.event.player; + +import org.bukkit.World; +import org.bukkit.entity.Player; +import org.bukkit.event.HandlerList; +import org.jetbrains.annotations.NotNull; + +/** + * Called when a player switches to another world. + */ +public class PlayerChangedWorldEvent extends PlayerEvent { + private static final HandlerList handlers = new HandlerList(); + private final World from; + + public PlayerChangedWorldEvent(@NotNull final Player player, @NotNull final World from) { + super(player); + this.from = from; + } + + /** + * Gets the world the player is switching from. + * + * @return player's previous world + */ + @NotNull + public World getFrom() { + return from; + } + + @NotNull + @Override + public HandlerList getHandlers() { + return handlers; + } + + @NotNull + public static HandlerList getHandlerList() { + return handlers; + } +} diff --git a/api/src/main/java/org/bukkit/event/player/PlayerChannelEvent.java b/api/src/main/java/org/bukkit/event/player/PlayerChannelEvent.java new file mode 100644 index 000000000..9316a525f --- /dev/null +++ b/api/src/main/java/org/bukkit/event/player/PlayerChannelEvent.java @@ -0,0 +1,35 @@ +package org.bukkit.event.player; + +import org.bukkit.entity.Player; +import org.bukkit.event.HandlerList; +import org.jetbrains.annotations.NotNull; + +/** + * This event is called after a player registers or unregisters a new plugin + * channel. + */ +public abstract class PlayerChannelEvent extends PlayerEvent { + private static final HandlerList handlers = new HandlerList(); + private final String channel; + + public PlayerChannelEvent(@NotNull final Player player, @NotNull final String channel) { + super(player); + this.channel = channel; + } + + @NotNull + public final String getChannel() { + return channel; + } + + @NotNull + @Override + public HandlerList getHandlers() { + return handlers; + } + + @NotNull + public static HandlerList getHandlerList() { + return handlers; + } +} diff --git a/api/src/main/java/org/bukkit/event/player/PlayerChatEvent.java b/api/src/main/java/org/bukkit/event/player/PlayerChatEvent.java new file mode 100644 index 000000000..fec303c9c --- /dev/null +++ b/api/src/main/java/org/bukkit/event/player/PlayerChatEvent.java @@ -0,0 +1,131 @@ +package org.bukkit.event.player; + +import java.util.HashSet; +import java.util.Set; + +import org.apache.commons.lang.Validate; +import org.bukkit.Warning; +import org.bukkit.entity.Player; +import org.bukkit.event.Cancellable; +import org.bukkit.event.HandlerList; +import org.jetbrains.annotations.NotNull; + +/** + * Holds information for player chat and commands + * + * @deprecated This event will fire from the main thread and allows the use of + * all of the Bukkit API, unlike the {@link AsyncPlayerChatEvent}. + *

+ * Listening to this event forces chat to wait for the main thread which + * causes delays for chat. {@link AsyncPlayerChatEvent} is the encouraged + * alternative for thread safe implementations. + */ +@Deprecated +@Warning(reason="Listening to this event forces chat to wait for the main thread, delaying chat messages.") +public class PlayerChatEvent extends PlayerEvent implements Cancellable { + private static final HandlerList handlers = new HandlerList(); + private boolean cancel = false; + private String message; + private String format; + private final Set recipients; + + public PlayerChatEvent(@NotNull final Player player, @NotNull final String message) { + super(player); + this.message = message; + this.format = "<%1$s> %2$s"; + this.recipients = new HashSet(player.getServer().getOnlinePlayers()); + } + + public PlayerChatEvent(@NotNull final Player player, @NotNull final String message, @NotNull final String format, @NotNull final Set recipients) { + super(player); + this.message = message; + this.format = format; + this.recipients = recipients; + } + + public boolean isCancelled() { + return cancel; + } + + public void setCancelled(boolean cancel) { + this.cancel = cancel; + } + + /** + * Gets the message that the player is attempting to send + * + * @return Message the player is attempting to send + */ + @NotNull + public String getMessage() { + return message; + } + + /** + * Sets the message that the player will send + * + * @param message New message that the player will send + */ + public void setMessage(@NotNull String message) { + this.message = message; + } + + /** + * Sets the player that this message will display as, or command will be + * executed as + * + * @param player New player which this event will execute as + */ + public void setPlayer(@NotNull final Player player) { + Validate.notNull(player, "Player cannot be null"); + this.player = player; + } + + /** + * Gets the format to use to display this chat message + * + * @return String.Format compatible format string + */ + @NotNull + public String getFormat() { + return format; + } + + /** + * Sets the format to use to display this chat message + * + * @param format String.Format compatible format string + */ + public void setFormat(@NotNull final String format) { + // Oh for a better way to do this! + try { + String.format(format, player, message); + } catch (RuntimeException ex) { + ex.fillInStackTrace(); + throw ex; + } + + this.format = format; + } + + /** + * Gets a set of recipients that this chat message will be displayed to + * + * @return All Players who will see this chat message + */ + @NotNull + public Set getRecipients() { + return recipients; + } + + @NotNull + @Override + public HandlerList getHandlers() { + return handlers; + } + + @NotNull + public static HandlerList getHandlerList() { + return handlers; + } +} diff --git a/api/src/main/java/org/bukkit/event/player/PlayerChatTabCompleteEvent.java b/api/src/main/java/org/bukkit/event/player/PlayerChatTabCompleteEvent.java new file mode 100644 index 000000000..c3ac37928 --- /dev/null +++ b/api/src/main/java/org/bukkit/event/player/PlayerChatTabCompleteEvent.java @@ -0,0 +1,81 @@ +package org.bukkit.event.player; + +import java.util.Collection; + +import org.apache.commons.lang.Validate; +import org.bukkit.Warning; +import org.bukkit.entity.Player; +import org.bukkit.event.HandlerList; +import org.jetbrains.annotations.NotNull; + +/** + * Called when a player attempts to tab-complete a chat message. + * + * @deprecated This event is no longer fired due to client changes + */ +@Deprecated +@Warning(reason = "This event is no longer fired due to client changes") +public class PlayerChatTabCompleteEvent extends PlayerEvent { + private static final HandlerList handlers = new HandlerList(); + private final String message; + private final String lastToken; + private final Collection completions; + + public PlayerChatTabCompleteEvent(@NotNull final Player who, @NotNull final String message, @NotNull final Collection completions) { + super(who); + Validate.notNull(message, "Message cannot be null"); + Validate.notNull(completions, "Completions cannot be null"); + this.message = message; + int i = message.lastIndexOf(' '); + if (i < 0) { + this.lastToken = message; + } else { + this.lastToken = message.substring(i + 1); + } + this.completions = completions; + } + + /** + * Gets the chat message being tab-completed. + * + * @return the chat message + */ + @NotNull + public String getChatMessage() { + return message; + } + + /** + * Gets the last 'token' of the message being tab-completed. + *

+ * The token is the substring starting with the character after the last + * space in the message. + * + * @return The last token for the chat message + */ + @NotNull + public String getLastToken() { + return lastToken; + } + + /** + * This is the collection of completions for this event. + * + * @return the current completions + */ + @NotNull + public Collection getTabCompletions() { + return completions; + } + + @NotNull + @Override + public HandlerList getHandlers() { + return handlers; + } + + @NotNull + public static HandlerList getHandlerList() { + return handlers; + } +} diff --git a/api/src/main/java/org/bukkit/event/player/PlayerCommandPreprocessEvent.java b/api/src/main/java/org/bukkit/event/player/PlayerCommandPreprocessEvent.java new file mode 100644 index 000000000..66c930da0 --- /dev/null +++ b/api/src/main/java/org/bukkit/event/player/PlayerCommandPreprocessEvent.java @@ -0,0 +1,144 @@ +package org.bukkit.event.player; + +import java.util.HashSet; +import java.util.Set; + +import org.apache.commons.lang.Validate; +import org.bukkit.entity.Player; +import org.bukkit.event.Cancellable; +import org.bukkit.event.HandlerList; +import org.jetbrains.annotations.NotNull; + +/** + * This event is called whenever a player runs a command (by placing a slash + * at the start of their message). It is called early in the command handling + * process, and modifications in this event (via {@link #setMessage(String)}) + * will be shown in the behavior. + *

+ * Many plugins will have no use for this event, and you should + * attempt to avoid using it if it is not necessary. + *

+ * Some examples of valid uses for this event are: + *

    + *
  • Logging executed commands to a separate file + *
  • Variable substitution. For example, replacing + * ${nearbyPlayer} with the name of the nearest other + * player, or simulating the @a and @p + * decorators used by Command Blocks in plugins that do not handle it. + *
  • Conditionally blocking commands belonging to other plugins. For + * example, blocking the use of the /home command in a + * combat arena. + *
  • Per-sender command aliases. For example, after a player runs the + * command /calias cr gamemode creative, the next time they + * run /cr, it gets replaced into + * /gamemode creative. (Global command aliases should be + * done by registering the alias.) + *
+ *

+ * Examples of incorrect uses are: + *

    + *
  • Using this event to run command logic + *
+ *

+ * If the event is cancelled, processing of the command will halt. + *

+ * The state of whether or not there is a slash (/) at the + * beginning of the message should be preserved. If a slash is added or + * removed, unexpected behavior may result. + */ +public class PlayerCommandPreprocessEvent extends PlayerEvent implements Cancellable { + private static final HandlerList handlers = new HandlerList(); + private boolean cancel = false; + private String message; + private final Set recipients; + + public PlayerCommandPreprocessEvent(@NotNull final Player player, @NotNull final String message) { + super(player); + this.recipients = new HashSet(player.getServer().getOnlinePlayers()); + this.message = message; + } + + public PlayerCommandPreprocessEvent(@NotNull final Player player, @NotNull final String message, @NotNull final Set recipients) { + super(player); + this.recipients = recipients; + this.message = message; + } + + public boolean isCancelled() { + return cancel; + } + + public void setCancelled(boolean cancel) { + this.cancel = cancel; + } + + /** + * Gets the command that the player is attempting to send. + *

+ * All commands begin with a special character; implementations do not + * consider the first character when executing the content. + * + * @return Message the player is attempting to send + */ + @NotNull + public String getMessage() { + return message; + } + + /** + * Sets the command that the player will send. + *

+ * All commands begin with a special character; implementations do not + * consider the first character when executing the content. + * + * @param command New message that the player will send + * @throws IllegalArgumentException if command is null or empty + */ + public void setMessage(@NotNull String command) throws IllegalArgumentException { + Validate.notNull(command, "Command cannot be null"); + Validate.notEmpty(command, "Command cannot be empty"); + this.message = command; + } + + /** + * Sets the player that this command will be executed as. + * + * @param player New player which this event will execute as + * @throws IllegalArgumentException if the player provided is null + */ + public void setPlayer(@NotNull final Player player) throws IllegalArgumentException { + Validate.notNull(player, "Player cannot be null"); + this.player = player; + } + + /** + * Gets a set of recipients that this chat message will be displayed to. + *

+ * The set returned is not guaranteed to be mutable and may auto-populate + * on access. Any listener accessing the returned set should be aware that + * it may reduce performance for a lazy set implementation. Listeners + * should be aware that modifying the list may throw {@link + * UnsupportedOperationException} if the event caller provides an + * unmodifiable set. + * + * @deprecated This method is provided for backward compatibility with no + * guarantee to the effect of viewing or modifying the set. + * @return All Players who will see this chat message + */ + @NotNull + @Deprecated + public Set getRecipients() { + return recipients; + } + + @NotNull + @Override + public HandlerList getHandlers() { + return handlers; + } + + @NotNull + public static HandlerList getHandlerList() { + return handlers; + } +} diff --git a/api/src/main/java/org/bukkit/event/player/PlayerCommandSendEvent.java b/api/src/main/java/org/bukkit/event/player/PlayerCommandSendEvent.java new file mode 100644 index 000000000..762825997 --- /dev/null +++ b/api/src/main/java/org/bukkit/event/player/PlayerCommandSendEvent.java @@ -0,0 +1,50 @@ +package org.bukkit.event.player; + +import java.util.Collection; +import org.bukkit.entity.Player; +import org.bukkit.event.HandlerList; +import org.jetbrains.annotations.NotNull; + +/** + * This event is called when the list of available server commands is sent to + * the player. + *
+ * Commands may be removed from display using this event, but implementations + * are not required to securely remove all traces of the command. If secure + * removal of commands is required, then the command should be assigned a + * permission which is not granted to the player. + */ +public class PlayerCommandSendEvent extends PlayerEvent { + + private static final HandlerList handlers = new HandlerList(); + private final Collection commands; + + public PlayerCommandSendEvent(@NotNull final Player player, @NotNull final Collection commands) { + super(player); + this.commands = commands; + } + + /** + * Returns a mutable collection of all top level commands to be sent. + *
+ * It is not legal to add entries to this collection, only remove them. + * Behaviour of adding entries is undefined. + * + * @return collection of all commands + */ + @NotNull + public Collection getCommands() { + return commands; + } + + @NotNull + @Override + public HandlerList getHandlers() { + return handlers; + } + + @NotNull + public static HandlerList getHandlerList() { + return handlers; + } +} diff --git a/api/src/main/java/org/bukkit/event/player/PlayerDropItemEvent.java b/api/src/main/java/org/bukkit/event/player/PlayerDropItemEvent.java new file mode 100644 index 000000000..4ac31e122 --- /dev/null +++ b/api/src/main/java/org/bukkit/event/player/PlayerDropItemEvent.java @@ -0,0 +1,50 @@ +package org.bukkit.event.player; + +import org.bukkit.entity.Item; +import org.bukkit.entity.Player; +import org.bukkit.event.Cancellable; +import org.bukkit.event.HandlerList; +import org.jetbrains.annotations.NotNull; + +/** + * Thrown when a player drops an item from their inventory + */ +public class PlayerDropItemEvent extends PlayerEvent implements Cancellable { + private static final HandlerList handlers = new HandlerList(); + private final Item drop; + private boolean cancel = false; + + public PlayerDropItemEvent(@NotNull final Player player, @NotNull final Item drop) { + super(player); + this.drop = drop; + } + + /** + * Gets the ItemDrop created by the player + * + * @return ItemDrop created by the player + */ + @NotNull + public Item getItemDrop() { + return drop; + } + + public boolean isCancelled() { + return cancel; + } + + public void setCancelled(boolean cancel) { + this.cancel = cancel; + } + + @NotNull + @Override + public HandlerList getHandlers() { + return handlers; + } + + @NotNull + public static HandlerList getHandlerList() { + return handlers; + } +} diff --git a/api/src/main/java/org/bukkit/event/player/PlayerEditBookEvent.java b/api/src/main/java/org/bukkit/event/player/PlayerEditBookEvent.java new file mode 100644 index 000000000..44e62e61b --- /dev/null +++ b/api/src/main/java/org/bukkit/event/player/PlayerEditBookEvent.java @@ -0,0 +1,132 @@ +package org.bukkit.event.player; + +import org.apache.commons.lang.Validate; +import org.bukkit.Bukkit; +import org.bukkit.entity.Player; +import org.bukkit.event.Cancellable; +import org.bukkit.event.HandlerList; +import org.bukkit.inventory.meta.BookMeta; +import org.jetbrains.annotations.NotNull; + +/** + * Called when a player edits or signs a book and quill item. If the event is + * cancelled, no changes are made to the BookMeta + */ +public class PlayerEditBookEvent extends PlayerEvent implements Cancellable { + private static final HandlerList handlers = new HandlerList(); + + private final BookMeta previousBookMeta; + private final int slot; + private BookMeta newBookMeta; + private boolean isSigning; + private boolean cancel; + + public PlayerEditBookEvent(@NotNull Player who, int slot, @NotNull BookMeta previousBookMeta, @NotNull BookMeta newBookMeta, boolean isSigning) { + super(who); + + Validate.isTrue(slot >= -1 && slot <= 8, "Slot must be in range (-1)-8 inclusive"); + Validate.notNull(previousBookMeta, "Previous book meta must not be null"); + Validate.notNull(newBookMeta, "New book meta must not be null"); + + Bukkit.getItemFactory().equals(previousBookMeta, newBookMeta); + + this.previousBookMeta = previousBookMeta; + this.newBookMeta = newBookMeta; + this.slot = slot; + this.isSigning = isSigning; + this.cancel = false; + } + + /** + * Gets the book meta currently on the book. + *

+ * Note: this is a copy of the book meta. You cannot use this object to + * change the existing book meta. + * + * @return the book meta currently on the book + */ + @NotNull + public BookMeta getPreviousBookMeta() { + return previousBookMeta.clone(); + } + + /** + * Gets the book meta that the player is attempting to add to the book. + *

+ * Note: this is a copy of the proposed new book meta. Use {@link + * #setNewBookMeta(BookMeta)} to change what will actually be added to the + * book. + * + * @return the book meta that the player is attempting to add + */ + @NotNull + public BookMeta getNewBookMeta() { + return newBookMeta.clone(); + } + + /** + * Gets the inventory slot number for the book item that triggered this + * event. + *

+ * This is a slot number on the player's hotbar in the range 0-8, or -1 for + * off hand. + * + * @return the inventory slot number that the book item occupies + * @deprecated books may be signed from off hand + */ + @Deprecated + public int getSlot() { + return slot; + } + + /** + * Sets the book meta that will actually be added to the book. + * + * @param newBookMeta new book meta + * @throws IllegalArgumentException if the new book meta is null + */ + public void setNewBookMeta(@NotNull BookMeta newBookMeta) throws IllegalArgumentException { + Validate.notNull(newBookMeta, "New book meta must not be null"); + Bukkit.getItemFactory().equals(newBookMeta, null); + this.newBookMeta = newBookMeta.clone(); + } + + /** + * Gets whether or not the book is being signed. If a book is signed the + * Material changes from BOOK_AND_QUILL to WRITTEN_BOOK. + * + * @return true if the book is being signed + */ + public boolean isSigning() { + return isSigning; + } + + /** + * Sets whether or not the book is being signed. If a book is signed the + * Material changes from BOOK_AND_QUILL to WRITTEN_BOOK. + * + * @param signing whether or not the book is being signed. + */ + public void setSigning(boolean signing) { + isSigning = signing; + } + + @NotNull + @Override + public HandlerList getHandlers() { + return handlers; + } + + @NotNull + public static HandlerList getHandlerList() { + return handlers; + } + + public boolean isCancelled() { + return cancel; + } + + public void setCancelled(boolean cancel) { + this.cancel = cancel; + } +} diff --git a/api/src/main/java/org/bukkit/event/player/PlayerEggThrowEvent.java b/api/src/main/java/org/bukkit/event/player/PlayerEggThrowEvent.java new file mode 100644 index 000000000..1712dae89 --- /dev/null +++ b/api/src/main/java/org/bukkit/event/player/PlayerEggThrowEvent.java @@ -0,0 +1,114 @@ +package org.bukkit.event.player; + +import org.bukkit.entity.Egg; +import org.bukkit.entity.EntityType; +import org.bukkit.entity.Player; +import org.bukkit.event.HandlerList; +import org.jetbrains.annotations.NotNull; + +/** + * Called when a player throws an egg and it might hatch + */ +public class PlayerEggThrowEvent extends PlayerEvent { + private static final HandlerList handlers = new HandlerList(); + private final Egg egg; + private boolean hatching; + private EntityType hatchType; + private byte numHatches; + + public PlayerEggThrowEvent(@NotNull final Player player, @NotNull final Egg egg, final boolean hatching, final byte numHatches, @NotNull final EntityType hatchingType) { + super(player); + this.egg = egg; + this.hatching = hatching; + this.numHatches = numHatches; + this.hatchType = hatchingType; + } + + /** + * Gets the egg involved in this event. + * + * @return the egg involved in this event + */ + @NotNull + public Egg getEgg() { + return egg; + } + + /** + * Gets whether the egg is hatching or not. Will be what the server + * would've done without interaction. + * + * @return boolean Whether the egg is going to hatch or not + */ + public boolean isHatching() { + return hatching; + } + + /** + * Sets whether the egg will hatch or not. + * + * @param hatching true if you want the egg to hatch, false if you want it + * not to + */ + public void setHatching(boolean hatching) { + this.hatching = hatching; + } + + /** + * Get the type of the mob being hatched (EntityType.CHICKEN by default) + * + * @return The type of the mob being hatched by the egg + */ + @NotNull + public EntityType getHatchingType() { + return hatchType; + } + + /** + * Change the type of mob being hatched by the egg + * + * @param hatchType The type of the mob being hatched by the egg + */ + public void setHatchingType(@NotNull EntityType hatchType) { + if(!hatchType.isSpawnable()) throw new IllegalArgumentException("Can't spawn that entity type from an egg!"); + this.hatchType = hatchType; + } + + /** + * Get the number of mob hatches from the egg. By default the number will + * be the number the server would've done + *

    + *
  • 7/8 chance of being 0 + *
  • 31/256 ~= 1/8 chance to be 1 + *
  • 1/256 chance to be 4 + *
+ * + * @return The number of mobs going to be hatched by the egg + */ + public byte getNumHatches() { + return numHatches; + } + + /** + * Change the number of mobs coming out of the hatched egg + *

+ * The boolean hatching will override this number. Ie. If hatching = + * false, this number will not matter + * + * @param numHatches The number of mobs coming out of the egg + */ + public void setNumHatches(byte numHatches) { + this.numHatches = numHatches; + } + + @NotNull + @Override + public HandlerList getHandlers() { + return handlers; + } + + @NotNull + public static HandlerList getHandlerList() { + return handlers; + } +} diff --git a/api/src/main/java/org/bukkit/event/player/PlayerEvent.java b/api/src/main/java/org/bukkit/event/player/PlayerEvent.java new file mode 100644 index 000000000..793b661b6 --- /dev/null +++ b/api/src/main/java/org/bukkit/event/player/PlayerEvent.java @@ -0,0 +1,32 @@ +package org.bukkit.event.player; + +import org.bukkit.entity.Player; +import org.bukkit.event.Event; +import org.jetbrains.annotations.NotNull; + +/** + * Represents a player related event + */ +public abstract class PlayerEvent extends Event { + protected Player player; + + public PlayerEvent(@NotNull final Player who) { + player = who; + } + + PlayerEvent(@NotNull final Player who, boolean async) { + super(async); + player = who; + + } + + /** + * Returns the player involved in this event + * + * @return Player who is involved in this event + */ + @NotNull + public final Player getPlayer() { + return player; + } +} diff --git a/api/src/main/java/org/bukkit/event/player/PlayerExpChangeEvent.java b/api/src/main/java/org/bukkit/event/player/PlayerExpChangeEvent.java new file mode 100644 index 000000000..7c340f539 --- /dev/null +++ b/api/src/main/java/org/bukkit/event/player/PlayerExpChangeEvent.java @@ -0,0 +1,69 @@ +package org.bukkit.event.player; + +import org.bukkit.entity.Entity; // Paper +import org.bukkit.entity.Player; +import org.bukkit.event.HandlerList; +import org.jetbrains.annotations.NotNull; + +import org.jetbrains.annotations.Nullable; // Paper + +/** + * Called when a players experience changes naturally + */ +public class PlayerExpChangeEvent extends PlayerEvent { + private static final HandlerList handlers = new HandlerList(); + // Paper start + @Nullable + private final Entity source; + private int exp; + + public PlayerExpChangeEvent(@NotNull final Player player, final int expAmount) { + this(player, null, expAmount); + } + + public PlayerExpChangeEvent(@NotNull final Player player, @Nullable final Entity sourceEntity, final int expAmount) { + super(player); + source = sourceEntity; + exp = expAmount; + } + + /** + * Get the source that provided the experience. + * + * @return The source of the experience + */ + @Nullable + public Entity getSource() { + return source; + } + // Paper end + + /** + * Get the amount of experience the player will receive + * + * @return The amount of experience + */ + public int getAmount() { + return exp; + } + + /** + * Set the amount of experience the player will receive + * + * @param amount The amount of experience to set + */ + public void setAmount(int amount) { + exp = amount; + } + + @NotNull + @Override + public HandlerList getHandlers() { + return handlers; + } + + @NotNull + public static HandlerList getHandlerList() { + return handlers; + } +} diff --git a/api/src/main/java/org/bukkit/event/player/PlayerFishEvent.java b/api/src/main/java/org/bukkit/event/player/PlayerFishEvent.java new file mode 100644 index 000000000..e31c1492c --- /dev/null +++ b/api/src/main/java/org/bukkit/event/player/PlayerFishEvent.java @@ -0,0 +1,142 @@ +package org.bukkit.event.player; + +import org.bukkit.entity.Player; +import org.bukkit.event.Cancellable; +import org.bukkit.entity.Entity; +import org.bukkit.entity.FishHook; +import org.bukkit.event.HandlerList; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +/** + * Thrown when a player is fishing + */ +public class PlayerFishEvent extends PlayerEvent implements Cancellable { + private static final HandlerList handlers = new HandlerList(); + private final Entity entity; + private boolean cancel = false; + private int exp; + private final State state; + private final FishHook hookEntity; + + public PlayerFishEvent(@NotNull final Player player, @Nullable final Entity entity, @NotNull final FishHook hookEntity, @NotNull final State state) { + super(player); + this.entity = entity; + this.hookEntity = hookEntity; + this.state = state; + } + + /** + * Gets the entity caught by the player. + *

+ * If player has fished successfully, the result may be cast to {@link + * org.bukkit.entity.Item}. + * + * @return Entity caught by the player, Entity if fishing, and null if + * bobber has gotten stuck in the ground or nothing has been caught + */ + @Nullable + public Entity getCaught() { + return entity; + } + + /** + * Gets the fishing hook. + * + * @return the entity representing the fishing hook/bobber. + */ + @NotNull + public FishHook getHook() { + return hookEntity; + } + + public boolean isCancelled() { + return cancel; + } + + public void setCancelled(boolean cancel) { + this.cancel = cancel; + } + + /** + * Gets the amount of experience received when fishing. + *

+ * Note: This value has no default effect unless the event state is {@link + * State#CAUGHT_FISH}. + * + * @return the amount of experience to drop + */ + public int getExpToDrop() { + return exp; + } + + /** + * Sets the amount of experience received when fishing. + *

+ * Note: This value has no default effect unless the event state is {@link + * State#CAUGHT_FISH}. + * + * @param amount the amount of experience to drop + */ + public void setExpToDrop(int amount) { + exp = amount; + } + + /** + * Gets the state of the fishing + * + * @return A State detailing the state of the fishing + */ + @NotNull + public State getState() { + return state; + } + + @NotNull + @Override + public HandlerList getHandlers() { + return handlers; + } + + @NotNull + public static HandlerList getHandlerList() { + return handlers; + } + + /** + * An enum to specify the state of the fishing + */ + public enum State { + + /** + * When a player is fishing, ie casting the line out. + */ + FISHING, + /** + * When a player has successfully caught a fish and is reeling it in. In + * this instance, a "fish" is any item retrieved from water as a result + * of fishing, ie an item, but not necessarily a fish. + */ + CAUGHT_FISH, + /** + * When a player has successfully caught an entity. This refers to any + * already spawned entity in the world that has been hooked directly by + * the rod. + */ + CAUGHT_ENTITY, + /** + * When a bobber is stuck in the ground. + */ + IN_GROUND, + /** + * When a player fails to catch anything while fishing usually due to + * poor aiming or timing. + */ + FAILED_ATTEMPT, + /** + * Called when there is a bite on the hook and it is ready to be reeled + * in. + */ + BITE + } +} diff --git a/api/src/main/java/org/bukkit/event/player/PlayerGameModeChangeEvent.java b/api/src/main/java/org/bukkit/event/player/PlayerGameModeChangeEvent.java new file mode 100644 index 000000000..020faee1c --- /dev/null +++ b/api/src/main/java/org/bukkit/event/player/PlayerGameModeChangeEvent.java @@ -0,0 +1,50 @@ +package org.bukkit.event.player; + +import org.bukkit.GameMode; +import org.bukkit.entity.Player; +import org.bukkit.event.Cancellable; +import org.bukkit.event.HandlerList; +import org.jetbrains.annotations.NotNull; + +/** + * Called when the GameMode of the player is changed. + */ +public class PlayerGameModeChangeEvent extends PlayerEvent implements Cancellable { + private static final HandlerList handlers = new HandlerList(); + private boolean cancelled; + private final GameMode newGameMode; + + public PlayerGameModeChangeEvent(@NotNull final Player player, @NotNull final GameMode newGameMode) { + super(player); + this.newGameMode = newGameMode; + } + + public boolean isCancelled() { + return cancelled; + } + + public void setCancelled(boolean cancel) { + this.cancelled = cancel; + } + + /** + * Gets the GameMode the player is switched to. + * + * @return player's new GameMode + */ + @NotNull + public GameMode getNewGameMode() { + return newGameMode; + } + + @NotNull + @Override + public HandlerList getHandlers() { + return handlers; + } + + @NotNull + public static HandlerList getHandlerList() { + return handlers; + } +} diff --git a/api/src/main/java/org/bukkit/event/player/PlayerInteractAtEntityEvent.java b/api/src/main/java/org/bukkit/event/player/PlayerInteractAtEntityEvent.java new file mode 100644 index 000000000..1075dbb81 --- /dev/null +++ b/api/src/main/java/org/bukkit/event/player/PlayerInteractAtEntityEvent.java @@ -0,0 +1,45 @@ +package org.bukkit.event.player; + +import org.bukkit.entity.Entity; +import org.bukkit.entity.Player; +import org.bukkit.event.HandlerList; +import org.bukkit.inventory.EquipmentSlot; +import org.bukkit.util.Vector; +import org.jetbrains.annotations.NotNull; + +/** + * Represents an event that is called when a player right clicks an entity that + * also contains the location where the entity was clicked. + *
+ * Note that the client may sometimes spuriously send this packet in addition to {@link PlayerInteractEntityEvent}. + * Users are advised to listen to this (parent) class unless specifically required. + */ +public class PlayerInteractAtEntityEvent extends PlayerInteractEntityEvent { + private static final HandlerList handlers = new HandlerList(); + private final Vector position; + + public PlayerInteractAtEntityEvent(@NotNull Player who, @NotNull Entity clickedEntity, @NotNull Vector position) { + this(who, clickedEntity, position, EquipmentSlot.HAND); + } + + public PlayerInteractAtEntityEvent(@NotNull Player who, @NotNull Entity clickedEntity, @NotNull Vector position, @NotNull EquipmentSlot hand) { + super(who, clickedEntity, hand); + this.position = position; + } + + @NotNull + public Vector getClickedPosition() { + return position.clone(); + } + + @NotNull + @Override + public HandlerList getHandlers() { + return handlers; + } + + @NotNull + public static HandlerList getHandlerList() { + return handlers; + } +} diff --git a/api/src/main/java/org/bukkit/event/player/PlayerInteractEntityEvent.java b/api/src/main/java/org/bukkit/event/player/PlayerInteractEntityEvent.java new file mode 100644 index 000000000..c4406610f --- /dev/null +++ b/api/src/main/java/org/bukkit/event/player/PlayerInteractEntityEvent.java @@ -0,0 +1,67 @@ +package org.bukkit.event.player; + +import org.bukkit.entity.Entity; +import org.bukkit.entity.Player; +import org.bukkit.event.Cancellable; +import org.bukkit.event.HandlerList; +import org.bukkit.inventory.EquipmentSlot; +import org.jetbrains.annotations.NotNull; + +/** + * Represents an event that is called when a player right clicks an entity. + */ +public class PlayerInteractEntityEvent extends PlayerEvent implements Cancellable { + private static final HandlerList handlers = new HandlerList(); + protected Entity clickedEntity; + boolean cancelled = false; + private EquipmentSlot hand; + + public PlayerInteractEntityEvent(@NotNull final Player who, @NotNull final Entity clickedEntity) { + this(who, clickedEntity, EquipmentSlot.HAND); + } + + public PlayerInteractEntityEvent(@NotNull final Player who, @NotNull final Entity clickedEntity, @NotNull final EquipmentSlot hand) { + super(who); + this.clickedEntity = clickedEntity; + this.hand = hand; + } + + public boolean isCancelled() { + return cancelled; + } + + public void setCancelled(boolean cancel) { + this.cancelled = cancel; + } + + /** + * Gets the entity that was right-clicked by the player. + * + * @return entity right clicked by player + */ + @NotNull + public Entity getRightClicked() { + return this.clickedEntity; + } + + /** + * The hand used to perform this interaction. + * + * @return the hand used to interact + */ + @NotNull + public EquipmentSlot getHand() { + return hand; + } + + @NotNull + @Override + public HandlerList getHandlers() { + return handlers; + } + + @NotNull + public static HandlerList getHandlerList() { + return handlers; + } +} diff --git a/api/src/main/java/org/bukkit/event/player/PlayerInteractEvent.java b/api/src/main/java/org/bukkit/event/player/PlayerInteractEvent.java new file mode 100644 index 000000000..3563aac37 --- /dev/null +++ b/api/src/main/java/org/bukkit/event/player/PlayerInteractEvent.java @@ -0,0 +1,226 @@ +package org.bukkit.event.player; + +import org.bukkit.block.Block; +import org.bukkit.block.BlockFace; +import org.bukkit.event.HandlerList; +import org.bukkit.inventory.ItemStack; +import org.bukkit.Material; +import org.bukkit.entity.Player; +import org.bukkit.event.Cancellable; +import org.bukkit.event.block.Action; +import org.bukkit.event.block.BlockCanBuildEvent; +import org.bukkit.inventory.EquipmentSlot; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +/** + * Represents an event that is called when a player interacts with an object or + * air, potentially fired once for each hand. The hand can be determined using + * {@link #getHand()}. + *

+ * This event will fire as cancelled if the vanilla behavior is to do nothing + * (e.g interacting with air). For the purpose of avoiding doubt, this means + * that the event will only be in the cancelled state if it is fired as a result + * of some prediction made by the server where no subsequent code will run, + * rather than when the subsequent interaction activity (e.g. placing a block in + * an illegal position ({@link BlockCanBuildEvent}) will fail. + */ +public class PlayerInteractEvent extends PlayerEvent implements Cancellable { + private static final HandlerList handlers = new HandlerList(); + protected ItemStack item; + protected Action action; + protected Block blockClicked; + protected BlockFace blockFace; + private Result useClickedBlock; + private Result useItemInHand; + private EquipmentSlot hand; + + public PlayerInteractEvent(@NotNull final Player who, @NotNull final Action action, @Nullable final ItemStack item, @Nullable final Block clickedBlock, @NotNull final BlockFace clickedFace) { + this(who, action, item, clickedBlock, clickedFace, EquipmentSlot.HAND); + } + + public PlayerInteractEvent(@NotNull final Player who, @NotNull final Action action, @Nullable final ItemStack item, @Nullable final Block clickedBlock, @NotNull final BlockFace clickedFace, @Nullable final EquipmentSlot hand) { + super(who); + this.action = action; + this.item = item; + this.blockClicked = clickedBlock; + this.blockFace = clickedFace; + this.hand = hand; + + useItemInHand = Result.DEFAULT; + useClickedBlock = clickedBlock == null ? Result.DENY : Result.ALLOW; + } + + /** + * Returns the action type + * + * @return Action returns the type of interaction + */ + @NotNull + public Action getAction() { + return action; + } + + /** + * Gets the cancellation state of this event. Set to true if you want to + * prevent buckets from placing water and so forth + * + * @return boolean cancellation state + */ + public boolean isCancelled() { + return useInteractedBlock() == Result.DENY; + } + + /** + * Sets the cancellation state of this event. A canceled event will not be + * executed in the server, but will still pass to other plugins + *

+ * Canceling this event will prevent use of food (player won't lose the + * food item), prevent bows/snowballs/eggs from firing, etc. (player won't + * lose the ammo) + * + * @param cancel true if you wish to cancel this event + */ + public void setCancelled(boolean cancel) { + setUseInteractedBlock(cancel ? Result.DENY : useInteractedBlock() == Result.DENY ? Result.DEFAULT : useInteractedBlock()); + setUseItemInHand(cancel ? Result.DENY : useItemInHand() == Result.DENY ? Result.DEFAULT : useItemInHand()); + } + + /** + * Returns the item in hand represented by this event + * + * @return ItemStack the item used + */ + @Nullable + public ItemStack getItem() { + return this.item; + } + + /** + * Convenience method. Returns the material of the item represented by + * this event + * + * @return Material the material of the item used + */ + @NotNull + public Material getMaterial() { + if (!hasItem()) { + return Material.AIR; + } + + return item.getType(); + } + + /** + * Check if this event involved a block + * + * @return boolean true if it did + */ + public boolean hasBlock() { + return this.blockClicked != null; + } + + /** + * Check if this event involved an item + * + * @return boolean true if it did + */ + public boolean hasItem() { + return this.item != null; + } + + /** + * Convenience method to inform the user whether this was a block + * placement event. + * + * @return boolean true if the item in hand was a block + */ + public boolean isBlockInHand() { + if (!hasItem()) { + return false; + } + + return item.getType().isBlock(); + } + + /** + * Returns the clicked block + * + * @return Block returns the block clicked with this item. + */ + @Nullable + public Block getClickedBlock() { + return blockClicked; + } + + /** + * Returns the face of the block that was clicked + * + * @return BlockFace returns the face of the block that was clicked + */ + @NotNull + public BlockFace getBlockFace() { + return blockFace; + } + + /** + * This controls the action to take with the block (if any) that was + * clicked on. This event gets processed for all blocks, but most don't + * have a default action + * + * @return the action to take with the interacted block + */ + @NotNull + public Result useInteractedBlock() { + return useClickedBlock; + } + + /** + * @param useInteractedBlock the action to take with the interacted block + */ + public void setUseInteractedBlock(@NotNull Result useInteractedBlock) { + this.useClickedBlock = useInteractedBlock; + } + + /** + * This controls the action to take with the item the player is holding. + * This includes both blocks and items (such as flint and steel or + * records). When this is set to default, it will be allowed if no action + * is taken on the interacted block. + * + * @return the action to take with the item in hand + */ + @NotNull + public Result useItemInHand() { + return useItemInHand; + } + + /** + * @param useItemInHand the action to take with the item in hand + */ + public void setUseItemInHand(@NotNull Result useItemInHand) { + this.useItemInHand = useItemInHand; + } + + /** + * The hand used to perform this interaction. May be null in the case of + * {@link Action#PHYSICAL}. + * + * @return the hand used to interact. May be null. + */ + @Nullable + public EquipmentSlot getHand() { + return hand; + } + + @NotNull + @Override + public HandlerList getHandlers() { + return handlers; + } + + @NotNull + public static HandlerList getHandlerList() { + return handlers; + } +} diff --git a/api/src/main/java/org/bukkit/event/player/PlayerItemBreakEvent.java b/api/src/main/java/org/bukkit/event/player/PlayerItemBreakEvent.java new file mode 100644 index 000000000..a16c9f576 --- /dev/null +++ b/api/src/main/java/org/bukkit/event/player/PlayerItemBreakEvent.java @@ -0,0 +1,43 @@ +package org.bukkit.event.player; + +import org.bukkit.entity.Player; +import org.bukkit.event.HandlerList; +import org.bukkit.inventory.ItemStack; +import org.jetbrains.annotations.NotNull; + +/** + * Fired when a player's item breaks (such as a shovel or flint and steel). + *

+ * The item that's breaking will exist in the inventory with a stack size of + * 0. After the event, the item's durability will be reset to 0. + */ +public class PlayerItemBreakEvent extends PlayerEvent { + private static final HandlerList handlers = new HandlerList(); + private final ItemStack brokenItem; + + public PlayerItemBreakEvent(@NotNull final Player player, @NotNull final ItemStack brokenItem) { + super(player); + this.brokenItem = brokenItem; + } + + /** + * Gets the item that broke + * + * @return The broken item + */ + @NotNull + public ItemStack getBrokenItem() { + return brokenItem; + } + + @NotNull + @Override + public HandlerList getHandlers() { + return handlers; + } + + @NotNull + public static HandlerList getHandlerList() { + return handlers; + } +} diff --git a/api/src/main/java/org/bukkit/event/player/PlayerItemConsumeEvent.java b/api/src/main/java/org/bukkit/event/player/PlayerItemConsumeEvent.java new file mode 100644 index 000000000..864c0a9e6 --- /dev/null +++ b/api/src/main/java/org/bukkit/event/player/PlayerItemConsumeEvent.java @@ -0,0 +1,103 @@ +package org.bukkit.event.player; + +import org.bukkit.Material; +import org.bukkit.entity.Player; +import org.bukkit.event.Cancellable; +import org.bukkit.event.HandlerList; +import org.bukkit.inventory.ItemStack; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +/** + * This event will fire when a player is finishing consuming an item (food, + * potion, milk bucket). + *
+ * If the ItemStack is modified the server will use the effects of the new + * item and not remove the original one from the player's inventory. + *
+ * If the event is cancelled the effect will not be applied and the item will + * not be removed from the player's inventory. + */ +public class PlayerItemConsumeEvent extends PlayerEvent implements Cancellable { + private static final HandlerList handlers = new HandlerList(); + private boolean isCancelled = false; + private ItemStack item; + @Nullable private ItemStack replacement; // Paper + + /** + * @param player the player consuming + * @param item the ItemStack being consumed + */ + public PlayerItemConsumeEvent(@NotNull final Player player, @NotNull final ItemStack item) { + super(player); + + this.item = item; + } + + /** + * Gets the item that is being consumed. Modifying the returned item will + * have no effect, you must use {@link + * #setItem(org.bukkit.inventory.ItemStack)} instead. + * + * @return an ItemStack for the item being consumed + */ + @NotNull + public ItemStack getItem() { + return item.clone(); + } + + /** + * Set the item being consumed + * + * @param item the item being consumed + */ + public void setItem(@Nullable ItemStack item) { + if (item == null) { + this.item = new ItemStack(Material.AIR); + } else { + this.item = item; + } + } + + // Paper start + /** + * Return the custom item stack that will replace the consumed item, or null if no + * custom replacement has been set (which means the default replacement will be used). + * + * @return The custom item stack that will replace the consumed item or null + */ + @Nullable + public ItemStack getReplacement() { + return this.replacement; + } + + /** + * Set a custom item stack to replace the consumed item. Pass null to clear any custom + * stack that has been set and use the default replacement. + * + * @param replacement Replacement item to set, null to clear any custom stack and use default + */ + public void setReplacement(@Nullable ItemStack replacement) { + this.replacement = replacement; + } + // Paper end + + public boolean isCancelled() { + return this.isCancelled; + } + + public void setCancelled(boolean cancel) { + this.isCancelled = cancel; + } + + @NotNull + @Override + public HandlerList getHandlers() { + return handlers; + } + + @NotNull + public static HandlerList getHandlerList() { + return handlers; + } +} diff --git a/api/src/main/java/org/bukkit/event/player/PlayerItemDamageEvent.java b/api/src/main/java/org/bukkit/event/player/PlayerItemDamageEvent.java new file mode 100644 index 000000000..2d0496339 --- /dev/null +++ b/api/src/main/java/org/bukkit/event/player/PlayerItemDamageEvent.java @@ -0,0 +1,69 @@ +package org.bukkit.event.player; + +import org.bukkit.entity.Player; +import org.bukkit.event.Cancellable; +import org.bukkit.event.HandlerList; +import org.bukkit.inventory.ItemStack; +import org.jetbrains.annotations.NotNull; + +/** + * Called when an item used by the player takes durability damage as a result of + * being used. + */ +public class PlayerItemDamageEvent extends PlayerEvent implements Cancellable { + + private static final HandlerList handlers = new HandlerList(); + private final ItemStack item; + private int damage; + private boolean cancelled = false; + + public PlayerItemDamageEvent(@NotNull Player player, @NotNull ItemStack what, int damage) { + super(player); + this.item = what; + this.damage = damage; + } + + /** + * Gets the item being damaged. + * + * @return the item + */ + @NotNull + public ItemStack getItem() { + return item; + } + + /** + * Gets the amount of durability damage this item will be taking. + * + * @return durability change + */ + public int getDamage() { + return damage; + } + + public void setDamage(int damage) { + this.damage = damage; + } + + @Override + public boolean isCancelled() { + return cancelled; + } + + @Override + public void setCancelled(boolean cancel) { + this.cancelled = cancel; + } + + @NotNull + @Override + public HandlerList getHandlers() { + return handlers; + } + + @NotNull + public static HandlerList getHandlerList() { + return handlers; + } +} diff --git a/api/src/main/java/org/bukkit/event/player/PlayerItemHeldEvent.java b/api/src/main/java/org/bukkit/event/player/PlayerItemHeldEvent.java new file mode 100644 index 000000000..d5100d8fe --- /dev/null +++ b/api/src/main/java/org/bukkit/event/player/PlayerItemHeldEvent.java @@ -0,0 +1,59 @@ +package org.bukkit.event.player; + +import org.bukkit.entity.Player; +import org.bukkit.event.Cancellable; +import org.bukkit.event.HandlerList; +import org.jetbrains.annotations.NotNull; + +/** + * Fired when a player changes their currently held item + */ +public class PlayerItemHeldEvent extends PlayerEvent implements Cancellable { + private static final HandlerList handlers = new HandlerList(); + private boolean cancel = false; + private final int previous; + private final int current; + + public PlayerItemHeldEvent(@NotNull final Player player, final int previous, final int current) { + super(player); + this.previous = previous; + this.current = current; + } + + /** + * Gets the previous held slot index + * + * @return Previous slot index + */ + public int getPreviousSlot() { + return previous; + } + + /** + * Gets the new held slot index + * + * @return New slot index + */ + public int getNewSlot() { + return current; + } + + public boolean isCancelled() { + return cancel; + } + + public void setCancelled(boolean cancel) { + this.cancel = cancel; + } + + @NotNull + @Override + public HandlerList getHandlers() { + return handlers; + } + + @NotNull + public static HandlerList getHandlerList() { + return handlers; + } +} diff --git a/api/src/main/java/org/bukkit/event/player/PlayerItemMendEvent.java b/api/src/main/java/org/bukkit/event/player/PlayerItemMendEvent.java new file mode 100644 index 000000000..2f0cbf928 --- /dev/null +++ b/api/src/main/java/org/bukkit/event/player/PlayerItemMendEvent.java @@ -0,0 +1,97 @@ +package org.bukkit.event.player; + +import org.bukkit.entity.ExperienceOrb; +import org.bukkit.entity.Player; +import org.bukkit.event.Cancellable; +import org.bukkit.event.HandlerList; +import org.bukkit.inventory.ItemStack; +import org.jetbrains.annotations.NotNull; + +/** + * Represents when a player has an item repaired via the Mending enchantment. + *
+ * This event is fired directly before the {@link PlayerExpChangeEvent}, and the + * results of this event directly affect the {@link PlayerExpChangeEvent}. + */ +public class PlayerItemMendEvent extends PlayerEvent implements Cancellable { + + private static final HandlerList handlers = new HandlerList(); + // + private final ItemStack item; + private final ExperienceOrb experienceOrb; + private int repairAmount; + private boolean cancelled; + + public PlayerItemMendEvent(@NotNull Player who, @NotNull ItemStack item, @NotNull ExperienceOrb experienceOrb, int repairAmount) { + super(who); + this.item = item; + this.experienceOrb = experienceOrb; + this.repairAmount = repairAmount; + } + + /** + * Get the {@link ItemStack} to be repaired. + * + * This is not necessarily the item the player is holding. + * + * @return the item to be repaired + */ + @NotNull + public ItemStack getItem() { + return item; + } + + /** + * Get the experience orb triggering the event. + * + * @return the experience orb + */ + @NotNull + public ExperienceOrb getExperienceOrb() { + return experienceOrb; + } + + /** + * Get the amount the item is to be repaired. + * + * The default value is twice the value of the consumed experience orb + * or the remaining damage left on the item, whichever is smaller. + * + * @return how much damage will be repaired by the experience orb + */ + public int getRepairAmount() { + return repairAmount; + } + + /** + * Set the amount the item will be repaired. + * + * Half of this value will be subtracted from the experience orb which initiated this event. + * + * @param amount how much damage will be repaired on the item + */ + public void setRepairAmount(int amount) { + this.repairAmount = amount; + } + + @Override + public boolean isCancelled() { + return cancelled; + } + + @Override + public void setCancelled(boolean cancelled) { + this.cancelled = cancelled; + } + + @NotNull + @Override + public HandlerList getHandlers() { + return handlers; + } + + @NotNull + public static HandlerList getHandlerList() { + return handlers; + } +} diff --git a/api/src/main/java/org/bukkit/event/player/PlayerJoinEvent.java b/api/src/main/java/org/bukkit/event/player/PlayerJoinEvent.java new file mode 100644 index 000000000..4c0920e21 --- /dev/null +++ b/api/src/main/java/org/bukkit/event/player/PlayerJoinEvent.java @@ -0,0 +1,48 @@ +package org.bukkit.event.player; + +import org.bukkit.entity.Player; +import org.bukkit.event.HandlerList; +import org.jetbrains.annotations.NotNull; + +/** + * Called when a player joins a server + */ +public class PlayerJoinEvent extends PlayerEvent { + private static final HandlerList handlers = new HandlerList(); + private String joinMessage; + + public PlayerJoinEvent(@NotNull final Player playerJoined, @NotNull final String joinMessage) { + super(playerJoined); + this.joinMessage = joinMessage; + } + + /** + * Gets the join message to send to all online players + * + * @return string join message + */ + @NotNull + public String getJoinMessage() { + return joinMessage; + } + + /** + * Sets the join message to send to all online players + * + * @param joinMessage join message + */ + public void setJoinMessage(@NotNull String joinMessage) { + this.joinMessage = joinMessage; + } + + @NotNull + @Override + public HandlerList getHandlers() { + return handlers; + } + + @NotNull + public static HandlerList getHandlerList() { + return handlers; + } +} diff --git a/api/src/main/java/org/bukkit/event/player/PlayerKickEvent.java b/api/src/main/java/org/bukkit/event/player/PlayerKickEvent.java new file mode 100644 index 000000000..fa1105f4e --- /dev/null +++ b/api/src/main/java/org/bukkit/event/player/PlayerKickEvent.java @@ -0,0 +1,80 @@ +package org.bukkit.event.player; + +import org.bukkit.entity.Player; +import org.bukkit.event.Cancellable; +import org.bukkit.event.HandlerList; +import org.jetbrains.annotations.NotNull; + +/** + * Called when a player gets kicked from the server + */ +public class PlayerKickEvent extends PlayerEvent implements Cancellable { + private static final HandlerList handlers = new HandlerList(); + private String leaveMessage; + private String kickReason; + private Boolean cancel; + + public PlayerKickEvent(@NotNull final Player playerKicked, @NotNull final String kickReason, @NotNull final String leaveMessage) { + super(playerKicked); + this.kickReason = kickReason; + this.leaveMessage = leaveMessage; + this.cancel = false; + } + + /** + * Gets the reason why the player is getting kicked + * + * @return string kick reason + */ + @NotNull + public String getReason() { + return kickReason; + } + + /** + * Gets the leave message send to all online players + * + * @return string kick reason + */ + @NotNull + public String getLeaveMessage() { + return leaveMessage; + } + + public boolean isCancelled() { + return cancel; + } + + public void setCancelled(boolean cancel) { + this.cancel = cancel; + } + + /** + * Sets the reason why the player is getting kicked + * + * @param kickReason kick reason + */ + public void setReason(@NotNull String kickReason) { + this.kickReason = kickReason; + } + + /** + * Sets the leave message send to all online players + * + * @param leaveMessage leave message + */ + public void setLeaveMessage(@NotNull String leaveMessage) { + this.leaveMessage = leaveMessage; + } + + @NotNull + @Override + public HandlerList getHandlers() { + return handlers; + } + + @NotNull + public static HandlerList getHandlerList() { + return handlers; + } +} diff --git a/api/src/main/java/org/bukkit/event/player/PlayerLevelChangeEvent.java b/api/src/main/java/org/bukkit/event/player/PlayerLevelChangeEvent.java new file mode 100644 index 000000000..00481d733 --- /dev/null +++ b/api/src/main/java/org/bukkit/event/player/PlayerLevelChangeEvent.java @@ -0,0 +1,49 @@ +package org.bukkit.event.player; + +import org.bukkit.entity.Player; +import org.bukkit.event.HandlerList; +import org.jetbrains.annotations.NotNull; + +/** + * Called when a players level changes + */ +public class PlayerLevelChangeEvent extends PlayerEvent { + private static final HandlerList handlers = new HandlerList(); + private final int oldLevel; + private final int newLevel; + + public PlayerLevelChangeEvent(@NotNull final Player player, final int oldLevel, final int newLevel) { + super(player); + this.oldLevel = oldLevel; + this.newLevel = newLevel; + } + + /** + * Gets the old level of the player + * + * @return The old level of the player + */ + public int getOldLevel() { + return oldLevel; + } + + /** + * Gets the new level of the player + * + * @return The new (current) level of the player + */ + public int getNewLevel() { + return newLevel; + } + + @NotNull + @Override + public HandlerList getHandlers() { + return handlers; + } + + @NotNull + public static HandlerList getHandlerList() { + return handlers; + } +} diff --git a/api/src/main/java/org/bukkit/event/player/PlayerLocaleChangeEvent.java b/api/src/main/java/org/bukkit/event/player/PlayerLocaleChangeEvent.java new file mode 100644 index 000000000..1db386bb7 --- /dev/null +++ b/api/src/main/java/org/bukkit/event/player/PlayerLocaleChangeEvent.java @@ -0,0 +1,41 @@ +package org.bukkit.event.player; + +import org.bukkit.entity.Player; +import org.bukkit.event.HandlerList; +import org.jetbrains.annotations.NotNull; + +/** + * Called when a player changes their locale in the client settings. + */ +public class PlayerLocaleChangeEvent extends PlayerEvent { + + private static final HandlerList handlers = new HandlerList(); + // + private final String locale; + + public PlayerLocaleChangeEvent(@NotNull Player who, @NotNull String locale) { + super(who); + this.locale = locale; + } + + /** + * @see Player#getLocale() + * + * @return the player's new locale + */ + @NotNull + public String getLocale() { + return locale; + } + + @NotNull + @Override + public HandlerList getHandlers() { + return handlers; + } + + @NotNull + public static HandlerList getHandlerList() { + return handlers; + } +} diff --git a/api/src/main/java/org/bukkit/event/player/PlayerLoginEvent.java b/api/src/main/java/org/bukkit/event/player/PlayerLoginEvent.java new file mode 100644 index 000000000..b493173e2 --- /dev/null +++ b/api/src/main/java/org/bukkit/event/player/PlayerLoginEvent.java @@ -0,0 +1,196 @@ +package org.bukkit.event.player; + +import java.net.InetAddress; + +import org.bukkit.entity.Player; +import org.bukkit.event.HandlerList; +import org.jetbrains.annotations.NotNull; + +/** + * Stores details for players attempting to log in. + *
+ * Note that this event is called early in the player initialization + * process. It is recommended that most options involving the Player + * entity be postponed to the {@link PlayerJoinEvent} instead. + */ +public class PlayerLoginEvent extends PlayerEvent { + private static final HandlerList handlers = new HandlerList(); + private final InetAddress address; + private final String hostname; + private Result result = Result.ALLOWED; + private String message = ""; + private final InetAddress realAddress; // Spigot + + /** + * This constructor defaults message to an empty string, and result to + * ALLOWED + * + * @param player The {@link Player} for this event + * @param hostname The hostname that was used to connect to the server + * @param address The address the player used to connect, provided for + * timing issues + * @param realAddress The unspoofed, actual address, that the player used to connect + */ + public PlayerLoginEvent(@NotNull final Player player, @NotNull final String hostname, @NotNull final InetAddress address, final @NotNull InetAddress realAddress) { // Spigot + super(player); + this.hostname = hostname; + this.address = address; + // Spigot start + this.realAddress = realAddress; + } + + public PlayerLoginEvent(@NotNull final Player player, @NotNull final String hostname, @NotNull final InetAddress address) { + this(player, hostname, address, address); + // Spigot end + } + + /** + * This constructor pre-configures the event with a result and message + * + * @param player The {@link Player} for this event + * @param hostname The hostname that was used to connect to the server + * @param address The address the player used to connect, provided for + * timing issues + * @param result The result status for this event + * @param message The message to be displayed if result denies login + * @param realAddress The unspoofed, actual address, that the player used to connect + */ + public PlayerLoginEvent(@NotNull final Player player, @NotNull String hostname, @NotNull final InetAddress address, @NotNull final Result result, @NotNull final String message, @NotNull final InetAddress realAddress) { // Spigot + this(player, hostname, address, realAddress); // Spigot + this.result = result; + this.message = message; + } + + // Spigot start + /** + * Gets the connection address of this player, regardless of whether it has been spoofed or not. + * + * @return the player's connection address + */ + @NotNull + public InetAddress getRealAddress() { + return realAddress; + } + // Spigot end + + /** + * Gets the current result of the login, as an enum + * + * @return Current Result of the login + */ + @NotNull + public Result getResult() { + return result; + } + + /** + * Sets the new result of the login, as an enum + * + * @param result New result to set + */ + public void setResult(@NotNull final Result result) { + this.result = result; + } + + /** + * Gets the current kick message that will be used if getResult() != + * Result.ALLOWED + * + * @return Current kick message + */ + @NotNull + public String getKickMessage() { + return message; + } + + /** + * Sets the kick message to display if getResult() != Result.ALLOWED + * + * @param message New kick message + */ + public void setKickMessage(@NotNull final String message) { + this.message = message; + } + + /** + * Gets the hostname that the player used to connect to the server, or + * blank if unknown + * + * @return The hostname + */ + @NotNull + public String getHostname() { + return hostname; + } + + /** + * Allows the player to log in + */ + public void allow() { + result = Result.ALLOWED; + message = ""; + } + + /** + * Disallows the player from logging in, with the given reason + * + * @param result New result for disallowing the player + * @param message Kick message to display to the user + */ + public void disallow(@NotNull final Result result, @NotNull final String message) { + this.result = result; + this.message = message; + } + + /** + * Gets the {@link InetAddress} for the Player associated with this event. + * This method is provided as a workaround for player.getAddress() + * returning null during PlayerLoginEvent. + * + * @return The address for this player. For legacy compatibility, this may + * be null. + */ + @NotNull + public InetAddress getAddress() { + return address; + } + + @NotNull + @Override + public HandlerList getHandlers() { + return handlers; + } + + @NotNull + public static HandlerList getHandlerList() { + return handlers; + } + + /** + * Basic kick reasons for communicating to plugins + */ + public enum Result { + + /** + * The player is allowed to log in + */ + ALLOWED, + /** + * The player is not allowed to log in, due to the server being full + */ + KICK_FULL, + /** + * The player is not allowed to log in, due to them being banned + */ + KICK_BANNED, + /** + * The player is not allowed to log in, due to them not being on the + * white list + */ + KICK_WHITELIST, + /** + * The player is not allowed to log in, for reasons undefined + */ + KICK_OTHER + } +} diff --git a/api/src/main/java/org/bukkit/event/player/PlayerMoveEvent.java b/api/src/main/java/org/bukkit/event/player/PlayerMoveEvent.java new file mode 100644 index 000000000..8b9018451 --- /dev/null +++ b/api/src/main/java/org/bukkit/event/player/PlayerMoveEvent.java @@ -0,0 +1,109 @@ +package org.bukkit.event.player; + +import com.google.common.base.Preconditions; +import org.bukkit.Location; +import org.bukkit.entity.Player; +import org.bukkit.event.Cancellable; +import org.bukkit.event.HandlerList; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +/** + * Holds information for player movement events + */ +public class PlayerMoveEvent extends PlayerEvent implements Cancellable { + private static final HandlerList handlers = new HandlerList(); + private boolean cancel = false; + private Location from; + private Location to; + + public PlayerMoveEvent(@NotNull final Player player, @NotNull final Location from, @Nullable final Location to) { + super(player); + this.from = from; + this.to = to; + } + + /** + * Gets the cancellation state of this event. A cancelled event will not + * be executed in the server, but will still pass to other plugins + *

+ * If a move or teleport event is cancelled, the player will be moved or + * teleported back to the Location as defined by getFrom(). This will not + * fire an event + * + * @return true if this event is cancelled + */ + public boolean isCancelled() { + return cancel; + } + + /** + * Sets the cancellation state of this event. A cancelled event will not + * be executed in the server, but will still pass to other plugins + *

+ * If a move or teleport event is cancelled, the player will be moved or + * teleported back to the Location as defined by getFrom(). This will not + * fire an event + * + * @param cancel true if you wish to cancel this event + */ + public void setCancelled(boolean cancel) { + this.cancel = cancel; + } + + /** + * Gets the location this player moved from + * + * @return Location the player moved from + */ + @NotNull + public Location getFrom() { + return from; + } + + /** + * Sets the location to mark as where the player moved from + * + * @param from New location to mark as the players previous location + */ + public void setFrom(@NotNull Location from) { + validateLocation(from); + this.from = from; + } + + /** + * Gets the location this player moved to + * + * @return Location the player moved to + */ + @Nullable + public Location getTo() { + return to; + } + + /** + * Sets the location that this player will move to + * + * @param to New Location this player will move to + */ + public void setTo(@NotNull Location to) { + validateLocation(to); + this.to = to; + } + + private void validateLocation(@NotNull Location loc) { + Preconditions.checkArgument(loc != null, "Cannot use null location!"); + Preconditions.checkArgument(loc.getWorld() != null, "Cannot use null location with null world!"); + } + + @NotNull + @Override + public HandlerList getHandlers() { + return handlers; + } + + @NotNull + public static HandlerList getHandlerList() { + return handlers; + } +} diff --git a/api/src/main/java/org/bukkit/event/player/PlayerPickupArrowEvent.java b/api/src/main/java/org/bukkit/event/player/PlayerPickupArrowEvent.java new file mode 100644 index 000000000..01870a063 --- /dev/null +++ b/api/src/main/java/org/bukkit/event/player/PlayerPickupArrowEvent.java @@ -0,0 +1,29 @@ +package org.bukkit.event.player; + +import org.bukkit.entity.Arrow; +import org.bukkit.entity.Item; +import org.bukkit.entity.Player; +import org.jetbrains.annotations.NotNull; + +/** + * Thrown when a player picks up an arrow from the ground. + */ +public class PlayerPickupArrowEvent extends PlayerPickupItemEvent { + + private final Arrow arrow; + + public PlayerPickupArrowEvent(@NotNull final Player player, @NotNull final Item item, @NotNull final Arrow arrow) { + super(player, item, 0); + this.arrow = arrow; + } + + /** + * Get the arrow being picked up by the player + * + * @return The arrow being picked up + */ + @NotNull + public Arrow getArrow() { + return arrow; + } +} diff --git a/api/src/main/java/org/bukkit/event/player/PlayerPickupItemEvent.java b/api/src/main/java/org/bukkit/event/player/PlayerPickupItemEvent.java new file mode 100644 index 000000000..5777d3802 --- /dev/null +++ b/api/src/main/java/org/bukkit/event/player/PlayerPickupItemEvent.java @@ -0,0 +1,89 @@ +package org.bukkit.event.player; + +import org.bukkit.Warning; +import org.bukkit.entity.Item; +import org.bukkit.entity.Player; +import org.bukkit.event.Cancellable; +import org.bukkit.event.HandlerList; +import org.bukkit.event.entity.EntityPickupItemEvent; +import org.jetbrains.annotations.NotNull; + +/** + * Thrown when a player picks an item up from the ground + * @deprecated {@link EntityPickupItemEvent} + */ +@Deprecated +@Warning(false) +public class PlayerPickupItemEvent extends PlayerEvent implements Cancellable { + private static final HandlerList handlers = new HandlerList(); + private final Item item; + private boolean flyAtPlayer = true; // Paper + private boolean cancel = false; + private final int remaining; + + public PlayerPickupItemEvent(@NotNull final Player player, @NotNull final Item item, final int remaining) { + super(player); + this.item = item; + this.remaining = remaining; + } + + /** + * Gets the Item picked up by the player. + * + * @return Item + */ + @NotNull + public Item getItem() { + return item; + } + + /** + * Gets the amount remaining on the ground, if any + * + * @return amount remaining on the ground + */ + public int getRemaining() { + return remaining; + } + + // Paper Start + /** + * Set if the item will fly at the player + *

Cancelling the event will set this value to false.

+ * + * @param flyAtPlayer True for item to fly at player + */ + public void setFlyAtPlayer(boolean flyAtPlayer) { + this.flyAtPlayer = flyAtPlayer; + } + + /** + * Gets if the item will fly at the player + * + * @return True if the item will fly at the player + */ + public boolean getFlyAtPlayer() { + return flyAtPlayer; + } + // Paper End + + public boolean isCancelled() { + return cancel; + } + + public void setCancelled(boolean cancel) { + this.cancel = cancel; + this.flyAtPlayer = !cancel; // Paper + } + + @NotNull + @Override + public HandlerList getHandlers() { + return handlers; + } + + @NotNull + public static HandlerList getHandlerList() { + return handlers; + } +} diff --git a/api/src/main/java/org/bukkit/event/player/PlayerPortalEvent.java b/api/src/main/java/org/bukkit/event/player/PlayerPortalEvent.java new file mode 100644 index 000000000..f8821c78e --- /dev/null +++ b/api/src/main/java/org/bukkit/event/player/PlayerPortalEvent.java @@ -0,0 +1,92 @@ +package org.bukkit.event.player; + +import org.bukkit.Location; +import org.bukkit.TravelAgent; +import org.bukkit.entity.Player; +import org.bukkit.event.HandlerList; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +/** + * Called when a player is about to teleport because it is in contact with a + * portal. + *

+ * For other entities see {@link org.bukkit.event.entity.EntityPortalEvent} + */ +public class PlayerPortalEvent extends PlayerTeleportEvent { + private static final HandlerList handlers = new HandlerList(); + protected boolean useTravelAgent = true; + protected TravelAgent travelAgent; + + public PlayerPortalEvent(@NotNull final Player player, @NotNull final Location from, @Nullable final Location to, @NotNull final TravelAgent pta) { + super(player, from, to); + this.travelAgent = pta; + } + + public PlayerPortalEvent(@NotNull Player player, @NotNull Location from, @Nullable Location to, @NotNull TravelAgent pta, @NotNull TeleportCause cause) { + super(player, from, to, cause); + this.travelAgent = pta; + } + + /** + * Sets whether or not the Travel Agent will be used. + *

+ * If this is set to true, the TravelAgent will try to find a Portal at + * the {@link #getTo()} Location, and will try to create one if there is + * none. + *

+ * If this is set to false, the {@link #getPlayer()} will only be + * teleported to the {@link #getTo()} Location. + * + * @param useTravelAgent whether to use the Travel Agent + */ + public void useTravelAgent(boolean useTravelAgent) { + this.useTravelAgent = useTravelAgent; + } + + /** + * Gets whether or not the Travel Agent will be used. + *

+ * If this is set to true, the TravelAgent will try to find a Portal at + * the {@link #getTo()} Location, and will try to create one if there is + * none. + *

+ * If this is set to false, the {@link #getPlayer()}} will only be + * teleported to the {@link #getTo()} Location. + * + * @return whether to use the Travel Agent + */ + public boolean useTravelAgent() { + return useTravelAgent && travelAgent != null; + } + + /** + * Gets the Travel Agent used (or not) in this event. + * + * @return the Travel Agent used (or not) in this event + */ + @NotNull + public TravelAgent getPortalTravelAgent() { + return this.travelAgent; + } + + /** + * Sets the Travel Agent used (or not) in this event. + * + * @param travelAgent the Travel Agent used (or not) in this event + */ + public void setPortalTravelAgent(@NotNull TravelAgent travelAgent) { + this.travelAgent = travelAgent; + } + + @NotNull + @Override + public HandlerList getHandlers() { + return handlers; + } + + @NotNull + public static HandlerList getHandlerList() { + return handlers; + } +} diff --git a/api/src/main/java/org/bukkit/event/player/PlayerPreLoginEvent.java b/api/src/main/java/org/bukkit/event/player/PlayerPreLoginEvent.java new file mode 100644 index 000000000..a23a06a4b --- /dev/null +++ b/api/src/main/java/org/bukkit/event/player/PlayerPreLoginEvent.java @@ -0,0 +1,167 @@ +package org.bukkit.event.player; + +import java.net.InetAddress; +import java.util.UUID; + +import org.bukkit.Warning; +import org.bukkit.event.Event; +import org.bukkit.event.HandlerList; +import org.jetbrains.annotations.NotNull; + +/** + * Stores details for players attempting to log in + * + * @deprecated This event causes synchronization from the login thread; {@link + * AsyncPlayerPreLoginEvent} is preferred to keep the secondary threads + * asynchronous. + */ +@Deprecated +@Warning(reason="This event causes a login thread to synchronize with the main thread") +public class PlayerPreLoginEvent extends Event { + private static final HandlerList handlers = new HandlerList(); + private Result result; + private String message; + private final String name; + private final InetAddress ipAddress; + private final UUID uniqueId; + + @Deprecated + public PlayerPreLoginEvent(@NotNull final String name, @NotNull final InetAddress ipAddress) { + this(name, ipAddress, null); + } + + public PlayerPreLoginEvent(@NotNull final String name, @NotNull final InetAddress ipAddress, @NotNull final UUID uniqueId) { + this.result = Result.ALLOWED; + this.message = ""; + this.name = name; + this.ipAddress = ipAddress; + this.uniqueId = uniqueId; + } + + /** + * Gets the current result of the login, as an enum + * + * @return Current Result of the login + */ + @NotNull + public Result getResult() { + return result; + } + + /** + * Sets the new result of the login, as an enum + * + * @param result New result to set + */ + public void setResult(@NotNull final Result result) { + this.result = result; + } + + /** + * Gets the current kick message that will be used if getResult() != + * Result.ALLOWED + * + * @return Current kick message + */ + @NotNull + public String getKickMessage() { + return message; + } + + /** + * Sets the kick message to display if getResult() != Result.ALLOWED + * + * @param message New kick message + */ + public void setKickMessage(@NotNull final String message) { + this.message = message; + } + + /** + * Allows the player to log in + */ + public void allow() { + result = Result.ALLOWED; + message = ""; + } + + /** + * Disallows the player from logging in, with the given reason + * + * @param result New result for disallowing the player + * @param message Kick message to display to the user + */ + public void disallow(@NotNull final Result result, @NotNull final String message) { + this.result = result; + this.message = message; + } + + /** + * Gets the player's name. + * + * @return the player's name + */ + @NotNull + public String getName() { + return name; + } + + /** + * Gets the player IP address. + * + * @return The IP address + */ + @NotNull + public InetAddress getAddress() { + return ipAddress; + } + + @NotNull + @Override + public HandlerList getHandlers() { + return handlers; + } + + /** + * Gets the player's unique ID. + * + * @return The unique ID + */ + @NotNull + public UUID getUniqueId() { + return uniqueId; + } + + @NotNull + public static HandlerList getHandlerList() { + return handlers; + } + + /** + * Basic kick reasons for communicating to plugins + */ + public enum Result { + + /** + * The player is allowed to log in + */ + ALLOWED, + /** + * The player is not allowed to log in, due to the server being full + */ + KICK_FULL, + /** + * The player is not allowed to log in, due to them being banned + */ + KICK_BANNED, + /** + * The player is not allowed to log in, due to them not being on the + * white list + */ + KICK_WHITELIST, + /** + * The player is not allowed to log in, for reasons undefined + */ + KICK_OTHER + } +} diff --git a/api/src/main/java/org/bukkit/event/player/PlayerQuitEvent.java b/api/src/main/java/org/bukkit/event/player/PlayerQuitEvent.java new file mode 100644 index 000000000..8e55e8393 --- /dev/null +++ b/api/src/main/java/org/bukkit/event/player/PlayerQuitEvent.java @@ -0,0 +1,48 @@ +package org.bukkit.event.player; + +import org.bukkit.entity.Player; +import org.bukkit.event.HandlerList; +import org.jetbrains.annotations.NotNull; + +/** + * Called when a player leaves a server + */ +public class PlayerQuitEvent extends PlayerEvent { + private static final HandlerList handlers = new HandlerList(); + private String quitMessage; + + public PlayerQuitEvent(@NotNull final Player who, @NotNull final String quitMessage) { + super(who); + this.quitMessage = quitMessage; + } + + /** + * Gets the quit message to send to all online players + * + * @return string quit message + */ + @NotNull + public String getQuitMessage() { + return quitMessage; + } + + /** + * Sets the quit message to send to all online players + * + * @param quitMessage quit message + */ + public void setQuitMessage(@NotNull String quitMessage) { + this.quitMessage = quitMessage; + } + + @NotNull + @Override + public HandlerList getHandlers() { + return handlers; + } + + @NotNull + public static HandlerList getHandlerList() { + return handlers; + } +} diff --git a/api/src/main/java/org/bukkit/event/player/PlayerRecipeDiscoverEvent.java b/api/src/main/java/org/bukkit/event/player/PlayerRecipeDiscoverEvent.java new file mode 100644 index 000000000..f41c29e98 --- /dev/null +++ b/api/src/main/java/org/bukkit/event/player/PlayerRecipeDiscoverEvent.java @@ -0,0 +1,54 @@ +package org.bukkit.event.player; + +import org.bukkit.NamespacedKey; +import org.bukkit.entity.Player; +import org.bukkit.event.Cancellable; +import org.bukkit.event.HandlerList; +import org.jetbrains.annotations.NotNull; + +/** + * Called when a player discovers a new recipe in the recipe book. + */ +public class PlayerRecipeDiscoverEvent extends PlayerEvent implements Cancellable { + + private static final HandlerList handlers = new HandlerList(); + + private boolean cancel = false; + private final NamespacedKey recipe; + + public PlayerRecipeDiscoverEvent(@NotNull Player who, @NotNull NamespacedKey recipe) { + super(who); + this.recipe = recipe; + } + + /** + * Get the namespaced key of the discovered recipe. + * + * @return the discovered recipe + */ + @NotNull + public NamespacedKey getRecipe() { + return recipe; + } + + @Override + public boolean isCancelled() { + return cancel; + } + + @Override + public void setCancelled(boolean cancel) { + this.cancel = cancel; + } + + @NotNull + @Override + public HandlerList getHandlers() { + return handlers; + } + + @NotNull + public static HandlerList getHandlerList() { + return handlers; + } +} diff --git a/api/src/main/java/org/bukkit/event/player/PlayerRegisterChannelEvent.java b/api/src/main/java/org/bukkit/event/player/PlayerRegisterChannelEvent.java new file mode 100644 index 000000000..97ae244fa --- /dev/null +++ b/api/src/main/java/org/bukkit/event/player/PlayerRegisterChannelEvent.java @@ -0,0 +1,14 @@ +package org.bukkit.event.player; + +import org.bukkit.entity.Player; +import org.jetbrains.annotations.NotNull; + +/** + * This is called immediately after a player registers for a plugin channel. + */ +public class PlayerRegisterChannelEvent extends PlayerChannelEvent { + + public PlayerRegisterChannelEvent(@NotNull final Player player, @NotNull final String channel) { + super(player, channel); + } +} diff --git a/api/src/main/java/org/bukkit/event/player/PlayerResourcePackStatusEvent.java b/api/src/main/java/org/bukkit/event/player/PlayerResourcePackStatusEvent.java new file mode 100644 index 000000000..4c2102a11 --- /dev/null +++ b/api/src/main/java/org/bukkit/event/player/PlayerResourcePackStatusEvent.java @@ -0,0 +1,84 @@ +package org.bukkit.event.player; + +import org.bukkit.entity.Player; +import org.bukkit.event.HandlerList; +import org.jetbrains.annotations.NotNull; + +/** + * Called when a player takes action on a resource pack request sent via + * {@link Player#setResourcePack(java.lang.String)}. + */ +public class PlayerResourcePackStatusEvent extends PlayerEvent { + + private static final HandlerList handlers = new HandlerList(); + @Deprecated + private final String hash; // Paper + private final Status status; + + public PlayerResourcePackStatusEvent(@NotNull final Player who, @NotNull Status resourcePackStatus) { + super(who); + this.hash = null; // Paper + this.status = resourcePackStatus; + } + + @Deprecated // Paper + public PlayerResourcePackStatusEvent(final Player who, Status resourcePackStatus, String hash) { + super(who); + this.hash = hash; // Paper + this.status = resourcePackStatus; + } + + @Deprecated + /** + * @deprecated Hash does not seem to ever be set + */ + public String getHash() { + return this.hash; + } + // Paper end + + /** + * Gets the status of this pack. + * + * @return the current status + */ + @NotNull + public Status getStatus() { + return status; + } + + @NotNull + @Override + public HandlerList getHandlers() { + return handlers; + } + + @NotNull + public static HandlerList getHandlerList() { + return handlers; + } + + /** + * Status of the resource pack. + */ + public enum Status { + + /** + * The resource pack has been successfully downloaded and applied to the + * client. + */ + SUCCESSFULLY_LOADED, + /** + * The client refused to accept the resource pack. + */ + DECLINED, + /** + * The client accepted the pack, but download failed. + */ + FAILED_DOWNLOAD, + /** + * The client accepted the pack and is beginning a download of it. + */ + ACCEPTED; + } +} diff --git a/api/src/main/java/org/bukkit/event/player/PlayerRespawnEvent.java b/api/src/main/java/org/bukkit/event/player/PlayerRespawnEvent.java new file mode 100644 index 000000000..71b566e44 --- /dev/null +++ b/api/src/main/java/org/bukkit/event/player/PlayerRespawnEvent.java @@ -0,0 +1,64 @@ +package org.bukkit.event.player; + +import org.apache.commons.lang.Validate; +import org.bukkit.Location; +import org.bukkit.entity.Player; +import org.bukkit.event.HandlerList; +import org.jetbrains.annotations.NotNull; + +/** + * Called when a player respawns. + */ +public class PlayerRespawnEvent extends PlayerEvent { + private static final HandlerList handlers = new HandlerList(); + private Location respawnLocation; + private final boolean isBedSpawn; + + public PlayerRespawnEvent(@NotNull final Player respawnPlayer, @NotNull final Location respawnLocation, final boolean isBedSpawn) { + super(respawnPlayer); + this.respawnLocation = respawnLocation; + this.isBedSpawn = isBedSpawn; + } + + /** + * Gets the current respawn location + * + * @return Location current respawn location + */ + @NotNull + public Location getRespawnLocation() { + return this.respawnLocation; + } + + /** + * Sets the new respawn location + * + * @param respawnLocation new location for the respawn + */ + public void setRespawnLocation(@NotNull Location respawnLocation) { + Validate.notNull(respawnLocation, "Respawn location can not be null"); + Validate.notNull(respawnLocation.getWorld(), "Respawn world can not be null"); + + this.respawnLocation = respawnLocation; + } + + /** + * Gets whether the respawn location is the player's bed. + * + * @return true if the respawn location is the player's bed. + */ + public boolean isBedSpawn() { + return this.isBedSpawn; + } + + @NotNull + @Override + public HandlerList getHandlers() { + return handlers; + } + + @NotNull + public static HandlerList getHandlerList() { + return handlers; + } +} diff --git a/api/src/main/java/org/bukkit/event/player/PlayerRiptideEvent.java b/api/src/main/java/org/bukkit/event/player/PlayerRiptideEvent.java new file mode 100644 index 000000000..c23142a72 --- /dev/null +++ b/api/src/main/java/org/bukkit/event/player/PlayerRiptideEvent.java @@ -0,0 +1,45 @@ +package org.bukkit.event.player; + +import org.bukkit.entity.Player; +import org.bukkit.event.HandlerList; +import org.bukkit.inventory.ItemStack; +import org.jetbrains.annotations.NotNull; + +/** + * This event is fired when the player activates the riptide enchantment, using + * their trident to propel them through the air. + *
+ * N.B. the riptide action is currently performed client side, so manipulating + * the player in this event may have undesired effects. + */ +public class PlayerRiptideEvent extends PlayerEvent { + + private static final HandlerList handlers = new HandlerList(); + private final ItemStack item; + + public PlayerRiptideEvent(@NotNull final Player who, @NotNull final ItemStack item) { + super(who); + this.item = item; + } + + /** + * Gets the item containing the used enchantment. + * + * @return held enchanted item + */ + @NotNull + public ItemStack getItem() { + return item; + } + + @NotNull + @Override + public HandlerList getHandlers() { + return handlers; + } + + @NotNull + public static HandlerList getHandlerList() { + return handlers; + } +} diff --git a/api/src/main/java/org/bukkit/event/player/PlayerShearEntityEvent.java b/api/src/main/java/org/bukkit/event/player/PlayerShearEntityEvent.java new file mode 100644 index 000000000..874349138 --- /dev/null +++ b/api/src/main/java/org/bukkit/event/player/PlayerShearEntityEvent.java @@ -0,0 +1,52 @@ +package org.bukkit.event.player; + +import org.bukkit.entity.Entity; +import org.bukkit.entity.Player; +import org.bukkit.event.Cancellable; +import org.bukkit.event.HandlerList; +import org.jetbrains.annotations.NotNull; + +/** + * Called when a player shears an entity + */ +public class PlayerShearEntityEvent extends PlayerEvent implements Cancellable { + private static final HandlerList handlers = new HandlerList(); + private boolean cancel; + private final Entity what; + + public PlayerShearEntityEvent(@NotNull final Player who, @NotNull final Entity what) { + super(who); + this.cancel = false; + this.what = what; + } + + public boolean isCancelled() { + return cancel; + } + + public void setCancelled(boolean cancel) { + this.cancel = cancel; + } + + /** + * Gets the entity the player is shearing + * + * @return the entity the player is shearing + */ + @NotNull + public Entity getEntity() { + return what; + } + + @NotNull + @Override + public HandlerList getHandlers() { + return handlers; + } + + @NotNull + public static HandlerList getHandlerList() { + return handlers; + } + +} diff --git a/api/src/main/java/org/bukkit/event/player/PlayerStatisticIncrementEvent.java b/api/src/main/java/org/bukkit/event/player/PlayerStatisticIncrementEvent.java new file mode 100644 index 000000000..ae7d0e6e3 --- /dev/null +++ b/api/src/main/java/org/bukkit/event/player/PlayerStatisticIncrementEvent.java @@ -0,0 +1,123 @@ +package org.bukkit.event.player; + +import org.bukkit.Material; +import org.bukkit.Statistic; +import org.bukkit.entity.EntityType; +import org.bukkit.entity.Player; +import org.bukkit.event.Cancellable; +import org.bukkit.event.HandlerList; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +/** + * Called when a player statistic is incremented. + *

+ * This event is not called for some high frequency statistics, e.g. movement + * based statistics. + * + */ +public class PlayerStatisticIncrementEvent extends PlayerEvent implements Cancellable { + private static final HandlerList handlers = new HandlerList(); + protected final Statistic statistic; + private final int initialValue; + private final int newValue; + private boolean isCancelled = false; + private final EntityType entityType; + private final Material material; + + public PlayerStatisticIncrementEvent(@NotNull Player player, @NotNull Statistic statistic, int initialValue, int newValue) { + super(player); + this.statistic = statistic; + this.initialValue = initialValue; + this.newValue = newValue; + this.entityType = null; + this.material = null; + } + + public PlayerStatisticIncrementEvent(@NotNull Player player, @NotNull Statistic statistic, int initialValue, int newValue, @NotNull EntityType entityType) { + super(player); + this.statistic = statistic; + this.initialValue = initialValue; + this.newValue = newValue; + this.entityType = entityType; + this.material = null; + } + + public PlayerStatisticIncrementEvent(@NotNull Player player, @NotNull Statistic statistic, int initialValue, int newValue, @NotNull Material material) { + super(player); + this.statistic = statistic; + this.initialValue = initialValue; + this.newValue = newValue; + this.entityType = null; + this.material = material; + } + + /** + * Gets the statistic that is being incremented. + * + * @return the incremented statistic + */ + @NotNull + public Statistic getStatistic() { + return statistic; + } + + /** + * Gets the previous value of the statistic. + * + * @return the previous value of the statistic + */ + public int getPreviousValue() { + return initialValue; + } + + /** + * Gets the new value of the statistic. + * + * @return the new value of the statistic + */ + public int getNewValue() { + return newValue; + } + + /** + * Gets the EntityType if {@link #getStatistic() getStatistic()} is an + * entity statistic otherwise returns null. + * + * @return the EntityType of the statistic + */ + @Nullable + public EntityType getEntityType() { + return entityType; + } + + /** + * Gets the Material if {@link #getStatistic() getStatistic()} is a block + * or item statistic otherwise returns null. + * + * @return the Material of the statistic + */ + @Nullable + public Material getMaterial() { + return material; + } + + public boolean isCancelled() { + return isCancelled; + } + + public void setCancelled(boolean cancel) { + this.isCancelled = cancel; + } + + @NotNull + @Override + public HandlerList getHandlers() { + return handlers; + } + + @NotNull + public static HandlerList getHandlerList() { + return handlers; + } +} diff --git a/api/src/main/java/org/bukkit/event/player/PlayerSwapHandItemsEvent.java b/api/src/main/java/org/bukkit/event/player/PlayerSwapHandItemsEvent.java new file mode 100644 index 000000000..9f592317c --- /dev/null +++ b/api/src/main/java/org/bukkit/event/player/PlayerSwapHandItemsEvent.java @@ -0,0 +1,87 @@ +package org.bukkit.event.player; + +import org.bukkit.entity.Player; +import org.bukkit.event.Cancellable; +import org.bukkit.event.HandlerList; +import org.bukkit.inventory.ItemStack; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +/** + * Called when a player swap items between main hand and off hand using the + * hotkey. + */ +public class PlayerSwapHandItemsEvent extends PlayerEvent implements Cancellable { + + private static final HandlerList handlers = new HandlerList(); + // + private ItemStack mainHandItem; + private ItemStack offHandItem; + private boolean cancelled; + + public PlayerSwapHandItemsEvent(@NotNull Player player, @NotNull ItemStack mainHandItem, @NotNull ItemStack offHandItem) { + super(player); + + this.mainHandItem = mainHandItem; + this.offHandItem = offHandItem; + } + + /** + * Gets the item switched to the main hand. + * + * @return item in the main hand + */ + @Nullable + public ItemStack getMainHandItem() { + return mainHandItem; + } + + /** + * Sets the item in the main hand. + * + * @param mainHandItem new item in the main hand + */ + public void setMainHandItem(@Nullable ItemStack mainHandItem) { + this.mainHandItem = mainHandItem; + } + + /** + * Gets the item switched to the off hand. + * + * @return item in the off hand + */ + @Nullable + public ItemStack getOffHandItem() { + return offHandItem; + } + + /** + * Sets the item in the off hand. + * + * @param offHandItem new item in the off hand + */ + public void setOffHandItem(@Nullable ItemStack offHandItem) { + this.offHandItem = offHandItem; + } + + @Override + public boolean isCancelled() { + return cancelled; + } + + @Override + public void setCancelled(boolean cancel) { + this.cancelled = cancel; + } + + @NotNull + @Override + public HandlerList getHandlers() { + return handlers; + } + + @NotNull + public static HandlerList getHandlerList() { + return handlers; + } +} diff --git a/api/src/main/java/org/bukkit/event/player/PlayerTeleportEvent.java b/api/src/main/java/org/bukkit/event/player/PlayerTeleportEvent.java new file mode 100644 index 000000000..553d77404 --- /dev/null +++ b/api/src/main/java/org/bukkit/event/player/PlayerTeleportEvent.java @@ -0,0 +1,93 @@ +package org.bukkit.event.player; + +import org.bukkit.Location; +import org.bukkit.entity.Player; +import org.bukkit.event.HandlerList; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +/** + * Holds information for player teleport events + */ +public class PlayerTeleportEvent extends PlayerMoveEvent { + private static final HandlerList handlers = new HandlerList(); + private TeleportCause cause = TeleportCause.UNKNOWN; + + public PlayerTeleportEvent(@NotNull final Player player, @NotNull final Location from, @Nullable final Location to) { + super(player, from, to); + } + + public PlayerTeleportEvent(@NotNull final Player player, @NotNull final Location from, @Nullable final Location to, @NotNull final TeleportCause cause) { + this(player, from, to); + + this.cause = cause; + } + + /** + * Gets the cause of this teleportation event + * + * @return Cause of the event + */ + @NotNull + public TeleportCause getCause() { + return cause; + } + + public enum TeleportCause { + /** + * Indicates the teleporation was caused by a player throwing an Ender + * Pearl + */ + ENDER_PEARL, + /** + * Indicates the teleportation was caused by a player executing a + * command + */ + COMMAND, + /** + * Indicates the teleportation was caused by a plugin + */ + PLUGIN, + /** + * Indicates the teleportation was caused by a player entering a + * Nether portal + */ + NETHER_PORTAL, + /** + * Indicates the teleportation was caused by a player entering an End + * portal + */ + END_PORTAL, + /** + * Indicates the teleportation was caused by a player teleporting to a + * Entity/Player via the spectator menu + */ + SPECTATE, + /** + * Indicates the teleportation was caused by a player entering an End + * gateway + */ + END_GATEWAY, + /** + * Indicates the teleportation was caused by a player consuming chorus + * fruit + */ + CHORUS_FRUIT, + /** + * Indicates the teleportation was caused by an event not covered by + * this enum + */ + UNKNOWN; + } + + @NotNull + @Override + public HandlerList getHandlers() { + return handlers; + } + + @NotNull + public static HandlerList getHandlerList() { + return handlers; + } +} diff --git a/api/src/main/java/org/bukkit/event/player/PlayerToggleFlightEvent.java b/api/src/main/java/org/bukkit/event/player/PlayerToggleFlightEvent.java new file mode 100644 index 000000000..3e53cf9c4 --- /dev/null +++ b/api/src/main/java/org/bukkit/event/player/PlayerToggleFlightEvent.java @@ -0,0 +1,48 @@ +package org.bukkit.event.player; + +import org.bukkit.entity.Player; +import org.bukkit.event.Cancellable; +import org.bukkit.event.HandlerList; +import org.jetbrains.annotations.NotNull; + +/** + * Called when a player toggles their flying state + */ +public class PlayerToggleFlightEvent extends PlayerEvent implements Cancellable { + private static final HandlerList handlers = new HandlerList(); + private final boolean isFlying; + private boolean cancel = false; + + public PlayerToggleFlightEvent(@NotNull final Player player, final boolean isFlying) { + super(player); + this.isFlying = isFlying; + } + + /** + * Returns whether the player is trying to start or stop flying. + * + * @return flying state + */ + public boolean isFlying() { + return isFlying; + } + + public boolean isCancelled() { + return cancel; + } + + public void setCancelled(boolean cancel) { + this.cancel = cancel; + } + + @NotNull + @Override + public HandlerList getHandlers() { + return handlers; + } + + @NotNull + public static HandlerList getHandlerList() { + return handlers; + } +} diff --git a/api/src/main/java/org/bukkit/event/player/PlayerToggleSneakEvent.java b/api/src/main/java/org/bukkit/event/player/PlayerToggleSneakEvent.java new file mode 100644 index 000000000..68d902ae2 --- /dev/null +++ b/api/src/main/java/org/bukkit/event/player/PlayerToggleSneakEvent.java @@ -0,0 +1,48 @@ +package org.bukkit.event.player; + +import org.bukkit.entity.Player; +import org.bukkit.event.Cancellable; +import org.bukkit.event.HandlerList; +import org.jetbrains.annotations.NotNull; + +/** + * Called when a player toggles their sneaking state + */ +public class PlayerToggleSneakEvent extends PlayerEvent implements Cancellable { + private static final HandlerList handlers = new HandlerList(); + private final boolean isSneaking; + private boolean cancel = false; + + public PlayerToggleSneakEvent(@NotNull final Player player, final boolean isSneaking) { + super(player); + this.isSneaking = isSneaking; + } + + /** + * Returns whether the player is now sneaking or not. + * + * @return sneaking state + */ + public boolean isSneaking() { + return isSneaking; + } + + public boolean isCancelled() { + return cancel; + } + + public void setCancelled(boolean cancel) { + this.cancel = cancel; + } + + @NotNull + @Override + public HandlerList getHandlers() { + return handlers; + } + + @NotNull + public static HandlerList getHandlerList() { + return handlers; + } +} diff --git a/api/src/main/java/org/bukkit/event/player/PlayerToggleSprintEvent.java b/api/src/main/java/org/bukkit/event/player/PlayerToggleSprintEvent.java new file mode 100644 index 000000000..3b84960d6 --- /dev/null +++ b/api/src/main/java/org/bukkit/event/player/PlayerToggleSprintEvent.java @@ -0,0 +1,48 @@ +package org.bukkit.event.player; + +import org.bukkit.entity.Player; +import org.bukkit.event.Cancellable; +import org.bukkit.event.HandlerList; +import org.jetbrains.annotations.NotNull; + +/** + * Called when a player toggles their sprinting state + */ +public class PlayerToggleSprintEvent extends PlayerEvent implements Cancellable { + private static final HandlerList handlers = new HandlerList(); + private final boolean isSprinting; + private boolean cancel = false; + + public PlayerToggleSprintEvent(@NotNull final Player player, final boolean isSprinting) { + super(player); + this.isSprinting = isSprinting; + } + + /** + * Gets whether the player is now sprinting or not. + * + * @return sprinting state + */ + public boolean isSprinting() { + return isSprinting; + } + + public boolean isCancelled() { + return cancel; + } + + public void setCancelled(boolean cancel) { + this.cancel = cancel; + } + + @NotNull + @Override + public HandlerList getHandlers() { + return handlers; + } + + @NotNull + public static HandlerList getHandlerList() { + return handlers; + } +} diff --git a/api/src/main/java/org/bukkit/event/player/PlayerUnleashEntityEvent.java b/api/src/main/java/org/bukkit/event/player/PlayerUnleashEntityEvent.java new file mode 100644 index 000000000..e2672aac1 --- /dev/null +++ b/api/src/main/java/org/bukkit/event/player/PlayerUnleashEntityEvent.java @@ -0,0 +1,38 @@ +package org.bukkit.event.player; + +import org.bukkit.entity.Entity; +import org.bukkit.entity.Player; +import org.bukkit.event.Cancellable; +import org.bukkit.event.entity.EntityUnleashEvent; +import org.jetbrains.annotations.NotNull; + +/** + * Called prior to an entity being unleashed due to a player's action. + */ +public class PlayerUnleashEntityEvent extends EntityUnleashEvent implements Cancellable { + private final Player player; + private boolean cancelled = false; + + public PlayerUnleashEntityEvent(@NotNull Entity entity, @NotNull Player player) { + super(entity, UnleashReason.PLAYER_UNLEASH); + this.player = player; + } + + /** + * Returns the player who is unleashing the entity. + * + * @return The player + */ + @NotNull + public Player getPlayer() { + return player; + } + + public boolean isCancelled() { + return cancelled; + } + + public void setCancelled(boolean cancel) { + this.cancelled = cancel; + } +} diff --git a/api/src/main/java/org/bukkit/event/player/PlayerUnregisterChannelEvent.java b/api/src/main/java/org/bukkit/event/player/PlayerUnregisterChannelEvent.java new file mode 100644 index 000000000..b22bc21f5 --- /dev/null +++ b/api/src/main/java/org/bukkit/event/player/PlayerUnregisterChannelEvent.java @@ -0,0 +1,14 @@ +package org.bukkit.event.player; + +import org.bukkit.entity.Player; +import org.jetbrains.annotations.NotNull; + +/** + * This is called immediately after a player unregisters for a plugin channel. + */ +public class PlayerUnregisterChannelEvent extends PlayerChannelEvent { + + public PlayerUnregisterChannelEvent(@NotNull final Player player, @NotNull final String channel) { + super(player, channel); + } +} diff --git a/api/src/main/java/org/bukkit/event/player/PlayerVelocityEvent.java b/api/src/main/java/org/bukkit/event/player/PlayerVelocityEvent.java new file mode 100644 index 000000000..c081c03ee --- /dev/null +++ b/api/src/main/java/org/bukkit/event/player/PlayerVelocityEvent.java @@ -0,0 +1,59 @@ +package org.bukkit.event.player; + +import org.bukkit.entity.Player; +import org.bukkit.event.Cancellable; +import org.bukkit.event.HandlerList; +import org.bukkit.util.Vector; +import org.jetbrains.annotations.NotNull; + +/** + * Called when the velocity of a player changes. + */ +public class PlayerVelocityEvent extends PlayerEvent implements Cancellable { + private static final HandlerList handlers = new HandlerList(); + private boolean cancel = false; + private Vector velocity; + + public PlayerVelocityEvent(@NotNull final Player player, @NotNull final Vector velocity) { + super(player); + this.velocity = velocity; + } + + public boolean isCancelled() { + return cancel; + } + + public void setCancelled(boolean cancel) { + this.cancel = cancel; + } + + /** + * Gets the velocity vector that will be sent to the player + * + * @return Vector the player will get + */ + @NotNull + public Vector getVelocity() { + return velocity; + } + + /** + * Sets the velocity vector that will be sent to the player + * + * @param velocity The velocity vector that will be sent to the player + */ + public void setVelocity(@NotNull Vector velocity) { + this.velocity = velocity; + } + + @NotNull + @Override + public HandlerList getHandlers() { + return handlers; + } + + @NotNull + public static HandlerList getHandlerList() { + return handlers; + } +} diff --git a/api/src/main/java/org/bukkit/event/server/BroadcastMessageEvent.java b/api/src/main/java/org/bukkit/event/server/BroadcastMessageEvent.java new file mode 100644 index 000000000..4bf9d5d82 --- /dev/null +++ b/api/src/main/java/org/bukkit/event/server/BroadcastMessageEvent.java @@ -0,0 +1,82 @@ +package org.bukkit.event.server; + +import java.util.Set; +import org.bukkit.command.CommandSender; +import org.bukkit.event.Cancellable; +import org.bukkit.event.HandlerList; +import org.jetbrains.annotations.NotNull; + +/** + * Event triggered for server broadcast messages such as from + * {@link org.bukkit.Server#broadcast(String, String)}. + */ +public class BroadcastMessageEvent extends ServerEvent implements Cancellable { + + private static final HandlerList handlers = new HandlerList(); + private String message; + private final Set recipients; + private boolean cancelled = false; + + public BroadcastMessageEvent(@NotNull String message, @NotNull Set recipients) { + this.message = message; + this.recipients = recipients; + } + + /** + * Get the message to broadcast. + * + * @return Message to broadcast + */ + @NotNull + public String getMessage() { + return message; + } + + /** + * Set the message to broadcast. + * + * @param message New message to broadcast + */ + public void setMessage(@NotNull String message) { + this.message = message; + } + + /** + * Gets a set of recipients that this chat message will be displayed to. + *

+ * The set returned is not guaranteed to be mutable and may auto-populate + * on access. Any listener accessing the returned set should be aware that + * it may reduce performance for a lazy set implementation. + *

+ * Listeners should be aware that modifying the list may throw {@link + * UnsupportedOperationException} if the event caller provides an + * unmodifiable set. + * + * @return All CommandSenders who will see this chat message + */ + @NotNull + public Set getRecipients() { + return recipients; + } + + @Override + public boolean isCancelled() { + return cancelled; + } + + @Override + public void setCancelled(boolean cancelled) { + this.cancelled = cancelled; + } + + @NotNull + @Override + public HandlerList getHandlers() { + return handlers; + } + + @NotNull + public static HandlerList getHandlerList() { + return handlers; + } +} diff --git a/api/src/main/java/org/bukkit/event/server/MapInitializeEvent.java b/api/src/main/java/org/bukkit/event/server/MapInitializeEvent.java new file mode 100644 index 000000000..dc7440d2e --- /dev/null +++ b/api/src/main/java/org/bukkit/event/server/MapInitializeEvent.java @@ -0,0 +1,38 @@ +package org.bukkit.event.server; + +import org.bukkit.event.HandlerList; +import org.bukkit.map.MapView; +import org.jetbrains.annotations.NotNull; + +/** + * Called when a map is initialized. + */ +public class MapInitializeEvent extends ServerEvent { + private static final HandlerList handlers = new HandlerList(); + private final MapView mapView; + + public MapInitializeEvent(@NotNull final MapView mapView) { + this.mapView = mapView; + } + + /** + * Gets the map initialized in this event. + * + * @return Map for this event + */ + @NotNull + public MapView getMap() { + return mapView; + } + + @NotNull + @Override + public HandlerList getHandlers() { + return handlers; + } + + @NotNull + public static HandlerList getHandlerList() { + return handlers; + } +} diff --git a/api/src/main/java/org/bukkit/event/server/PluginDisableEvent.java b/api/src/main/java/org/bukkit/event/server/PluginDisableEvent.java new file mode 100644 index 000000000..a4fe2d7b8 --- /dev/null +++ b/api/src/main/java/org/bukkit/event/server/PluginDisableEvent.java @@ -0,0 +1,27 @@ +package org.bukkit.event.server; + +import org.bukkit.event.HandlerList; +import org.bukkit.plugin.Plugin; +import org.jetbrains.annotations.NotNull; + +/** + * Called when a plugin is disabled. + */ +public class PluginDisableEvent extends PluginEvent { + private static final HandlerList handlers = new HandlerList(); + + public PluginDisableEvent(@NotNull final Plugin plugin) { + super(plugin); + } + + @NotNull + @Override + public HandlerList getHandlers() { + return handlers; + } + + @NotNull + public static HandlerList getHandlerList() { + return handlers; + } +} diff --git a/api/src/main/java/org/bukkit/event/server/PluginEnableEvent.java b/api/src/main/java/org/bukkit/event/server/PluginEnableEvent.java new file mode 100644 index 000000000..fe78757d6 --- /dev/null +++ b/api/src/main/java/org/bukkit/event/server/PluginEnableEvent.java @@ -0,0 +1,27 @@ +package org.bukkit.event.server; + +import org.bukkit.event.HandlerList; +import org.bukkit.plugin.Plugin; +import org.jetbrains.annotations.NotNull; + +/** + * Called when a plugin is enabled. + */ +public class PluginEnableEvent extends PluginEvent { + private static final HandlerList handlers = new HandlerList(); + + public PluginEnableEvent(@NotNull final Plugin plugin) { + super(plugin); + } + + @NotNull + @Override + public HandlerList getHandlers() { + return handlers; + } + + @NotNull + public static HandlerList getHandlerList() { + return handlers; + } +} diff --git a/api/src/main/java/org/bukkit/event/server/PluginEvent.java b/api/src/main/java/org/bukkit/event/server/PluginEvent.java new file mode 100644 index 000000000..89487b359 --- /dev/null +++ b/api/src/main/java/org/bukkit/event/server/PluginEvent.java @@ -0,0 +1,25 @@ +package org.bukkit.event.server; + +import org.bukkit.plugin.Plugin; +import org.jetbrains.annotations.NotNull; + +/** + * Used for plugin enable and disable events + */ +public abstract class PluginEvent extends ServerEvent { + private final Plugin plugin; + + public PluginEvent(@NotNull final Plugin plugin) { + this.plugin = plugin; + } + + /** + * Gets the plugin involved in this event + * + * @return Plugin for this event + */ + @NotNull + public Plugin getPlugin() { + return plugin; + } +} diff --git a/api/src/main/java/org/bukkit/event/server/RemoteServerCommandEvent.java b/api/src/main/java/org/bukkit/event/server/RemoteServerCommandEvent.java new file mode 100644 index 000000000..2dac594c5 --- /dev/null +++ b/api/src/main/java/org/bukkit/event/server/RemoteServerCommandEvent.java @@ -0,0 +1,28 @@ +package org.bukkit.event.server; + +import org.bukkit.command.CommandSender; +import org.bukkit.event.HandlerList; +import org.jetbrains.annotations.NotNull; + +/** + * This event is called when a command is received over RCON. See the javadocs + * of {@link ServerCommandEvent} for more information. + */ +public class RemoteServerCommandEvent extends ServerCommandEvent { + private static final HandlerList handlers = new HandlerList(); + + public RemoteServerCommandEvent(@NotNull final CommandSender sender, @NotNull final String command) { + super(sender, command); + } + + @NotNull + @Override + public HandlerList getHandlers() { + return handlers; + } + + @NotNull + public static HandlerList getHandlerList() { + return handlers; + } +} diff --git a/api/src/main/java/org/bukkit/event/server/ServerCommandEvent.java b/api/src/main/java/org/bukkit/event/server/ServerCommandEvent.java new file mode 100644 index 000000000..617459afa --- /dev/null +++ b/api/src/main/java/org/bukkit/event/server/ServerCommandEvent.java @@ -0,0 +1,103 @@ +package org.bukkit.event.server; + +import org.bukkit.command.CommandSender; +import org.bukkit.event.Cancellable; +import org.bukkit.event.HandlerList; +import org.jetbrains.annotations.NotNull; + +/** + * This event is called when a command is run by a non-player. It is + * called early in the command handling process, and modifications in this + * event (via {@link #setCommand(String)}) will be shown in the behavior. + *

+ * Many plugins will have no use for this event, and you should + * attempt to avoid using it if it is not necessary. + *

+ * Some examples of valid uses for this event are: + *

    + *
  • Logging executed commands to a separate file + *
  • Variable substitution. For example, replacing ${ip:Steve} + * with the connection IP of the player named Steve, or simulating the + * @a and @p decorators used by Command Blocks + * for plugins that do not handle it. + *
  • Conditionally blocking commands belonging to other plugins. + *
  • Per-sender command aliases. For example, after the console runs the + * command /calias cr gamemode creative, the next time they + * run /cr, it gets replaced into + * /gamemode creative. (Global command aliases should be + * done by registering the alias.) + *
+ *

+ * Examples of incorrect uses are: + *

    + *
  • Using this event to run command logic + *
+ *

+ * If the event is cancelled, processing of the command will halt. + *

+ * The state of whether or not there is a slash (/) at the + * beginning of the message should be preserved. If a slash is added or + * removed, unexpected behavior may result. + */ +public class ServerCommandEvent extends ServerEvent implements Cancellable { + private static final HandlerList handlers = new HandlerList(); + private String command; + private final CommandSender sender; + private boolean cancel = false; + + public ServerCommandEvent(@NotNull final CommandSender sender, @NotNull final String command) { + this.command = command; + this.sender = sender; + } + + /** + * Gets the command that the user is attempting to execute from the + * console + * + * @return Command the user is attempting to execute + */ + @NotNull + public String getCommand() { + return command; + } + + /** + * Sets the command that the server will execute + * + * @param message New message that the server will execute + */ + public void setCommand(@NotNull String message) { + this.command = message; + } + + /** + * Get the command sender. + * + * @return The sender + */ + @NotNull + public CommandSender getSender() { + return sender; + } + + @NotNull + @Override + public HandlerList getHandlers() { + return handlers; + } + + @NotNull + public static HandlerList getHandlerList() { + return handlers; + } + + @Override + public boolean isCancelled() { + return cancel; + } + + @Override + public void setCancelled(boolean cancel) { + this.cancel = cancel; + } +} diff --git a/api/src/main/java/org/bukkit/event/server/ServerEvent.java b/api/src/main/java/org/bukkit/event/server/ServerEvent.java new file mode 100644 index 000000000..70416c811 --- /dev/null +++ b/api/src/main/java/org/bukkit/event/server/ServerEvent.java @@ -0,0 +1,19 @@ +package org.bukkit.event.server; + +import org.bukkit.Bukkit; +import org.bukkit.event.Event; + +/** + * Miscellaneous server events + */ +public abstract class ServerEvent extends Event { + // Paper start + public ServerEvent(boolean isAsync) { + super(isAsync); + } + + public ServerEvent() { + super(!Bukkit.isPrimaryThread()); + } + // Paper end +} diff --git a/api/src/main/java/org/bukkit/event/server/ServerListPingEvent.java b/api/src/main/java/org/bukkit/event/server/ServerListPingEvent.java new file mode 100644 index 000000000..b68d7b2f8 --- /dev/null +++ b/api/src/main/java/org/bukkit/event/server/ServerListPingEvent.java @@ -0,0 +1,155 @@ +package org.bukkit.event.server; + +import java.net.InetAddress; +import java.util.Iterator; + +import org.apache.commons.lang.Validate; +import org.bukkit.UndefinedNullability; +import org.bukkit.entity.Player; +import org.bukkit.event.HandlerList; +import org.bukkit.util.CachedServerIcon; +import org.jetbrains.annotations.NotNull; + +/** + * Called when a server list ping is coming in. Displayed players can be + * checked and removed by {@link #iterator() iterating} over this event. + */ +public class ServerListPingEvent extends ServerEvent implements Iterable { + private static final int MAGIC_PLAYER_COUNT = Integer.MIN_VALUE; + private static final HandlerList handlers = new HandlerList(); + private final InetAddress address; + private String motd; + private final int numPlayers; + private int maxPlayers; + + public ServerListPingEvent(@NotNull final InetAddress address, @NotNull final String motd, final int numPlayers, final int maxPlayers) { + super(); // Paper - Is this event being fired async? + Validate.isTrue(numPlayers >= 0, "Cannot have negative number of players online", numPlayers); + this.address = address; + this.motd = motd; + this.numPlayers = numPlayers; + this.maxPlayers = maxPlayers; + } + + /** + * This constructor is intended for implementations that provide the + * {@link #iterator()} method, thus provided the {@link #getNumPlayers()} + * count. + * + * @param address the address of the pinger + * @param motd the message of the day + * @param maxPlayers the max number of players + */ + protected ServerListPingEvent(@NotNull final InetAddress address, @NotNull final String motd, final int maxPlayers) { + super(); // Paper - Is this event being fired async? + this.numPlayers = MAGIC_PLAYER_COUNT; + this.address = address; + this.motd = motd; + this.maxPlayers = maxPlayers; + } + + /** + * Get the address the ping is coming from. + * + * @return the address + */ + @NotNull + public InetAddress getAddress() { + return address; + } + + /** + * Get the message of the day message. + * + * @return the message of the day + */ + @NotNull + public String getMotd() { + return motd; + } + + /** + * Change the message of the day message. + * + * @param motd the message of the day + */ + public void setMotd(@NotNull String motd) { + this.motd = motd; + } + + /** + * Get the number of players sent. + * + * @return the number of players + */ + public int getNumPlayers() { + int numPlayers = this.numPlayers; + if (numPlayers == MAGIC_PLAYER_COUNT) { + numPlayers = 0; + for (@SuppressWarnings("unused") final Player player : this) { + numPlayers++; + } + } + return numPlayers; + } + + /** + * Get the maximum number of players sent. + * + * @return the maximum number of players + */ + public int getMaxPlayers() { + return maxPlayers; + } + + /** + * Set the maximum number of players sent. + * + * @param maxPlayers the maximum number of player + */ + public void setMaxPlayers(int maxPlayers) { + this.maxPlayers = maxPlayers; + } + + /** + * Sets the server-icon sent to the client. + * + * @param icon the icon to send to the client + * @throws IllegalArgumentException if the {@link CachedServerIcon} is not + * created by the caller of this event; null may be accepted for some + * implementations + * @throws UnsupportedOperationException if the caller of this event does + * not support setting the server icon + */ + public void setServerIcon(@UndefinedNullability("implementation dependent") CachedServerIcon icon) throws IllegalArgumentException, UnsupportedOperationException { + throw new UnsupportedOperationException(); + } + + @NotNull + @Override + public HandlerList getHandlers() { + return handlers; + } + + @NotNull + public static HandlerList getHandlerList() { + return handlers; + } + + /** + * {@inheritDoc} + *

+ * Calling the {@link Iterator#remove()} method will force that particular + * player to not be displayed on the player list, decrease the size + * returned by {@link #getNumPlayers()}, and will not be returned again by + * any new iterator. + * + * @throws UnsupportedOperationException if the caller of this event does + * not support removing players + */ + @NotNull + @Override + public Iterator iterator() throws UnsupportedOperationException { + throw new UnsupportedOperationException(); + } +} diff --git a/api/src/main/java/org/bukkit/event/server/ServerLoadEvent.java b/api/src/main/java/org/bukkit/event/server/ServerLoadEvent.java new file mode 100644 index 000000000..c9a252d7e --- /dev/null +++ b/api/src/main/java/org/bukkit/event/server/ServerLoadEvent.java @@ -0,0 +1,50 @@ +package org.bukkit.event.server; + +import org.bukkit.event.HandlerList; +import org.jetbrains.annotations.NotNull; + +/** + * This event is called when either the server startup or reload has completed. + */ +public class ServerLoadEvent extends ServerEvent { + + /** + * Represents the context in which the enclosing event has been completed. + */ + public enum LoadType { + STARTUP, RELOAD; + } + + private static final HandlerList handlers = new HandlerList(); + private final LoadType type; + + /** + * Creates a {@code ServerLoadEvent} with a given loading type. + * + * @param type the context in which the server was loaded + */ + public ServerLoadEvent(@NotNull LoadType type) { + this.type = type; + } + + /** + * Gets the context in which the server was loaded. + * + * @return the context in which the server was loaded + */ + @NotNull + public LoadType getType() { + return type; + } + + @NotNull + @Override + public HandlerList getHandlers() { + return handlers; + } + + @NotNull + public static HandlerList getHandlerList() { + return handlers; + } +} diff --git a/api/src/main/java/org/bukkit/event/server/ServiceEvent.java b/api/src/main/java/org/bukkit/event/server/ServiceEvent.java new file mode 100644 index 000000000..bbc2de504 --- /dev/null +++ b/api/src/main/java/org/bukkit/event/server/ServiceEvent.java @@ -0,0 +1,21 @@ +package org.bukkit.event.server; + +import org.bukkit.plugin.RegisteredServiceProvider; +import org.jetbrains.annotations.NotNull; + +/** + * An event relating to a registered service. This is called in a {@link + * org.bukkit.plugin.ServicesManager} + */ +public abstract class ServiceEvent extends ServerEvent { + private final RegisteredServiceProvider provider; + + public ServiceEvent(@NotNull final RegisteredServiceProvider provider) { + this.provider = provider; + } + + @NotNull + public RegisteredServiceProvider getProvider() { + return provider; + } +} \ No newline at end of file diff --git a/api/src/main/java/org/bukkit/event/server/ServiceRegisterEvent.java b/api/src/main/java/org/bukkit/event/server/ServiceRegisterEvent.java new file mode 100644 index 000000000..d7f9227ef --- /dev/null +++ b/api/src/main/java/org/bukkit/event/server/ServiceRegisterEvent.java @@ -0,0 +1,30 @@ +package org.bukkit.event.server; + +import org.bukkit.event.HandlerList; +import org.bukkit.plugin.RegisteredServiceProvider; +import org.jetbrains.annotations.NotNull; + +/** + * This event is called when a service is registered. + *

+ * Warning: The order in which register and unregister events are called + * should not be relied upon. + */ +public class ServiceRegisterEvent extends ServiceEvent { + private static final HandlerList handlers = new HandlerList(); + + public ServiceRegisterEvent(@NotNull RegisteredServiceProvider registeredProvider) { + super(registeredProvider); + } + + @NotNull + @Override + public HandlerList getHandlers() { + return handlers; + } + + @NotNull + public static HandlerList getHandlerList() { + return handlers; + } +} diff --git a/api/src/main/java/org/bukkit/event/server/ServiceUnregisterEvent.java b/api/src/main/java/org/bukkit/event/server/ServiceUnregisterEvent.java new file mode 100644 index 000000000..f286799ee --- /dev/null +++ b/api/src/main/java/org/bukkit/event/server/ServiceUnregisterEvent.java @@ -0,0 +1,30 @@ +package org.bukkit.event.server; + +import org.bukkit.event.HandlerList; +import org.bukkit.plugin.RegisteredServiceProvider; +import org.jetbrains.annotations.NotNull; + +/** + * This event is called when a service is unregistered. + *

+ * Warning: The order in which register and unregister events are called + * should not be relied upon. + */ +public class ServiceUnregisterEvent extends ServiceEvent { + private static final HandlerList handlers = new HandlerList(); + + public ServiceUnregisterEvent(@NotNull RegisteredServiceProvider serviceProvider) { + super(serviceProvider); + } + + @NotNull + @Override + public HandlerList getHandlers() { + return handlers; + } + + @NotNull + public static HandlerList getHandlerList() { + return handlers; + } +} diff --git a/api/src/main/java/org/bukkit/event/server/TabCompleteEvent.java b/api/src/main/java/org/bukkit/event/server/TabCompleteEvent.java new file mode 100644 index 000000000..f96c4ba53 --- /dev/null +++ b/api/src/main/java/org/bukkit/event/server/TabCompleteEvent.java @@ -0,0 +1,132 @@ +package org.bukkit.event.server; + +import java.util.ArrayList; +import java.util.List; +import org.apache.commons.lang.Validate; +import org.bukkit.command.CommandSender; +import org.bukkit.event.Cancellable; +import org.bukkit.event.Event; +import org.bukkit.event.HandlerList; +import org.bukkit.event.player.PlayerCommandSendEvent; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +/** + * Called when a {@link CommandSender} of any description (ie: player or + * console) attempts to tab complete. + *
+ * Note that due to client changes, if the sender is a Player, this event will + * only begin to fire once command arguments are specified, not commands + * themselves. Plugins wishing to remove commands from tab completion are + * advised to ensure the client does not have permission for the relevant + * commands, or use {@link PlayerCommandSendEvent}. + */ +public class TabCompleteEvent extends Event implements Cancellable { + + private static final HandlerList handlers = new HandlerList(); + // + private final CommandSender sender; + private final String buffer; + private List completions; + private boolean cancelled; + + public TabCompleteEvent(@NotNull CommandSender sender, @NotNull String buffer, @NotNull List completions) { + // Paper start + this(sender, buffer, completions, sender instanceof org.bukkit.command.ConsoleCommandSender || buffer.startsWith("/"), null); + } + public TabCompleteEvent(@NotNull CommandSender sender, @NotNull String buffer, @NotNull List completions, boolean isCommand, @Nullable org.bukkit.Location location) { + this.isCommand = isCommand; + this.loc = location; + // Paper end + Validate.notNull(sender, "sender"); + Validate.notNull(buffer, "buffer"); + Validate.notNull(completions, "completions"); + + this.sender = sender; + this.buffer = buffer; + this.completions = completions; + } + + /** + * Get the sender completing this command. + * + * @return the {@link CommandSender} instance + */ + @NotNull + public CommandSender getSender() { + return sender; + } + + /** + * Return the entire buffer which formed the basis of this completion. + * + * @return command buffer, as entered + */ + @NotNull + public String getBuffer() { + return buffer; + } + + /** + * The list of completions which will be offered to the sender, in order. + * This list is mutable and reflects what will be offered. + * + * @return a list of offered completions + */ + @NotNull + public List getCompletions() { + return completions; + } + + // Paper start + private final boolean isCommand; + private final org.bukkit.Location loc; + /** + * @return True if it is a command being tab completed, false if it is a chat message. + */ + public boolean isCommand() { + return isCommand; + } + + /** + * @return The position looked at by the sender, or null if none + */ + @Nullable + public org.bukkit.Location getLocation() { + return loc; + } + // Paper end + + /** + * Set the completions offered, overriding any already set. + * + * The passed collection will be cloned to a new List. You must call {{@link #getCompletions()}} to mutate from here + * + * @param completions the new completions + */ + public void setCompletions(@NotNull List completions) { + Validate.notNull(completions); + this.completions = new ArrayList<>(completions); // Paper + } + + @Override + public boolean isCancelled() { + return cancelled; + } + + @Override + public void setCancelled(boolean cancelled) { + this.cancelled = cancelled; + } + + @NotNull + @Override + public HandlerList getHandlers() { + return handlers; + } + + @NotNull + public static HandlerList getHandlerList() { + return handlers; + } +} diff --git a/api/src/main/java/org/bukkit/event/vehicle/VehicleBlockCollisionEvent.java b/api/src/main/java/org/bukkit/event/vehicle/VehicleBlockCollisionEvent.java new file mode 100644 index 000000000..316f625aa --- /dev/null +++ b/api/src/main/java/org/bukkit/event/vehicle/VehicleBlockCollisionEvent.java @@ -0,0 +1,40 @@ +package org.bukkit.event.vehicle; + +import org.bukkit.block.Block; +import org.bukkit.entity.Vehicle; +import org.bukkit.event.HandlerList; +import org.jetbrains.annotations.NotNull; + +/** + * Raised when a vehicle collides with a block. + */ +public class VehicleBlockCollisionEvent extends VehicleCollisionEvent { + private static final HandlerList handlers = new HandlerList(); + private final Block block; + + public VehicleBlockCollisionEvent(@NotNull final Vehicle vehicle, @NotNull final Block block) { + super(vehicle); + this.block = block; + } + + /** + * Gets the block the vehicle collided with + * + * @return the block the vehicle collided with + */ + @NotNull + public Block getBlock() { + return block; + } + + @NotNull + @Override + public HandlerList getHandlers() { + return handlers; + } + + @NotNull + public static HandlerList getHandlerList() { + return handlers; + } +} diff --git a/api/src/main/java/org/bukkit/event/vehicle/VehicleCollisionEvent.java b/api/src/main/java/org/bukkit/event/vehicle/VehicleCollisionEvent.java new file mode 100644 index 000000000..9d493c155 --- /dev/null +++ b/api/src/main/java/org/bukkit/event/vehicle/VehicleCollisionEvent.java @@ -0,0 +1,13 @@ +package org.bukkit.event.vehicle; + +import org.bukkit.entity.Vehicle; +import org.jetbrains.annotations.NotNull; + +/** + * Raised when a vehicle collides. + */ +public abstract class VehicleCollisionEvent extends VehicleEvent { + public VehicleCollisionEvent(@NotNull final Vehicle vehicle) { + super(vehicle); + } +} diff --git a/api/src/main/java/org/bukkit/event/vehicle/VehicleCreateEvent.java b/api/src/main/java/org/bukkit/event/vehicle/VehicleCreateEvent.java new file mode 100644 index 000000000..c1f107db9 --- /dev/null +++ b/api/src/main/java/org/bukkit/event/vehicle/VehicleCreateEvent.java @@ -0,0 +1,39 @@ +package org.bukkit.event.vehicle; + +import org.bukkit.entity.Vehicle; +import org.bukkit.event.Cancellable; +import org.bukkit.event.HandlerList; +import org.jetbrains.annotations.NotNull; + +/** + * Raised when a vehicle is created. + */ +public class VehicleCreateEvent extends VehicleEvent implements Cancellable { + private static final HandlerList handlers = new HandlerList(); + private boolean cancelled; + + public VehicleCreateEvent(@NotNull final Vehicle vehicle) { + super(vehicle); + } + + @Override + public boolean isCancelled() { + return cancelled; + } + + @Override + public void setCancelled(boolean cancelled) { + this.cancelled = cancelled; + } + + @NotNull + @Override + public HandlerList getHandlers() { + return handlers; + } + + @NotNull + public static HandlerList getHandlerList() { + return handlers; + } +} diff --git a/api/src/main/java/org/bukkit/event/vehicle/VehicleDamageEvent.java b/api/src/main/java/org/bukkit/event/vehicle/VehicleDamageEvent.java new file mode 100644 index 000000000..ced91e4e4 --- /dev/null +++ b/api/src/main/java/org/bukkit/event/vehicle/VehicleDamageEvent.java @@ -0,0 +1,71 @@ +package org.bukkit.event.vehicle; + +import org.bukkit.entity.Entity; +import org.bukkit.entity.Vehicle; +import org.bukkit.event.Cancellable; +import org.bukkit.event.HandlerList; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +/** + * Raised when a vehicle receives damage. + */ +public class VehicleDamageEvent extends VehicleEvent implements Cancellable { + private static final HandlerList handlers = new HandlerList(); + private final Entity attacker; + private double damage; + private boolean cancelled; + + public VehicleDamageEvent(@NotNull final Vehicle vehicle, @Nullable final Entity attacker, final double damage) { + super(vehicle); + this.attacker = attacker; + this.damage = damage; + } + + /** + * Gets the Entity that is attacking the vehicle + * + * @return the Entity that is attacking the vehicle + */ + @Nullable + public Entity getAttacker() { + return attacker; + } + + /** + * Gets the damage done to the vehicle + * + * @return the damage done to the vehicle + */ + public double getDamage() { + return damage; + } + + /** + * Sets the damage done to the vehicle + * + * @param damage The damage + */ + public void setDamage(double damage) { + this.damage = damage; + } + + public boolean isCancelled() { + return cancelled; + } + + public void setCancelled(boolean cancel) { + this.cancelled = cancel; + } + + @NotNull + @Override + public HandlerList getHandlers() { + return handlers; + } + + @NotNull + public static HandlerList getHandlerList() { + return handlers; + } +} diff --git a/api/src/main/java/org/bukkit/event/vehicle/VehicleDestroyEvent.java b/api/src/main/java/org/bukkit/event/vehicle/VehicleDestroyEvent.java new file mode 100644 index 000000000..2ee6ffd0f --- /dev/null +++ b/api/src/main/java/org/bukkit/event/vehicle/VehicleDestroyEvent.java @@ -0,0 +1,53 @@ +package org.bukkit.event.vehicle; + +import org.bukkit.entity.Entity; +import org.bukkit.entity.Vehicle; +import org.bukkit.event.Cancellable; +import org.bukkit.event.HandlerList; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +/** + * Raised when a vehicle is destroyed, which could be caused by either a + * player or the environment. This is not raised if the boat is simply + * 'removed' due to other means. + */ +public class VehicleDestroyEvent extends VehicleEvent implements Cancellable { + private static final HandlerList handlers = new HandlerList(); + private final Entity attacker; + private boolean cancelled; + + public VehicleDestroyEvent(@NotNull final Vehicle vehicle, @Nullable final Entity attacker) { + super(vehicle); + this.attacker = attacker; + } + + /** + * Gets the Entity that has destroyed the vehicle, potentially null + * + * @return the Entity that has destroyed the vehicle, potentially null + */ + @Nullable + public Entity getAttacker() { + return attacker; + } + + public boolean isCancelled() { + return cancelled; + } + + public void setCancelled(boolean cancel) { + this.cancelled = cancel; + } + + @NotNull + @Override + public HandlerList getHandlers() { + return handlers; + } + + @NotNull + public static HandlerList getHandlerList() { + return handlers; + } +} diff --git a/api/src/main/java/org/bukkit/event/vehicle/VehicleEnterEvent.java b/api/src/main/java/org/bukkit/event/vehicle/VehicleEnterEvent.java new file mode 100644 index 000000000..066992f0a --- /dev/null +++ b/api/src/main/java/org/bukkit/event/vehicle/VehicleEnterEvent.java @@ -0,0 +1,50 @@ +package org.bukkit.event.vehicle; + +import org.bukkit.entity.Entity; +import org.bukkit.entity.Vehicle; +import org.bukkit.event.Cancellable; +import org.bukkit.event.HandlerList; +import org.jetbrains.annotations.NotNull; + +/** + * Raised when an entity enters a vehicle. + */ +public class VehicleEnterEvent extends VehicleEvent implements Cancellable { + private static final HandlerList handlers = new HandlerList(); + private boolean cancelled; + private final Entity entered; + + public VehicleEnterEvent(@NotNull final Vehicle vehicle, @NotNull final Entity entered) { + super(vehicle); + this.entered = entered; + } + + /** + * Gets the Entity that entered the vehicle. + * + * @return the Entity that entered the vehicle + */ + @NotNull + public Entity getEntered() { + return entered; + } + + public boolean isCancelled() { + return cancelled; + } + + public void setCancelled(boolean cancel) { + this.cancelled = cancel; + } + + @NotNull + @Override + public HandlerList getHandlers() { + return handlers; + } + + @NotNull + public static HandlerList getHandlerList() { + return handlers; + } +} diff --git a/api/src/main/java/org/bukkit/event/vehicle/VehicleEntityCollisionEvent.java b/api/src/main/java/org/bukkit/event/vehicle/VehicleEntityCollisionEvent.java new file mode 100644 index 000000000..235c68727 --- /dev/null +++ b/api/src/main/java/org/bukkit/event/vehicle/VehicleEntityCollisionEvent.java @@ -0,0 +1,63 @@ +package org.bukkit.event.vehicle; + +import org.bukkit.entity.Entity; +import org.bukkit.entity.Vehicle; +import org.bukkit.event.Cancellable; +import org.bukkit.event.HandlerList; +import org.jetbrains.annotations.NotNull; + +/** + * Raised when a vehicle collides with an entity. + */ +public class VehicleEntityCollisionEvent extends VehicleCollisionEvent implements Cancellable { + private static final HandlerList handlers = new HandlerList(); + private final Entity entity; + private boolean cancelled = false; + private boolean cancelledPickup = false; + private boolean cancelledCollision = false; + + public VehicleEntityCollisionEvent(@NotNull final Vehicle vehicle, @NotNull final Entity entity) { + super(vehicle); + this.entity = entity; + } + + @NotNull + public Entity getEntity() { + return entity; + } + + public boolean isCancelled() { + return cancelled; + } + + public void setCancelled(boolean cancel) { + this.cancelled = cancel; + } + + public boolean isPickupCancelled() { + return cancelledPickup; + } + + public void setPickupCancelled(boolean cancel) { + cancelledPickup = cancel; + } + + public boolean isCollisionCancelled() { + return cancelledCollision; + } + + public void setCollisionCancelled(boolean cancel) { + cancelledCollision = cancel; + } + + @NotNull + @Override + public HandlerList getHandlers() { + return handlers; + } + + @NotNull + public static HandlerList getHandlerList() { + return handlers; + } +} diff --git a/api/src/main/java/org/bukkit/event/vehicle/VehicleEvent.java b/api/src/main/java/org/bukkit/event/vehicle/VehicleEvent.java new file mode 100644 index 000000000..63df27056 --- /dev/null +++ b/api/src/main/java/org/bukkit/event/vehicle/VehicleEvent.java @@ -0,0 +1,26 @@ +package org.bukkit.event.vehicle; + +import org.bukkit.entity.Vehicle; +import org.bukkit.event.Event; +import org.jetbrains.annotations.NotNull; + +/** + * Represents a vehicle-related event. + */ +public abstract class VehicleEvent extends Event { + protected Vehicle vehicle; + + public VehicleEvent(@NotNull final Vehicle vehicle) { + this.vehicle = vehicle; + } + + /** + * Get the vehicle. + * + * @return the vehicle + */ + @NotNull + public final Vehicle getVehicle() { + return vehicle; + } +} diff --git a/api/src/main/java/org/bukkit/event/vehicle/VehicleExitEvent.java b/api/src/main/java/org/bukkit/event/vehicle/VehicleExitEvent.java new file mode 100644 index 000000000..f0c0bae56 --- /dev/null +++ b/api/src/main/java/org/bukkit/event/vehicle/VehicleExitEvent.java @@ -0,0 +1,67 @@ +package org.bukkit.event.vehicle; + +import org.bukkit.entity.LivingEntity; +import org.bukkit.entity.Vehicle; +import org.bukkit.event.Cancellable; +import org.bukkit.event.HandlerList; +import org.jetbrains.annotations.NotNull; + +/** + * Raised when a living entity exits a vehicle. + */ +public class VehicleExitEvent extends VehicleEvent implements Cancellable { + private static final HandlerList handlers = new HandlerList(); + private boolean cancelled; + private final LivingEntity exited; + private final boolean isCancellable; // Paper + + public VehicleExitEvent(@NotNull final Vehicle vehicle, @NotNull final LivingEntity exited, boolean isCancellable) { // Paper + super(vehicle); + this.exited = exited; + // Paper start + this.isCancellable = isCancellable; + } + + public VehicleExitEvent(@NotNull final Vehicle vehicle, @NotNull final LivingEntity exited) { + this(vehicle, exited, true); + // Paper end + } + + /** + * Get the living entity that exited the vehicle. + * + * @return The entity. + */ + @NotNull + public LivingEntity getExited() { + return exited; + } + + public boolean isCancelled() { + return cancelled; + } + + public void setCancelled(boolean cancel) { + // Paper start + if (cancel && !isCancellable) { + return; + } + this.cancelled = cancel; + } + + public boolean isCancellable() { + return isCancellable; + // paper end + } + + @NotNull + @Override + public HandlerList getHandlers() { + return handlers; + } + + @NotNull + public static HandlerList getHandlerList() { + return handlers; + } +} diff --git a/api/src/main/java/org/bukkit/event/vehicle/VehicleMoveEvent.java b/api/src/main/java/org/bukkit/event/vehicle/VehicleMoveEvent.java new file mode 100644 index 000000000..7bfb84d39 --- /dev/null +++ b/api/src/main/java/org/bukkit/event/vehicle/VehicleMoveEvent.java @@ -0,0 +1,54 @@ +package org.bukkit.event.vehicle; + +import org.bukkit.Location; +import org.bukkit.entity.Vehicle; +import org.bukkit.event.HandlerList; +import org.jetbrains.annotations.NotNull; + +/** + * Raised when a vehicle moves. + */ +public class VehicleMoveEvent extends VehicleEvent { + private static final HandlerList handlers = new HandlerList(); + private final Location from; + private final Location to; + + public VehicleMoveEvent(@NotNull final Vehicle vehicle, @NotNull final Location from, @NotNull final Location to) { + super(vehicle); + + this.from = from; + this.to = to; + } + + /** + * Get the previous position. + * + * @return Old position. + */ + @NotNull + public Location getFrom() { + return from; + } + + /** + * Get the next position. + * + * @return New position. + */ + @NotNull + public Location getTo() { + return to; + } + + + @NotNull + @Override + public HandlerList getHandlers() { + return handlers; + } + + @NotNull + public static HandlerList getHandlerList() { + return handlers; + } +} diff --git a/api/src/main/java/org/bukkit/event/vehicle/VehicleUpdateEvent.java b/api/src/main/java/org/bukkit/event/vehicle/VehicleUpdateEvent.java new file mode 100644 index 000000000..098192a5e --- /dev/null +++ b/api/src/main/java/org/bukkit/event/vehicle/VehicleUpdateEvent.java @@ -0,0 +1,27 @@ +package org.bukkit.event.vehicle; + +import org.bukkit.entity.Vehicle; +import org.bukkit.event.HandlerList; +import org.jetbrains.annotations.NotNull; + +/** + * Called when a vehicle updates + */ +public class VehicleUpdateEvent extends VehicleEvent { + private static final HandlerList handlers = new HandlerList(); + + public VehicleUpdateEvent(@NotNull final Vehicle vehicle) { + super(vehicle); + } + + @NotNull + @Override + public HandlerList getHandlers() { + return handlers; + } + + @NotNull + public static HandlerList getHandlerList() { + return handlers; + } +} diff --git a/api/src/main/java/org/bukkit/event/weather/LightningStrikeEvent.java b/api/src/main/java/org/bukkit/event/weather/LightningStrikeEvent.java new file mode 100644 index 000000000..45f26a7b3 --- /dev/null +++ b/api/src/main/java/org/bukkit/event/weather/LightningStrikeEvent.java @@ -0,0 +1,90 @@ +package org.bukkit.event.weather; + +import org.bukkit.World; +import org.bukkit.entity.LightningStrike; +import org.bukkit.event.Cancellable; +import org.bukkit.event.HandlerList; +import org.jetbrains.annotations.NotNull; + +/** + * Stores data for lightning striking + */ +public class LightningStrikeEvent extends WeatherEvent implements Cancellable { + private static final HandlerList handlers = new HandlerList(); + private boolean canceled; + private final LightningStrike bolt; + private final Cause cause; + + @Deprecated + public LightningStrikeEvent(@NotNull final World world, @NotNull final LightningStrike bolt) { + this(world, bolt, Cause.UNKNOWN); + } + + public LightningStrikeEvent(@NotNull final World world, @NotNull final LightningStrike bolt, @NotNull final Cause cause) { + super(world); + this.bolt = bolt; + this.cause = cause; + } + + public boolean isCancelled() { + return canceled; + } + + public void setCancelled(boolean cancel) { + canceled = cancel; + } + + /** + * Gets the bolt which is striking the earth. + * + * @return lightning entity + */ + @NotNull + public LightningStrike getLightning() { + return bolt; + } + + /** + * Gets the cause of this lightning strike. + * + * @return strike cause + */ + @NotNull + public Cause getCause() { + return cause; + } + + @NotNull + @Override + public HandlerList getHandlers() { + return handlers; + } + + @NotNull + public static HandlerList getHandlerList() { + return handlers; + } + + public enum Cause { + /** + * Triggered by the /summon command. + */ + COMMAND, + /** + * Triggered by an enchanted trident. + */ + TRIDENT, + /** + * Triggered by a skeleton horse trap. + */ + TRAP, + /** + * Triggered by weather. + */ + WEATHER, + /** + * Unknown trigger. + */ + UNKNOWN; + } +} diff --git a/api/src/main/java/org/bukkit/event/weather/ThunderChangeEvent.java b/api/src/main/java/org/bukkit/event/weather/ThunderChangeEvent.java new file mode 100644 index 000000000..641769dfe --- /dev/null +++ b/api/src/main/java/org/bukkit/event/weather/ThunderChangeEvent.java @@ -0,0 +1,48 @@ +package org.bukkit.event.weather; + +import org.bukkit.World; +import org.bukkit.event.Cancellable; +import org.bukkit.event.HandlerList; +import org.jetbrains.annotations.NotNull; + +/** + * Stores data for thunder state changing in a world + */ +public class ThunderChangeEvent extends WeatherEvent implements Cancellable { + private static final HandlerList handlers = new HandlerList(); + private boolean canceled; + private final boolean to; + + public ThunderChangeEvent(@NotNull final World world, final boolean to) { + super(world); + this.to = to; + } + + public boolean isCancelled() { + return canceled; + } + + public void setCancelled(boolean cancel) { + canceled = cancel; + } + + /** + * Gets the state of thunder that the world is being set to + * + * @return true if the weather is being set to thundering, false otherwise + */ + public boolean toThunderState() { + return to; + } + + @NotNull + @Override + public HandlerList getHandlers() { + return handlers; + } + + @NotNull + public static HandlerList getHandlerList() { + return handlers; + } +} diff --git a/api/src/main/java/org/bukkit/event/weather/WeatherChangeEvent.java b/api/src/main/java/org/bukkit/event/weather/WeatherChangeEvent.java new file mode 100644 index 000000000..db19e7909 --- /dev/null +++ b/api/src/main/java/org/bukkit/event/weather/WeatherChangeEvent.java @@ -0,0 +1,48 @@ +package org.bukkit.event.weather; + +import org.bukkit.World; +import org.bukkit.event.Cancellable; +import org.bukkit.event.HandlerList; +import org.jetbrains.annotations.NotNull; + +/** + * Stores data for weather changing in a world + */ +public class WeatherChangeEvent extends WeatherEvent implements Cancellable { + private static final HandlerList handlers = new HandlerList(); + private boolean canceled; + private final boolean to; + + public WeatherChangeEvent(@NotNull final World world, final boolean to) { + super(world); + this.to = to; + } + + public boolean isCancelled() { + return canceled; + } + + public void setCancelled(boolean cancel) { + canceled = cancel; + } + + /** + * Gets the state of weather that the world is being set to + * + * @return true if the weather is being set to raining, false otherwise + */ + public boolean toWeatherState() { + return to; + } + + @NotNull + @Override + public HandlerList getHandlers() { + return handlers; + } + + @NotNull + public static HandlerList getHandlerList() { + return handlers; + } +} diff --git a/api/src/main/java/org/bukkit/event/weather/WeatherEvent.java b/api/src/main/java/org/bukkit/event/weather/WeatherEvent.java new file mode 100644 index 000000000..e1854d807 --- /dev/null +++ b/api/src/main/java/org/bukkit/event/weather/WeatherEvent.java @@ -0,0 +1,26 @@ +package org.bukkit.event.weather; + +import org.bukkit.World; +import org.bukkit.event.Event; +import org.jetbrains.annotations.NotNull; + +/** + * Represents a Weather-related event + */ +public abstract class WeatherEvent extends Event { + protected World world; + + public WeatherEvent(@NotNull final World where) { + world = where; + } + + /** + * Returns the World where this event is occurring + * + * @return World this event is occurring in + */ + @NotNull + public final World getWorld() { + return world; + } +} diff --git a/api/src/main/java/org/bukkit/event/world/ChunkEvent.java b/api/src/main/java/org/bukkit/event/world/ChunkEvent.java new file mode 100644 index 000000000..7ffc6a77b --- /dev/null +++ b/api/src/main/java/org/bukkit/event/world/ChunkEvent.java @@ -0,0 +1,26 @@ +package org.bukkit.event.world; + +import org.bukkit.Chunk; +import org.jetbrains.annotations.NotNull; + +/** + * Represents a Chunk related event + */ +public abstract class ChunkEvent extends WorldEvent { + protected Chunk chunk; + + protected ChunkEvent(@NotNull final Chunk chunk) { + super(chunk.getWorld()); + this.chunk = chunk; + } + + /** + * Gets the chunk being loaded/unloaded + * + * @return Chunk that triggered this event + */ + @NotNull + public Chunk getChunk() { + return chunk; + } +} diff --git a/api/src/main/java/org/bukkit/event/world/ChunkLoadEvent.java b/api/src/main/java/org/bukkit/event/world/ChunkLoadEvent.java new file mode 100644 index 000000000..ae8ef3392 --- /dev/null +++ b/api/src/main/java/org/bukkit/event/world/ChunkLoadEvent.java @@ -0,0 +1,40 @@ +package org.bukkit.event.world; + +import org.bukkit.Chunk; +import org.bukkit.event.HandlerList; +import org.jetbrains.annotations.NotNull; + +/** + * Called when a chunk is loaded + */ +public class ChunkLoadEvent extends ChunkEvent { + private static final HandlerList handlers = new HandlerList(); + private final boolean newChunk; + + public ChunkLoadEvent(@NotNull final Chunk chunk, final boolean newChunk) { + super(chunk); + this.newChunk = newChunk; + } + + /** + * Gets if this chunk was newly created or not. + *

+ * Note that if this chunk is new, it will not be populated at this time. + * + * @return true if the chunk is new, otherwise false + */ + public boolean isNewChunk() { + return newChunk; + } + + @NotNull + @Override + public HandlerList getHandlers() { + return handlers; + } + + @NotNull + public static HandlerList getHandlerList() { + return handlers; + } +} diff --git a/api/src/main/java/org/bukkit/event/world/ChunkPopulateEvent.java b/api/src/main/java/org/bukkit/event/world/ChunkPopulateEvent.java new file mode 100644 index 000000000..46d91403c --- /dev/null +++ b/api/src/main/java/org/bukkit/event/world/ChunkPopulateEvent.java @@ -0,0 +1,31 @@ +package org.bukkit.event.world; + +import org.bukkit.Chunk; +import org.bukkit.event.HandlerList; +import org.bukkit.generator.BlockPopulator; +import org.jetbrains.annotations.NotNull; + +/** + * Thrown when a new chunk has finished being populated. + *

+ * If your intent is to populate the chunk using this event, please see {@link + * BlockPopulator} + */ +public class ChunkPopulateEvent extends ChunkEvent { + private static final HandlerList handlers = new HandlerList(); + + public ChunkPopulateEvent(@NotNull final Chunk chunk) { + super(chunk); + } + + @NotNull + @Override + public HandlerList getHandlers() { + return handlers; + } + + @NotNull + public static HandlerList getHandlerList() { + return handlers; + } +} diff --git a/api/src/main/java/org/bukkit/event/world/ChunkUnloadEvent.java b/api/src/main/java/org/bukkit/event/world/ChunkUnloadEvent.java new file mode 100644 index 000000000..145cbe2eb --- /dev/null +++ b/api/src/main/java/org/bukkit/event/world/ChunkUnloadEvent.java @@ -0,0 +1,61 @@ +package org.bukkit.event.world; + +import org.bukkit.Chunk; +import org.bukkit.event.Cancellable; +import org.bukkit.event.HandlerList; +import org.jetbrains.annotations.NotNull; + +/** + * Called when a chunk is unloaded + */ +public class ChunkUnloadEvent extends ChunkEvent implements Cancellable { + private static final HandlerList handlers = new HandlerList(); + private boolean cancel = false; + private boolean saveChunk; + + public ChunkUnloadEvent(@NotNull final Chunk chunk) { + this(chunk, true); + } + + public ChunkUnloadEvent(@NotNull Chunk chunk, boolean save) { + super(chunk); + this.saveChunk = save; + } + + /** + * Return whether this chunk will be saved to disk. + * + * @return chunk save status + */ + public boolean isSaveChunk() { + return saveChunk; + } + + /** + * Set whether this chunk will be saved to disk. + * + * @param saveChunk chunk save status + */ + public void setSaveChunk(boolean saveChunk) { + this.saveChunk = saveChunk; + } + + public boolean isCancelled() { + return cancel; + } + + public void setCancelled(boolean cancel) { + this.cancel = cancel; + } + + @NotNull + @Override + public HandlerList getHandlers() { + return handlers; + } + + @NotNull + public static HandlerList getHandlerList() { + return handlers; + } +} diff --git a/api/src/main/java/org/bukkit/event/world/PortalCreateEvent.java b/api/src/main/java/org/bukkit/event/world/PortalCreateEvent.java new file mode 100644 index 000000000..714272662 --- /dev/null +++ b/api/src/main/java/org/bukkit/event/world/PortalCreateEvent.java @@ -0,0 +1,82 @@ +package org.bukkit.event.world; + +import org.bukkit.block.Block; +import org.bukkit.World; +import org.bukkit.event.Cancellable; +import org.bukkit.event.HandlerList; +import org.jetbrains.annotations.NotNull; + +import java.util.ArrayList; +import java.util.Collection; + +/** + * Called when a portal is created + */ +public class PortalCreateEvent extends WorldEvent implements Cancellable { + private static final HandlerList handlers = new HandlerList(); + private boolean cancel = false; + private final ArrayList blocks = new ArrayList(); + private CreateReason reason = CreateReason.FIRE; + + public PortalCreateEvent(@NotNull final Collection blocks, @NotNull final World world, @NotNull CreateReason reason) { + super(world); + + this.blocks.addAll(blocks); + this.reason = reason; + } + + /** + * Gets an array list of all the blocks associated with the created portal + * + * @return array list of all the blocks associated with the created portal + */ + @NotNull + public ArrayList getBlocks() { + return this.blocks; + } + + public boolean isCancelled() { + return cancel; + } + + public void setCancelled(boolean cancel) { + this.cancel = cancel; + } + + /** + * Gets the reason for the portal's creation + * + * @return CreateReason for the portal's creation + */ + @NotNull + public CreateReason getReason() { + return reason; + } + + @NotNull + @Override + public HandlerList getHandlers() { + return handlers; + } + + @NotNull + public static HandlerList getHandlerList() { + return handlers; + } + + /** + * An enum to specify the various reasons for a portal's creation + */ + public enum CreateReason { + /** + * When a portal is created 'traditionally' due to a portal frame + * being set on fire. + */ + FIRE, + /** + * When a portal is created as a destination for an existing portal + * when using the custom PortalTravelAgent + */ + OBC_DESTINATION + } +} diff --git a/api/src/main/java/org/bukkit/event/world/SpawnChangeEvent.java b/api/src/main/java/org/bukkit/event/world/SpawnChangeEvent.java new file mode 100644 index 000000000..4c49c7619 --- /dev/null +++ b/api/src/main/java/org/bukkit/event/world/SpawnChangeEvent.java @@ -0,0 +1,41 @@ +package org.bukkit.event.world; + +import org.bukkit.World; +import org.bukkit.Location; +import org.bukkit.event.HandlerList; +import org.jetbrains.annotations.NotNull; + +/** + * An event that is called when a world's spawn changes. The world's previous + * spawn location is included. + */ +public class SpawnChangeEvent extends WorldEvent { + private static final HandlerList handlers = new HandlerList(); + private final Location previousLocation; + + public SpawnChangeEvent(@NotNull final World world, @NotNull final Location previousLocation) { + super(world); + this.previousLocation = previousLocation; + } + + /** + * Gets the previous spawn location + * + * @return Location that used to be spawn + */ + @NotNull + public Location getPreviousLocation() { + return previousLocation; + } + + @NotNull + @Override + public HandlerList getHandlers() { + return handlers; + } + + @NotNull + public static HandlerList getHandlerList() { + return handlers; + } +} diff --git a/api/src/main/java/org/bukkit/event/world/StructureGrowEvent.java b/api/src/main/java/org/bukkit/event/world/StructureGrowEvent.java new file mode 100644 index 000000000..578337699 --- /dev/null +++ b/api/src/main/java/org/bukkit/event/world/StructureGrowEvent.java @@ -0,0 +1,104 @@ +package org.bukkit.event.world; + +import java.util.List; +import org.bukkit.Location; +import org.bukkit.TreeType; +import org.bukkit.block.BlockState; +import org.bukkit.entity.Player; +import org.bukkit.event.Cancellable; +import org.bukkit.event.HandlerList; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +/** + * Event that is called when an organic structure attempts to grow (Sapling {@literal ->} + * Tree), (Mushroom {@literal ->} Huge Mushroom), naturally or using bonemeal. + */ +public class StructureGrowEvent extends WorldEvent implements Cancellable { + private static final HandlerList handlers = new HandlerList(); + private boolean cancelled = false; + private final Location location; + private final TreeType species; + private final boolean bonemeal; + private final Player player; + private final List blocks; + + public StructureGrowEvent(@NotNull final Location location, @NotNull final TreeType species, final boolean bonemeal, @Nullable final Player player, @NotNull final List blocks) { + super(location.getWorld()); + this.location = location; + this.species = species; + this.bonemeal = bonemeal; + this.player = player; + this.blocks = blocks; + } + + /** + * Gets the location of the structure. + * + * @return Location of the structure + */ + @NotNull + public Location getLocation() { + return location; + } + + /** + * Gets the species type (birch, normal, pine, red mushroom, brown + * mushroom) + * + * @return Structure species + */ + @NotNull + public TreeType getSpecies() { + return species; + } + + /** + * Checks if structure was grown using bonemeal. + * + * @return True if the structure was grown using bonemeal. + */ + public boolean isFromBonemeal() { + return bonemeal; + } + + /** + * Gets the player that created the structure. + * + * @return Player that created the structure, null if was not created + * manually + */ + @Nullable + public Player getPlayer() { + return player; + } + + /** + * Gets a list of all blocks associated with the structure. + * + * @return list of all blocks associated with the structure. + */ + @NotNull + public List getBlocks() { + return blocks; + } + + public boolean isCancelled() { + return cancelled; + } + + public void setCancelled(boolean cancel) { + cancelled = cancel; + } + + @NotNull + @Override + public HandlerList getHandlers() { + return handlers; + } + + @NotNull + public static HandlerList getHandlerList() { + return handlers; + } +} diff --git a/api/src/main/java/org/bukkit/event/world/WorldEvent.java b/api/src/main/java/org/bukkit/event/world/WorldEvent.java new file mode 100644 index 000000000..cffeff33f --- /dev/null +++ b/api/src/main/java/org/bukkit/event/world/WorldEvent.java @@ -0,0 +1,26 @@ +package org.bukkit.event.world; + +import org.bukkit.World; +import org.bukkit.event.Event; +import org.jetbrains.annotations.NotNull; + +/** + * Represents events within a world + */ +public abstract class WorldEvent extends Event { + private final World world; + + public WorldEvent(@NotNull final World world) { + this.world = world; + } + + /** + * Gets the world primarily involved with this event + * + * @return World which caused this event + */ + @NotNull + public World getWorld() { + return world; + } +} diff --git a/api/src/main/java/org/bukkit/event/world/WorldInitEvent.java b/api/src/main/java/org/bukkit/event/world/WorldInitEvent.java new file mode 100644 index 000000000..8b521deda --- /dev/null +++ b/api/src/main/java/org/bukkit/event/world/WorldInitEvent.java @@ -0,0 +1,27 @@ +package org.bukkit.event.world; + +import org.bukkit.World; +import org.bukkit.event.HandlerList; +import org.jetbrains.annotations.NotNull; + +/** + * Called when a World is initializing + */ +public class WorldInitEvent extends WorldEvent { + private static final HandlerList handlers = new HandlerList(); + + public WorldInitEvent(@NotNull final World world) { + super(world); + } + + @NotNull + @Override + public HandlerList getHandlers() { + return handlers; + } + + @NotNull + public static HandlerList getHandlerList() { + return handlers; + } +} diff --git a/api/src/main/java/org/bukkit/event/world/WorldLoadEvent.java b/api/src/main/java/org/bukkit/event/world/WorldLoadEvent.java new file mode 100644 index 000000000..552e15cd4 --- /dev/null +++ b/api/src/main/java/org/bukkit/event/world/WorldLoadEvent.java @@ -0,0 +1,27 @@ +package org.bukkit.event.world; + +import org.bukkit.World; +import org.bukkit.event.HandlerList; +import org.jetbrains.annotations.NotNull; + +/** + * Called when a World is loaded + */ +public class WorldLoadEvent extends WorldEvent { + private static final HandlerList handlers = new HandlerList(); + + public WorldLoadEvent(@NotNull final World world) { + super(world); + } + + @NotNull + @Override + public HandlerList getHandlers() { + return handlers; + } + + @NotNull + public static HandlerList getHandlerList() { + return handlers; + } +} diff --git a/api/src/main/java/org/bukkit/event/world/WorldSaveEvent.java b/api/src/main/java/org/bukkit/event/world/WorldSaveEvent.java new file mode 100644 index 000000000..9a9bb1362 --- /dev/null +++ b/api/src/main/java/org/bukkit/event/world/WorldSaveEvent.java @@ -0,0 +1,27 @@ +package org.bukkit.event.world; + +import org.bukkit.World; +import org.bukkit.event.HandlerList; +import org.jetbrains.annotations.NotNull; + +/** + * Called when a World is saved. + */ +public class WorldSaveEvent extends WorldEvent { + private static final HandlerList handlers = new HandlerList(); + + public WorldSaveEvent(@NotNull final World world) { + super(world); + } + + @NotNull + @Override + public HandlerList getHandlers() { + return handlers; + } + + @NotNull + public static HandlerList getHandlerList() { + return handlers; + } +} diff --git a/api/src/main/java/org/bukkit/event/world/WorldUnloadEvent.java b/api/src/main/java/org/bukkit/event/world/WorldUnloadEvent.java new file mode 100644 index 000000000..d1cb72116 --- /dev/null +++ b/api/src/main/java/org/bukkit/event/world/WorldUnloadEvent.java @@ -0,0 +1,37 @@ +package org.bukkit.event.world; + +import org.bukkit.World; +import org.bukkit.event.Cancellable; +import org.bukkit.event.HandlerList; +import org.jetbrains.annotations.NotNull; + +/** + * Called when a World is unloaded + */ +public class WorldUnloadEvent extends WorldEvent implements Cancellable { + private static final HandlerList handlers = new HandlerList(); + private boolean isCancelled; + + public WorldUnloadEvent(@NotNull final World world) { + super(world); + } + + public boolean isCancelled() { + return this.isCancelled; + } + + public void setCancelled(boolean cancel) { + this.isCancelled = cancel; + } + + @NotNull + @Override + public HandlerList getHandlers() { + return handlers; + } + + @NotNull + public static HandlerList getHandlerList() { + return handlers; + } +} diff --git a/api/src/main/java/org/bukkit/generator/BlockPopulator.java b/api/src/main/java/org/bukkit/generator/BlockPopulator.java new file mode 100644 index 000000000..e5e3a6a54 --- /dev/null +++ b/api/src/main/java/org/bukkit/generator/BlockPopulator.java @@ -0,0 +1,30 @@ +package org.bukkit.generator; + +import java.util.Random; +import org.bukkit.Chunk; +import org.bukkit.World; +import org.jetbrains.annotations.NotNull; + +/** + * A block populator is responsible for generating a small area of blocks. + *

+ * For example, generating glowstone inside the nether or generating dungeons + * full of treasure + */ +public abstract class BlockPopulator { + + /** + * Populates an area of blocks at or around the given chunk. + *

+ * The chunks on each side of the specified chunk must already exist; that + * is, there must be one north, east, south and west of the specified + * chunk. The "corner" chunks may not exist, in which scenario the + * populator should record any changes required for those chunks and + * perform the changes when they are ready. + * + * @param world The world to generate in + * @param random The random generator to use + * @param source The chunk to generate for + */ + public abstract void populate(@NotNull World world, @NotNull Random random, @NotNull Chunk source); +} diff --git a/api/src/main/java/org/bukkit/generator/ChunkGenerator.java b/api/src/main/java/org/bukkit/generator/ChunkGenerator.java new file mode 100644 index 000000000..9a18a05bb --- /dev/null +++ b/api/src/main/java/org/bukkit/generator/ChunkGenerator.java @@ -0,0 +1,291 @@ +package org.bukkit.generator; + +import java.util.ArrayList; +import java.util.List; +import java.util.Random; +import org.bukkit.Bukkit; +import org.bukkit.Location; +import org.bukkit.Material; +import org.bukkit.World; +import org.bukkit.block.Biome; +import org.bukkit.block.Block; +import org.bukkit.block.data.BlockData; +import org.bukkit.material.MaterialData; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +/** + * A chunk generator is responsible for the initial shaping of an entire + * chunk. For example, the nether chunk generator should shape netherrack and + * soulsand + */ +public abstract class ChunkGenerator { + + /** + * Interface to biome section for chunk to be generated: initialized with + * default values for world type and seed. + *

+ * Custom generator is free to access and tailor values during + * generateBlockSections() or generateExtBlockSections(). + */ + public interface BiomeGrid { + + /** + * Get biome at x, z within chunk being generated + * + * @param x - 0-15 + * @param z - 0-15 + * @return Biome value + */ + @NotNull + Biome getBiome(int x, int z); + + /** + * Set biome at x, z within chunk being generated + * + * @param x - 0-15 + * @param z - 0-15 + * @param bio - Biome value + */ + void setBiome(int x, int z, @NotNull Biome bio); + } + + /** + * Shapes the chunk for the given coordinates. + * + * This method must return a ChunkData. + *

+ * Notes: + *

+ * This method should never attempt to get the Chunk at + * the passed coordinates, as doing so may cause an infinite loop + *

+ * This method should never modify a ChunkData after it has + * been returned. + *

+ * This method must return a ChunkData returned by {@link ChunkGenerator#createChunkData(org.bukkit.World)} + * + * @param world The world this chunk will be used for + * @param random The random generator to use + * @param x The X-coordinate of the chunk + * @param z The Z-coordinate of the chunk + * @param biome Proposed biome values for chunk - can be updated by + * generator + * @return ChunkData containing the types for each block created by this + * generator + */ + @NotNull + public ChunkData generateChunkData(@NotNull World world, @NotNull Random random, int x, int z, @NotNull BiomeGrid biome) { + throw new UnsupportedOperationException("Custom generator is missing required method generateChunkData"); + } + + /** + * Create a ChunkData for a world. + * @param world the world the ChunkData is for + * @return a new ChunkData for world + */ + @NotNull + protected final ChunkData createChunkData(@NotNull World world) { + return Bukkit.getServer().createChunkData(world); + } + + /** + * Tests if the specified location is valid for a natural spawn position + * + * @param world The world we're testing on + * @param x X-coordinate of the block to test + * @param z Z-coordinate of the block to test + * @return true if the location is valid, otherwise false + */ + public boolean canSpawn(@NotNull World world, int x, int z) { + Block highest = world.getBlockAt(x, world.getHighestBlockYAt(x, z), z); + + switch (world.getEnvironment()) { + case NETHER: + return true; + case THE_END: + return highest.getType() != Material.AIR && highest.getType() != Material.WATER && highest.getType() != Material.LAVA; + case NORMAL: + default: + return highest.getType() == Material.SAND || highest.getType() == Material.GRAVEL; + } + } + + /** + * Gets a list of default {@link BlockPopulator}s to apply to a given + * world + * + * @param world World to apply to + * @return List containing any amount of BlockPopulators + */ + @NotNull + public List getDefaultPopulators(@NotNull World world) { + return new ArrayList(); + } + + /** + * Gets a fixed spawn location to use for a given world. + *

+ * A null value is returned if a world should not use a fixed spawn point, + * and will instead attempt to find one randomly. + * + * @param world The world to locate a spawn point for + * @param random Random generator to use in the calculation + * @return Location containing a new spawn point, otherwise null + */ + @Nullable + public Location getFixedSpawnLocation(@NotNull World world, @NotNull Random random) { + return null; + } + + /** + * Data for a Chunk. + */ + public static interface ChunkData { + /** + * Get the maximum height for the chunk. + * + * Setting blocks at or above this height will do nothing. + * + * @return the maximum height + */ + public int getMaxHeight(); + + /** + * Set the block at x,y,z in the chunk data to material. + * + * Note: setting blocks outside the chunk's bounds does nothing. + * + * @param x the x location in the chunk from 0-15 inclusive + * @param y the y location in the chunk from 0 (inclusive) - maxHeight (exclusive) + * @param z the z location in the chunk from 0-15 inclusive + * @param material the type to set the block to + */ + public void setBlock(int x, int y, int z, @NotNull Material material); + + /** + * Set the block at x,y,z in the chunk data to material. + * + * Setting blocks outside the chunk's bounds does nothing. + * + * @param x the x location in the chunk from 0-15 inclusive + * @param y the y location in the chunk from 0 (inclusive) - maxHeight (exclusive) + * @param z the z location in the chunk from 0-15 inclusive + * @param material the type to set the block to + */ + public void setBlock(int x, int y, int z, @NotNull MaterialData material); + + /** + * Set the block at x,y,z in the chunk data to material. + * + * Setting blocks outside the chunk's bounds does nothing. + * + * @param x the x location in the chunk from 0-15 inclusive + * @param y the y location in the chunk from 0 (inclusive) - maxHeight (exclusive) + * @param z the z location in the chunk from 0-15 inclusive + * @param blockData the type to set the block to + */ + public void setBlock(int x, int y, int z, @NotNull BlockData blockData); + + /** + * Set a region of this chunk from xMin, yMin, zMin (inclusive) + * to xMax, yMax, zMax (exclusive) to material. + * + * Setting blocks outside the chunk's bounds does nothing. + * + * @param xMin minimum x location (inclusive) in the chunk to set + * @param yMin minimum y location (inclusive) in the chunk to set + * @param zMin minimum z location (inclusive) in the chunk to set + * @param xMax maximum x location (exclusive) in the chunk to set + * @param yMax maximum y location (exclusive) in the chunk to set + * @param zMax maximum z location (exclusive) in the chunk to set + * @param material the type to set the blocks to + */ + public void setRegion(int xMin, int yMin, int zMin, int xMax, int yMax, int zMax, @NotNull Material material); + + /** + * Set a region of this chunk from xMin, yMin, zMin (inclusive) + * to xMax, yMax, zMax (exclusive) to material. + * + * Setting blocks outside the chunk's bounds does nothing. + * + * @param xMin minimum x location (inclusive) in the chunk to set + * @param yMin minimum y location (inclusive) in the chunk to set + * @param zMin minimum z location (inclusive) in the chunk to set + * @param xMax maximum x location (exclusive) in the chunk to set + * @param yMax maximum y location (exclusive) in the chunk to set + * @param zMax maximum z location (exclusive) in the chunk to set + * @param material the type to set the blocks to + */ + public void setRegion(int xMin, int yMin, int zMin, int xMax, int yMax, int zMax, @NotNull MaterialData material); + + /** + * Set a region of this chunk from xMin, yMin, zMin (inclusive) to xMax, + * yMax, zMax (exclusive) to material. + * + * Setting blocks outside the chunk's bounds does nothing. + * + * @param xMin minimum x location (inclusive) in the chunk to set + * @param yMin minimum y location (inclusive) in the chunk to set + * @param zMin minimum z location (inclusive) in the chunk to set + * @param xMax maximum x location (exclusive) in the chunk to set + * @param yMax maximum y location (exclusive) in the chunk to set + * @param zMax maximum z location (exclusive) in the chunk to set + * @param blockData the type to set the blocks to + */ + public void setRegion(int xMin, int yMin, int zMin, int xMax, int yMax, int zMax, @NotNull BlockData blockData); + + /** + * Get the type of the block at x, y, z. + * + * Getting blocks outside the chunk's bounds returns air. + * + * @param x the x location in the chunk from 0-15 inclusive + * @param y the y location in the chunk from 0 (inclusive) - maxHeight (exclusive) + * @param z the z location in the chunk from 0-15 inclusive + * @return the type of the block or Material.AIR if x, y or z are outside the chunk's bounds + */ + @NotNull + public Material getType(int x, int y, int z); + + /** + * Get the type and data of the block at x, y, z. + * + * Getting blocks outside the chunk's bounds returns air. + * + * @param x the x location in the chunk from 0-15 inclusive + * @param y the y location in the chunk from 0 (inclusive) - maxHeight (exclusive) + * @param z the z location in the chunk from 0-15 inclusive + * @return the type and data of the block or the MaterialData for air if x, y or z are outside the chunk's bounds + */ + @NotNull + public MaterialData getTypeAndData(int x, int y, int z); + + /** + * Get the type and data of the block at x, y, z. + * + * Getting blocks outside the chunk's bounds returns air. + * + * @param x the x location in the chunk from 0-15 inclusive + * @param y the y location in the chunk from 0 (inclusive) - maxHeight (exclusive) + * @param z the z location in the chunk from 0-15 inclusive + * @return the data of the block or the BlockData for air if x, y or z are outside the chunk's bounds + */ + @NotNull + public BlockData getBlockData(int x, int y, int z); + + /** + * Get the block data at x,y,z in the chunk data. + * + * Getting blocks outside the chunk's bounds returns 0. + * + * @param x the x location in the chunk from 0-15 inclusive + * @param y the y location in the chunk from 0 (inclusive) - maxHeight (exclusive) + * @param z the z location in the chunk from 0-15 inclusive + * @return the block data value or air if x, y or z are outside the chunk's bounds + * @deprecated Uses magic values + */ + @Deprecated + public byte getData(int x, int y, int z); + } +} diff --git a/api/src/main/java/org/bukkit/help/GenericCommandHelpTopic.java b/api/src/main/java/org/bukkit/help/GenericCommandHelpTopic.java new file mode 100644 index 000000000..b05f570a8 --- /dev/null +++ b/api/src/main/java/org/bukkit/help/GenericCommandHelpTopic.java @@ -0,0 +1,78 @@ +package org.bukkit.help; + +import org.bukkit.ChatColor; +import org.bukkit.command.Command; +import org.bukkit.command.CommandSender; +import org.apache.commons.lang.StringUtils; +import org.bukkit.command.ConsoleCommandSender; +import org.jetbrains.annotations.NotNull; + +/** + * Lacking an alternative, the help system will create instances of + * GenericCommandHelpTopic for each command in the server's CommandMap. You + * can use this class as a base class for custom help topics, or as an example + * for how to write your own. + */ +public class GenericCommandHelpTopic extends HelpTopic { + + protected Command command; + + public GenericCommandHelpTopic(@NotNull Command command) { + this.command = command; + + if (command.getLabel().startsWith("/")) { + name = command.getLabel(); + } else { + name = "/" + command.getLabel(); + } + + // The short text is the first line of the description + int i = command.getDescription().indexOf('\n'); + if (i > 1) { + shortText = command.getDescription().substring(0, i - 1); + } else { + shortText = command.getDescription(); + } + + // Build full text + StringBuilder sb = new StringBuilder(); + + sb.append(ChatColor.GOLD); + sb.append("Description: "); + sb.append(ChatColor.WHITE); + sb.append(command.getDescription()); + + sb.append("\n"); + + sb.append(ChatColor.GOLD); + sb.append("Usage: "); + sb.append(ChatColor.WHITE); + sb.append(command.getUsage().replace("", name.substring(1))); + + if (command.getAliases().size() > 0) { + sb.append("\n"); + sb.append(ChatColor.GOLD); + sb.append("Aliases: "); + sb.append(ChatColor.WHITE); + sb.append(ChatColor.WHITE + StringUtils.join(command.getAliases(), ", ")); + } + fullText = sb.toString(); + } + + public boolean canSee(@NotNull CommandSender sender) { + if (!command.isRegistered()) { + // Unregistered commands should not show up in the help + return false; + } + + if (sender instanceof ConsoleCommandSender) { + return true; + } + + if (amendedPermission != null) { + return sender.hasPermission(amendedPermission); + } else { + return command.testPermissionSilent(sender); + } + } +} diff --git a/api/src/main/java/org/bukkit/help/HelpMap.java b/api/src/main/java/org/bukkit/help/HelpMap.java new file mode 100644 index 000000000..45845238e --- /dev/null +++ b/api/src/main/java/org/bukkit/help/HelpMap.java @@ -0,0 +1,85 @@ +package org.bukkit.help; + +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +import java.util.Collection; +import java.util.List; + +/** + * The HelpMap tracks all help topics registered in a Bukkit server. When the + * server starts up or is reloaded, help is processed and topics are added in + * the following order: + * + *

    + *
  1. General topics are loaded from the help.yml + *
  2. Plugins load and optionally call {@code addTopic()} + *
  3. Registered plugin commands are processed by {@link HelpTopicFactory} + * objects to create topics + *
  4. Topic contents are amended as directed in help.yml + *
+ */ +public interface HelpMap { + /** + * Returns a help topic for a given topic name. + * + * @param topicName The help topic name to look up. + * @return A {@link HelpTopic} object matching the topic name or null if + * none can be found. + */ + @Nullable + public HelpTopic getHelpTopic(@NotNull String topicName); + + /** + * Returns a collection of all the registered help topics. + * + * @return All the registered help topics. + */ + @NotNull + public Collection getHelpTopics(); + + /** + * Adds a topic to the server's help index. + * + * @param topic The new help topic to add. + */ + public void addTopic(@NotNull HelpTopic topic); + + /** + * Clears out the contents of the help index. Normally called during + * server reload. + */ + public void clear(); + + /** + * Associates a {@link HelpTopicFactory} object with given command base + * class. Plugins typically call this method during {@code onLoad()}. Once + * registered, the custom HelpTopicFactory will be used to create a custom + * {@link HelpTopic} for all commands deriving from the {@code + * commandClass} base class, or all commands deriving from {@link + * org.bukkit.command.PluginCommand} who's executor derives from {@code + * commandClass} base class. + * + * @param commandClass The class for which the custom HelpTopicFactory + * applies. Must derive from either {@link org.bukkit.command.Command} + * or {@link org.bukkit.command.CommandExecutor}. + * @param factory The {@link HelpTopicFactory} implementation to associate + * with the {@code commandClass}. + * @throws IllegalArgumentException Thrown if {@code commandClass} does + * not derive from a legal base class. + */ + public void registerHelpTopicFactory(@NotNull Class commandClass, @NotNull HelpTopicFactory factory); + + /** + * Gets the list of plugins the server administrator has chosen to exclude + * from the help index. Plugin authors who choose to directly extend + * {@link org.bukkit.command.Command} instead of {@link + * org.bukkit.command.PluginCommand} will need to check this collection in + * their {@link HelpTopicFactory} implementations to ensure they meet the + * server administrator's expectations. + * + * @return A list of plugins that should be excluded from the help index. + */ + @NotNull + public List getIgnoredPlugins(); +} diff --git a/api/src/main/java/org/bukkit/help/HelpTopic.java b/api/src/main/java/org/bukkit/help/HelpTopic.java new file mode 100644 index 000000000..2e2e1af50 --- /dev/null +++ b/api/src/main/java/org/bukkit/help/HelpTopic.java @@ -0,0 +1,127 @@ +package org.bukkit.help; + +import org.bukkit.command.CommandSender; +import org.bukkit.entity.Player; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +/** + * HelpTopic implementations are displayed to the user when the user uses the + * /help command. + *

+ * Custom implementations of this class can work at two levels. A simple + * implementation only needs to set the value of {@code name}, {@code + * shortText}, and {@code fullText} in the constructor. This base class will + * take care of the rest. + *

+ * Complex implementations can be created by overriding the behavior of all + * the methods in this class. + */ +public abstract class HelpTopic { + protected String name = ""; + protected String shortText = ""; + protected String fullText = ""; + protected String amendedPermission = null; + + /** + * Determines if a {@link Player} is allowed to see this help topic. + *

+ * HelpTopic implementations should take server administrator wishes into + * account as set by the {@link HelpTopic#amendCanSee(String)} function. + * + * @param player The Player in question. + * @return True of the Player can see this help topic, false otherwise. + */ + public abstract boolean canSee(@NotNull CommandSender player); + + /** + * Allows the server administrator to override the permission required to + * see a help topic. + *

+ * HelpTopic implementations should take this into account when + * determining topic visibility on the {@link + * HelpTopic#canSee(org.bukkit.command.CommandSender)} function. + * + * @param amendedPermission The permission node the server administrator + * wishes to apply to this topic. + */ + public void amendCanSee(@Nullable String amendedPermission) { + this.amendedPermission = amendedPermission; + } + + /** + * Returns the name of this help topic. + * + * @return The topic name. + */ + @NotNull + public String getName() { + return name; + } + + /** + * Returns a brief description that will be displayed in the topic index. + * + * @return A brief topic description. + */ + @NotNull + public String getShortText() { + return shortText; + } + + /** + * Returns the full description of this help topic that is displayed when + * the user requests this topic's details. + *

+ * The result will be paginated to properly fit the user's client. + * + * @param forWho The player or console requesting the full text. Useful + * for further security trimming the command's full text based on + * sub-permissions in custom implementations. + * + * @return A full topic description. + */ + @NotNull + public String getFullText(@NotNull CommandSender forWho) { + return fullText; + } + + /** + * Allows the server admin (or another plugin) to add or replace the + * contents of a help topic. + *

+ * A null in either parameter will leave that part of the topic unchanged. + * In either amending parameter, the string {@literal } is replaced + * with the existing contents in the help topic. Use this to append or + * prepend additional content into an automatically generated help topic. + * + * @param amendedShortText The new topic short text to use, or null to + * leave alone. + * @param amendedFullText The new topic full text to use, or null to leave + * alone. + */ + public void amendTopic(@Nullable String amendedShortText, @Nullable String amendedFullText) { + shortText = applyAmendment(shortText, amendedShortText); + fullText = applyAmendment(fullText, amendedFullText); + } + + /** + * Developers implementing their own custom HelpTopic implementations can + * use this utility method to ensure their implementations comply with the + * expected behavior of the {@link HelpTopic#amendTopic(String, String)} + * method. + * + * @param baseText The existing text of the help topic. + * @param amendment The amending text from the amendTopic() method. + * @return The application of the amending text to the existing text, + * according to the expected rules of amendTopic(). + */ + @NotNull + protected String applyAmendment(@NotNull String baseText, @Nullable String amendment) { + if (amendment == null) { + return baseText; + } else { + return amendment.replaceAll("", baseText); + } + } +} diff --git a/api/src/main/java/org/bukkit/help/HelpTopicComparator.java b/api/src/main/java/org/bukkit/help/HelpTopicComparator.java new file mode 100644 index 000000000..e586826d9 --- /dev/null +++ b/api/src/main/java/org/bukkit/help/HelpTopicComparator.java @@ -0,0 +1,50 @@ +package org.bukkit.help; + +import org.jetbrains.annotations.NotNull; + +import java.util.Comparator; + +/** + * Used to impose a custom total ordering on help topics. + *

+ * All topics are listed in alphabetic order, but topics that start with a + * slash come after topics that don't. + */ +public class HelpTopicComparator implements Comparator { + + // Singleton implementations + private static final TopicNameComparator tnc = new TopicNameComparator(); + @NotNull + public static TopicNameComparator topicNameComparatorInstance() { + return tnc; + } + + private static final HelpTopicComparator htc = new HelpTopicComparator(); + @NotNull + public static HelpTopicComparator helpTopicComparatorInstance() { + return htc; + } + + private HelpTopicComparator() {} + + public int compare(@NotNull HelpTopic lhs, @NotNull HelpTopic rhs) { + return tnc.compare(lhs.getName(), rhs.getName()); + } + + public static class TopicNameComparator implements Comparator { + private TopicNameComparator(){} + + public int compare(@NotNull String lhs, @NotNull String rhs) { + boolean lhsStartSlash = lhs.startsWith("/"); + boolean rhsStartSlash = rhs.startsWith("/"); + + if (lhsStartSlash && !rhsStartSlash) { + return 1; + } else if (!lhsStartSlash && rhsStartSlash) { + return -1; + } else { + return lhs.compareToIgnoreCase(rhs); + } + } + } +} diff --git a/api/src/main/java/org/bukkit/help/HelpTopicFactory.java b/api/src/main/java/org/bukkit/help/HelpTopicFactory.java new file mode 100644 index 000000000..b85da3f6d --- /dev/null +++ b/api/src/main/java/org/bukkit/help/HelpTopicFactory.java @@ -0,0 +1,45 @@ +package org.bukkit.help; + +import org.bukkit.command.Command; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +/** + * A HelpTopicFactory is used to create custom {@link HelpTopic} objects from + * commands that inherit from a common base class or have executors that + * inherit from a common base class. You can use a custom HelpTopic to change + * the way all the commands in your plugin display in the help. If your plugin + * implements a complex permissions system, a custom help topic may also be + * appropriate. + *

+ * To automatically bind your plugin's commands to your custom HelpTopic + * implementation, first make sure all your commands or executors derive from + * a custom base class (it doesn't have to do anything). Next implement a + * custom HelpTopicFactory that accepts your custom command base class and + * instantiates an instance of your custom HelpTopic from it. Finally, + * register your HelpTopicFactory against your command base class using the + * {@link HelpMap#registerHelpTopicFactory(Class, HelpTopicFactory)} method. + *

+ * As the help system iterates over all registered commands to make help + * topics, it first checks to see if there is a HelpTopicFactory registered + * for the command's base class. If so, the factory is used to make a help + * topic rather than a generic help topic. If no factory is found for the + * command's base class and the command derives from {@link + * org.bukkit.command.PluginCommand}, then the type of the command's executor + * is inspected looking for a registered HelpTopicFactory. Finally, if no + * factory is found, a generic help topic is created for the command. + * + * @param The base class for your custom commands. + */ +public interface HelpTopicFactory { + /** + * This method accepts a command deriving from a custom command base class + * and constructs a custom HelpTopic for it. + * + * @param command The custom command to build a help topic for. + * @return A new custom help topic or {@code null} to intentionally NOT + * create a topic. + */ + @Nullable + public HelpTopic createTopic(@NotNull TCommand command); +} diff --git a/api/src/main/java/org/bukkit/help/IndexHelpTopic.java b/api/src/main/java/org/bukkit/help/IndexHelpTopic.java new file mode 100644 index 000000000..2914c9f8a --- /dev/null +++ b/api/src/main/java/org/bukkit/help/IndexHelpTopic.java @@ -0,0 +1,117 @@ +package org.bukkit.help; + +import org.bukkit.ChatColor; +import org.bukkit.command.CommandSender; +import org.bukkit.command.ConsoleCommandSender; +import org.bukkit.entity.Player; +import org.bukkit.util.ChatPaginator; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +import java.util.Collection; + +/** + * This help topic generates a list of other help topics. This class is useful + * for adding your own index help topics. To enforce a particular order, use a + * sorted collection. + *

+ * If a preamble is provided to the constructor, that text will be displayed + * before the first item in the index. + */ +public class IndexHelpTopic extends HelpTopic { + + protected String permission; + protected String preamble; + protected Collection allTopics; + + public IndexHelpTopic(@NotNull String name, @Nullable String shortText, @Nullable String permission, @NotNull Collection topics) { + this(name, shortText, permission, topics, null); + } + + public IndexHelpTopic(@NotNull String name, @Nullable String shortText, @Nullable String permission, @NotNull Collection topics, @Nullable String preamble) { + this.name = name; + this.shortText = (shortText == null) ? "" : shortText; + this.permission = permission; + this.preamble = (preamble == null) ? "" : preamble; + setTopicsCollection(topics); + } + + /** + * Sets the contents of the internal allTopics collection. + * + * @param topics The topics to set. + */ + protected void setTopicsCollection(@NotNull Collection topics) { + this.allTopics = topics; + } + + public boolean canSee(@NotNull CommandSender sender) { + if (sender instanceof ConsoleCommandSender) { + return true; + } + if (permission == null) { + return true; + } + return sender.hasPermission(permission); + } + + @Override + public void amendCanSee(@Nullable String amendedPermission) { + permission = amendedPermission; + } + + @NotNull + public String getFullText(@NotNull CommandSender sender) { + StringBuilder sb = new StringBuilder(); + + if (preamble != null) { + sb.append(buildPreamble(sender)); + sb.append("\n"); + } + + for (HelpTopic topic : allTopics) { + if (topic.canSee(sender)) { + String lineStr = buildIndexLine(sender, topic).replace("\n", ". "); + if (sender instanceof Player && lineStr.length() > ChatPaginator.GUARANTEED_NO_WRAP_CHAT_PAGE_WIDTH) { + sb.append(lineStr, 0, ChatPaginator.GUARANTEED_NO_WRAP_CHAT_PAGE_WIDTH - 3); + sb.append("..."); + } else { + sb.append(lineStr); + } + sb.append("\n"); + } + } + return sb.toString(); + } + + /** + * Builds the topic preamble. Override this method to change how the index + * preamble looks. + * + * @param sender The command sender requesting the preamble. + * @return The topic preamble. + */ + @NotNull + protected String buildPreamble(@NotNull CommandSender sender) { + return ChatColor.GRAY + preamble; + } + + /** + * Builds individual lines in the index topic. Override this method to + * change how index lines are rendered. + * + * @param sender The command sender requesting the index line. + * @param topic The topic to render into an index line. + * @return The rendered index line. + */ + @NotNull + protected String buildIndexLine(@NotNull CommandSender sender, @NotNull HelpTopic topic) { + StringBuilder line = new StringBuilder(); + line.append(ChatColor.GOLD); + line.append(topic.getName()); + line.append(": "); + line.append(ChatColor.WHITE); + line.append(topic.getShortText()); + return line.toString(); + } +} diff --git a/api/src/main/java/org/bukkit/inventory/AbstractHorseInventory.java b/api/src/main/java/org/bukkit/inventory/AbstractHorseInventory.java new file mode 100644 index 000000000..93c1158f8 --- /dev/null +++ b/api/src/main/java/org/bukkit/inventory/AbstractHorseInventory.java @@ -0,0 +1,25 @@ +package org.bukkit.inventory; + +import org.bukkit.entity.AbstractHorse; +import org.jetbrains.annotations.Nullable; + +/** + * An interface to the inventory of an {@link AbstractHorse}. + */ +public interface AbstractHorseInventory extends Inventory { + + /** + * Gets the item in the horse's saddle slot. + * + * @return the saddle item + */ + @Nullable + ItemStack getSaddle(); + + /** + * Sets the item in the horse's saddle slot. + * + * @param stack the new item + */ + void setSaddle(@Nullable ItemStack stack); +} diff --git a/api/src/main/java/org/bukkit/inventory/AnvilInventory.java b/api/src/main/java/org/bukkit/inventory/AnvilInventory.java new file mode 100644 index 000000000..4af562426 --- /dev/null +++ b/api/src/main/java/org/bukkit/inventory/AnvilInventory.java @@ -0,0 +1,52 @@ +package org.bukkit.inventory; + +import org.jetbrains.annotations.Nullable; + +/** + * Interface to the inventory of an Anvil. + */ +public interface AnvilInventory extends Inventory { + + /** + * Get the name to be applied to the repaired item. An empty string denotes + * the default item name. + * + * @return the rename text + */ + @Nullable + String getRenameText(); + + /** + * Get the experience cost (in levels) to complete the current repair. + * + * @return the experience cost + */ + int getRepairCost(); + + /** + * Set the experience cost (in levels) to complete the current repair. + * + * @param levels the experience cost + */ + void setRepairCost(int levels); + + /** + * Get the maximum experience cost (in levels) to be allowed by the current + * repair. If the result of {@link #getRepairCost()} exceeds the returned + * value, the repair result will be air to due being "too expensive". + *

+ * By default, this level is set to 40. Players in creative mode ignore the + * maximum repair cost. + * + * @return the maximum experience cost + */ + int getMaximumRepairCost(); + + /** + * Set the maximum experience cost (in levels) to be allowed by the current + * repair. The default value set by vanilla Minecraft is 40. + * + * @param levels the maximum experience cost + */ + void setMaximumRepairCost(int levels); +} diff --git a/api/src/main/java/org/bukkit/inventory/ArmoredHorseInventory.java b/api/src/main/java/org/bukkit/inventory/ArmoredHorseInventory.java new file mode 100644 index 000000000..163ffe8ff --- /dev/null +++ b/api/src/main/java/org/bukkit/inventory/ArmoredHorseInventory.java @@ -0,0 +1,21 @@ +package org.bukkit.inventory; + +import org.jetbrains.annotations.Nullable; + +public interface ArmoredHorseInventory extends AbstractHorseInventory { + + /** + * Gets the item in the horse's armor slot. + * + * @return the armor item + */ + @Nullable + ItemStack getArmor(); + + /** + * Sets the item in the horse's armor slot. + * + * @param stack the new item + */ + void setArmor(@Nullable ItemStack stack); +} diff --git a/api/src/main/java/org/bukkit/inventory/BeaconInventory.java b/api/src/main/java/org/bukkit/inventory/BeaconInventory.java new file mode 100644 index 000000000..bd2289b18 --- /dev/null +++ b/api/src/main/java/org/bukkit/inventory/BeaconInventory.java @@ -0,0 +1,24 @@ +package org.bukkit.inventory; + +import org.jetbrains.annotations.Nullable; + +/** + * Interface to the inventory of a Beacon. + */ +public interface BeaconInventory extends Inventory { + + /** + * Set the item powering the beacon. + * + * @param item The new item + */ + void setItem(@Nullable ItemStack item); + + /** + * Get the item powering the beacon. + * + * @return The current item. + */ + @Nullable + ItemStack getItem(); +} diff --git a/api/src/main/java/org/bukkit/inventory/BrewerInventory.java b/api/src/main/java/org/bukkit/inventory/BrewerInventory.java new file mode 100644 index 000000000..df45423e6 --- /dev/null +++ b/api/src/main/java/org/bukkit/inventory/BrewerInventory.java @@ -0,0 +1,45 @@ +package org.bukkit.inventory; + +import org.bukkit.Material; +import org.bukkit.block.BrewingStand; +import org.jetbrains.annotations.Nullable; + +/** + * Interface to the inventory of a Brewing Stand. + */ +public interface BrewerInventory extends Inventory { + + /** + * Get the current ingredient for brewing. + * + * @return The ingredient. + */ + @Nullable + ItemStack getIngredient(); + + /** + * Set the current ingredient for brewing. + * + * @param ingredient The ingredient + */ + void setIngredient(@Nullable ItemStack ingredient); + + /** + * Get the current fuel for brewing. + * + * @return The fuel + */ + @Nullable + ItemStack getFuel(); + + /** + * Set the current fuel for brewing. Generally only + * {@link Material#BLAZE_POWDER} will be of use. + * + * @param fuel The fuel + */ + void setFuel(@Nullable ItemStack fuel); + + @Nullable + BrewingStand getHolder(); +} diff --git a/api/src/main/java/org/bukkit/inventory/CraftingInventory.java b/api/src/main/java/org/bukkit/inventory/CraftingInventory.java new file mode 100644 index 000000000..df81bac9e --- /dev/null +++ b/api/src/main/java/org/bukkit/inventory/CraftingInventory.java @@ -0,0 +1,51 @@ +package org.bukkit.inventory; + +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +/** + * Interface to the crafting inventories + */ +public interface CraftingInventory extends Inventory { + + /** + * Check what item is in the result slot of this crafting inventory. + * + * @return The result item. + */ + @Nullable + ItemStack getResult(); + + /** + * Get the contents of the crafting matrix. + * + * @return The contents. Individual entries may be null. + */ + @NotNull + ItemStack[] getMatrix(); + + /** + * Set the item in the result slot of the crafting inventory. + * + * @param newResult The new result item. + */ + void setResult(@Nullable ItemStack newResult); + + /** + * Replace the contents of the crafting matrix + * + * @param contents The new contents. Individual entries may be null. + * @throws IllegalArgumentException if the length of contents is greater + * than the size of the crafting matrix. + */ + void setMatrix(@NotNull ItemStack[] contents); + + /** + * Get the current recipe formed on the crafting inventory, if any. + * + * @return The recipe, or null if the current contents don't match any + * recipe. + */ + @Nullable + Recipe getRecipe(); +} diff --git a/api/src/main/java/org/bukkit/inventory/DoubleChestInventory.java b/api/src/main/java/org/bukkit/inventory/DoubleChestInventory.java new file mode 100644 index 000000000..ca18cc8af --- /dev/null +++ b/api/src/main/java/org/bukkit/inventory/DoubleChestInventory.java @@ -0,0 +1,30 @@ +package org.bukkit.inventory; + +import org.bukkit.block.DoubleChest; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +/** + * Interface to the inventory of a Double Chest. + */ +public interface DoubleChestInventory extends Inventory { + + /** + * Get the left half of this double chest. + * + * @return The left side inventory + */ + @NotNull + Inventory getLeftSide(); + + /** + * Get the right side of this double chest. + * + * @return The right side inventory + */ + @NotNull + Inventory getRightSide(); + + @Nullable + DoubleChest getHolder(); +} diff --git a/api/src/main/java/org/bukkit/inventory/EnchantingInventory.java b/api/src/main/java/org/bukkit/inventory/EnchantingInventory.java new file mode 100644 index 000000000..ea47af00c --- /dev/null +++ b/api/src/main/java/org/bukkit/inventory/EnchantingInventory.java @@ -0,0 +1,39 @@ +package org.bukkit.inventory; + +import org.jetbrains.annotations.Nullable; + +/** + * Interface to the inventory of an Enchantment Table. + */ +public interface EnchantingInventory extends Inventory { + + /** + * Set the item being enchanted. + * + * @param item The new item + */ + void setItem(@Nullable ItemStack item); + + /** + * Get the item being enchanted. + * + * @return The current item. + */ + @Nullable + ItemStack getItem(); + + /** + * Set the secondary item being used for the enchant. + * + * @param item The new item + */ + void setSecondary(@Nullable ItemStack item); + + /** + * Get the secondary item being used for the enchant. + * + * @return The second item + */ + @Nullable + ItemStack getSecondary(); +} diff --git a/api/src/main/java/org/bukkit/inventory/EntityEquipment.java b/api/src/main/java/org/bukkit/inventory/EntityEquipment.java new file mode 100644 index 000000000..6875dbc72 --- /dev/null +++ b/api/src/main/java/org/bukkit/inventory/EntityEquipment.java @@ -0,0 +1,334 @@ +package org.bukkit.inventory; + +import org.bukkit.entity.Entity; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +/** + * An interface to a creatures inventory + */ +public interface EntityEquipment { + + /** + * Gets a copy of the item the entity is currently holding + * in their main hand. + * + * @return the currently held item + */ + @NotNull + ItemStack getItemInMainHand(); + + /** + * Sets the item the entity is holding in their main hand. + * + * @param item The item to put into the entities hand + */ + void setItemInMainHand(@Nullable ItemStack item); + + /** + * Gets a copy of the item the entity is currently holding + * in their off hand. + * + * @return the currently held item + */ + @NotNull + ItemStack getItemInOffHand(); + + /** + * Sets the item the entity is holding in their off hand. + * + * @param item The item to put into the entities hand + */ + void setItemInOffHand(@Nullable ItemStack item); + + /** + * Gets a copy of the item the entity is currently holding + * + * @deprecated entities can duel wield now use the methods for the + * specific hand instead + * @see #getItemInMainHand() + * @see #getItemInOffHand() + * @return the currently held item + */ + @Deprecated + @NotNull + ItemStack getItemInHand(); + + /** + * Sets the item the entity is holding + * + * @deprecated entities can duel wield now use the methods for the + * specific hand instead + * @see #setItemInMainHand(ItemStack) + * @see #setItemInOffHand(ItemStack) + * @param stack The item to put into the entities hand + */ + @Deprecated + void setItemInHand(@Nullable ItemStack stack); + + /** + * Gets a copy of the helmet currently being worn by the entity + * + * @return The helmet being worn + */ + @Nullable + ItemStack getHelmet(); + + /** + * Sets the helmet worn by the entity + * + * @param helmet The helmet to put on the entity + */ + void setHelmet(@Nullable ItemStack helmet); + + /** + * Gets a copy of the chest plate currently being worn by the entity + * + * @return The chest plate being worn + */ + @Nullable + ItemStack getChestplate(); + + /** + * Sets the chest plate worn by the entity + * + * @param chestplate The chest plate to put on the entity + */ + void setChestplate(@Nullable ItemStack chestplate); + + /** + * Gets a copy of the leggings currently being worn by the entity + * + * @return The leggings being worn + */ + @Nullable + ItemStack getLeggings(); + + /** + * Sets the leggings worn by the entity + * + * @param leggings The leggings to put on the entity + */ + void setLeggings(@Nullable ItemStack leggings); + + /** + * Gets a copy of the boots currently being worn by the entity + * + * @return The boots being worn + */ + @Nullable + ItemStack getBoots(); + + /** + * Sets the boots worn by the entity + * + * @param boots The boots to put on the entity + */ + void setBoots(@Nullable ItemStack boots); + + /** + * Gets a copy of all worn armor + * + * @return The array of worn armor. Individual items may be null. + */ + @NotNull + ItemStack[] getArmorContents(); + + /** + * Sets the entities armor to the provided array of ItemStacks + * + * @param items The items to set the armor as. Individual items may be null. + */ + void setArmorContents(@NotNull ItemStack[] items); + + /** + * Clears the entity of all armor and held items + */ + void clear(); + + /** + * @deprecated entities can duel wield now use the methods for the specific + * hand instead + * @see #getItemInMainHandDropChance() + * @see #getItemInOffHandDropChance() + * @return drop chance + */ + @Deprecated + float getItemInHandDropChance(); + + /** + * @deprecated entities can duel wield now use the methods for the specific + * hand instead + * @see #setItemInMainHandDropChance(float) + * @see #setItemInOffHandDropChance(float) + * @param chance drop chance + */ + @Deprecated + void setItemInHandDropChance(float chance); + + /** + * Gets the chance of the main hand item being dropped upon this creature's + * death. + * + *

    + *
  • A drop chance of 0.0F will never drop + *
  • A drop chance of 1.0F will always drop + *
+ * + * @return chance of the currently held item being dropped (1 for players) + */ + float getItemInMainHandDropChance(); + + /** + * Sets the chance of the item this creature is currently holding in their + * main hand being dropped upon this creature's death. + * + *
    + *
  • A drop chance of 0.0F will never drop + *
  • A drop chance of 1.0F will always drop + *
+ * + * @param chance the chance of the main hand item being dropped + * @throws UnsupportedOperationException when called on players + */ + void setItemInMainHandDropChance(float chance); + + /** + * Gets the chance of the off hand item being dropped upon this creature's + * death. + * + *
    + *
  • A drop chance of 0.0F will never drop + *
  • A drop chance of 1.0F will always drop + *
+ * + * @return chance of the off hand item being dropped (1 for players) + */ + float getItemInOffHandDropChance(); + + /** + * Sets the chance of the off hand item being dropped upon this creature's + * death. + * + *
    + *
  • A drop chance of 0.0F will never drop + *
  • A drop chance of 1.0F will always drop + *
+ * + * @param chance the chance of off hand item being dropped + * @throws UnsupportedOperationException when called on players + */ + void setItemInOffHandDropChance(float chance); + + /** + * Gets the chance of the helmet being dropped upon this creature's death. + * + *
    + *
  • A drop chance of 0.0F will never drop + *
  • A drop chance of 1.0F will always drop + *
+ * + * @return the chance of the helmet being dropped (1 for players) + */ + float getHelmetDropChance(); + + /** + * Sets the chance of the helmet being dropped upon this creature's death. + * + *
    + *
  • A drop chance of 0.0F will never drop + *
  • A drop chance of 1.0F will always drop + *
+ * + * @param chance of the helmet being dropped + * @throws UnsupportedOperationException when called on players + */ + void setHelmetDropChance(float chance); + + /** + * Gets the chance of the chest plate being dropped upon this creature's + * death. + * + *
    + *
  • A drop chance of 0.0F will never drop + *
  • A drop chance of 1.0F will always drop + *
+ * + * @return the chance of the chest plate being dropped (1 for players) + */ + float getChestplateDropChance(); + + /** + * Sets the chance of the chest plate being dropped upon this creature's + * death. + * + *
    + *
  • A drop chance of 0.0F will never drop + *
  • A drop chance of 1.0F will always drop + *
+ * + * @param chance of the chest plate being dropped + * @throws UnsupportedOperationException when called on players + */ + void setChestplateDropChance(float chance); + + /** + * Gets the chance of the leggings being dropped upon this creature's + * death. + * + *
    + *
  • A drop chance of 0.0F will never drop + *
  • A drop chance of 1.0F will always drop + *
+ * + * @return the chance of the leggings being dropped (1 for players) + */ + float getLeggingsDropChance(); + + /** + * Sets the chance of the leggings being dropped upon this creature's + * death. + * + *
    + *
  • A drop chance of 0.0F will never drop + *
  • A drop chance of 1.0F will always drop + *
+ * + * @param chance chance of the leggings being dropped + * @throws UnsupportedOperationException when called on players + */ + void setLeggingsDropChance(float chance); + + /** + * Gets the chance of the boots being dropped upon this creature's death. + * + *
    + *
  • A drop chance of 0.0F will never drop + *
  • A drop chance of 1.0F will always drop + *
+ * + * @return the chance of the boots being dropped (1 for players) + */ + float getBootsDropChance(); + + /** + * Sets the chance of the boots being dropped upon this creature's death. + * + *
    + *
  • A drop chance of 0.0F will never drop + *
  • A drop chance of 1.0F will always drop + *
+ * + * @param chance of the boots being dropped + * @throws UnsupportedOperationException when called on players + */ + void setBootsDropChance(float chance); + + /** + * Get the entity this EntityEquipment belongs to + * + * @return the entity this EntityEquipment belongs to + */ + @Nullable + Entity getHolder(); +} diff --git a/api/src/main/java/org/bukkit/inventory/EquipmentSlot.java b/api/src/main/java/org/bukkit/inventory/EquipmentSlot.java new file mode 100644 index 000000000..1e7d77118 --- /dev/null +++ b/api/src/main/java/org/bukkit/inventory/EquipmentSlot.java @@ -0,0 +1,11 @@ +package org.bukkit.inventory; + +public enum EquipmentSlot { + + HAND, + OFF_HAND, + FEET, + LEGS, + CHEST, + HEAD +} diff --git a/api/src/main/java/org/bukkit/inventory/FurnaceInventory.java b/api/src/main/java/org/bukkit/inventory/FurnaceInventory.java new file mode 100644 index 000000000..65d8fa437 --- /dev/null +++ b/api/src/main/java/org/bukkit/inventory/FurnaceInventory.java @@ -0,0 +1,58 @@ +package org.bukkit.inventory; + +import org.bukkit.block.Furnace; +import org.jetbrains.annotations.Nullable; + +/** + * Interface to the inventory of a Furnace. + */ +public interface FurnaceInventory extends Inventory { + + /** + * Get the current item in the result slot. + * + * @return The item + */ + @Nullable + ItemStack getResult(); + + /** + * Get the current fuel. + * + * @return The item + */ + @Nullable + ItemStack getFuel(); + + /** + * Get the item currently smelting. + * + * @return The item + */ + @Nullable + ItemStack getSmelting(); + + /** + * Set the current fuel. + * + * @param stack The item + */ + void setFuel(@Nullable ItemStack stack); + + /** + * Set the current item in the result slot. + * + * @param stack The item + */ + void setResult(@Nullable ItemStack stack); + + /** + * Set the item currently smelting. + * + * @param stack The item + */ + void setSmelting(@Nullable ItemStack stack); + + @Nullable + Furnace getHolder(); +} diff --git a/api/src/main/java/org/bukkit/inventory/FurnaceRecipe.java b/api/src/main/java/org/bukkit/inventory/FurnaceRecipe.java new file mode 100644 index 000000000..d40ffbe79 --- /dev/null +++ b/api/src/main/java/org/bukkit/inventory/FurnaceRecipe.java @@ -0,0 +1,222 @@ +package org.bukkit.inventory; + +import com.google.common.base.Preconditions; +import java.util.Collections; +import org.bukkit.Keyed; +import org.bukkit.Material; +import org.bukkit.NamespacedKey; +import org.bukkit.material.MaterialData; +import org.jetbrains.annotations.NotNull; + +/** + * Represents a smelting recipe. + */ +public class FurnaceRecipe implements Recipe, Keyed { + private final NamespacedKey key; + private ItemStack output; + private RecipeChoice ingredient; + private float experience; + private int cookingTime; + private String group = ""; + + @Deprecated + public FurnaceRecipe(@NotNull ItemStack result, @NotNull Material source) { + this(NamespacedKey.randomKey(), result, source, 0, 0, 200); + } + + @Deprecated + public FurnaceRecipe(@NotNull ItemStack result, @NotNull MaterialData source) { + this(NamespacedKey.randomKey(), result, source.getItemType(), source.getData(), 0, 200); + } + + @Deprecated + public FurnaceRecipe(@NotNull ItemStack result, @NotNull MaterialData source, float experience) { + this(NamespacedKey.randomKey(), result, source.getItemType(), source.getData(), experience, 200); + } + + @Deprecated + public FurnaceRecipe(@NotNull ItemStack result, @NotNull Material source, int data) { + this(NamespacedKey.randomKey(), result, source, data, 0, 200); + } + + /** + * Create a furnace recipe to craft the specified ItemStack. + * + * @param key The unique recipe key + * @param result The item you want the recipe to create. + * @param source The input material. + * @param experience The experience given by this recipe + * @param cookingTime The cooking time (in ticks) + */ + public FurnaceRecipe(@NotNull NamespacedKey key, @NotNull ItemStack result, @NotNull Material source, float experience, int cookingTime) { + this(key, result, source, 0, experience, cookingTime); + } + + @Deprecated + public FurnaceRecipe(@NotNull NamespacedKey key, @NotNull ItemStack result, @NotNull Material source, int data, float experience, int cookingTime) { + this(key, result, new RecipeChoice.MaterialChoice(Collections.singletonList(source)), experience, cookingTime); + } + + /** + * Create a furnace recipe to craft the specified ItemStack. + * + * @param key The unique recipe key + * @param result The item you want the recipe to create. + * @param input The input choices. + * @param experience The experience given by this recipe + * @param cookingTime The cooking time (in ticks) + */ + public FurnaceRecipe(@NotNull NamespacedKey key, @NotNull ItemStack result, @NotNull RecipeChoice input, float experience, int cookingTime) { + this.key = key; + this.output = new ItemStack(result); + this.ingredient = input; + this.experience = experience; + this.cookingTime = cookingTime; + } + + /** + * Sets the input of this furnace recipe. + * + * @param input The input material. + * @return The changed recipe, so you can chain calls. + */ + @NotNull + public FurnaceRecipe setInput(@NotNull MaterialData input) { + return setInput(input.getItemType(), input.getData()); + } + + /** + * Sets the input of this furnace recipe. + * + * @param input The input material. + * @return The changed recipe, so you can chain calls. + */ + @NotNull + public FurnaceRecipe setInput(@NotNull Material input) { + return setInput(input, 0); + } + + /** + * Sets the input of this furnace recipe. + * + * @param input The input material. + * @param data The data value. (Note: This is currently ignored by the + * CraftBukkit server.) + * @return The changed recipe, so you can chain calls. + * @deprecated Magic value + */ + @Deprecated + @NotNull + public FurnaceRecipe setInput(@NotNull Material input, int data) { + this.ingredient = new RecipeChoice.MaterialChoice(Collections.singletonList(input)); + return this; + } + + /** + * Get the input material. + * + * @return The input material. + */ + @NotNull + public ItemStack getInput() { + return this.ingredient.getItemStack(); + } + + /** + * Sets the input of this furnace recipe. + * + * @param input The input choice. + * @return The changed recipe, so you can chain calls. + */ + @NotNull + public FurnaceRecipe setInputChoice(@NotNull RecipeChoice input) { + this.ingredient = input; + return this; + } + + /** + * Get the input choice. + * + * @return The input choice. + */ + @NotNull + public RecipeChoice getInputChoice() { + return this.ingredient.clone(); + } + + /** + * Get the result of this recipe. + * + * @return The resulting stack. + */ + @NotNull + public ItemStack getResult() { + return output.clone(); + } + + /** + * Sets the experience given by this recipe. + * + * @param experience the experience level + */ + public void setExperience(float experience) { + this.experience = experience; + } + + /** + * Get the experience given by this recipe. + * + * @return experience level + */ + public float getExperience() { + return experience; + } + + /** + * Set the cooking time for this recipe in ticks. + * + * @param cookingTime new cooking time + */ + public void setCookingTime(int cookingTime) { + Preconditions.checkArgument(cookingTime >= 0, "cookingTime must be >= 0"); + this.cookingTime = cookingTime; + } + + /** + * Get the cooking time for this recipe in ticks. + * + * @return cooking time + */ + public int getCookingTime() { + return cookingTime; + } + + @NotNull + @Override + public NamespacedKey getKey() { + return key; + } + + /** + * Get the group of this recipe. Recipes with the same group may be grouped + * together when displayed in the client. + * + * @return recipe group. An empty string denotes no group. May not be null. + */ + @NotNull + public String getGroup() { + return group; + } + + /** + * Set the group of this recipe. Recipes with the same group may be grouped + * together when displayed in the client. + * + * @param group recipe group. An empty string denotes no group. May not be + * null. + */ + public void setGroup(@NotNull String group) { + Preconditions.checkArgument(group != null, "group"); + this.group = group; + } +} diff --git a/api/src/main/java/org/bukkit/inventory/HorseInventory.java b/api/src/main/java/org/bukkit/inventory/HorseInventory.java new file mode 100644 index 000000000..53498debe --- /dev/null +++ b/api/src/main/java/org/bukkit/inventory/HorseInventory.java @@ -0,0 +1,8 @@ +package org.bukkit.inventory; + +import org.jetbrains.annotations.Nullable; + +/** + * An interface to the inventory of a Horse. + */ +public interface HorseInventory extends AbstractHorseInventory, ArmoredHorseInventory {} diff --git a/api/src/main/java/org/bukkit/inventory/Inventory.java b/api/src/main/java/org/bukkit/inventory/Inventory.java new file mode 100644 index 000000000..a358e9014 --- /dev/null +++ b/api/src/main/java/org/bukkit/inventory/Inventory.java @@ -0,0 +1,427 @@ +package org.bukkit.inventory; + +import java.util.HashMap; +import java.util.List; +import java.util.ListIterator; + +import org.bukkit.Location; +import org.bukkit.Material; +import org.bukkit.entity.HumanEntity; +import org.bukkit.event.inventory.InventoryType; +import org.jetbrains.annotations.Contract; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +/** + * Interface to the various inventories. Behavior relating to {@link + * Material#AIR} is unspecified. + * + *
+ * Note that whilst {@link #iterator()} deals with the entire inventory, add + * / contains / remove methods deal only with the storage contents. + *
+ * Consider using {@link #getContents()} and {@link #getStorageContents()} for + * specific iteration. + * + * @see #getContents() + * @see #getStorageContents() + */ +public interface Inventory extends Iterable { + + /** + * Returns the size of the inventory + * + * @return The size of the inventory + */ + public int getSize(); + + /** + * Returns the maximum stack size for an ItemStack in this inventory. + * + * @return The maximum size for an ItemStack in this inventory. + */ + public int getMaxStackSize(); + + /** + * This method allows you to change the maximum stack size for an + * inventory. + *

+ * Caveats: + *

    + *
  • Not all inventories respect this value. + *
  • Stacks larger than 127 may be clipped when the world is saved. + *
  • This value is not guaranteed to be preserved; be sure to set it + * before every time you want to set a slot over the max stack size. + *
  • Stacks larger than the default max size for this type of inventory + * may not display correctly in the client. + *
+ * + * @param size The new maximum stack size for items in this inventory. + */ + public void setMaxStackSize(int size); + + /** + * Returns the name of the inventory + * + * @return The String with the name of the inventory + * @deprecated different instances of the same inventory may have different names; + * it is not clear what this method is meant to return + * @see InventoryView#getTitle() + */ + @Deprecated + @NotNull + public String getName(); + + /** + * Returns the ItemStack found in the slot at the given index + * + * @param index The index of the Slot's ItemStack to return + * @return The ItemStack in the slot + */ + @Nullable + public ItemStack getItem(int index); + + /** + * Stores the ItemStack at the given index of the inventory. + * + * @param index The index where to put the ItemStack + * @param item The ItemStack to set + */ + public void setItem(int index, @Nullable ItemStack item); + + /** + * Stores the given ItemStacks in the inventory. This will try to fill + * existing stacks and empty slots as well as it can. + *

+ * The returned HashMap contains what it couldn't store, where the key is + * the index of the parameter, and the value is the ItemStack at that + * index of the varargs parameter. If all items are stored, it will return + * an empty HashMap. + *

+ * If you pass in ItemStacks which exceed the maximum stack size for the + * Material, first they will be added to partial stacks where + * Material.getMaxStackSize() is not exceeded, up to + * Material.getMaxStackSize(). When there are no partial stacks left + * stacks will be split on Inventory.getMaxStackSize() allowing you to + * exceed the maximum stack size for that material. + *

+ * It is known that in some implementations this method will also set + * the inputted argument amount to the number of that item not placed in + * slots. + * + * @param items The ItemStacks to add + * @return A HashMap containing items that didn't fit. + * @throws IllegalArgumentException if items or any element in it is null + */ + @NotNull + public HashMap addItem(@NotNull ItemStack... items) throws IllegalArgumentException; + + /** + * Removes the given ItemStacks from the inventory. + *

+ * It will try to remove 'as much as possible' from the types and amounts + * you give as arguments. + *

+ * The returned HashMap contains what it couldn't remove, where the key is + * the index of the parameter, and the value is the ItemStack at that + * index of the varargs parameter. If all the given ItemStacks are + * removed, it will return an empty HashMap. + *

+ * It is known that in some implementations this method will also set the + * inputted argument amount to the number of that item not removed from + * slots. + * + * @param items The ItemStacks to remove + * @return A HashMap containing items that couldn't be removed. + * @throws IllegalArgumentException if items is null + */ + @NotNull + public HashMap removeItem(@NotNull ItemStack... items) throws IllegalArgumentException; + + // Paper start + /** + * Searches all possible inventory slots in order to remove the given ItemStacks. + *

+ * Similar to {@link Inventory#removeItem(ItemStack...)} in behavior, except this + * method will check all possible slots in the inventory, rather than just the main + * storage contents. + *

+ * It will try to remove 'as much as possible' from the types and amounts + * you give as arguments. + *

+ * The returned HashMap contains what it couldn't remove, where the key is + * the index of the parameter, and the value is the ItemStack at that + * index of the varargs parameter. If all the given ItemStacks are + * removed, it will return an empty HashMap. + *

+ * It is known that in some implementations this method will also set the + * inputted argument amount to the number of that item not removed from + * slots. + * + * @param items The ItemStacks to remove + * @return A HashMap containing items that couldn't be removed. + * @throws IllegalArgumentException if items is null + */ + @NotNull + public HashMap removeItemAnySlot(@NotNull ItemStack... items) throws IllegalArgumentException; + // Paper end + + /** + * Returns all ItemStacks from the inventory + * + * @return An array of ItemStacks from the inventory. Individual items may be null. + */ + @NotNull + public ItemStack[] getContents(); + + /** + * Completely replaces the inventory's contents. Removes all existing + * contents and replaces it with the ItemStacks given in the array. + * + * @param items A complete replacement for the contents; the length must + * be less than or equal to {@link #getSize()}. + * @throws IllegalArgumentException If the array has more items than the + * inventory. + */ + public void setContents(@NotNull ItemStack[] items) throws IllegalArgumentException; + + /** + * Return the contents from the section of the inventory where items can + * reasonably be expected to be stored. In most cases this will represent + * the entire inventory, but in some cases it may exclude armor or result + * slots. + *
+ * It is these contents which will be used for add / contains / remove + * methods which look for a specific stack. + * + * @return inventory storage contents. Individual items may be null. + */ + @NotNull + public ItemStack[] getStorageContents(); + + /** + * Put the given ItemStacks into the storage slots + * + * @param items The ItemStacks to use as storage contents + * @throws IllegalArgumentException If the array has more items than the + * inventory. + */ + public void setStorageContents(@NotNull ItemStack[] items) throws IllegalArgumentException; + + /** + * Checks if the inventory contains any ItemStacks with the given + * material. + * + * @param material The material to check for + * @return true if an ItemStack is found with the given Material + * @throws IllegalArgumentException if material is null + */ + public boolean contains(@NotNull Material material) throws IllegalArgumentException; + + /** + * Checks if the inventory contains any ItemStacks matching the given + * ItemStack. + *

+ * This will only return true if both the type and the amount of the stack + * match. + * + * @param item The ItemStack to match against + * @return false if item is null, true if any exactly matching ItemStacks + * were found + */ + @Contract("null -> false") + public boolean contains(@Nullable ItemStack item); + + /** + * Checks if the inventory contains any ItemStacks with the given + * material, adding to at least the minimum amount specified. + * + * @param material The material to check for + * @param amount The minimum amount + * @return true if amount is less than 1, true if enough ItemStacks were + * found to add to the given amount + * @throws IllegalArgumentException if material is null + */ + public boolean contains(@NotNull Material material, int amount) throws IllegalArgumentException; + + /** + * Checks if the inventory contains at least the minimum amount specified + * of exactly matching ItemStacks. + *

+ * An ItemStack only counts if both the type and the amount of the stack + * match. + * + * @param item the ItemStack to match against + * @param amount how many identical stacks to check for + * @return false if item is null, true if amount less than 1, true if + * amount of exactly matching ItemStacks were found + * @see #containsAtLeast(ItemStack, int) + */ + @Contract("null, _ -> false") + public boolean contains(@Nullable ItemStack item, int amount); + + /** + * Checks if the inventory contains ItemStacks matching the given + * ItemStack whose amounts sum to at least the minimum amount specified. + * + * @param item the ItemStack to match against + * @param amount the minimum amount + * @return false if item is null, true if amount less than 1, true if + * enough ItemStacks were found to add to the given amount + */ + @Contract("null, _ -> false") + public boolean containsAtLeast(@Nullable ItemStack item, int amount); + + /** + * Returns a HashMap with all slots and ItemStacks in the inventory with + * the given Material. + *

+ * The HashMap contains entries where, the key is the slot index, and the + * value is the ItemStack in that slot. If no matching ItemStack with the + * given Material is found, an empty map is returned. + * + * @param material The material to look for + * @return A HashMap containing the slot index, ItemStack pairs + * @throws IllegalArgumentException if material is null + */ + @NotNull + public HashMap all(@NotNull Material material) throws IllegalArgumentException; + + /** + * Finds all slots in the inventory containing any ItemStacks with the + * given ItemStack. This will only match slots if both the type and the + * amount of the stack match + *

+ * The HashMap contains entries where, the key is the slot index, and the + * value is the ItemStack in that slot. If no matching ItemStack with the + * given Material is found, an empty map is returned. + * + * @param item The ItemStack to match against + * @return A map from slot indexes to item at index + */ + @NotNull + public HashMap all(@Nullable ItemStack item); + + /** + * Finds the first slot in the inventory containing an ItemStack with the + * given material + * + * @param material The material to look for + * @return The slot index of the given Material or -1 if not found + * @throws IllegalArgumentException if material is null + */ + public int first(@NotNull Material material) throws IllegalArgumentException; + + /** + * Returns the first slot in the inventory containing an ItemStack with + * the given stack. This will only match a slot if both the type and the + * amount of the stack match + * + * @param item The ItemStack to match against + * @return The slot index of the given ItemStack or -1 if not found + */ + public int first(@NotNull ItemStack item); + + /** + * Returns the first empty Slot. + * + * @return The first empty Slot found, or -1 if no empty slots. + */ + public int firstEmpty(); + + /** + * Removes all stacks in the inventory matching the given material. + * + * @param material The material to remove + * @throws IllegalArgumentException if material is null + */ + public void remove(@NotNull Material material) throws IllegalArgumentException; + + /** + * Removes all stacks in the inventory matching the given stack. + *

+ * This will only match a slot if both the type and the amount of the + * stack match + * + * @param item The ItemStack to match against + */ + public void remove(@NotNull ItemStack item); + + /** + * Clears out a particular slot in the index. + * + * @param index The index to empty. + */ + public void clear(int index); + + /** + * Clears out the whole Inventory. + */ + public void clear(); + + /** + * Gets a list of players viewing the inventory. Note that a player is + * considered to be viewing their own inventory and internal crafting + * screen even when said inventory is not open. They will normally be + * considered to be viewing their inventory even when they have a + * different inventory screen open, but it's possible for customized + * inventory screens to exclude the viewer's inventory, so this should + * never be assumed to be non-empty. + * + * @return A list of HumanEntities who are viewing this Inventory. + */ + @NotNull + public List getViewers(); + + /** + * Returns the title of this inventory. + * + * @return A String with the title. + * @deprecated different instances of the same inventory may have different titles + * @see InventoryView#getTitle() + */ + @Deprecated + @NotNull + public String getTitle(); + + /** + * Returns what type of inventory this is. + * + * @return The InventoryType representing the type of inventory. + */ + @NotNull + public InventoryType getType(); + + /** + * Gets the block or entity belonging to the open inventory + * + * @return The holder of the inventory; null if it has no holder. + */ + @Nullable + public InventoryHolder getHolder(); + + @NotNull + @Override + public ListIterator iterator(); + + /** + * Returns an iterator starting at the given index. If the index is + * positive, then the first call to next() will return the item at that + * index; if it is negative, the first call to previous will return the + * item at index (getSize() + index). + * + * @param index The index. + * @return An iterator. + */ + @NotNull + public ListIterator iterator(int index); + + /** + * Get the location of the block or entity which corresponds to this inventory. May return null if this container + * was custom created or is a virtual / subcontainer. + * + * @return location or null if not applicable. + */ + @Nullable + public Location getLocation(); +} diff --git a/api/src/main/java/org/bukkit/inventory/InventoryHolder.java b/api/src/main/java/org/bukkit/inventory/InventoryHolder.java new file mode 100644 index 000000000..c7b17eabf --- /dev/null +++ b/api/src/main/java/org/bukkit/inventory/InventoryHolder.java @@ -0,0 +1,14 @@ +package org.bukkit.inventory; + +import org.jetbrains.annotations.NotNull; + +public interface InventoryHolder { + + /** + * Get the object's inventory. + * + * @return The inventory. + */ + @NotNull + public Inventory getInventory(); +} diff --git a/api/src/main/java/org/bukkit/inventory/InventoryView.java b/api/src/main/java/org/bukkit/inventory/InventoryView.java new file mode 100644 index 000000000..38b9132f4 --- /dev/null +++ b/api/src/main/java/org/bukkit/inventory/InventoryView.java @@ -0,0 +1,433 @@ +package org.bukkit.inventory; + +import com.google.common.base.Preconditions; +import org.bukkit.entity.HumanEntity; +import org.bukkit.event.inventory.InventoryType; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +/** + * Represents a view linking two inventories and a single player (whose + * inventory may or may not be one of the two). + *

+ * Note: If you implement this interface but fail to satisfy the expected + * contracts of certain methods, there's no guarantee that the game will work + * as it should. + */ +public abstract class InventoryView { + public final static int OUTSIDE = -999; + /** + * Represents various extra properties of certain inventory windows. + */ + public enum Property { + /** + * The progress of the down-pointing arrow in a brewing inventory. + */ + BREW_TIME(0, InventoryType.BREWING), + /** + * The progress of the flame in a furnace inventory. + */ + BURN_TIME(0, InventoryType.FURNACE), + /** + * How many total ticks the current fuel should last. + */ + TICKS_FOR_CURRENT_FUEL(1, InventoryType.FURNACE), + /** + * The progress of the right-pointing arrow in a furnace inventory. + */ + COOK_TIME(2, InventoryType.FURNACE), + /** + * How many total ticks the current smelting should last. + */ + TICKS_FOR_CURRENT_SMELTING(3, InventoryType.FURNACE), + /** + * In an enchanting inventory, the top button's experience level + * value. + */ + ENCHANT_BUTTON1(0, InventoryType.ENCHANTING), + /** + * In an enchanting inventory, the middle button's experience level + * value. + */ + ENCHANT_BUTTON2(1, InventoryType.ENCHANTING), + /** + * In an enchanting inventory, the bottom button's experience level + * value. + */ + ENCHANT_BUTTON3(2, InventoryType.ENCHANTING), + /** + * In an enchanting inventory, the first four bits of the player's xpSeed. + */ + ENCHANT_XP_SEED(3, InventoryType.ENCHANTING), + /** + * In an enchanting inventory, the top button's enchantment's id + */ + ENCHANT_ID1(4, InventoryType.ENCHANTING), + /** + * In an enchanting inventory, the middle button's enchantment's id + */ + ENCHANT_ID2(5, InventoryType.ENCHANTING), + /** + * In an enchanting inventory, the bottom button's enchantment's id + */ + ENCHANT_ID3(6, InventoryType.ENCHANTING), + /** + * In an enchanting inventory, the top button's level value. + */ + ENCHANT_LEVEL1(7, InventoryType.ENCHANTING), + /** + * In an enchanting inventory, the middle button's level value. + */ + ENCHANT_LEVEL2(8, InventoryType.ENCHANTING), + /** + * In an enchanting inventory, the bottom button's level value. + */ + ENCHANT_LEVEL3(9, InventoryType.ENCHANTING), + /** + * In an beacon inventory, the levels of the beacon + */ + LEVELS(0, InventoryType.BEACON), + /** + * In an beacon inventory, the primary potion effect + */ + PRIMARY_EFFECT(1, InventoryType.BEACON), + /** + * In an beacon inventory, the secondary potion effect + */ + SECONDARY_EFFECT(2, InventoryType.BEACON), + /** + * The repair's cost in xp levels + */ + REPAIR_COST(0, InventoryType.ANVIL); + int id; + InventoryType style; + private Property(int id, /*@NotNull*/ InventoryType appliesTo) { + this.id = id; + style = appliesTo; + } + + @NotNull + public InventoryType getType() { + return style; + } + + /** + * + * @return the id of this view + * @deprecated Magic value + */ + @Deprecated + public int getId() { + return id; + } + } + /** + * Get the upper inventory involved in this transaction. + * + * @return the inventory + */ + @NotNull + public abstract Inventory getTopInventory(); + + /** + * Get the lower inventory involved in this transaction. + * + * @return the inventory + */ + @NotNull + public abstract Inventory getBottomInventory(); + + /** + * Get the player viewing. + * + * @return the player + */ + @NotNull + public abstract HumanEntity getPlayer(); + + /** + * Determine the type of inventory involved in the transaction. This + * indicates the window style being shown. It will never return PLAYER, + * since that is common to all windows. + * + * @return the inventory type + */ + @NotNull + public abstract InventoryType getType(); + + /** + * Sets one item in this inventory view by its raw slot ID. + *

+ * Note: If slot ID -999 is chosen, it may be expected that the item is + * dropped on the ground. This is not required behaviour, however. + * + * @param slot The ID as returned by InventoryClickEvent.getRawSlot() + * @param item The new item to put in the slot, or null to clear it. + */ + public void setItem(int slot, @Nullable ItemStack item) { + Inventory inventory = getInventory(slot); + if (inventory != null) { + inventory.setItem(convertSlot(slot), item); + } else if (item != null) { + getPlayer().getWorld().dropItemNaturally(getPlayer().getLocation(), item); + } + } + + /** + * Gets one item in this inventory view by its raw slot ID. + * + * @param slot The ID as returned by InventoryClickEvent.getRawSlot() + * @return The item currently in the slot. + */ + @Nullable + public ItemStack getItem(int slot) { + Inventory inventory = getInventory(slot); + return (inventory == null) ? null : inventory.getItem(convertSlot(slot)); + } + + /** + * Sets the item on the cursor of one of the viewing players. + * + * @param item The item to put on the cursor, or null to remove the item + * on their cursor. + */ + public final void setCursor(@Nullable ItemStack item) { + getPlayer().setItemOnCursor(item); + } + + /** + * Get the item on the cursor of one of the viewing players. + * + * @return The item on the player's cursor, or null if they aren't holding + * one. + */ + @Nullable + public final ItemStack getCursor() { + return getPlayer().getItemOnCursor(); + } + + /** + * Gets the inventory corresponding to the given raw slot ID. + * + * If the slot ID is {@link #OUTSIDE} null will be returned, otherwise + * behaviour for illegal and negative slot IDs is undefined. + * + * May be used with {@link #convertSlot(int)} to directly index an + * underlying inventory. + * + * @param rawSlot The raw slot ID. + * @return corresponding inventory, or null + */ + @Nullable + public final Inventory getInventory(int rawSlot) { + // Slot may be -1 if not properly detected due to client bug + // e.g. dropping an item into part of the enchantment list section of an enchanting table + if (rawSlot == OUTSIDE || rawSlot == -1) { + return null; + } + Preconditions.checkArgument(rawSlot >= 0, "Negative, non outside slot %s", rawSlot); + Preconditions.checkArgument(rawSlot < countSlots(), "Slot %s greater than inventory slot count", rawSlot); + + if (rawSlot < getTopInventory().getSize()) { + return getTopInventory(); + } else { + return getBottomInventory(); + } + } + + /** + * Converts a raw slot ID into its local slot ID into whichever of the two + * inventories the slot points to. + *

+ * If the raw slot refers to the upper inventory, it will be returned + * unchanged and thus be suitable for getTopInventory().getItem(); if it + * refers to the lower inventory, the output will differ from the input + * and be suitable for getBottomInventory().getItem(). + * + * @param rawSlot The raw slot ID. + * @return The converted slot ID. + */ + public final int convertSlot(int rawSlot) { + int numInTop = getTopInventory().getSize(); + // Index from the top inventory as having slots from [0,size] + if (rawSlot < numInTop) { + return rawSlot; + } + + // Move down the slot index by the top size + int slot = rawSlot - numInTop; + + // Player crafting slots are indexed differently. The matrix is caught by the first return. + // Creative mode is the same, except that you can't see the crafting slots (but the IDs are still used) + if (getType() == InventoryType.CRAFTING || getType() == InventoryType.CREATIVE) { + /** + * Raw Slots: + * + * 5 1 2 0 + * 6 3 4 + * 7 + * 8 45 + * 9 10 11 12 13 14 15 16 17 + * 18 19 20 21 22 23 24 25 26 + * 27 28 29 30 31 32 33 34 35 + * 36 37 38 39 40 41 42 43 44 + */ + + /** + * Converted Slots: + * + * 39 1 2 0 + * 38 3 4 + * 37 + * 36 40 + * 9 10 11 12 13 14 15 16 17 + * 18 19 20 21 22 23 24 25 26 + * 27 28 29 30 31 32 33 34 35 + * 0 1 2 3 4 5 6 7 8 + */ + + if (slot < 4) { + // Send [5,8] to [39,36] + return 39 - slot; + } else if (slot > 39) { + // Slot lives in the extra slot section + return slot; + } else { + // Reset index so 9 -> 0 + slot -= 4; + } + } + + // 27 = 36 - 9 + if (slot >= 27) { + // Put into hotbar section + slot -= 27; + } else { + // Take out of hotbar section + // 9 = 36 - 27 + slot += 9; + } + + return slot; + } + + /** + * Determine the type of the slot by its raw slot ID. + *

+ * If the type of the slot is unknown, then + * {@link InventoryType.SlotType#CONTAINER} will be returned. + * + * @param slot The raw slot ID + * @return the slot type + */ + @NotNull + public final InventoryType.SlotType getSlotType(int slot) { + InventoryType.SlotType type = InventoryType.SlotType.CONTAINER; + if (slot >= 0 && slot < this.getTopInventory().getSize()) { + switch(this.getType()) { + case FURNACE: + if (slot == 2) { + type = InventoryType.SlotType.RESULT; + } else if(slot == 1) { + type = InventoryType.SlotType.FUEL; + } else { + type = InventoryType.SlotType.CRAFTING; + } + break; + case BREWING: + if (slot == 3) { + type = InventoryType.SlotType.FUEL; + } else { + type = InventoryType.SlotType.CRAFTING; + } + break; + case ENCHANTING: + type = InventoryType.SlotType.CRAFTING; + break; + case WORKBENCH: + case CRAFTING: + if (slot == 0) { + type = InventoryType.SlotType.RESULT; + } else { + type = InventoryType.SlotType.CRAFTING; + } + break; + case MERCHANT: + if (slot == 2) { + type = InventoryType.SlotType.RESULT; + } else { + type = InventoryType.SlotType.CRAFTING; + } + break; + case BEACON: + type = InventoryType.SlotType.CRAFTING; + break; + case ANVIL: + if (slot == 2) { + type = InventoryType.SlotType.RESULT; + } else { + type = InventoryType.SlotType.CRAFTING; + } + break; + default: + // Nothing to do, it's a CONTAINER slot + } + } else { + if (slot < 0) { + type = InventoryType.SlotType.OUTSIDE; + } else if (this.getType() == InventoryType.CRAFTING) { // Also includes creative inventory + if (slot < 9) { + type = InventoryType.SlotType.ARMOR; + } else if (slot > 35) { + type = InventoryType.SlotType.QUICKBAR; + } + } else if (slot >= (this.countSlots() - (9 + 4 + 1))) { // Quickbar, Armor, Offhand + type = InventoryType.SlotType.QUICKBAR; + } + } + return type; + } + + /** + * Closes the inventory view. + */ + public final void close() { + getPlayer().closeInventory(); + } + + /** + * Check the total number of slots in this view, combining the upper and + * lower inventories. + *

+ * Note though that it's possible for this to be greater than the sum of + * the two inventories if for example some slots are not being used. + * + * @return The total size + */ + public final int countSlots() { + return getTopInventory().getSize() + getBottomInventory().getSize(); + } + + /** + * Sets an extra property of this inventory if supported by that + * inventory, for example the state of a progress bar. + * + * @param prop the window property to update + * @param value the new value for the window property + * @return true if the property was updated successfully, false if the + * property is not supported by that inventory + */ + public final boolean setProperty(@NotNull Property prop, int value) { + return getPlayer().setWindowProperty(prop, value); + } + + /** + * Get the title of this inventory window. + * + * @return The title. + */ + @NotNull + public final String getTitle() { + return getTopInventory().getTitle(); + } +} diff --git a/api/src/main/java/org/bukkit/inventory/ItemFactory.java b/api/src/main/java/org/bukkit/inventory/ItemFactory.java new file mode 100644 index 000000000..56734f8ee --- /dev/null +++ b/api/src/main/java/org/bukkit/inventory/ItemFactory.java @@ -0,0 +1,169 @@ +package org.bukkit.inventory; + +import org.bukkit.Color; +import org.bukkit.Material; +import org.bukkit.Server; +import org.bukkit.UndefinedNullability; +import org.bukkit.inventory.meta.BookMeta; +import org.bukkit.inventory.meta.ItemMeta; +import org.bukkit.inventory.meta.SkullMeta; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +/** + * An instance of the ItemFactory can be obtained with {@link + * Server#getItemFactory()}. + *

+ * The ItemFactory is solely responsible for creating item meta containers to + * apply on item stacks. + */ +public interface ItemFactory { + + /** + * This creates a new item meta for the material. + * + * @param material The material to consider as base for the meta + * @return a new ItemMeta that could be applied to an item stack of the + * specified material + */ + @UndefinedNullability // Paper + ItemMeta getItemMeta(@NotNull final Material material); + + /** + * This method checks the item meta to confirm that it is applicable (no + * data lost if applied) to the specified ItemStack. + *

+ * A {@link SkullMeta} would not be valid for a sword, but a normal {@link + * ItemMeta} from an enchanted dirt block would. + * + * @param meta Meta to check + * @param stack Item that meta will be applied to + * @return true if the meta can be applied without losing data, false + * otherwise + * @throws IllegalArgumentException if the meta was not created by this + * factory + */ + boolean isApplicable(@Nullable final ItemMeta meta, @Nullable final ItemStack stack) throws IllegalArgumentException; + + /** + * This method checks the item meta to confirm that it is applicable (no + * data lost if applied) to the specified Material. + *

+ * A {@link SkullMeta} would not be valid for a sword, but a normal {@link + * ItemMeta} from an enchanted dirt block would. + * + * @param meta Meta to check + * @param material Material that meta will be applied to + * @return true if the meta can be applied without losing data, false + * otherwise + * @throws IllegalArgumentException if the meta was not created by this + * factory + */ + boolean isApplicable(@Nullable final ItemMeta meta, @Nullable final Material material) throws IllegalArgumentException; + + /** + * This method is used to compare two item meta data objects. + * + * @param meta1 First meta to compare, and may be null to indicate no data + * @param meta2 Second meta to compare, and may be null to indicate no + * data + * @return false if one of the meta has data the other does not, otherwise + * true + * @throws IllegalArgumentException if either meta was not created by this + * factory + */ + boolean equals(@Nullable final ItemMeta meta1, @Nullable final ItemMeta meta2) throws IllegalArgumentException; + + /** + * Returns an appropriate item meta for the specified stack. + *

+ * The item meta returned will always be a valid meta for a given + * ItemStack of the specified material. It may be a more or less specific + * meta, and could also be the same meta or meta type as the parameter. + * The item meta returned will also always be the most appropriate meta. + *

+ * Example, if a {@link SkullMeta} is being applied to a book, this method + * would return a {@link BookMeta} containing all information in the + * specified meta that is applicable to an {@link ItemMeta}, the highest + * common interface. + * + * @param meta the meta to convert + * @param stack the stack to convert the meta for + * @return An appropriate item meta for the specified item stack. No + * guarantees are made as to if a copy is returned. This will be null + * for a stack of air. + * @throws IllegalArgumentException if the specified meta was not created + * by this factory + */ + @Nullable + ItemMeta asMetaFor(@NotNull final ItemMeta meta, @NotNull final ItemStack stack) throws IllegalArgumentException; + + /** + * Returns an appropriate item meta for the specified material. + *

+ * The item meta returned will always be a valid meta for a given + * ItemStack of the specified material. It may be a more or less specific + * meta, and could also be the same meta or meta type as the parameter. + * The item meta returned will also always be the most appropriate meta. + *

+ * Example, if a {@link SkullMeta} is being applied to a book, this method + * would return a {@link BookMeta} containing all information in the + * specified meta that is applicable to an {@link ItemMeta}, the highest + * common interface. + * + * @param meta the meta to convert + * @param material the material to convert the meta for + * @return An appropriate item meta for the specified item material. No + * guarantees are made as to if a copy is returned. This will be null for air. + * @throws IllegalArgumentException if the specified meta was not created + * by this factory + */ + @Nullable + ItemMeta asMetaFor(@NotNull final ItemMeta meta, @NotNull final Material material) throws IllegalArgumentException; + + /** + * Returns the default color for all leather armor. + * + * @return the default color for leather armor + */ + @NotNull + Color getDefaultLeatherColor(); + + /** + * Apply a material change for an item meta. Do not use under any + * circumstances. + * + * @param meta meta + * @param material material + * @return updated material + * @throws IllegalArgumentException exception + * @deprecated for internal use only + */ + @Deprecated + @NotNull + Material updateMaterial(@NotNull final ItemMeta meta, @NotNull final Material material) throws IllegalArgumentException; + // Paper start + /** + * Minecart updates are converting simple item stacks into more complex NBT oriented Item Stacks. + * + * Use this method to to ensure any desired data conversions are processed. + * The input itemstack will not be the same as the returned itemstack. + * + * @param item The item to process conversions on + * @return A potentially Data Converted ItemStack + */ + @NotNull + ItemStack ensureServerConversions(@NotNull ItemStack item); + + /** + * Gets the Display name as seen in the Client. + * Currently the server only supports the English language. To override this, + * You must replace the language file embedded in the server jar. + * + * @param item Item to return Display name of + * @return Display name of Item + */ + @Nullable + String getI18NDisplayName(@Nullable ItemStack item); + // Paper end +} diff --git a/api/src/main/java/org/bukkit/inventory/ItemFlag.java b/api/src/main/java/org/bukkit/inventory/ItemFlag.java new file mode 100644 index 000000000..2a8af7ba8 --- /dev/null +++ b/api/src/main/java/org/bukkit/inventory/ItemFlag.java @@ -0,0 +1,32 @@ +package org.bukkit.inventory; + +/** + * A ItemFlag can hide some Attributes from ItemStacks + */ +public enum ItemFlag { + + /** + * Setting to show/hide enchants + */ + HIDE_ENCHANTS, + /** + * Setting to show/hide Attributes like Damage + */ + HIDE_ATTRIBUTES, + /** + * Setting to show/hide the unbreakable State + */ + HIDE_UNBREAKABLE, + /** + * Setting to show/hide what the ItemStack can break/destroy + */ + HIDE_DESTROYS, + /** + * Setting to show/hide where this ItemStack can be build/placed on + */ + HIDE_PLACED_ON, + /** + * Setting to show/hide potion effects on this ItemStack + */ + HIDE_POTION_EFFECTS; +} diff --git a/api/src/main/java/org/bukkit/inventory/ItemStack.java b/api/src/main/java/org/bukkit/inventory/ItemStack.java new file mode 100644 index 000000000..1d3b0a312 --- /dev/null +++ b/api/src/main/java/org/bukkit/inventory/ItemStack.java @@ -0,0 +1,752 @@ +package org.bukkit.inventory; + +import com.google.common.collect.ImmutableMap; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Map; +import java.util.Set; + +import org.apache.commons.lang.Validate; +import org.bukkit.Bukkit; +import org.bukkit.Material; +import org.bukkit.UndefinedNullability; +import org.bukkit.Utility; +import org.bukkit.configuration.serialization.ConfigurationSerializable; +import org.bukkit.enchantments.Enchantment; +import org.bukkit.inventory.meta.Damageable; +import org.bukkit.inventory.meta.ItemMeta; +import org.bukkit.material.MaterialData; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +/** + * Represents a stack of items + */ +public class ItemStack implements Cloneable, ConfigurationSerializable { + private Material type = Material.AIR; + private int amount = 0; + private MaterialData data = null; + private ItemMeta meta; + + @Utility + protected ItemStack() {} + + /** + * Defaults stack size to 1, with no extra data + * + * @param type item material + */ + public ItemStack(@NotNull final Material type) { + this(type, 1); + } + + /** + * An item stack with no extra data + * + * @param type item material + * @param amount stack size + */ + public ItemStack(@NotNull final Material type, final int amount) { + this(type, amount, (short) 0); + } + + /** + * An item stack with the specified damage / durability + * + * @param type item material + * @param amount stack size + * @param damage durability / damage + * @deprecated see {@link #setDurability(short)} + */ + public ItemStack(@NotNull final Material type, final int amount, final short damage) { + this(type, amount, damage, null); + } + + /** + * @param type the type + * @param amount the amount in the stack + * @param damage the damage value of the item + * @param data the data value or null + * @deprecated this method uses an ambiguous data byte object + */ + @Deprecated + public ItemStack(@NotNull final Material type, final int amount, final short damage, @Nullable final Byte data) { + Validate.notNull(type, "Material cannot be null"); + this.type = type; + this.amount = amount; + if (damage != 0) { + setDurability(damage); + } + if (data != null) { + createData(data); + } + } + + /** + * Creates a new item stack derived from the specified stack + * + * @param stack the stack to copy + * @throws IllegalArgumentException if the specified stack is null or + * returns an item meta not created by the item factory + */ + public ItemStack(@NotNull final ItemStack stack) throws IllegalArgumentException { + Validate.notNull(stack, "Cannot copy null stack"); + this.type = stack.getType(); + this.amount = stack.getAmount(); + this.data = stack.getData(); + if (stack.hasItemMeta()) { + setItemMeta0(stack.getItemMeta(), type); + } + } + + /** + * Gets the type of this item + * + * @return Type of the items in this stack + */ + @Utility + @NotNull + public Material getType() { + return type; + } + + /** + * Sets the type of this item + *

+ * Note that in doing so you will reset the MaterialData for this stack + * + * @param type New type to set the items in this stack to + */ + @Utility + public void setType(@NotNull Material type) { + Validate.notNull(type, "Material cannot be null"); + this.type = type; + if (this.meta != null) { + this.meta = Bukkit.getItemFactory().asMetaFor(meta, type); + } + if (type.isLegacy()) { + createData((byte) 0); + } else { + this.data = null; + } + } + + /** + * Gets the amount of items in this stack + * + * @return Amount of items in this stack + */ + public int getAmount() { + return amount; + } + + /** + * Sets the amount of items in this stack + * + * @param amount New amount of items in this stack + */ + public void setAmount(int amount) { + this.amount = amount; + } + + /** + * Gets the MaterialData for this stack of items + * + * @return MaterialData for this item + */ + @Nullable + public MaterialData getData() { + Material mat = Bukkit.getUnsafe().toLegacy(getType()); + if (data == null && mat != null && mat.getData() != null) { + data = mat.getNewData((byte) this.getDurability()); + } + + return data; + } + + /** + * Sets the MaterialData for this stack of items + * + * @param data New MaterialData for this item + */ + public void setData(@Nullable MaterialData data) { + Material mat = Bukkit.getUnsafe().toLegacy(getType()); + + if (data == null || mat == null || mat.getData() == null) { + this.data = data; + } else { + if ((data.getClass() == mat.getData()) || (data.getClass() == MaterialData.class)) { + this.data = data; + } else { + throw new IllegalArgumentException("Provided data is not of type " + mat.getData().getName() + ", found " + data.getClass().getName()); + } + } + } + + /** + * Sets the durability of this item + * + * @param durability Durability of this item + * @deprecated durability is now part of ItemMeta. To avoid confusion and + * misuse, {@link #getItemMeta()}, {@link #setItemMeta(ItemMeta)} and + * {@link Damageable#setDamage(int)} should be used instead. This is because + * any call to this method will be overwritten by subsequent setting of + * ItemMeta which was created before this call. + */ + @Deprecated + public void setDurability(final short durability) { + ItemMeta meta = getItemMeta(); + if (meta != null) { + ((Damageable) meta).setDamage(durability); + setItemMeta(meta); + } + } + + /** + * Gets the durability of this item + * + * @return Durability of this item + * @deprecated see {@link #setDurability(short)} + */ + @Deprecated + public short getDurability() { + ItemMeta meta = getItemMeta(); + return (meta == null) ? 0 : (short) ((Damageable) meta).getDamage(); + } + + /** + * Get the maximum stacksize for the material hold in this ItemStack. + * (Returns -1 if it has no idea) + * + * @return The maximum you can stack this material to. + */ + @Utility + public int getMaxStackSize() { + Material material = getType(); + if (material != null) { + return material.getMaxStackSize(); + } + return -1; + } + + private void createData(final byte data) { + this.data = type.getNewData(data); + } + + @Override + @Utility + public String toString() { + StringBuilder toString = new StringBuilder("ItemStack{").append(getType().name()).append(" x ").append(getAmount()); + if (hasItemMeta()) { + toString.append(", ").append(getItemMeta()); + } + return toString.append('}').toString(); + } + + @Override + @Utility + public boolean equals(Object obj) { + if (this == obj) { + return true; + } + if (!(obj instanceof ItemStack)) { + return false; + } + + ItemStack stack = (ItemStack) obj; + return getAmount() == stack.getAmount() && isSimilar(stack); + } + + /** + * This method is the same as equals, but does not consider stack size + * (amount). + * + * @param stack the item stack to compare to + * @return true if the two stacks are equal, ignoring the amount + */ + @Utility + public boolean isSimilar(@Nullable ItemStack stack) { + if (stack == null) { + return false; + } + if (stack == this) { + return true; + } + Material comparisonType = (this.type.isLegacy()) ? Bukkit.getUnsafe().fromLegacy(this.getData(), true) : this.type; // This may be called from legacy item stacks, try to get the right material + return comparisonType == stack.getType() && getDurability() == stack.getDurability() && hasItemMeta() == stack.hasItemMeta() && (hasItemMeta() ? Bukkit.getItemFactory().equals(getItemMeta(), stack.getItemMeta()) : true); + } + + @NotNull + @Override + public ItemStack clone() { + try { + ItemStack itemStack = (ItemStack) super.clone(); + + if (this.meta != null) { + itemStack.meta = this.meta.clone(); + } + + if (this.data != null) { + itemStack.data = this.data.clone(); + } + + return itemStack; + } catch (CloneNotSupportedException e) { + throw new Error(e); + } + } + + @Override + @Utility + public int hashCode() { + int hash = 1; + + hash = hash * 31 + getType().hashCode(); + hash = hash * 31 + getAmount(); + hash = hash * 31 + (getDurability() & 0xffff); + hash = hash * 31 + (hasItemMeta() ? (meta == null ? getItemMeta().hashCode() : meta.hashCode()) : 0); + + return hash; + } + + /** + * Checks if this ItemStack contains the given {@link Enchantment} + * + * @param ench Enchantment to test + * @return True if this has the given enchantment + */ + public boolean containsEnchantment(@NotNull Enchantment ench) { + return meta == null ? false : meta.hasEnchant(ench); + } + + /** + * Gets the level of the specified enchantment on this item stack + * + * @param ench Enchantment to check + * @return Level of the enchantment, or 0 + */ + public int getEnchantmentLevel(@NotNull Enchantment ench) { + return meta == null ? 0 : meta.getEnchantLevel(ench); + } + + /** + * Gets a map containing all enchantments and their levels on this item. + * + * @return Map of enchantments. + */ + @NotNull + public Map getEnchantments() { + return meta == null ? ImmutableMap.of() : meta.getEnchants(); + } + + /** + * Adds the specified enchantments to this item stack. + *

+ * This method is the same as calling {@link + * #addEnchantment(org.bukkit.enchantments.Enchantment, int)} for each + * element of the map. + * + * @param enchantments Enchantments to add + * @throws IllegalArgumentException if the specified enchantments is null + * @throws IllegalArgumentException if any specific enchantment or level + * is null. Warning: Some enchantments may be added before this + * exception is thrown. + */ + @Utility + public void addEnchantments(@NotNull Map enchantments) { + Validate.notNull(enchantments, "Enchantments cannot be null"); + for (Map.Entry entry : enchantments.entrySet()) { + addEnchantment(entry.getKey(), entry.getValue()); + } + } + + /** + * Adds the specified {@link Enchantment} to this item stack. + *

+ * If this item stack already contained the given enchantment (at any + * level), it will be replaced. + * + * @param ench Enchantment to add + * @param level Level of the enchantment + * @throws IllegalArgumentException if enchantment null, or enchantment is + * not applicable + */ + @Utility + public void addEnchantment(@NotNull Enchantment ench, int level) { + Validate.notNull(ench, "Enchantment cannot be null"); + if ((level < ench.getStartLevel()) || (level > ench.getMaxLevel())) { + throw new IllegalArgumentException("Enchantment level is either too low or too high (given " + level + ", bounds are " + ench.getStartLevel() + " to " + ench.getMaxLevel() + ")"); + } else if (!ench.canEnchantItem(this)) { + throw new IllegalArgumentException("Specified enchantment cannot be applied to this itemstack"); + } + + addUnsafeEnchantment(ench, level); + } + + /** + * Adds the specified enchantments to this item stack in an unsafe manner. + *

+ * This method is the same as calling {@link + * #addUnsafeEnchantment(org.bukkit.enchantments.Enchantment, int)} for + * each element of the map. + * + * @param enchantments Enchantments to add + */ + @Utility + public void addUnsafeEnchantments(@NotNull Map enchantments) { + for (Map.Entry entry : enchantments.entrySet()) { + addUnsafeEnchantment(entry.getKey(), entry.getValue()); + } + } + + /** + * Adds the specified {@link Enchantment} to this item stack. + *

+ * If this item stack already contained the given enchantment (at any + * level), it will be replaced. + *

+ * This method is unsafe and will ignore level restrictions or item type. + * Use at your own discretion. + * + * @param ench Enchantment to add + * @param level Level of the enchantment + */ + public void addUnsafeEnchantment(@NotNull Enchantment ench, int level) { + ItemMeta itemMeta = (meta == null ? meta = Bukkit.getItemFactory().getItemMeta(type) : meta); + if (itemMeta != null) { + itemMeta.addEnchant(ench, level, true); + } + } + + /** + * Removes the specified {@link Enchantment} if it exists on this + * ItemStack + * + * @param ench Enchantment to remove + * @return Previous level, or 0 + */ + public int removeEnchantment(@NotNull Enchantment ench) { + int level = getEnchantmentLevel(ench); + if (level == 0 || meta == null) { + return level; + } + meta.removeEnchant(ench); + return level; + } + + @NotNull + @Utility + public Map serialize() { + Map result = new LinkedHashMap(); + + result.put("v", Bukkit.getUnsafe().getDataVersion()); // Include version to indicate we are using modern material names (or LEGACY prefix) + result.put("type", getType().name()); + + if (getAmount() != 1) { + result.put("amount", getAmount()); + } + + ItemMeta meta = getItemMeta(); + if (!Bukkit.getItemFactory().equals(meta, null)) { + result.put("meta", meta); + } + + return result; + } + + /** + * Required method for configuration serialization + * + * @param args map to deserialize + * @return deserialized item stack + * @see ConfigurationSerializable + */ + @NotNull + public static ItemStack deserialize(@NotNull Map args) { + int version = (args.containsKey("v")) ? ((Number) args.get("v")).intValue() : -1; + short damage = 0; + int amount = 1; + + if (args.containsKey("damage")) { + damage = ((Number) args.get("damage")).shortValue(); + } + + Material type; + if (version < 0) { + type = Material.getMaterial(Material.LEGACY_PREFIX + (String) args.get("type")); + + byte dataVal = (type != null && type.getMaxDurability() == 0) ? (byte) damage : 0; // Actually durable items get a 0 passed into conversion + type = Bukkit.getUnsafe().fromLegacy(new MaterialData(type, dataVal), true); + + // We've converted now so the data val isn't a thing and can be reset + if (dataVal != 0) { + damage = 0; + } + } else { + type = Material.getMaterial((String) args.get("type")); + } + + if (args.containsKey("amount")) { + amount = ((Number) args.get("amount")).intValue(); + } + + ItemStack result = new ItemStack(type, amount, damage); + + if (args.containsKey("enchantments")) { // Backward compatiblity, @deprecated + Object raw = args.get("enchantments"); + + if (raw instanceof Map) { + Map map = (Map) raw; + + for (Map.Entry entry : map.entrySet()) { + Enchantment enchantment = Enchantment.getByName(entry.getKey().toString()); + + if ((enchantment != null) && (entry.getValue() instanceof Integer)) { + result.addUnsafeEnchantment(enchantment, (Integer) entry.getValue()); + } + } + } + } else if (args.containsKey("meta")) { // We cannot and will not have meta when enchantments (pre-ItemMeta) exist + Object raw = args.get("meta"); + if (raw instanceof ItemMeta) { + result.setItemMeta((ItemMeta) raw); + } + } + + if (version < 0) { + // Set damage again incase meta overwrote it + if (args.containsKey("damage")) { + result.setDurability(damage); + } + } + + return result.ensureServerConversions(); // Paper + } + + /** + * Get a copy of this ItemStack's {@link ItemMeta}. + * + * @return a copy of the current ItemStack's ItemData + */ + @UndefinedNullability // Paper + public ItemMeta getItemMeta() { + return this.meta == null ? Bukkit.getItemFactory().getItemMeta(this.type) : this.meta.clone(); + } + + /** + * Checks to see if any meta data has been defined. + * + * @return Returns true if some meta data has been set for this item + */ + public boolean hasItemMeta() { + return !Bukkit.getItemFactory().equals(meta, null); + } + + /** + * Set the ItemMeta of this ItemStack. + * + * @param itemMeta new ItemMeta, or null to indicate meta data be cleared. + * @return True if successfully applied ItemMeta, see {@link + * ItemFactory#isApplicable(ItemMeta, ItemStack)} + * @throws IllegalArgumentException if the item meta was not created by + * the {@link ItemFactory} + */ + public boolean setItemMeta(@Nullable ItemMeta itemMeta) { + return setItemMeta0(itemMeta, type); + } + + /* + * Cannot be overridden, so it's safe for constructor call + */ + private boolean setItemMeta0(@Nullable ItemMeta itemMeta, @NotNull Material material) { + if (itemMeta == null) { + this.meta = null; + return true; + } + if (!Bukkit.getItemFactory().isApplicable(itemMeta, material)) { + return false; + } + this.meta = Bukkit.getItemFactory().asMetaFor(itemMeta, material); + + Material newType = Bukkit.getItemFactory().updateMaterial(meta, material); + if (this.type != newType) { + this.type = newType; + } + + if (this.meta == itemMeta) { + this.meta = itemMeta.clone(); + } + + return true; + } + + // Paper start + /** + * Minecart updates are converting simple item stacks into more complex NBT oriented Item Stacks. + * + * Use this method to to ensure any desired data conversions are processed. + * The input itemstack will not be the same as the returned itemstack. + * + * @return A potentially Data Converted ItemStack + */ + @NotNull + public ItemStack ensureServerConversions() { + return Bukkit.getServer().getItemFactory().ensureServerConversions(this); + } + + /** + * Gets the Display name as seen in the Client. + * Currently the server only supports the English language. To override this, + * You must replace the language file embedded in the server jar. + * + * @return Display name of Item + */ + @Nullable + public String getI18NDisplayName() { + return Bukkit.getServer().getItemFactory().getI18NDisplayName(this); + } + + public int getMaxItemUseDuration() { + if (type == null || type == Material.AIR || !type.isItem()) { + return 0; + } + // Requires access to NMS + return ensureServerConversions().getMaxItemUseDuration(); + } + + /** + * Clones the itemstack and returns it a single quantity. + * @return The new itemstack with 1 quantity + */ + @NotNull + public ItemStack asOne() { + return asQuantity(1); + } + + /** + * Clones the itemstack and returns it as the specified quantity + * @param qty The quantity of the cloned item + * @return The new itemstack with specified quantity + */ + @NotNull + public ItemStack asQuantity(int qty) { + ItemStack clone = clone(); + clone.setAmount(qty); + return clone; + } + + /** + * Adds 1 to this itemstack. Will not go over the items max stack size. + * @return The same item (not a clone) + */ + @NotNull + public ItemStack add() { + return add(1); + } + + /** + * Adds quantity to this itemstack. Will not go over the items max stack size. + * + * @param qty The amount to add + * @return The same item (not a clone) + */ + @NotNull + public ItemStack add(int qty) { + setAmount(Math.min(getMaxStackSize(), getAmount() + qty)); + return this; + } + + /** + * Subtracts 1 to this itemstack. Going to 0 or less will invalidate the item. + * @return The same item (not a clone) + */ + @NotNull + public ItemStack subtract() { + return subtract(1); + } + + /** + * Subtracts quantity to this itemstack. Going to 0 or less will invalidate the item. + * + * @param qty The amount to add + * @return The same item (not a clone) + */ + @NotNull + public ItemStack subtract(int qty) { + setAmount(Math.max(0, getAmount() - qty)); + return this; + } + + /** + * If the item has lore, returns it, else it will return null + * @return The lore, or null + */ + @Nullable + public List getLore() { + if (!hasItemMeta()) { + return null; + } + ItemMeta itemMeta = getItemMeta(); + if (!itemMeta.hasLore()) { + return null; + } + return itemMeta.getLore(); + } + + /** + * Sets the lore for this item. + * Removes lore when given null. + * + * @param lore the lore that will be set + */ + public void setLore(@Nullable List lore) { + ItemMeta itemMeta = getItemMeta(); + itemMeta.setLore(lore); + setItemMeta(itemMeta); + } + + /** + * Set itemflags which should be ignored when rendering a ItemStack in the Client. This Method does silently ignore double set itemFlags. + * + * @param itemFlags The hideflags which shouldn't be rendered + */ + public void addItemFlags(@NotNull ItemFlag... itemFlags) { + ItemMeta itemMeta = getItemMeta(); + itemMeta.addItemFlags(itemFlags); + setItemMeta(itemMeta); + } + + /** + * Remove specific set of itemFlags. This tells the Client it should render it again. This Method does silently ignore double removed itemFlags. + * + * @param itemFlags Hideflags which should be removed + */ + public void removeItemFlags(@NotNull ItemFlag... itemFlags) { + ItemMeta itemMeta = getItemMeta(); + itemMeta.removeItemFlags(itemFlags); + setItemMeta(itemMeta); + } + + /** + * Get current set itemFlags. The collection returned is unmodifiable. + * + * @return A set of all itemFlags set + */ + @NotNull + public Set getItemFlags() { + ItemMeta itemMeta = getItemMeta(); + return itemMeta.getItemFlags(); + } + + /** + * Check if the specified flag is present on this item. + * + * @param flag the flag to check + * @return if it is present + */ + public boolean hasItemFlag(@NotNull ItemFlag flag) { + ItemMeta itemMeta = getItemMeta(); + return itemMeta.hasItemFlag(flag); + } + // Paper end +} diff --git a/api/src/main/java/org/bukkit/inventory/LlamaInventory.java b/api/src/main/java/org/bukkit/inventory/LlamaInventory.java new file mode 100644 index 000000000..5ac1afb8a --- /dev/null +++ b/api/src/main/java/org/bukkit/inventory/LlamaInventory.java @@ -0,0 +1,25 @@ +package org.bukkit.inventory; + +import org.bukkit.entity.Llama; +import org.jetbrains.annotations.Nullable; + +/** + * An interface to the inventory of a {@link Llama}. + */ +public interface LlamaInventory extends SaddledHorseInventory { + + /** + * Gets the item in the llama's decor slot. + * + * @return the decor item + */ + @Nullable + ItemStack getDecor(); + + /** + * Sets the item in the llama's decor slot. + * + * @param stack the new item + */ + void setDecor(@Nullable ItemStack stack); +} diff --git a/api/src/main/java/org/bukkit/inventory/MainHand.java b/api/src/main/java/org/bukkit/inventory/MainHand.java new file mode 100644 index 000000000..75f12f156 --- /dev/null +++ b/api/src/main/java/org/bukkit/inventory/MainHand.java @@ -0,0 +1,9 @@ +package org.bukkit.inventory; + +/** + * Represents the chosen main hand of a player + */ +public enum MainHand { + LEFT, + RIGHT +} diff --git a/api/src/main/java/org/bukkit/inventory/Merchant.java b/api/src/main/java/org/bukkit/inventory/Merchant.java new file mode 100644 index 000000000..5ff1f93d1 --- /dev/null +++ b/api/src/main/java/org/bukkit/inventory/Merchant.java @@ -0,0 +1,74 @@ +package org.bukkit.inventory; + +import java.util.List; + +import org.bukkit.entity.HumanEntity; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +/** + * Represents a merchant. A merchant is a special type of inventory which can + * facilitate custom trades between items. + */ +public interface Merchant { + + /** + * Get a list of trades currently available from this merchant. + * + * @return an immutable list of trades + */ + @NotNull + List getRecipes(); + + /** + * Set the list of trades currently available from this merchant. + *
+ * This will not change the selected trades of players currently trading + * with this merchant. + * + * @param recipes a list of recipes + */ + void setRecipes(@NotNull List recipes); + + /** + * Get the recipe at a certain index of this merchant's trade list. + * + * @param i the index + * @return the recipe + * @throws IndexOutOfBoundsException Throws when specified index is larger than Merchant's inventory + */ + @NotNull + MerchantRecipe getRecipe(int i) throws IndexOutOfBoundsException; + + /** + * Set the recipe at a certain index of this merchant's trade list. + * + * @param i the index + * @param recipe the recipe + * @throws IndexOutOfBoundsException Throws when specified index is larger than Merchant's inventory + */ + void setRecipe(int i, @NotNull MerchantRecipe recipe) throws IndexOutOfBoundsException; + + /** + * Get the number of trades this merchant currently has available. + * + * @return the recipe count + */ + int getRecipeCount(); + + /** + * Gets whether this merchant is currently trading. + * + * @return whether the merchant is trading + */ + boolean isTrading(); + + /** + * Gets the player this merchant is trading with, or null if it is not + * currently trading. + * + * @return the trader, or null + */ + @Nullable + HumanEntity getTrader(); +} diff --git a/api/src/main/java/org/bukkit/inventory/MerchantInventory.java b/api/src/main/java/org/bukkit/inventory/MerchantInventory.java new file mode 100644 index 000000000..edc67fecc --- /dev/null +++ b/api/src/main/java/org/bukkit/inventory/MerchantInventory.java @@ -0,0 +1,33 @@ +package org.bukkit.inventory; + +import org.jetbrains.annotations.Nullable; + +/** + * Represents a trading inventory between a player and a merchant. + *
+ * The holder of this Inventory is the owning Villager, or null if the player is + * trading with a merchant created by a plugin. + */ +public interface MerchantInventory extends Inventory { + + /** + * Get the index of the currently selected recipe. + * + * @return the index of the currently selected recipe + */ + int getSelectedRecipeIndex(); + + /** + * Get the currently active recipe. + *

+ * This will be null if the items provided by the player do + * not match the ingredients of the selected recipe. This does not + * necessarily match the recipe selected by the player: If the player has + * selected the first recipe, the merchant will search all of its offers + * for a matching recipe to activate. + * + * @return the currently active recipe + */ + @Nullable + MerchantRecipe getSelectedRecipe(); +} diff --git a/api/src/main/java/org/bukkit/inventory/MerchantRecipe.java b/api/src/main/java/org/bukkit/inventory/MerchantRecipe.java new file mode 100644 index 000000000..dabbdf252 --- /dev/null +++ b/api/src/main/java/org/bukkit/inventory/MerchantRecipe.java @@ -0,0 +1,130 @@ +package org.bukkit.inventory; + +import com.google.common.base.Preconditions; +import org.jetbrains.annotations.NotNull; + +import java.util.ArrayList; +import java.util.List; + +/** + * Represents a merchant's trade. + * + * Trades can take one or two ingredients, and provide one result. The + * ingredients' ItemStack amounts are respected in the trade. + *
+ * A trade has a limited number of uses, after which the trade can no longer be + * used, unless the player uses a different trade, which will cause its maximum + * uses to increase. + *
+ * A trade may or may not reward experience for being completed. + * + * @see org.bukkit.event.entity.VillagerReplenishTradeEvent + */ +public class MerchantRecipe implements Recipe { + + private ItemStack result; + private List ingredients = new ArrayList(); + private int uses; + private int maxUses; + private boolean experienceReward; + + public MerchantRecipe(@NotNull ItemStack result, int maxUses) { + this(result, 0, maxUses, false); + } + + public MerchantRecipe(@NotNull ItemStack result, int uses, int maxUses, boolean experienceReward) { + this.result = result; + this.uses = uses; + this.maxUses = maxUses; + this.experienceReward = experienceReward; + } + + @NotNull + @Override + public ItemStack getResult() { + return result; + } + + public void addIngredient(@NotNull ItemStack item) { + Preconditions.checkState(ingredients.size() < 2, "MerchantRecipe can only have maximum 2 ingredients"); + ingredients.add(item.clone()); + } + + public void removeIngredient(int index) { + ingredients.remove(index); + } + + public void setIngredients(@NotNull List ingredients) { + Preconditions.checkState(ingredients.size() <= 2, "MerchantRecipe can only have maximum 2 ingredients"); + this.ingredients = new ArrayList(); + for (ItemStack item : ingredients) { + this.ingredients.add(item.clone()); + } + } + + @NotNull + public List getIngredients() { + List copy = new ArrayList(); + for (ItemStack item : ingredients) { + copy.add(item.clone()); + } + return copy; + } + + /** + * Get the number of times this trade has been used. + * + * @return the number of uses + */ + public int getUses() { + return uses; + } + + /** + * Set the number of times this trade has been used. + * + * @param uses the number of uses + */ + public void setUses(int uses) { + this.uses = uses; + } + + /** + * Get the maximum number of uses this trade has. + *
+ * The maximum uses of this trade may increase when a player trades with the + * owning merchant. + * + * @return the maximum number of uses + */ + public int getMaxUses() { + return maxUses; + } + + /** + * Set the maximum number of uses this trade has. + * + * @param maxUses the maximum number of time this trade can be used + */ + public void setMaxUses(int maxUses) { + this.maxUses = maxUses; + } + + /** + * Whether to reward experience for the trade. + * + * @return whether to reward experience for completing this trade + */ + public boolean hasExperienceReward() { + return experienceReward; + } + + /** + * Set whether to reward experience for the trade. + * + * @param flag whether to reward experience for completing this trade + */ + public void setExperienceReward(boolean flag) { + this.experienceReward = flag; + } +} diff --git a/api/src/main/java/org/bukkit/inventory/PlayerInventory.java b/api/src/main/java/org/bukkit/inventory/PlayerInventory.java new file mode 100644 index 000000000..eb71f01e4 --- /dev/null +++ b/api/src/main/java/org/bukkit/inventory/PlayerInventory.java @@ -0,0 +1,236 @@ +package org.bukkit.inventory; + +import org.bukkit.entity.HumanEntity; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +/** + * Interface to the inventory of a Player, including the four armor slots and any extra slots. + */ +public interface PlayerInventory extends Inventory { + + /** + * Get all ItemStacks from the armor slots + * + * @return All the ItemStacks from the armor slots. Individual items can be null. + */ + @NotNull + public ItemStack[] getArmorContents(); + + /** + * Get all additional ItemStacks stored in this inventory. + *
+ * NB: What defines an extra slot is up to the implementation, however it + * will not be contained within {@link #getStorageContents()} or + * {@link #getArmorContents()} + * + * @return All additional ItemStacks. Individual items can be null. + */ + @NotNull + public ItemStack[] getExtraContents(); + + /** + * Return the ItemStack from the helmet slot + * + * @return The ItemStack in the helmet slot + */ + @Nullable + public ItemStack getHelmet(); + + /** + * Return the ItemStack from the chestplate slot + * + * @return The ItemStack in the chestplate slot + */ + @Nullable + public ItemStack getChestplate(); + + /** + * Return the ItemStack from the leg slot + * + * @return The ItemStack in the leg slot + */ + @Nullable + public ItemStack getLeggings(); + + /** + * Return the ItemStack from the boots slot + * + * @return The ItemStack in the boots slot + */ + @Nullable + public ItemStack getBoots(); + + /** + * Stores the ItemStack at the given index of the inventory. + *

+ * Indexes 0 through 8 refer to the hotbar. 9 through 35 refer to the main inventory, counting up from 9 at the top + * left corner of the inventory, moving to the right, and moving to the row below it back on the left side when it + * reaches the end of the row. It follows the same path in the inventory like you would read a book. + *

+ * Indexes 36 through 39 refer to the armor slots. Though you can set armor with this method using these indexes, + * you are encouraged to use the provided methods for those slots. + *

+ * Index 40 refers to the off hand (shield) item slot. Though you can set off hand with this method using this index, + * you are encouraged to use the provided method for this slot. + *

+ * If you attempt to use this method with an index less than 0 or greater than 40, an ArrayIndexOutOfBounds + * exception will be thrown. + * + * @param index The index where to put the ItemStack + * @param item The ItemStack to set + * @throws ArrayIndexOutOfBoundsException when index < 0 || index > 40 + * @see #setBoots(ItemStack) + * @see #setChestplate(ItemStack) + * @see #setHelmet(ItemStack) + * @see #setLeggings(ItemStack) + * @see #setItemInOffHand(ItemStack) + */ + @Override + public void setItem(int index, @Nullable ItemStack item); + + /** + * Put the given ItemStacks into the armor slots + * + * @param items The ItemStacks to use as armour + */ + public void setArmorContents(@Nullable ItemStack[] items); + + /** + * Put the given ItemStacks into the extra slots + *
+ * See {@link #getExtraContents()} for an explanation of extra slots. + * + * @param items The ItemStacks to use as extra + */ + public void setExtraContents(@Nullable ItemStack[] items); + + /** + * Put the given ItemStack into the helmet slot. This does not check if + * the ItemStack is a helmet + * + * @param helmet The ItemStack to use as helmet + */ + public void setHelmet(@Nullable ItemStack helmet); + + /** + * Put the given ItemStack into the chestplate slot. This does not check + * if the ItemStack is a chestplate + * + * @param chestplate The ItemStack to use as chestplate + */ + public void setChestplate(@Nullable ItemStack chestplate); + + /** + * Put the given ItemStack into the leg slot. This does not check if the + * ItemStack is a pair of leggings + * + * @param leggings The ItemStack to use as leggings + */ + public void setLeggings(@Nullable ItemStack leggings); + + /** + * Put the given ItemStack into the boots slot. This does not check if the + * ItemStack is a boots + * + * @param boots The ItemStack to use as boots + */ + public void setBoots(@Nullable ItemStack boots); + + /** + * Gets a copy of the item the player is currently holding + * in their main hand. + * + * @return the currently held item + */ + @NotNull + ItemStack getItemInMainHand(); + + /** + * Sets the item the player is holding in their main hand. + * + * @param item The item to put into the player's hand + */ + void setItemInMainHand(@Nullable ItemStack item); + + /** + * Gets a copy of the item the player is currently holding + * in their off hand. + * + * @return the currently held item + */ + @NotNull + ItemStack getItemInOffHand(); + + /** + * Sets the item the player is holding in their off hand. + * + * @param item The item to put into the player's hand + */ + void setItemInOffHand(@Nullable ItemStack item); + + /** + * Gets a copy of the item the player is currently holding + * + * @deprecated players can duel wield now use the methods for the + * specific hand instead + * @see #getItemInMainHand() + * @see #getItemInOffHand() + * @return the currently held item + */ + @Deprecated + @NotNull + public ItemStack getItemInHand(); + + /** + * Sets the item the player is holding + * + * @deprecated players can duel wield now use the methods for the + * specific hand instead + * @see #setItemInMainHand(ItemStack) + * @see #setItemInOffHand(ItemStack) + * @param stack The item to put into the player's hand + */ + @Deprecated + public void setItemInHand(@Nullable ItemStack stack); + + /** + * Get the slot number of the currently held item + * + * @return Held item slot number + */ + public int getHeldItemSlot(); + + /** + * Set the slot number of the currently held item. + *

+ * This validates whether the slot is between 0 and 8 inclusive. + * + * @param slot The new slot number + * @throws IllegalArgumentException Thrown if slot is not between 0 and 8 + * inclusive + */ + public void setHeldItemSlot(int slot); + + @Nullable + public HumanEntity getHolder(); + + // Paper start + /** + * Gets the {@link ItemStack} found in the slot. + * + * @param slot The slot + * @return The item stack in the slot + */ + @Nullable + ItemStack getItem(@NotNull EquipmentSlot slot); + + /** + * Sets the {@link ItemStack} at the given {@link EquipmentSlot}. + * + * @param slot The slot for the stack + * @param stack The item stack to set + */ + void setItem(@NotNull EquipmentSlot slot, @Nullable ItemStack stack); + // Paper end +} diff --git a/api/src/main/java/org/bukkit/inventory/Recipe.java b/api/src/main/java/org/bukkit/inventory/Recipe.java new file mode 100644 index 000000000..4927a7ddc --- /dev/null +++ b/api/src/main/java/org/bukkit/inventory/Recipe.java @@ -0,0 +1,17 @@ +package org.bukkit.inventory; + +import org.jetbrains.annotations.NotNull; + +/** + * Represents some type of crafting recipe. + */ +public interface Recipe { + + /** + * Get the result of this recipe. + * + * @return The result stack + */ + @NotNull + ItemStack getResult(); +} diff --git a/api/src/main/java/org/bukkit/inventory/RecipeChoice.java b/api/src/main/java/org/bukkit/inventory/RecipeChoice.java new file mode 100644 index 000000000..3d2c2b83a --- /dev/null +++ b/api/src/main/java/org/bukkit/inventory/RecipeChoice.java @@ -0,0 +1,229 @@ +package org.bukkit.inventory; + +import com.google.common.base.Preconditions; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collections; +import java.util.List; +import java.util.Objects; +import java.util.function.Predicate; +import org.bukkit.Material; +import org.jetbrains.annotations.NotNull; + +/** + * Represents a potential item match within a recipe. All choices within a + * recipe must be satisfied for it to be craftable. + * + * This class is not legal for implementation by plugins! + */ +public interface RecipeChoice extends Predicate, Cloneable { + + /** + * Gets a single item stack representative of this stack choice. + * + * @return a single representative item + * @deprecated for compatability only + */ + @Deprecated + @NotNull + ItemStack getItemStack(); + + @NotNull + RecipeChoice clone(); + + @Override + boolean test(@NotNull ItemStack itemStack); + + /** + * Represents a choice of multiple matching Materials. + */ + public static class MaterialChoice implements RecipeChoice { + + private List choices; + + public MaterialChoice(@NotNull Material choice) { + this(Arrays.asList(choice)); + } + + public MaterialChoice(@NotNull Material... choices) { + this(Arrays.asList(choices)); + } + + public MaterialChoice(@NotNull List choices) { + Preconditions.checkArgument(choices != null, "choices"); + Preconditions.checkArgument(!choices.isEmpty(), "Must have at least one choice"); + for (Material choice : choices) { + Preconditions.checkArgument(choice != null, "Cannot have null choice"); + } + + this.choices = new ArrayList<>(choices); + } + + @Override + public boolean test(@NotNull ItemStack t) { + for (Material match : choices) { + if (t.getType() == match) { + return true; + } + } + + return false; + } + + @NotNull + @Override + public ItemStack getItemStack() { + ItemStack stack = new ItemStack(choices.get(0)); + + // For compat + if (choices.size() > 1) { + stack.setDurability(Short.MAX_VALUE); + } + + return stack; + } + + @NotNull + public List getChoices() { + return Collections.unmodifiableList(choices); + } + + @NotNull + @Override + public MaterialChoice clone() { + try { + MaterialChoice clone = (MaterialChoice) super.clone(); + clone.choices = new ArrayList<>(choices); + return clone; + } catch (CloneNotSupportedException ex) { + throw new AssertionError(ex); + } + } + + @Override + public int hashCode() { + int hash = 3; + hash = 37 * hash + Objects.hashCode(this.choices); + return hash; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } + if (obj == null) { + return false; + } + if (getClass() != obj.getClass()) { + return false; + } + final MaterialChoice other = (MaterialChoice) obj; + if (!Objects.equals(this.choices, other.choices)) { + return false; + } + return true; + } + + @Override + public String toString() { + return "MaterialChoice{" + "choices=" + choices + '}'; + } + } + + /** + * Represents a choice that will be valid only one of the stacks is exactly + * matched (aside from stack size). + *
+ * Not valid for shapeless recipes + * + * @deprecated draft API + */ + @Deprecated + public static class ExactChoice implements RecipeChoice { + + private List choices; + + public ExactChoice(@NotNull ItemStack stack) { + this(Arrays.asList(stack)); + } + + public ExactChoice(@NotNull ItemStack... stacks) { + this(Arrays.asList(stacks)); + } + + public ExactChoice(@NotNull List choices) { + Preconditions.checkArgument(choices != null, "choices"); + Preconditions.checkArgument(!choices.isEmpty(), "Must have at least one choice"); + for (ItemStack choice : choices) { + Preconditions.checkArgument(choice != null, "Cannot have null choice"); + } + + this.choices = new ArrayList<>(choices); + } + + @NotNull + @Override + public ItemStack getItemStack() { + return choices.get(0).clone(); + } + + @NotNull + public List getChoices() { + return Collections.unmodifiableList(choices); + } + + @NotNull + @Override + public ExactChoice clone() { + try { + ExactChoice clone = (ExactChoice) super.clone(); + clone.choices = new ArrayList<>(choices); + return clone; + } catch (CloneNotSupportedException ex) { + throw new AssertionError(ex); + } + } + + @Override + public boolean test(@NotNull ItemStack t) { + for (ItemStack match : choices) { + if (t.isSimilar(match)) { + return true; + } + } + + return false; + } + + @Override + public int hashCode() { + int hash = 7; + hash = 41 * hash + Objects.hashCode(this.choices); + return hash; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } + if (obj == null) { + return false; + } + if (getClass() != obj.getClass()) { + return false; + } + final ExactChoice other = (ExactChoice) obj; + if (!Objects.equals(this.choices, other.choices)) { + return false; + } + return true; + } + + @Override + public String toString() { + return "ExactChoice{" + "choices=" + choices + '}'; + } + } +} diff --git a/api/src/main/java/org/bukkit/inventory/SaddledHorseInventory.java b/api/src/main/java/org/bukkit/inventory/SaddledHorseInventory.java new file mode 100644 index 000000000..7944f26a3 --- /dev/null +++ b/api/src/main/java/org/bukkit/inventory/SaddledHorseInventory.java @@ -0,0 +1,3 @@ +package org.bukkit.inventory; + +public interface SaddledHorseInventory extends AbstractHorseInventory {} diff --git a/api/src/main/java/org/bukkit/inventory/ShapedRecipe.java b/api/src/main/java/org/bukkit/inventory/ShapedRecipe.java new file mode 100644 index 000000000..76b2dd7cb --- /dev/null +++ b/api/src/main/java/org/bukkit/inventory/ShapedRecipe.java @@ -0,0 +1,235 @@ +package org.bukkit.inventory; + +import com.google.common.base.Preconditions; +import java.util.Collections; +import java.util.HashMap; +import java.util.Map; + +import org.apache.commons.lang.Validate; + +import org.bukkit.Keyed; +import org.bukkit.Material; +import org.bukkit.NamespacedKey; +import org.bukkit.material.MaterialData; +import org.jetbrains.annotations.NotNull; + +/** + * Represents a shaped (ie normal) crafting recipe. + */ +public class ShapedRecipe implements Recipe, Keyed { + private final NamespacedKey key; + private final ItemStack output; + private String[] rows; + private Map ingredients = new HashMap<>(); + private String group = ""; + + @Deprecated + public ShapedRecipe(@NotNull ItemStack result) { + this.key = NamespacedKey.randomKey(); + new Throwable("Warning: A plugin is creating a recipe using a Deprecated method. This will cause you to receive warnings stating 'Tried to load unrecognized recipe: bukkit:'. Please ask the author to give their recipe a static key using NamespacedKey.").printStackTrace(); + this.output = new ItemStack(result); + } + + /** + * Create a shaped recipe to craft the specified ItemStack. The + * constructor merely determines the result and type; to set the actual + * recipe, you'll need to call the appropriate methods. + * + * @param key the unique recipe key + * @param result The item you want the recipe to create. + * @see ShapedRecipe#shape(String...) + * @see ShapedRecipe#setIngredient(char, Material) + * @see ShapedRecipe#setIngredient(char, Material, int) + * @see ShapedRecipe#setIngredient(char, MaterialData) + */ + public ShapedRecipe(@NotNull NamespacedKey key, @NotNull ItemStack result) { + Preconditions.checkArgument(key != null, "key"); + + this.key = key; + this.output = new ItemStack(result); + } + + /** + * Set the shape of this recipe to the specified rows. Each character + * represents a different ingredient; exactly what each character + * represents is set separately. The first row supplied corresponds with + * the upper most part of the recipe on the workbench e.g. if all three + * rows are supplies the first string represents the top row on the + * workbench. + * + * @param shape The rows of the recipe (up to 3 rows). + * @return The changed recipe, so you can chain calls. + */ + @NotNull + public ShapedRecipe shape(@NotNull final String... shape) { + Validate.notNull(shape, "Must provide a shape"); + Validate.isTrue(shape.length > 0 && shape.length < 4, "Crafting recipes should be 1, 2 or 3 rows, not ", shape.length); + + int lastLen = -1; + for (String row : shape) { + Validate.notNull(row, "Shape cannot have null rows"); + Validate.isTrue(row.length() > 0 && row.length() < 4, "Crafting rows should be 1, 2, or 3 characters, not ", row.length()); + + Validate.isTrue(lastLen == -1 || lastLen == row.length(), "Crafting recipes must be rectangular"); + lastLen = row.length(); + } + this.rows = new String[shape.length]; + for (int i = 0; i < shape.length; i++) { + this.rows[i] = shape[i]; + } + + // Remove character mappings for characters that no longer exist in the shape + HashMap newIngredients = new HashMap<>(); + for (String row : shape) { + for (Character c : row.toCharArray()) { + newIngredients.put(c, ingredients.get(c)); + } + } + this.ingredients = newIngredients; + + return this; + } + + /** + * Sets the material that a character in the recipe shape refers to. + * + * @param key The character that represents the ingredient in the shape. + * @param ingredient The ingredient. + * @return The changed recipe, so you can chain calls. + */ + @NotNull + public ShapedRecipe setIngredient(char key, @NotNull MaterialData ingredient) { + return setIngredient(key, ingredient.getItemType(), ingredient.getData()); + } + + /** + * Sets the material that a character in the recipe shape refers to. + * + * @param key The character that represents the ingredient in the shape. + * @param ingredient The ingredient. + * @return The changed recipe, so you can chain calls. + */ + @NotNull + public ShapedRecipe setIngredient(char key, @NotNull Material ingredient) { + return setIngredient(key, ingredient, 0); + } + + /** + * Sets the material that a character in the recipe shape refers to. + * + * @param key The character that represents the ingredient in the shape. + * @param ingredient The ingredient. + * @param raw The raw material data as an integer. + * @return The changed recipe, so you can chain calls. + * @deprecated Magic value + */ + @Deprecated + @NotNull + public ShapedRecipe setIngredient(char key, @NotNull Material ingredient, int raw) { + Validate.isTrue(ingredients.containsKey(key), "Symbol does not appear in the shape:", key); + + // -1 is the old wildcard, map to Short.MAX_VALUE as the new one + if (raw == -1) { + raw = Short.MAX_VALUE; + } + + ingredients.put(key, new RecipeChoice.MaterialChoice(Collections.singletonList(ingredient))); + return this; + } + + @NotNull + public ShapedRecipe setIngredient(char key, @NotNull RecipeChoice ingredient) { + Validate.isTrue(ingredients.containsKey(key), "Symbol does not appear in the shape:", key); + + ingredients.put(key, ingredient); + return this; + } + + // Paper start + @NotNull + public ShapedRecipe setIngredient(char key, @NotNull ItemStack item) { + return setIngredient(key, new RecipeChoice.ExactChoice(item)); + } + // Paper end + + /** + * Get a copy of the ingredients map. + * + * @return The mapping of character to ingredients. + */ + @NotNull + public Map getIngredientMap() { + HashMap result = new HashMap(); + for (Map.Entry ingredient : ingredients.entrySet()) { + if (ingredient.getValue() == null) { + result.put(ingredient.getKey(), null); + } else { + result.put(ingredient.getKey(), ingredient.getValue().getItemStack().clone()); + } + } + return result; + } + + @NotNull + public Map getChoiceMap() { + Map result = new HashMap<>(); + for (Map.Entry ingredient : ingredients.entrySet()) { + if (ingredient.getValue() == null) { + result.put(ingredient.getKey(), null); + } else { + result.put(ingredient.getKey(), ingredient.getValue().clone()); + } + } + return result; + } + + /** + * Get the shape. + * + * @return The recipe's shape. + * @throws NullPointerException when not set yet + */ + @NotNull + public String[] getShape() { + return rows.clone(); + } + + /** + * Get the result. + * + * @return The result stack. + */ + @NotNull + public ItemStack getResult() { + return output.clone(); + } + + @NotNull + @Override + public NamespacedKey getKey() { + return key; + } + + /** + * Get the group of this recipe. Recipes with the same group may be grouped + * together when displayed in the client. + * + * @return recipe group. An empty string denotes no group. May not be null. + */ + @NotNull + public String getGroup() { + return group; + } + + /** + * Set the group of this recipe. Recipes with the same group may be grouped + * together when displayed in the client. + * + * @param group recipe group. An empty string denotes no group. May not be + * null. + */ + public void setGroup(@NotNull String group) { + Preconditions.checkArgument(group != null, "group"); + this.group = group; + } +} diff --git a/api/src/main/java/org/bukkit/inventory/ShapelessRecipe.java b/api/src/main/java/org/bukkit/inventory/ShapelessRecipe.java new file mode 100644 index 000000000..818bf2936 --- /dev/null +++ b/api/src/main/java/org/bukkit/inventory/ShapelessRecipe.java @@ -0,0 +1,349 @@ +package org.bukkit.inventory; + +import com.google.common.base.Preconditions; +import java.util.ArrayList; +import java.util.Collections; +import java.util.Iterator; +import java.util.List; + +import org.apache.commons.lang.Validate; +import org.bukkit.Keyed; + +import org.bukkit.Material; +import org.bukkit.NamespacedKey; +import org.bukkit.material.MaterialData; +import org.jetbrains.annotations.NotNull; + +/** + * Represents a shapeless recipe, where the arrangement of the ingredients on + * the crafting grid does not matter. + */ +public class ShapelessRecipe implements Recipe, Keyed { + private final NamespacedKey key; + private final ItemStack output; + private final List ingredients = new ArrayList<>(); + private String group = ""; + + @Deprecated + public ShapelessRecipe(@NotNull ItemStack result) { + this.key = NamespacedKey.randomKey(); + new Throwable("Warning: A plugin is creating a recipe using a Deprecated method. This will cause you to receive warnings stating 'Tried to load unrecognized recipe: bukkit:'. Please ask the author to give their recipe a static key using NamespacedKey.").printStackTrace(); + this.output = new ItemStack(result); + } + + /** + * Create a shapeless recipe to craft the specified ItemStack. The + * constructor merely determines the result and type; to set the actual + * recipe, you'll need to call the appropriate methods. + * + * @param key the unique recipe key + * @param result The item you want the recipe to create. + * @see ShapelessRecipe#addIngredient(Material) + * @see ShapelessRecipe#addIngredient(MaterialData) + * @see ShapelessRecipe#addIngredient(Material,int) + * @see ShapelessRecipe#addIngredient(int,Material) + * @see ShapelessRecipe#addIngredient(int,MaterialData) + * @see ShapelessRecipe#addIngredient(int,Material,int) + */ + public ShapelessRecipe(@NotNull NamespacedKey key, @NotNull ItemStack result) { + this.key = key; + this.output = new ItemStack(result); + } + + /** + * Adds the specified ingredient. + * + * @param ingredient The ingredient to add. + * @return The changed recipe, so you can chain calls. + */ + @NotNull + public ShapelessRecipe addIngredient(@NotNull MaterialData ingredient) { + return addIngredient(1, ingredient); + } + + /** + * Adds the specified ingredient. + * + * @param ingredient The ingredient to add. + * @return The changed recipe, so you can chain calls. + */ + @NotNull + public ShapelessRecipe addIngredient(@NotNull Material ingredient) { + return addIngredient(1, ingredient, 0); + } + + /** + * Adds the specified ingredient. + * + * @param ingredient The ingredient to add. + * @param rawdata The data value, or -1 to allow any data value. + * @return The changed recipe, so you can chain calls. + * @deprecated Magic value + */ + @Deprecated + @NotNull + public ShapelessRecipe addIngredient(@NotNull Material ingredient, int rawdata) { + return addIngredient(1, ingredient, rawdata); + } + + /** + * Adds multiples of the specified ingredient. + * + * @param count How many to add (can't be more than 9!) + * @param ingredient The ingredient to add. + * @return The changed recipe, so you can chain calls. + */ + @NotNull + public ShapelessRecipe addIngredient(int count, @NotNull MaterialData ingredient) { + return addIngredient(count, ingredient.getItemType(), ingredient.getData()); + } + + /** + * Adds multiples of the specified ingredient. + * + * @param count How many to add (can't be more than 9!) + * @param ingredient The ingredient to add. + * @return The changed recipe, so you can chain calls. + */ + @NotNull + public ShapelessRecipe addIngredient(int count, @NotNull Material ingredient) { + return addIngredient(count, ingredient, 0); + } + + /** + * Adds multiples of the specified ingredient. + * + * @param count How many to add (can't be more than 9!) + * @param ingredient The ingredient to add. + * @param rawdata The data value, or -1 to allow any data value. + * @return The changed recipe, so you can chain calls. + * @deprecated Magic value + */ + @Deprecated + @NotNull + public ShapelessRecipe addIngredient(int count, @NotNull Material ingredient, int rawdata) { + Validate.isTrue(ingredients.size() + count <= 9, "Shapeless recipes cannot have more than 9 ingredients"); + + // -1 is the old wildcard, map to Short.MAX_VALUE as the new one + if (rawdata == -1) { + rawdata = Short.MAX_VALUE; + } + + while (count-- > 0) { + ingredients.add(new RecipeChoice.MaterialChoice(Collections.singletonList(ingredient))); + } + return this; + } + + @NotNull + public ShapelessRecipe addIngredient(@NotNull RecipeChoice ingredient) { + Validate.isTrue(ingredients.size() + 1 <= 9, "Shapeless recipes cannot have more than 9 ingredients"); + + ingredients.add(ingredient); + return this; + } + + // Paper start + @NotNull + public ShapelessRecipe addIngredient(@NotNull ItemStack item) { + return addIngredient(1, item); + } + + @NotNull + public ShapelessRecipe addIngredient(int count, @NotNull ItemStack item) { + Validate.isTrue(ingredients.size() + count <= 9, "Shapeless recipes cannot have more than 9 ingredients"); + while (count-- > 0) { + ingredients.add(new RecipeChoice.ExactChoice(item)); + } + return this; + } + + @NotNull + public ShapelessRecipe removeIngredient(@NotNull ItemStack item) { + return removeIngredient(1, item); + } + + @NotNull + public ShapelessRecipe removeIngredient(int count, @NotNull ItemStack item) { + Iterator iterator = ingredients.iterator(); + while (count > 0 && iterator.hasNext()) { + ItemStack stack = iterator.next().getItemStack(); + if (stack.equals(item)) { + iterator.remove(); + count--; + } + } + return this; + } + // Paper end + + /** + * Removes an ingredient from the list. + * + * @param ingredient The ingredient to remove + * @return The changed recipe. + */ + @NotNull + public ShapelessRecipe removeIngredient(@NotNull RecipeChoice ingredient) { + ingredients.remove(ingredient); + + return this; + } + + /** + * Removes an ingredient from the list. If the ingredient occurs multiple + * times, only one instance of it is removed. Only removes exact matches, + * with a data value of 0. + * + * @param ingredient The ingredient to remove + * @return The changed recipe. + */ + @NotNull + public ShapelessRecipe removeIngredient(@NotNull Material ingredient) { + return removeIngredient(ingredient, 0); + } + + /** + * Removes an ingredient from the list. If the ingredient occurs multiple + * times, only one instance of it is removed. If the data value is -1, + * only ingredients with a -1 data value will be removed. + * + * @param ingredient The ingredient to remove + * @return The changed recipe. + */ + @NotNull + public ShapelessRecipe removeIngredient(@NotNull MaterialData ingredient) { + return removeIngredient(ingredient.getItemType(), ingredient.getData()); + } + + /** + * Removes multiple instances of an ingredient from the list. If there are + * less instances then specified, all will be removed. Only removes exact + * matches, with a data value of 0. + * + * @param count The number of copies to remove. + * @param ingredient The ingredient to remove + * @return The changed recipe. + */ + @NotNull + public ShapelessRecipe removeIngredient(int count, @NotNull Material ingredient) { + return removeIngredient(count, ingredient, 0); + } + + /** + * Removes multiple instances of an ingredient from the list. If there are + * less instances then specified, all will be removed. If the data value + * is -1, only ingredients with a -1 data value will be removed. + * + * @param count The number of copies to remove. + * @param ingredient The ingredient to remove. + * @return The changed recipe. + */ + @NotNull + public ShapelessRecipe removeIngredient(int count, @NotNull MaterialData ingredient) { + return removeIngredient(count, ingredient.getItemType(), ingredient.getData()); + } + + /** + * Removes an ingredient from the list. If the ingredient occurs multiple + * times, only one instance of it is removed. If the data value is -1, + * only ingredients with a -1 data value will be removed. + * + * @param ingredient The ingredient to remove + * @param rawdata The data value; + * @return The changed recipe. + * @deprecated Magic value + */ + @Deprecated + @NotNull + public ShapelessRecipe removeIngredient(@NotNull Material ingredient, int rawdata) { + return removeIngredient(1, ingredient, rawdata); + } + + /** + * Removes multiple instances of an ingredient from the list. If there are + * less instances then specified, all will be removed. If the data value + * is -1, only ingredients with a -1 data value will be removed. + * + * @param count The number of copies to remove. + * @param ingredient The ingredient to remove. + * @param rawdata The data value. + * @return The changed recipe. + * @deprecated Magic value + */ + @Deprecated + @NotNull + public ShapelessRecipe removeIngredient(int count, @NotNull Material ingredient, int rawdata) { + Iterator iterator = ingredients.iterator(); + while (count > 0 && iterator.hasNext()) { + ItemStack stack = iterator.next().getItemStack(); + if (stack.getType() == ingredient && stack.getDurability() == rawdata) { + iterator.remove(); + count--; + } + } + return this; + } + + /** + * Get the result of this recipe. + * + * @return The result stack. + */ + @NotNull + public ItemStack getResult() { + return output.clone(); + } + + /** + * Get the list of ingredients used for this recipe. + * + * @return The input list + */ + @NotNull + public List getIngredientList() { + ArrayList result = new ArrayList(ingredients.size()); + for (RecipeChoice ingredient : ingredients) { + result.add(ingredient.getItemStack().clone()); + } + return result; + } + + @NotNull + public List getChoiceList() { + List result = new ArrayList<>(ingredients.size()); + for (RecipeChoice ingredient : ingredients) { + result.add(ingredient.clone()); + } + return result; + } + + @NotNull + @Override + public NamespacedKey getKey() { + return key; + } + + /** + * Get the group of this recipe. Recipes with the same group may be grouped + * together when displayed in the client. + * + * @return recipe group. An empty string denotes no group. May not be null. + */ + @NotNull + public String getGroup() { + return group; + } + + /** + * Set the group of this recipe. Recipes with the same group may be grouped + * together when displayed in the client. + * + * @param group recipe group. An empty string denotes no group. May not be + * null. + */ + public void setGroup(@NotNull String group) { + Preconditions.checkArgument(group != null, "group"); + this.group = group; + } +} diff --git a/api/src/main/java/org/bukkit/inventory/meta/BannerMeta.java b/api/src/main/java/org/bukkit/inventory/meta/BannerMeta.java new file mode 100644 index 000000000..4739d2ecc --- /dev/null +++ b/api/src/main/java/org/bukkit/inventory/meta/BannerMeta.java @@ -0,0 +1,89 @@ +package org.bukkit.inventory.meta; + +import java.util.List; +import org.bukkit.DyeColor; +import org.bukkit.block.banner.Pattern; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +public interface BannerMeta extends ItemMeta { + + /** + * Returns the base color for this banner + * + * @return the base color + * @deprecated banner color is now stored as the data value, not meta. + */ + @Deprecated + @Nullable + DyeColor getBaseColor(); + + /** + * Sets the base color for this banner + * + * @param color the base color + * @deprecated banner color is now stored as the data value, not meta. + */ + @Deprecated + void setBaseColor(@Nullable DyeColor color); + + /** + * Returns a list of patterns on this banner + * + * @return the patterns + */ + @NotNull + List getPatterns(); + + /** + * Sets the patterns used on this banner + * + * @param patterns the new list of patterns + */ + void setPatterns(@NotNull List patterns); + + /** + * Adds a new pattern on top of the existing + * patterns + * + * @param pattern the new pattern to add + */ + void addPattern(@NotNull Pattern pattern); + + /** + * Returns the pattern at the specified index + * + * @param i the index + * @return the pattern + * @throws IndexOutOfBoundsException when index is not in [0, numberOfPatterns()) range + */ + @NotNull + Pattern getPattern(int i); + + /** + * Removes the pattern at the specified index + * + * @param i the index + * @return the removed pattern + * @throws IndexOutOfBoundsException when index is not in [0, numberOfPatterns()) range + */ + @NotNull + Pattern removePattern(int i); + + /** + * Sets the pattern at the specified index + * + * @param i the index + * @param pattern the new pattern + * @throws IndexOutOfBoundsException when index is not in [0, numberOfPatterns()) range + */ + void setPattern(int i, @NotNull Pattern pattern); + + /** + * Returns the number of patterns on this + * banner + * + * @return the number of patterns + */ + int numberOfPatterns(); +} diff --git a/api/src/main/java/org/bukkit/inventory/meta/BlockStateMeta.java b/api/src/main/java/org/bukkit/inventory/meta/BlockStateMeta.java new file mode 100644 index 000000000..d71a4eaf1 --- /dev/null +++ b/api/src/main/java/org/bukkit/inventory/meta/BlockStateMeta.java @@ -0,0 +1,37 @@ + +package org.bukkit.inventory.meta; + +import org.bukkit.block.BlockState; +import org.jetbrains.annotations.NotNull; + +public interface BlockStateMeta extends ItemMeta { + + /** + * Returns whether the item has a block state currently + * attached to it. + * + * @return whether a block state is already attached + */ + boolean hasBlockState(); + + /** + * Returns the currently attached block state for this + * item or creates a new one if one doesn't exist. + * + * The state is a copy, it must be set back (or to another + * item) with {@link #setBlockState(org.bukkit.block.BlockState)} + * + * @return the attached state or a new state + */ + @NotNull + BlockState getBlockState(); + + /** + * Attaches a copy of the passed block state to the item. + * + * @param blockState the block state to attach to the block. + * @throws IllegalArgumentException if the blockState is null + * or invalid for this item. + */ + void setBlockState(@NotNull BlockState blockState); +} diff --git a/api/src/main/java/org/bukkit/inventory/meta/BookMeta.java b/api/src/main/java/org/bukkit/inventory/meta/BookMeta.java new file mode 100644 index 000000000..92c149a33 --- /dev/null +++ b/api/src/main/java/org/bukkit/inventory/meta/BookMeta.java @@ -0,0 +1,263 @@ +package org.bukkit.inventory.meta; + +import java.util.List; +import net.md_5.bungee.api.chat.BaseComponent; + +import org.bukkit.Material; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +/** + * Represents a book ({@link Material#WRITABLE_BOOK} or {@link + * Material#WRITTEN_BOOK}) that can have a title, an author, and pages. + */ +public interface BookMeta extends ItemMeta { + + /** + * Represents the generation (or level of copying) of a written book + */ + enum Generation { + /** + * Book written into a book-and-quill. Can be copied. (Default value) + */ + ORIGINAL, + /** + * Book that was copied from an original. Can be copied. + */ + COPY_OF_ORIGINAL, + /** + * Book that was copied from a copy of an original. Can't be copied. + */ + COPY_OF_COPY, + /** + * Unused; unobtainable by players. Can't be copied. + */ + TATTERED; + } + + /** + * Checks for the existence of a title in the book. + * + * @return true if the book has a title + */ + boolean hasTitle(); + + /** + * Gets the title of the book. + *

+ * Plugins should check that hasTitle() returns true before calling this + * method. + * + * @return the title of the book + */ + @Nullable + String getTitle(); + + /** + * Sets the title of the book. + *

+ * Limited to 16 characters. Removes title when given null. + * + * @param title the title to set + * @return true if the title was successfully set + */ + boolean setTitle(@Nullable String title); + + /** + * Checks for the existence of an author in the book. + * + * @return true if the book has an author + */ + boolean hasAuthor(); + + /** + * Gets the author of the book. + *

+ * Plugins should check that hasAuthor() returns true before calling this + * method. + * + * @return the author of the book + */ + @Nullable + String getAuthor(); + + /** + * Sets the author of the book. Removes author when given null. + * + * @param author the author to set + */ + void setAuthor(@Nullable String author); + + /** + * Checks for the existence of generation level in the book. + * + * @return true if the book has a generation level + */ + boolean hasGeneration(); + + /** + * Gets the generation of the book. + *

+ * Plugins should check that hasGeneration() returns true before calling + * this method. + * + * @return the generation of the book + */ + @Nullable + Generation getGeneration(); + + /** + * Sets the generation of the book. Removes generation when given null. + * + * @param generation the generation to set + */ + void setGeneration(@Nullable Generation generation); + + /** + * Checks for the existence of pages in the book. + * + * @return true if the book has pages + */ + boolean hasPages(); + + /** + * Gets the specified page in the book. The given page must exist. + *

+ * Pages are 1-indexed. + * + * @param page the page number to get, in range [1, getPageCount()] + * @return the page from the book + */ + @NotNull + String getPage(int page); + + /** + * Sets the specified page in the book. Pages of the book must be + * contiguous. + *

+ * The data can be up to 256 characters in length, additional characters + * are truncated. + *

+ * Pages are 1-indexed. + * + * @param page the page number to set, in range [1, getPageCount()] + * @param data the data to set for that page + */ + void setPage(int page, @NotNull String data); + + /** + * Gets all the pages in the book. + * + * @return list of all the pages in the book + */ + @NotNull + List getPages(); + + /** + * Clears the existing book pages, and sets the book to use the provided + * pages. Maximum 50 pages with 256 characters per page. + * + * @param pages A list of pages to set the book to use + */ + void setPages(@NotNull List pages); + + /** + * Clears the existing book pages, and sets the book to use the provided + * pages. Maximum 50 pages with 256 characters per page. + * + * @param pages A list of strings, each being a page + */ + void setPages(@NotNull String... pages); + + /** + * Adds new pages to the end of the book. Up to a maximum of 50 pages with + * 256 characters per page. + * + * @param pages A list of strings, each being a page + */ + void addPage(@NotNull String... pages); + + /** + * Gets the number of pages in the book. + * + * @return the number of pages in the book + */ + int getPageCount(); + + @NotNull + BookMeta clone(); + + // Spigot start + public class Spigot extends ItemMeta.Spigot { + + /** + * Gets the specified page in the book. The given page must exist. + * + * @param page the page number to get + * @return the page from the book + */ + @NotNull + public BaseComponent[] getPage(int page) { + throw new UnsupportedOperationException("Not supported yet."); + } + + /** + * Sets the specified page in the book. Pages of the book must be + * contiguous. + *

+ * The data can be up to 256 characters in length, additional characters + * are truncated. + * + * @param page the page number to set + * @param data the data to set for that page + */ + public void setPage(int page, @Nullable BaseComponent... data) { + throw new UnsupportedOperationException("Not supported yet."); + } + + /** + * Gets all the pages in the book. + * + * @return list of all the pages in the book + */ + @NotNull + public List getPages() { + throw new UnsupportedOperationException("Not supported yet."); + } + + /** + * Clears the existing book pages, and sets the book to use the provided + * pages. Maximum 50 pages with 256 characters per page. + * + * @param pages A list of pages to set the book to use + */ + public void setPages(@NotNull List pages) { + throw new UnsupportedOperationException("Not supported yet."); + } + + /** + * Clears the existing book pages, and sets the book to use the provided + * pages. Maximum 50 pages with 256 characters per page. + * + * @param pages A list of component arrays, each being a page + */ + public void setPages(@NotNull BaseComponent[]... pages) { + throw new UnsupportedOperationException("Not supported yet."); + } + + /** + * Adds new pages to the end of the book. Up to a maximum of 50 pages + * with 256 characters per page. + * + * @param pages A list of component arrays, each being a page + */ + public void addPage(@NotNull BaseComponent[]... pages) { + throw new UnsupportedOperationException("Not supported yet."); + } + } + + @NotNull + @Override + Spigot spigot(); + // Spigot end +} diff --git a/api/src/main/java/org/bukkit/inventory/meta/Damageable.java b/api/src/main/java/org/bukkit/inventory/meta/Damageable.java new file mode 100644 index 000000000..6392362b9 --- /dev/null +++ b/api/src/main/java/org/bukkit/inventory/meta/Damageable.java @@ -0,0 +1,33 @@ +package org.bukkit.inventory.meta; + +import org.jetbrains.annotations.NotNull; + +/** + * Represents an item that has durability and can take damage. + */ +public interface Damageable { + + /** + * Checks to see if this item has damage + * + * @return true if this has damage + */ + boolean hasDamage(); + + /** + * Gets the damage + * + * @return the damage + */ + int getDamage(); + + /** + * Sets the damage + * + * @param damage item damage + */ + void setDamage(int damage); + + @NotNull + Damageable clone(); +} diff --git a/api/src/main/java/org/bukkit/inventory/meta/EnchantmentStorageMeta.java b/api/src/main/java/org/bukkit/inventory/meta/EnchantmentStorageMeta.java new file mode 100644 index 000000000..c44a227e0 --- /dev/null +++ b/api/src/main/java/org/bukkit/inventory/meta/EnchantmentStorageMeta.java @@ -0,0 +1,82 @@ +package org.bukkit.inventory.meta; + +import java.util.Map; + +import org.bukkit.Material; +import org.bukkit.enchantments.Enchantment; +import org.jetbrains.annotations.NotNull; + +/** + * EnchantmentMeta is specific to items that can store enchantments, as + * opposed to being enchanted. {@link Material#ENCHANTED_BOOK} is an example + * of an item with enchantment storage. + */ +public interface EnchantmentStorageMeta extends ItemMeta { + + /** + * Checks for the existence of any stored enchantments. + * + * @return true if an enchantment exists on this meta + */ + boolean hasStoredEnchants(); + + /** + * Checks for storage of the specified enchantment. + * + * @param ench enchantment to check + * @return true if this enchantment is stored in this meta + */ + boolean hasStoredEnchant(@NotNull Enchantment ench); + + /** + * Checks for the level of the stored enchantment. + * + * @param ench enchantment to check + * @return The level that the specified stored enchantment has, or 0 if + * none + */ + int getStoredEnchantLevel(@NotNull Enchantment ench); + + /** + * Gets a copy the stored enchantments in this ItemMeta. + * + * @return An immutable copy of the stored enchantments + */ + @NotNull + Map getStoredEnchants(); + + /** + * Stores the specified enchantment in this item meta. + * + * @param ench Enchantment to store + * @param level Level for the enchantment + * @param ignoreLevelRestriction this indicates the enchantment should be + * applied, ignoring the level limit + * @return true if the item meta changed as a result of this call, false + * otherwise + * @throws IllegalArgumentException if enchantment is null + */ + boolean addStoredEnchant(@NotNull Enchantment ench, int level, boolean ignoreLevelRestriction); + + /** + * Remove the specified stored enchantment from this item meta. + * + * @param ench Enchantment to remove + * @return true if the item meta changed as a result of this call, false + * otherwise + * @throws IllegalArgumentException if enchantment is null + */ + boolean removeStoredEnchant(@NotNull Enchantment ench) throws IllegalArgumentException; + + /** + * Checks if the specified enchantment conflicts with any enchantments in + * this ItemMeta. + * + * @param ench enchantment to test + * @return true if the enchantment conflicts, false otherwise + */ + boolean hasConflictingStoredEnchant(@NotNull Enchantment ench); + + @NotNull + EnchantmentStorageMeta clone(); +} diff --git a/api/src/main/java/org/bukkit/inventory/meta/FireworkEffectMeta.java b/api/src/main/java/org/bukkit/inventory/meta/FireworkEffectMeta.java new file mode 100644 index 000000000..e50a029df --- /dev/null +++ b/api/src/main/java/org/bukkit/inventory/meta/FireworkEffectMeta.java @@ -0,0 +1,38 @@ +package org.bukkit.inventory.meta; + +import org.bukkit.FireworkEffect; +import org.bukkit.Material; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +/** + * Represents a meta that can store a single FireworkEffect. An example + * includes {@link Material#FIREWORK_STAR}. + */ +public interface FireworkEffectMeta extends ItemMeta { + + /** + * Sets the firework effect for this meta. + * + * @param effect the effect to set, or null to indicate none. + */ + void setEffect(@Nullable FireworkEffect effect); + + /** + * Checks if this meta has an effect. + * + * @return true if this meta has an effect, false otherwise + */ + boolean hasEffect(); + + /** + * Gets the firework effect for this meta. + * + * @return the current effect, or null if none + */ + @Nullable + FireworkEffect getEffect(); + + @NotNull + FireworkEffectMeta clone(); +} diff --git a/api/src/main/java/org/bukkit/inventory/meta/FireworkMeta.java b/api/src/main/java/org/bukkit/inventory/meta/FireworkMeta.java new file mode 100644 index 000000000..f1fe2b074 --- /dev/null +++ b/api/src/main/java/org/bukkit/inventory/meta/FireworkMeta.java @@ -0,0 +1,97 @@ +package org.bukkit.inventory.meta; + +import java.util.List; + +import org.bukkit.FireworkEffect; +import org.bukkit.Material; +import org.jetbrains.annotations.NotNull; + +/** + * Represents a {@link Material#FIREWORK_ROCKET} and its effects. + */ +public interface FireworkMeta extends ItemMeta { + + /** + * Add another effect to this firework. + * + * @param effect The firework effect to add + * @throws IllegalArgumentException If effect is null + */ + void addEffect(@NotNull FireworkEffect effect) throws IllegalArgumentException; + + /** + * Add several effects to this firework. + * + * @param effects The firework effects to add + * @throws IllegalArgumentException If effects is null + * @throws IllegalArgumentException If any effect is null (may be thrown + * after changes have occurred) + */ + void addEffects(@NotNull FireworkEffect... effects) throws IllegalArgumentException; + + /** + * Add several firework effects to this firework. + * + * @param effects An iterable object whose iterator yields the desired + * firework effects + * @throws IllegalArgumentException If effects is null + * @throws IllegalArgumentException If any effect is null (may be thrown + * after changes have occurred) + */ + void addEffects(@NotNull Iterable effects) throws IllegalArgumentException; + + /** + * Get the effects in this firework. + * + * @return An immutable list of the firework effects + */ + @NotNull + List getEffects(); + + /** + * Get the number of effects in this firework. + * + * @return The number of effects + */ + int getEffectsSize(); + + /** + * Remove an effect from this firework. + * + * @param index The index of the effect to remove + * @throws IndexOutOfBoundsException If index {@literal < 0 or index >} {@link + * #getEffectsSize()} + */ + void removeEffect(int index) throws IndexOutOfBoundsException; + + /** + * Remove all effects from this firework. + */ + void clearEffects(); + + /** + * Get whether this firework has any effects. + * + * @return true if it has effects, false if there are no effects + */ + boolean hasEffects(); + + /** + * Gets the approximate height the firework will fly. + * + * @return approximate flight height of the firework. + */ + int getPower(); + + /** + * Sets the approximate power of the firework. Each level of power is half + * a second of flight time. + * + * @param power the power of the firework, from 0-128 + * @throws IllegalArgumentException if {@literal height<0 or height>128} + */ + void setPower(int power) throws IllegalArgumentException; + + @NotNull + FireworkMeta clone(); +} diff --git a/api/src/main/java/org/bukkit/inventory/meta/ItemMeta.java b/api/src/main/java/org/bukkit/inventory/meta/ItemMeta.java new file mode 100644 index 000000000..3ed7ea06a --- /dev/null +++ b/api/src/main/java/org/bukkit/inventory/meta/ItemMeta.java @@ -0,0 +1,461 @@ +package org.bukkit.inventory.meta; + +import java.util.Collection; +import java.util.List; +import java.util.Map; +import java.util.Set; + +import com.google.common.collect.Multimap; +import org.bukkit.attribute.Attribute; +import org.bukkit.attribute.AttributeModifier; +import org.bukkit.configuration.serialization.ConfigurationSerializable; +import org.bukkit.enchantments.Enchantment; +import org.bukkit.inventory.EquipmentSlot; +import org.bukkit.inventory.ItemFlag; +import org.bukkit.inventory.meta.tags.CustomItemTagContainer; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +/** + * This type represents the storage mechanism for auxiliary item data. + *

+ * An implementation will handle the creation and application for ItemMeta. + * This class should not be implemented by a plugin in a live environment. + */ +public interface ItemMeta extends Cloneable, ConfigurationSerializable { + + /** + * Checks for existence of a display name. + * + * @return true if this has a display name + */ + boolean hasDisplayName(); + + /** + * Gets the display name that is set. + *

+ * Plugins should check that hasDisplayName() returns true + * before calling this method. + * + * @return the display name that is set + */ + @NotNull + String getDisplayName(); + + /** + * Sets the display name. + * + * @param name the name to set + */ + void setDisplayName(@Nullable String name); + + /** + * Checks for existence of a localized name. + * + * @return true if this has a localized name + */ + boolean hasLocalizedName(); + + /** + * Gets the localized display name that is set. + *

+ * Plugins should check that hasLocalizedName() returns true + * before calling this method. + * + * @return the localized name that is set + */ + @NotNull + String getLocalizedName(); + + /** + * Sets the localized name. + * + * @param name the name to set + */ + void setLocalizedName(@Nullable String name); + + /** + * Checks for existence of lore. + * + * @return true if this has lore + */ + boolean hasLore(); + + /** + * Gets the lore that is set. + *

+ * Plugins should check if hasLore() returns true before + * calling this method. + * + * @return a list of lore that is set + */ + @Nullable + List getLore(); + + /** + * Sets the lore for this item. + * Removes lore when given null. + * + * @param lore the lore that will be set + */ + void setLore(@Nullable List lore); + + /** + * Checks for the existence of any enchantments. + * + * @return true if an enchantment exists on this meta + */ + boolean hasEnchants(); + + /** + * Checks for existence of the specified enchantment. + * + * @param ench enchantment to check + * @return true if this enchantment exists for this meta + */ + boolean hasEnchant(@NotNull Enchantment ench); + + /** + * Checks for the level of the specified enchantment. + * + * @param ench enchantment to check + * @return The level that the specified enchantment has, or 0 if none + */ + int getEnchantLevel(@NotNull Enchantment ench); + + /** + * Returns a copy the enchantments in this ItemMeta.
+ * Returns an empty map if none. + * + * @return An immutable copy of the enchantments + */ + @NotNull + Map getEnchants(); + + /** + * Adds the specified enchantment to this item meta. + * + * @param ench Enchantment to add + * @param level Level for the enchantment + * @param ignoreLevelRestriction this indicates the enchantment should be + * applied, ignoring the level limit + * @return true if the item meta changed as a result of this call, false + * otherwise + */ + boolean addEnchant(@NotNull Enchantment ench, int level, boolean ignoreLevelRestriction); + + /** + * Removes the specified enchantment from this item meta. + * + * @param ench Enchantment to remove + * @return true if the item meta changed as a result of this call, false + * otherwise + */ + boolean removeEnchant(@NotNull Enchantment ench); + + /** + * Checks if the specified enchantment conflicts with any enchantments in + * this ItemMeta. + * + * @param ench enchantment to test + * @return true if the enchantment conflicts, false otherwise + */ + boolean hasConflictingEnchant(@NotNull Enchantment ench); + + /** + * Set itemflags which should be ignored when rendering a ItemStack in the Client. This Method does silently ignore double set itemFlags. + * + * @param itemFlags The hideflags which shouldn't be rendered + */ + void addItemFlags(@NotNull ItemFlag... itemFlags); + + /** + * Remove specific set of itemFlags. This tells the Client it should render it again. This Method does silently ignore double removed itemFlags. + * + * @param itemFlags Hideflags which should be removed + */ + void removeItemFlags(@NotNull ItemFlag... itemFlags); + + /** + * Get current set itemFlags. The collection returned is unmodifiable. + * + * @return A set of all itemFlags set + */ + @NotNull + Set getItemFlags(); + + /** + * Check if the specified flag is present on this item. + * + * @param flag the flag to check + * @return if it is present + */ + boolean hasItemFlag(@NotNull ItemFlag flag); + + /** + * Return if the unbreakable tag is true. An unbreakable item will not lose + * durability. + * + * @return true if the unbreakable tag is true + */ + boolean isUnbreakable(); + + /** + * Sets the unbreakable tag. An unbreakable item will not lose durability. + * + * @param unbreakable true if set unbreakable + */ + void setUnbreakable(boolean unbreakable); + + /** + * Checks for the existence of any AttributeModifiers. + * + * @return true if any AttributeModifiers exist + */ + boolean hasAttributeModifiers(); + + /** + * Return an immutable copy of all Attributes and + * their modifiers in this ItemMeta.
+ * Returns null if none exist. + * + * @return an immutable {@link Multimap} of Attributes + * and their AttributeModifiers, or null if none exist + */ + @Nullable + Multimap getAttributeModifiers(); + + /** + * Return an immutable copy of all {@link Attribute}s and their + * {@link AttributeModifier}s for a given {@link EquipmentSlot}.
+ * Any {@link AttributeModifier} that does have have a given + * {@link EquipmentSlot} will be returned. This is because + * AttributeModifiers without a slot are active in any slot.
+ * If there are no attributes set for the given slot, an empty map + * will be returned. + * + * @param slot the {@link EquipmentSlot} to check + * @return the immutable {@link Multimap} with the + * respective Attributes and modifiers, or an empty map + * if no attributes are set. + */ + @NotNull + Multimap getAttributeModifiers(@NotNull EquipmentSlot slot); + + /** + * Return an immutable copy of all {@link AttributeModifier}s + * for a given {@link Attribute} + * + * @param attribute the {@link Attribute} + * @return an immutable collection of {@link AttributeModifier}s + * or null if no AttributeModifiers exist for the Attribute. + * @throws NullPointerException if Attribute is null + */ + @Nullable + Collection getAttributeModifiers(@NotNull Attribute attribute); + + /** + * Add an Attribute and it's Modifier. + * AttributeModifiers can now support {@link EquipmentSlot}s. + * If not set, the {@link AttributeModifier} will be active in ALL slots. + *
+ * Two {@link AttributeModifier}s that have the same {@link java.util.UUID} + * cannot exist on the same Attribute. + * + * @param attribute the {@link Attribute} to modify + * @param modifier the {@link AttributeModifier} specifying the modification + * @return true if the Attribute and AttributeModifier were + * successfully added + * @throws NullPointerException if Attribute is null + * @throws NullPointerException if AttributeModifier is null + * @throws IllegalArgumentException if AttributeModifier already exists + */ + boolean addAttributeModifier(@NotNull Attribute attribute, @NotNull AttributeModifier modifier); + + /** + * Set all {@link Attribute}s and their {@link AttributeModifier}s. + * To clear all currently set Attributes and AttributeModifiers use + * null or an empty Multimap. + * If not null nor empty, this will filter all entries that are not-null + * and add them to the ItemStack. + * + * @param attributeModifiers the new Multimap containing the Attributes + * and their AttributeModifiers + */ + void setAttributeModifiers(@Nullable Multimap attributeModifiers); + + /** + * Remove all {@link AttributeModifier}s associated with the given + * {@link Attribute}. + * This will return false if nothing was removed. + * + * @param attribute attribute to remove + * @return true if all modifiers were removed from a given + * Attribute. Returns false if no attributes were + * removed. + * @throws NullPointerException if Attribute is null + */ + boolean removeAttributeModifier(@NotNull Attribute attribute); + + /** + * Remove all {@link Attribute}s and {@link AttributeModifier}s for a + * given {@link EquipmentSlot}.
+ * If the given {@link EquipmentSlot} is null, this will remove all + * {@link AttributeModifier}s that do not have an EquipmentSlot set. + * + * @param slot the {@link EquipmentSlot} to clear all Attributes and + * their modifiers for + * @return true if all modifiers were removed that match the given + * EquipmentSlot. + */ + boolean removeAttributeModifier(@NotNull EquipmentSlot slot); + + /** + * Remove a specific {@link Attribute} and {@link AttributeModifier}. + * AttributeModifiers are matched according to their {@link java.util.UUID}. + * + * @param attribute the {@link Attribute} to remove + * @param modifier the {@link AttributeModifier} to remove + * @return if any attribute modifiers were remove + * + * @see AttributeModifier#getUniqueId() + * + * @throws NullPointerException if the Attribute is null + * @throws NullPointerException if the AttributeModifier is null + */ + boolean removeAttributeModifier(@NotNull Attribute attribute, @NotNull AttributeModifier modifier); + + /** + * Returns a public custom tag container capable of storing tags on the + * item. + * + * Those tags will be sent to the client with all of their content, so the + * client is capable of reading them. This will result in the player seeing + * a NBT Tag notification on the item. + * + * These tags can also be modified by the client once in creative mode + * + * @return the custom tag container + */ + @NotNull + CustomItemTagContainer getCustomTagContainer(); + + @SuppressWarnings("javadoc") + @NotNull + ItemMeta clone(); + + // Spigot start + public class Spigot + { + + /** + * Sets the unbreakable tag + * + * @param unbreakable true if set unbreakable + * @deprecated see {@link ItemMeta#setUnbreakable(boolean)} + */ + @Deprecated + public void setUnbreakable(boolean unbreakable) + { + throw new UnsupportedOperationException( "Not supported yet." ); + } + + /** + * Return if the unbreakable tag is true + * + * @return true if the unbreakable tag is true + * @deprecated see {@link ItemMeta#isUnbreakable()} + */ + @Deprecated + public boolean isUnbreakable() + { + throw new UnsupportedOperationException( "Not supported yet." ); + } + } + + @NotNull + Spigot spigot(); + // Spigot end + // Paper start - Add an API for CanPlaceOn and CanDestroy NBT values + /** + * Gets set of materials what given item can destroy in {@link org.bukkit.GameMode#ADVENTURE} + * + * @return Set of materials + * @deprecated Minecraft does not limit this to the material enum, Use {@link #getDestroyableKeys()} as a replacement + */ + @Deprecated + Set getCanDestroy(); + + /** + * Sets set of materials what given item can destroy in {@link org.bukkit.GameMode#ADVENTURE} + * + * @param canDestroy Set of materials + * @deprecated Minecraft does not limit this to the material enum, Use {@link #setDestroyableKeys(Collection)} as a replacement + */ + @Deprecated + void setCanDestroy(Set canDestroy); + + /** + * Gets set of materials where given item can be placed on in {@link org.bukkit.GameMode#ADVENTURE} + * + * @return Set of materials + * @deprecated Minecraft does not limit this to the material enum, Use {@link #getPlaceableKeys()} as a replacement + */ + @Deprecated + Set getCanPlaceOn(); + + /** + * Sets set of materials where given item can be placed on in {@link org.bukkit.GameMode#ADVENTURE} + * + * @param canPlaceOn Set of materials + * @deprecated Minecraft does not limit this to the material enum, Use {@link #setPlaceableKeys(Collection)} as a replacement + */ + @Deprecated + void setCanPlaceOn(Set canPlaceOn); + + /** + * Gets the collection of namespaced keys that the item can destroy in {@link org.bukkit.GameMode#ADVENTURE} + * + * @return Set of {@link com.destroystokyo.paper.Namespaced} + */ + @NotNull + Set getDestroyableKeys(); + + /** + * Sets the collection of namespaced keys that the item can destroy in {@link org.bukkit.GameMode#ADVENTURE} + * + * @param canDestroy Collection of {@link com.destroystokyo.paper.Namespaced} + */ + void setDestroyableKeys(@NotNull Collection canDestroy); + + /** + * Gets the collection of namespaced keys that the item can be placed on in {@link org.bukkit.GameMode#ADVENTURE} + * + * @return Set of {@link com.destroystokyo.paper.Namespaced} + */ + @NotNull + Set getPlaceableKeys(); + + /** + * Sets the set of namespaced keys that the item can be placed on in {@link org.bukkit.GameMode#ADVENTURE} + * + * @param canPlaceOn Collection of {@link com.destroystokyo.paper.Namespaced} + */ + @NotNull + void setPlaceableKeys(@NotNull Collection canPlaceOn); + + /** + * Checks for the existence of any keys that the item can be placed on + * + * @return true if this item has placeable keys + */ + boolean hasPlaceableKeys(); + + /** + * Checks for the existence of any keys that the item can destroy + * + * @return true if this item has destroyable keys + */ + boolean hasDestroyableKeys(); + // Paper end +} diff --git a/api/src/main/java/org/bukkit/inventory/meta/KnowledgeBookMeta.java b/api/src/main/java/org/bukkit/inventory/meta/KnowledgeBookMeta.java new file mode 100644 index 000000000..736c60c71 --- /dev/null +++ b/api/src/main/java/org/bukkit/inventory/meta/KnowledgeBookMeta.java @@ -0,0 +1,42 @@ +package org.bukkit.inventory.meta; + +import java.util.List; +import org.bukkit.NamespacedKey; +import org.jetbrains.annotations.NotNull; + +public interface KnowledgeBookMeta extends ItemMeta { + + /** + * Checks for the existence of recipes in the book. + * + * @return true if the book has recipes + */ + boolean hasRecipes(); + + /** + * Gets all the recipes in the book. + * + * @return list of all the recipes in the book + */ + @NotNull + List getRecipes(); + + /** + * Clears the existing book recipes, and sets the book to use the provided + * recipes. + * + * @param recipes A list of recipes to set the book to use + */ + void setRecipes(@NotNull List recipes); + + /** + * Adds new recipe to the end of the book. + * + * @param recipes A list of recipe keys + */ + void addRecipe(@NotNull NamespacedKey... recipes); + + @NotNull + @Override + KnowledgeBookMeta clone(); +} diff --git a/api/src/main/java/org/bukkit/inventory/meta/LeatherArmorMeta.java b/api/src/main/java/org/bukkit/inventory/meta/LeatherArmorMeta.java new file mode 100644 index 000000000..0b6779c6d --- /dev/null +++ b/api/src/main/java/org/bukkit/inventory/meta/LeatherArmorMeta.java @@ -0,0 +1,35 @@ +package org.bukkit.inventory.meta; + +import org.bukkit.Color; +import org.bukkit.Material; +import org.bukkit.inventory.ItemFactory; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +/** + * Represents leather armor ({@link Material#LEATHER_BOOTS}, {@link + * Material#LEATHER_CHESTPLATE}, {@link Material#LEATHER_HELMET}, or {@link + * Material#LEATHER_LEGGINGS}) that can be colored. + */ +public interface LeatherArmorMeta extends ItemMeta { + + /** + * Gets the color of the armor. If it has not been set otherwise, it will + * be {@link ItemFactory#getDefaultLeatherColor()}. + * + * @return the color of the armor, never null + */ + @NotNull + Color getColor(); + + /** + * Sets the color of the armor. + * + * @param color the color to set. Setting it to null is equivalent to + * setting it to {@link ItemFactory#getDefaultLeatherColor()}. + */ + void setColor(@Nullable Color color); + + @NotNull + LeatherArmorMeta clone(); +} diff --git a/api/src/main/java/org/bukkit/inventory/meta/MapMeta.java b/api/src/main/java/org/bukkit/inventory/meta/MapMeta.java new file mode 100644 index 000000000..c90ad2379 --- /dev/null +++ b/api/src/main/java/org/bukkit/inventory/meta/MapMeta.java @@ -0,0 +1,161 @@ +package org.bukkit.inventory.meta; + +import org.bukkit.Color; +import org.bukkit.UndefinedNullability; +import org.bukkit.map.MapView; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +/** + * Represents a map that can be scalable. + */ +public interface MapMeta extends ItemMeta { + + /** + * Checks for existence of a map ID number. + * + * @return true if this has a map ID number. + * @deprecated These methods are poor API: They rely on the caller to pass + * in an only an integer property, and have poorly defined implementation + * behavior if that integer is not a valid map (the current implementation + * for example will generate a new map with a different ID). The xxxMapView + * family of methods should be used instead. + * @see #hasMapView() + */ + @Deprecated + boolean hasMapId(); + + /** + * Gets the map ID that is set. This is used to determine what map is + * displayed. + *

+ * Plugins should check that hasMapId() returns true before + * calling this method. + * + * @return the map ID that is set + * @deprecated These methods are poor API: They rely on the caller to pass + * in an only an integer property, and have poorly defined implementation + * behavior if that integer is not a valid map (the current implementation + * for example will generate a new map with a different ID). The xxxMapView + * family of methods should be used instead. + * @see #getMapView() + */ + @Deprecated + int getMapId(); + + /** + * Sets the map ID. This is used to determine what map is displayed. + * + * @param id the map id to set + * @deprecated These methods are poor API: They rely on the caller to pass + * in an only an integer property, and have poorly defined implementation + * behavior if that integer is not a valid map (the current implementation + * for example will generate a new map with a different ID). The xxxMapView + * family of methods should be used instead. + * @see #setMapView(org.bukkit.map.MapView) + */ + @Deprecated + void setMapId(int id); + + /** + * Checks for existence of an associated map. + * + * @return true if this item has an associated map + */ + boolean hasMapView(); + + /** + * Gets the map view that is associated with this map item. + * + *

+ * Plugins should check that hasMapView() returns true before + * calling this method. + * + * @return the map view, or null if the item hasMapView(), but this map does + * not exist on the server + */ + @Nullable + MapView getMapView(); + + /** + * Sets the associated map. This is used to determine what map is displayed. + * + *

+ * The implementation may allow null to clear the associated map, but + * this is not required and is liable to generate a new (undefined) map when + * the item is first used. + * + * @param map the map to set + */ + void setMapView(@UndefinedNullability("implementation defined") MapView map); + + /** + * Checks to see if this map is scaling. + * + * @return true if this map is scaling + */ + boolean isScaling(); + + /** + * Sets if this map is scaling or not. + * + * @param value true to scale + */ + void setScaling(boolean value); + + /** + * Checks for existence of a location name. + * + * @return true if this has a location name + */ + boolean hasLocationName(); + + /** + * Gets the location name that is set. + *

+ * Plugins should check that hasLocationName() returns true + * before calling this method. + * + * @return the location name that is set + */ + @Nullable + String getLocationName(); + + /** + * Sets the location name. A custom map color will alter the display of the + * map in an inventory slot. + * + * @param name the name to set + */ + void setLocationName(@Nullable String name); + + /** + * Checks for existence of a map color. + * + * @return true if this has a custom map color + */ + boolean hasColor(); + + /** + * Gets the map color that is set. A custom map color will alter the display + * of the map in an inventory slot. + *

+ * Plugins should check that hasColor() returns true before + * calling this method. + * + * @return the map color that is set + */ + @Nullable + Color getColor(); + + /** + * Sets the map color. A custom map color will alter the display of the map + * in an inventory slot. + * + * @param color the color to set + */ + void setColor(@Nullable Color color); + + @NotNull + MapMeta clone(); +} diff --git a/api/src/main/java/org/bukkit/inventory/meta/PotionMeta.java b/api/src/main/java/org/bukkit/inventory/meta/PotionMeta.java new file mode 100644 index 000000000..f26ec165f --- /dev/null +++ b/api/src/main/java/org/bukkit/inventory/meta/PotionMeta.java @@ -0,0 +1,125 @@ +package org.bukkit.inventory.meta; + +import org.bukkit.Color; +import org.bukkit.potion.PotionEffect; +import org.bukkit.potion.PotionEffectType; +import org.bukkit.potion.PotionData; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +import java.util.List; + +/** + * Represents a potion or item that can have custom effects. + */ +public interface PotionMeta extends ItemMeta { + + /** + * Sets the underlying potion data + * + * @param data PotionData to set the base potion state to + */ + void setBasePotionData(@NotNull PotionData data); + + /** + * Returns the potion data about the base potion + * + * @return a PotionData object + */ + @NotNull + PotionData getBasePotionData(); + + /** + * Checks for the presence of custom potion effects. + * + * @return true if custom potion effects are applied + */ + boolean hasCustomEffects(); + + /** + * Gets an immutable list containing all custom potion effects applied to + * this potion. + *

+ * Plugins should check that hasCustomEffects() returns true before calling + * this method. + * + * @return the immutable list of custom potion effects + */ + @NotNull + List getCustomEffects(); + + /** + * Adds a custom potion effect to this potion. + * + * @param effect the potion effect to add + * @param overwrite true if any existing effect of the same type should be + * overwritten + * @return true if the potion meta changed as a result of this call + */ + boolean addCustomEffect(@NotNull PotionEffect effect, boolean overwrite); + + /** + * Removes a custom potion effect from this potion. + * + * @param type the potion effect type to remove + * @return true if the potion meta changed as a result of this call + */ + boolean removeCustomEffect(@NotNull PotionEffectType type); + + /** + * Checks for a specific custom potion effect type on this potion. + * + * @param type the potion effect type to check for + * @return true if the potion has this effect + */ + boolean hasCustomEffect(@NotNull PotionEffectType type); + + /** + * Moves a potion effect to the top of the potion effect list. + *

+ * This causes the client to display the potion effect in the potion's name. + * + * @param type the potion effect type to move + * @return true if the potion meta changed as a result of this call + * @deprecated use {@link org.bukkit.potion.PotionType#PotionType} + */ + @Deprecated + boolean setMainEffect(@NotNull PotionEffectType type); + + /** + * Removes all custom potion effects from this potion. + * + * @return true if the potion meta changed as a result of this call + */ + boolean clearCustomEffects(); + + /** + * Checks for existence of a potion color. + * + * @return true if this has a custom potion color + */ + boolean hasColor(); + + /** + * Gets the potion color that is set. A custom potion color will alter the + * display of the potion in an inventory slot. + *

+ * Plugins should check that hasColor() returns true before + * calling this method. + * + * @return the potion color that is set + */ + @Nullable + Color getColor(); + + /** + * Sets the potion color. A custom potion color will alter the display of + * the potion in an inventory slot. + * + * @param color the color to set + */ + void setColor(@Nullable Color color); + + @Override + PotionMeta clone(); +} diff --git a/api/src/main/java/org/bukkit/inventory/meta/Repairable.java b/api/src/main/java/org/bukkit/inventory/meta/Repairable.java new file mode 100644 index 000000000..0a1d543db --- /dev/null +++ b/api/src/main/java/org/bukkit/inventory/meta/Repairable.java @@ -0,0 +1,34 @@ +package org.bukkit.inventory.meta; + +import org.jetbrains.annotations.NotNull; + +/** + * Represents an item that can be repaired at an anvil. + */ +public interface Repairable { + + /** + * Checks to see if this has a repair penalty + * + * @return true if this has a repair penalty + */ + boolean hasRepairCost(); + + /** + * Gets the repair penalty + * + * @return the repair penalty + */ + int getRepairCost(); + + /** + * Sets the repair penalty + * + * @param cost repair penalty + */ + void setRepairCost(int cost); + + @SuppressWarnings("javadoc") + @NotNull + Repairable clone(); +} diff --git a/api/src/main/java/org/bukkit/inventory/meta/SkullMeta.java b/api/src/main/java/org/bukkit/inventory/meta/SkullMeta.java new file mode 100644 index 000000000..43cdc4c66 --- /dev/null +++ b/api/src/main/java/org/bukkit/inventory/meta/SkullMeta.java @@ -0,0 +1,76 @@ +package org.bukkit.inventory.meta; + +import com.destroystokyo.paper.profile.PlayerProfile; +import org.bukkit.OfflinePlayer; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + + +/** + * Represents a skull that can have an owner. + */ +public interface SkullMeta extends ItemMeta { + + /** + * Gets the owner of the skull. + * + * @return the owner if the skull + * @deprecated see {@link #setOwningPlayer(org.bukkit.OfflinePlayer)}. + */ + @Deprecated + @Nullable + String getOwner(); + + /** + * Checks to see if the skull has an owner. + * + * @return true if the skull has an owner + */ + boolean hasOwner(); + + /** + * Sets the owner of the skull. + * + * @param owner the new owner of the skull + * @return true if the owner was successfully set + * @deprecated see {@link #setOwningPlayer(org.bukkit.OfflinePlayer)}. + */ + @Deprecated + boolean setOwner(@Nullable String owner); + + // Paper start + /** + * Sets this skull to use the supplied Player Profile, which can include textures already prefilled. + * @param profile The profile to set this Skull to use, or null to clear owner + */ + void setPlayerProfile(@Nullable PlayerProfile profile); + + /** + * If the skull has an owner, per {@link #hasOwner()}, return the owners {@link PlayerProfile} + * @return The profile of the owner, if set + */ + @Nullable PlayerProfile getPlayerProfile(); + // Paper end + + /** + * Gets the owner of the skull. + * + * @return the owner if the skull + */ + @Nullable + OfflinePlayer getOwningPlayer(); + + /** + * Sets the owner of the skull. + *

+ * Plugins should check that hasOwner() returns true before calling this + * plugin. + * + * @param owner the new owner of the skull + * @return true if the owner was successfully set + */ + boolean setOwningPlayer(@Nullable OfflinePlayer owner); + + @NotNull + SkullMeta clone(); +} diff --git a/api/src/main/java/org/bukkit/inventory/meta/SpawnEggMeta.java b/api/src/main/java/org/bukkit/inventory/meta/SpawnEggMeta.java new file mode 100644 index 000000000..9ae84de43 --- /dev/null +++ b/api/src/main/java/org/bukkit/inventory/meta/SpawnEggMeta.java @@ -0,0 +1,36 @@ +package org.bukkit.inventory.meta; + +import org.bukkit.entity.EntityType; +import org.jetbrains.annotations.Contract; +import org.jetbrains.annotations.NotNull; + +/** + * Represents a spawn egg and it's spawned type. + */ +public interface SpawnEggMeta extends ItemMeta { + + /** + * Get the type of entity this egg will spawn. + * + * @return The entity type. May be null for implementation specific default. + * @deprecated different types are different items + */ + @Deprecated + @Contract("-> fail") + EntityType getSpawnedType(); + + /** + * Set the type of entity this egg will spawn. + * + * @param type The entity type. May be null for implementation specific + * default. + * @deprecated different types are different items + */ + @Deprecated + @Contract("_ -> fail") + void setSpawnedType(EntityType type); + + @NotNull + @Override + SpawnEggMeta clone(); +} diff --git a/api/src/main/java/org/bukkit/inventory/meta/TropicalFishBucketMeta.java b/api/src/main/java/org/bukkit/inventory/meta/TropicalFishBucketMeta.java new file mode 100644 index 000000000..86db7cfa2 --- /dev/null +++ b/api/src/main/java/org/bukkit/inventory/meta/TropicalFishBucketMeta.java @@ -0,0 +1,85 @@ +package org.bukkit.inventory.meta; + +import org.bukkit.DyeColor; +import org.bukkit.entity.TropicalFish; +import org.jetbrains.annotations.NotNull; + +/** + * Represents a bucket of tropical fish. + */ +public interface TropicalFishBucketMeta extends ItemMeta { + + /** + * Gets the color of the fish's pattern. + *

+ * Plugins should check that hasVariant() returns true before + * calling this method. + * + * @return pattern color + */ + @NotNull + DyeColor getPatternColor(); + + /** + * Sets the color of the fish's pattern. + *

+ * Setting this when hasVariant() returns false will initialize + * all other values to unspecified defaults. + * + * @param color pattern color + */ + void setPatternColor(@NotNull DyeColor color); + + /** + * Gets the color of the fish's body. + *

+ * Plugins should check that hasVariant() returns true before + * calling this method. + * + * @return pattern color + */ + @NotNull + DyeColor getBodyColor(); + + /** + * Sets the color of the fish's body. + *

+ * Setting this when hasVariant() returns false will initialize + * all other values to unspecified defaults. + * + * @param color body color + */ + void setBodyColor(@NotNull DyeColor color); + + /** + * Gets the fish's pattern. + *

+ * Plugins should check that hasVariant() returns true before + * calling this method. + * + * @return pattern + */ + @NotNull + TropicalFish.Pattern getPattern(); + + /** + * Sets the fish's pattern. + *

+ * Setting this when hasVariant() returns false will initialize + * all other values to unspecified defaults. + * + * @param pattern new pattern + */ + void setPattern(@NotNull TropicalFish.Pattern pattern); + + /** + * Checks for existence of a variant tag indicating a specific fish will be + * spawned. + * + * @return if there is a variant + */ + boolean hasVariant(); + + @NotNull + TropicalFishBucketMeta clone(); +} diff --git a/api/src/main/java/org/bukkit/inventory/meta/tags/CustomItemTagContainer.java b/api/src/main/java/org/bukkit/inventory/meta/tags/CustomItemTagContainer.java new file mode 100644 index 000000000..e3b77998f --- /dev/null +++ b/api/src/main/java/org/bukkit/inventory/meta/tags/CustomItemTagContainer.java @@ -0,0 +1,106 @@ +package org.bukkit.inventory.meta.tags; + +import org.bukkit.NamespacedKey; +import org.bukkit.inventory.meta.ItemMeta; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +/** + * This interface represents a map like object, capable of storing custom tags + * in it. + */ +public interface CustomItemTagContainer { + + /** + * Stores a custom value on the {@link ItemMeta}. + * + * This API cannot be used to manipulate minecraft tags, as the values will + * be stored using your namespace. This method will override any existing + * value the meta may have stored under the provided key. + * + * @param key the key this value will be stored under + * @param type the type this item tag uses + * @param value the value stored in the tag + * @param the generic java type of the tag value + * @param the generic type of the object to store + * @throws NullPointerException if the key is null + * @throws NullPointerException if the type is null + * @throws NullPointerException if the value is null. Removing a custom tag + * should be done using {@link #removeCustomTag(org.bukkit.NamespacedKey)} + * @throws IllegalArgumentException if no suitable adapter will be found for + * the {@link ItemTagType#getPrimitiveType()} + */ + void setCustomTag(@NotNull NamespacedKey key, @NotNull ItemTagType type, @NotNull Z value); + + /** + * Returns if the item meta has a custom tag registered matching the + * provided parameters. + * + * This method will only return if the found value has the same primitive + * data type as the provided key. + * + * Storing a value using a custom {@link ItemTagType} implementation will + * not store the complex data type. Therefore storing a UUID (by storing a + * byte[]) will match hasCustomTag("key" , {@link ItemTagType#BYTE_ARRAY}). + * Likewise a stored byte[] will always match your UUID {@link ItemTagType} + * even if it is not 16 bytes long. + * + * This method is only usable for custom object keys. Overwriting existing + * tags, like the the display name, will not work as the values are stored + * using your namespace. + * + * @param key the key the value is stored under + * @param type the type which primitive storage type has to match the value + * @param the generic type of the stored primitive + * @param the generic type of the eventually created complex object + * @return if a value + * @throws NullPointerException if the key to look up is null + * @throws NullPointerException if the type to cast the found object to is + * null + */ + boolean hasCustomTag(@NotNull NamespacedKey key, @NotNull ItemTagType type); + + /** + * Returns the custom tag's value that is stored on the item. + * + * @param key the key to look up in the custom tag map + * @param type the type the value must have and will be casted to + * @param the generic type of the stored primitive + * @param the generic type of the eventually created complex object + * @return the value or {@code null} if no value was mapped under the given + * value + * @throws NullPointerException if the key to look up is null + * @throws NullPointerException if the type to cast the found object to is + * null + * @throws IllegalArgumentException if the value exists under the given key, + * but cannot be access using the given type + * @throws IllegalArgumentException if no suitable adapter will be found for + * the {@link ItemTagType#getPrimitiveType()} + */ + @Nullable + Z getCustomTag(@NotNull NamespacedKey key, @NotNull ItemTagType type); + + /** + * Removes a custom key from the item meta. + * + * @param key the key + * @throws NullPointerException if the provided key is null + */ + void removeCustomTag(@NotNull NamespacedKey key); + + /** + * Returns if the container instance is empty, therefore has no entries + * inside it. + * + * @return the boolean + */ + boolean isEmpty(); + + /** + * Returns the adapter context this tag container uses. + * + * @return the tag context + */ + @NotNull + ItemTagAdapterContext getAdapterContext(); +} diff --git a/api/src/main/java/org/bukkit/inventory/meta/tags/ItemTagAdapterContext.java b/api/src/main/java/org/bukkit/inventory/meta/tags/ItemTagAdapterContext.java new file mode 100644 index 000000000..9de37daa3 --- /dev/null +++ b/api/src/main/java/org/bukkit/inventory/meta/tags/ItemTagAdapterContext.java @@ -0,0 +1,18 @@ +package org.bukkit.inventory.meta.tags; + +import org.jetbrains.annotations.NotNull; + +/** + * This interface represents the context in which the {@link ItemTagType} can + * serialize and deserialize the passed values. + */ +public interface ItemTagAdapterContext { + + /** + * Creates a new and empty tag container instance. + * + * @return the fresh container instance + */ + @NotNull + CustomItemTagContainer newTagContainer(); +} diff --git a/api/src/main/java/org/bukkit/inventory/meta/tags/ItemTagType.java b/api/src/main/java/org/bukkit/inventory/meta/tags/ItemTagType.java new file mode 100644 index 000000000..eab84fe86 --- /dev/null +++ b/api/src/main/java/org/bukkit/inventory/meta/tags/ItemTagType.java @@ -0,0 +1,153 @@ +package org.bukkit.inventory.meta.tags; + +import org.jetbrains.annotations.NotNull; + +/** + * This class represents an enum with a generic content type. It defines the + * types a custom item tag can have. + *

+ * This interface can be used to create your own custom {@link ItemTagType} with + * different complex types. This may be useful for the likes of a + * UUIDItemTagType: + *

+ * {@code
+ * public class UUIDItemTagType implements ItemTagType {
+ *
+ *         {@literal @Override}
+ *         public Class getPrimitiveType() {
+ *             return byte[].class;
+ *         }
+ *
+ *         {@literal @Override}
+ *         public Class getComplexType() {
+ *             return UUID.class;
+ *         }
+ *
+ *         {@literal @Override}
+ *         public byte[] toPrimitive(UUID complex, ItemTagAdapterContext context) {
+ *             ByteBuffer bb = ByteBuffer.wrap(new byte[16]);
+ *             bb.putLong(complex.getMostSignificantBits());
+ *             bb.putLong(complex.getLeastSignificantBits());
+ *             return bb.array();
+ *         }
+ *
+ *         {@literal @Override}
+ *         public UUID fromPrimitive(byte[] primitive, ItemTagAdapterContext context) {
+ *             ByteBuffer bb = ByteBuffer.wrap(primitive);
+ *             long firstLong = bb.getLong();
+ *             long secondLong = bb.getLong();
+ *             return new UUID(firstLong, secondLong);
+ *         }
+ *     }}
+ * + * @param the primary object type that is stored in the given tag + * @param the retrieved object type when applying this item tag type + */ +public interface ItemTagType { + + /* + The primitive one value types. + */ + ItemTagType BYTE = new PrimitiveTagType<>(Byte.class); + ItemTagType SHORT = new PrimitiveTagType<>(Short.class); + ItemTagType INTEGER = new PrimitiveTagType<>(Integer.class); + ItemTagType LONG = new PrimitiveTagType<>(Long.class); + ItemTagType FLOAT = new PrimitiveTagType<>(Float.class); + ItemTagType DOUBLE = new PrimitiveTagType<>(Double.class); + + /* + String. + */ + ItemTagType STRING = new PrimitiveTagType<>(String.class); + + /* + Primitive Arrays. + */ + ItemTagType BYTE_ARRAY = new PrimitiveTagType<>(byte[].class); + ItemTagType INTEGER_ARRAY = new PrimitiveTagType<>(int[].class); + ItemTagType LONG_ARRAY = new PrimitiveTagType<>(long[].class); + + /* + Nested TagContainer. + */ + ItemTagType TAG_CONTAINER = new PrimitiveTagType<>(CustomItemTagContainer.class); + + /** + * Returns the primitive data type of this tag. + * + * @return the class + */ + @NotNull + Class getPrimitiveType(); + + /** + * Returns the complex object type the primitive value resembles. + * + * @return the class type + */ + @NotNull + Class getComplexType(); + + /** + * Returns the primitive data that resembles the complex object passed to + * this method. + * + * @param complex the complex object instance + * @param context the context this operation is running in + * @return the primitive value + */ + @NotNull + T toPrimitive(@NotNull Z complex, @NotNull ItemTagAdapterContext context); + + /** + * Creates a complex object based of the passed primitive value + * + * @param primitive the primitive value + * @param context the context this operation is running in + * @return the complex object instance + */ + @NotNull + Z fromPrimitive(@NotNull T primitive, @NotNull ItemTagAdapterContext context); + + /** + * A default implementation that simply exists to pass on the retrieved or + * inserted value to the next layer. + * + * This implementation does not add any kind of logic, but is used to + * provide default implementations for the primitive types. + * + * @param the generic type of the primitive objects + */ + class PrimitiveTagType implements ItemTagType { + + private final Class primitiveType; + + PrimitiveTagType(@NotNull Class primitiveType) { + this.primitiveType = primitiveType; + } + + @NotNull + @Override + public Class getPrimitiveType() { + return primitiveType; + } + + @NotNull + @Override + public Class getComplexType() { + return primitiveType; + } + + @NotNull + @Override + public T toPrimitive(@NotNull T complex, @NotNull ItemTagAdapterContext context) { + return complex; + } + + @NotNull + @Override + public T fromPrimitive(@NotNull T primitive, @NotNull ItemTagAdapterContext context) { + return primitive; + } + } +} diff --git a/api/src/main/java/org/bukkit/loot/LootContext.java b/api/src/main/java/org/bukkit/loot/LootContext.java new file mode 100644 index 000000000..e307f9fc4 --- /dev/null +++ b/api/src/main/java/org/bukkit/loot/LootContext.java @@ -0,0 +1,178 @@ +package org.bukkit.loot; + +import org.apache.commons.lang.Validate; +import org.bukkit.Location; +import org.bukkit.entity.Entity; +import org.bukkit.entity.HumanEntity; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +/** + * Represents additional information a {@link LootTable} can use to modify it's + * generated loot. + */ +public final class LootContext { + + public static final int DEFAULT_LOOT_MODIFIER = -1; + + private final Location location; + private final float luck; + private final int lootingModifier; + private final Entity lootedEntity; + private final HumanEntity killer; + + private LootContext(@NotNull Location location, float luck, int lootingModifier, @Nullable Entity lootedEntity, @Nullable HumanEntity killer) { + Validate.notNull(location, "LootContext location cannot be null"); + Validate.notNull(location.getWorld(), "LootContext World cannot be null"); + this.location = location; + this.luck = luck; + this.lootingModifier = lootingModifier; + this.lootedEntity = lootedEntity; + this.killer = killer; + } + + /** + * The {@link Location} to store where the loot will be generated. + * + * @return the Location of where the loot will be generated + */ + @NotNull + public Location getLocation() { + return location; + } + + /** + * Represents the {@link org.bukkit.potion.PotionEffectType#LUCK} that an + * entity can have. The higher the value the better chance of receiving more + * loot. + * + * @return luck + */ + public float getLuck() { + return luck; + } + + /** + * Represents the + * {@link org.bukkit.enchantments.Enchantment#LOOT_BONUS_MOBS} the + * {@link #getKiller()} entity has on their equipped item. + * + * This value is only set via + * {@link LootContext.Builder#lootingModifier(int)}. If not set, the + * {@link #getKiller()} entity's looting level will be used instead. + * + * @return the looting level + */ + public int getLootingModifier() { + return lootingModifier; + } + + /** + * Get the {@link Entity} that was killed. Can be null. + * + * @return the looted entity or null + */ + @Nullable + public Entity getLootedEntity() { + return lootedEntity; + } + + /** + * Get the {@link HumanEntity} who killed the {@link #getLootedEntity()}. + * Can be null. + * + * @return the killer entity, or null. + */ + @Nullable + public HumanEntity getKiller() { + return killer; + } + + /** + * Utility class to make building {@link LootContext} easier. The only + * required argument is {@link Location} with a valid (non-null) + * {@link org.bukkit.World}. + */ + public static class Builder { + + private final Location location; + private float luck; + private int lootingModifier = LootContext.DEFAULT_LOOT_MODIFIER; + private Entity lootedEntity; + private HumanEntity killer; + + /** + * Creates a new LootContext.Builder instance to facilitate easy + * creation of {@link LootContext}s. + * + * @param location the location the LootContext should use + */ + public Builder(@NotNull Location location) { + this.location = location; + } + + /** + * Set how much luck to have when generating loot. + * + * @param luck the luck level + * @return the Builder + */ + @NotNull + public Builder luck(float luck) { + this.luck = luck; + return this; + } + + /** + * Set the {@link org.bukkit.enchantments.Enchantment#LOOT_BONUS_MOBS} + * level equivalent to use when generating loot. Values less than or + * equal to 0 will force the {@link LootTable} to only return a single + * {@link org.bukkit.inventory.ItemStack} per pool. + * + * @param modifier the looting level modifier + * @return the Builder + */ + @NotNull + public Builder lootingModifier(int modifier) { + this.lootingModifier = modifier; + return this; + } + + /** + * The entity that was killed. + * + * @param lootedEntity the looted entity + * @return the Builder + */ + @NotNull + public Builder lootedEntity(@Nullable Entity lootedEntity) { + this.lootedEntity = lootedEntity; + return this; + } + + /** + * Set the {@link org.bukkit.entity.HumanEntity} that killed + * {@link #getLootedEntity()}. This entity will be used to get the + * looting level if {@link #lootingModifier(int)} is not set. + * + * @param killer the killer entity + * @return the Builder + */ + @NotNull + public Builder killer(@Nullable HumanEntity killer) { + this.killer = killer; + return this; + } + + /** + * Create a new {@link LootContext} instance using the supplied + * parameters. + * + * @return a new {@link LootContext} instance + */ + @NotNull + public LootContext build() { + return new LootContext(location, luck, lootingModifier, lootedEntity, killer); + } + } +} diff --git a/api/src/main/java/org/bukkit/loot/LootTable.java b/api/src/main/java/org/bukkit/loot/LootTable.java new file mode 100644 index 000000000..b391b55de --- /dev/null +++ b/api/src/main/java/org/bukkit/loot/LootTable.java @@ -0,0 +1,39 @@ +package org.bukkit.loot; + +import org.bukkit.Keyed; +import org.bukkit.inventory.Inventory; +import org.bukkit.inventory.ItemStack; +import org.jetbrains.annotations.NotNull; + +import java.util.Collection; +import java.util.Random; + +/** + * LootTables are technical files that represent what items should be in + * naturally generated containers, what items should be dropped when killing a + * mob, or what items can be fished. + * + * See the + * Minecraft Wiki for more information. + */ +public interface LootTable extends Keyed { + + /** + * Returns a mutable list of loot generated by this LootTable. + * + * @param random the random instance to use to generate loot + * @param context context within to populate loot + * @return a list of ItemStacks + */ + @NotNull + Collection populateLoot(@NotNull Random random, @NotNull LootContext context); + + /** + * Attempt to fill an inventory with this LootTable's loot. + * + * @param inventory the inventory to fill + * @param random the random instance to use to generate loot + * @param context context within to populate loot + */ + void fillInventory(@NotNull Inventory inventory, @NotNull Random random, @NotNull LootContext context); +} diff --git a/api/src/main/java/org/bukkit/loot/LootTables.java b/api/src/main/java/org/bukkit/loot/LootTables.java new file mode 100644 index 000000000..7b7dcfa27 --- /dev/null +++ b/api/src/main/java/org/bukkit/loot/LootTables.java @@ -0,0 +1,142 @@ +package org.bukkit.loot; + +import org.bukkit.Bukkit; +import org.bukkit.Keyed; +import org.bukkit.NamespacedKey; +import org.jetbrains.annotations.NotNull; + +/** + * This enum holds a list of all known {@link LootTable}s offered by Mojang. + * This list is not guaranteed to be accurate in future versions. + * + * See the + * + * Minecraft Wiki for more information on loot tables. + */ +public enum LootTables implements Keyed { + + EMPTY("empty"), + // Chests/Dispensers - treasure chests + ABANDONED_MINESHAFT("chests/abandoned_mineshaft"), + BURIED_TREASURE("chests/buried_treasure"), + DESERT_PYRAMID("chests/desert_pyramid"), + END_CITY_TREASURE("chests/end_city_treasure"), + IGLOO_CHEST("chests/igloo_chest"), + JUNGLE_TEMPLE("chests/jungle_temple"), + JUNGLE_TEMPLE_DISPENSER("chests/jungle_temple_dispenser"), + NETHER_BRIDGE("chests/nether_bridge"), + SHIPWRECK_MAP("chests/shipwreck_map"), + SHIPWRECK_SUPPLY("chests/shipwreck_supply"), + SHIPWRECK_TREASURE("chests/shipwreck_treasure"), + SIMPLE_DUNGEON("chests/simple_dungeon"), + SPAWN_BONUS_CHEST("chests/spawn_bonus_chest"), + STRONGHOLD_CORRIDOR("chests/stronghold_corridor"), + STRONGHOLD_CROSSING("chests/stronghold_crossing"), + STRONGHOLD_LIBRARY("chests/stronghold_library"), + UNDERWATER_RUIN_BIG("chests/underwater_ruin_big"), + UNDERWATER_RUIN_SMALL("chests/underwater_ruin_small"), + VILLAGE_BLACKSMITH("chests/village_blacksmith"), + WOODLAND_MANSION("chests/woodland_mansion"), + // Entities + BAT("entities/bat"), + BLAZE("entities/blaze"), + CAVE_SPIDER("entities/cave_spider"), + CHICKEN("entities/chicken"), + COD("entities/cod"), + COW("entities/cow"), + CREEPER("entities/creeper"), + DOLPHIN("entities/dolphin"), + DONKEY("entities/donkey"), + DROWNED("entities/drowned"), + ELDER_GUARDIAN("entities/elder_guardian"), + ENDERMAN("entities/enderman"), + ENDERMITE("entities/endermite"), + ENDER_DRAGON("entities/ender_dragon"), + EVOKER("entities/evoker"), + GHAST("entities/ghast"), + GIANT("entities/giant"), + GUARDIAN("entities/guardian"), + HORSE("entities/horse"), + HUSK("entities/husk"), + IRON_GOLEM("entities/iron_golem"), + LLAMA("entities/llama"), + MAGMA_CUBE("entities/magma_cube"), + MULE("entities/mule"), + MUSHROOM_COW("entities/mushroom_cow"), + OCELOT("entities/ocelot"), + PARROT("entities/parrot"), + PHANTOM("entities/phantom"), + PIG("entities/pig"), + POLAR_BEAR("entities/polar_bear"), + PUFFERFISH("entities/pufferfish"), + RABBIT("entities/rabbit"), + SALMON("entities/salmon"), + // Sheep entry here, moved below for organizational purposes + SHULKER("entities/shulker"), + SILVERFISH("entities/silverfish"), + SKELETON("entities/skeleton"), + SKELETON_HORSE("entities/skeleton_horse"), + SLIME("entities/slime"), + SNOW_GOLEM("entities/snow_golem"), + SPIDER("entities/spider"), + SQUID("entities/squid"), + STRAY("entities/stray"), + TROPICAL_FISH("entities/tropical_fish"), + TURTLE("entities/turtle"), + VEX("entities/vex"), + VILLAGER("entities/villager"), + VINDICATOR("entities/vindicator"), + WITCH("entities/witch"), + WITHER_SKELETON("entities/wither_skeleton"), + WOLF("entities/wolf"), + ZOMBIE("entities/zombie"), + ZOMBIE_HORSE("entities/zombie_horse"), + ZOMBIE_PIGMAN("entities/zombie_pigman"), + ZOMBIE_VILLAGER("entities/zombie_villager"), + // Gameplay + FISHING("gameplay/fishing"), + FISHING_FISH("gameplay/fishing/fish"), + FISHING_JUNK("gameplay/fishing/junk"), + FISHING_TREASURE("gameplay/fishing/treasure"), + // Sheep + SHEEP("entities/sheep"), + SHEEP_BLACK("entities/sheep/black"), + SHEEP_BLUE("entities/sheep/blue"), + SHEEP_BROWN("entities/sheep/brown"), + SHEEP_CYAN("entities/sheep/cyan"), + SHEEP_GRAY("entities/sheep/gray"), + SHEEP_GREEN("entities/sheep/green"), + SHEEP_LIGHT_BLUE("entities/sheep/light_blue"), + SHEEP_LIME("entities/sheep/lime"), + SHEEP_MAGENTA("entities/sheep/magenta"), + SHEEP_ORANGE("entities/sheep/orange"), + SHEEP_PINK("entities/sheep/pink"), + SHEEP_PURPLE("entities/sheep/purple"), + SHEEP_RED("entities/sheep/red"), + SHEEP_WHITE("entities/sheep/white"), + SHEEP_YELLOW("entities/sheep/yellow"), + ; + + private final String location; + + private LootTables(/*@NotNull*/ String location) { + this.location = location; + } + + @NotNull + @Override + public NamespacedKey getKey() { + return NamespacedKey.minecraft(location); + } + + /** + * Get the {@link LootTable} corresponding to this constant. This is + * equivalent to calling {@code Bukkit.getLootTable(this.getKey());}. + * + * @return the associated LootTable + */ + @NotNull + public LootTable getLootTable() { + return Bukkit.getLootTable(getKey()); + } +} diff --git a/api/src/main/java/org/bukkit/loot/Lootable.java b/api/src/main/java/org/bukkit/loot/Lootable.java new file mode 100644 index 000000000..901db8524 --- /dev/null +++ b/api/src/main/java/org/bukkit/loot/Lootable.java @@ -0,0 +1,82 @@ +package org.bukkit.loot; + +import org.jetbrains.annotations.Nullable; + +/** + * Represents a {@link org.bukkit.block.Container} or a + * {@link org.bukkit.entity.Mob} that can have a loot table. + *
+ * Container loot will only generate upon opening, and only when the container + * is first opened. + *
+ * Entities will only generate loot upon death. + */ +public interface Lootable { + + /** + * Set the loot table for a container or entity. + *
+ * To remove a loot table use null. Do not use {@link LootTables#EMPTY} to + * clear a LootTable. + * + * @param table the Loot Table this {@link org.bukkit.block.Container} or + * {@link org.bukkit.entity.Mob} will have. + */ + void setLootTable(@Nullable LootTable table); + + /** + * Gets the Loot Table attached to this block or entity. + *
+ * + * If an block/entity does not have a loot table, this will return null, NOT + * an empty loot table. + * + * @return the Loot Table attached to this block or entity. + */ + @Nullable + LootTable getLootTable(); + + // Paper start + /** + * Set the loot table and seed for a container or entity at the same time. + * + * @param table the Loot Table this {@link org.bukkit.block.Container} or {@link org.bukkit.entity.Mob} will have. + * @param seed the seed to used to generate loot. Default is 0. + */ + default void setLootTable(@Nullable LootTable table, long seed) { + setLootTable(table); + setSeed(seed); + } + + /** + * Returns whether or not this object has a Loot Table + * @return Has a loot table + */ + default boolean hasLootTable() { + return getLootTable() != null; + } + + /** + * Clears the associated Loot Table to this object + */ + default void clearLootTable() { + setLootTable(null); + } + // Paper end + + /** + * Set the seed used when this Loot Table generates loot. + * + * @param seed the seed to used to generate loot. Default is 0. + */ + void setSeed(long seed); + + /** + * Get the Loot Table's seed. + *
+ * The seed is used when generating loot. + * + * @return the seed + */ + long getSeed(); +} diff --git a/api/src/main/java/org/bukkit/map/MapCanvas.java b/api/src/main/java/org/bukkit/map/MapCanvas.java new file mode 100644 index 000000000..f04b01088 --- /dev/null +++ b/api/src/main/java/org/bukkit/map/MapCanvas.java @@ -0,0 +1,88 @@ +package org.bukkit.map; + +import org.jetbrains.annotations.NotNull; + +import java.awt.Image; + +/** + * Represents a canvas for drawing to a map. Each canvas is associated with a + * specific {@link MapRenderer} and represents that renderer's layer on the + * map. + */ +public interface MapCanvas { + + /** + * Get the map this canvas is attached to. + * + * @return The MapView this canvas is attached to. + */ + @NotNull + public MapView getMapView(); + + /** + * Get the cursor collection associated with this canvas. + * + * @return The MapCursorCollection associated with this canvas. + */ + @NotNull + public MapCursorCollection getCursors(); + + /** + * Set the cursor collection associated with this canvas. This does not + * usually need to be called since a MapCursorCollection is already + * provided. + * + * @param cursors The MapCursorCollection to associate with this canvas. + */ + public void setCursors(@NotNull MapCursorCollection cursors); + + /** + * Draw a pixel to the canvas. + * + * @param x The x coordinate, from 0 to 127. + * @param y The y coordinate, from 0 to 127. + * @param color The color. See {@link MapPalette}. + */ + public void setPixel(int x, int y, byte color); + + /** + * Get a pixel from the canvas. + * + * @param x The x coordinate, from 0 to 127. + * @param y The y coordinate, from 0 to 127. + * @return The color. See {@link MapPalette}. + */ + public byte getPixel(int x, int y); + + /** + * Get a pixel from the layers below this canvas. + * + * @param x The x coordinate, from 0 to 127. + * @param y The y coordinate, from 0 to 127. + * @return The color. See {@link MapPalette}. + */ + public byte getBasePixel(int x, int y); + + /** + * Draw an image to the map. The image will be clipped if necessary. + * + * @param x The x coordinate of the image. + * @param y The y coordinate of the image. + * @param image The Image to draw. + */ + public void drawImage(int x, int y, @NotNull Image image); + + /** + * Render text to the map using fancy formatting. Newline (\n) characters + * will move down one line and return to the original column, and the text + * color can be changed using sequences such as "§12;", replacing 12 with + * the palette index of the color (see {@link MapPalette}). + * + * @param x The column to start rendering on. + * @param y The row to start rendering on. + * @param font The font to use. + * @param text The formatted text to render. + */ + public void drawText(int x, int y, @NotNull MapFont font, @NotNull String text); + +} diff --git a/api/src/main/java/org/bukkit/map/MapCursor.java b/api/src/main/java/org/bukkit/map/MapCursor.java new file mode 100644 index 000000000..264c17820 --- /dev/null +++ b/api/src/main/java/org/bukkit/map/MapCursor.java @@ -0,0 +1,289 @@ +package org.bukkit.map; + +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +/** + * Represents a cursor on a map. + */ +public final class MapCursor { + private byte x, y; + private byte direction, type; + private boolean visible; + private String caption; + + /** + * Initialize the map cursor. + * + * @param x The x coordinate, from -128 to 127. + * @param y The y coordinate, from -128 to 127. + * @param direction The facing of the cursor, from 0 to 15. + * @param type The type (color/style) of the map cursor. + * @param visible Whether the cursor is visible by default. + * @deprecated Magic value + */ + @Deprecated + public MapCursor(byte x, byte y, byte direction, byte type, boolean visible) { + this(x, y, direction, type, visible, null); + } + + /** + * Initialize the map cursor. + * + * @param x The x coordinate, from -128 to 127. + * @param y The y coordinate, from -128 to 127. + * @param direction The facing of the cursor, from 0 to 15. + * @param type The type (color/style) of the map cursor. + * @param visible Whether the cursor is visible by default. + */ + public MapCursor(byte x, byte y, byte direction, @NotNull Type type, boolean visible) { + this(x, y, direction, type, visible, null); + } + + /** + * Initialize the map cursor. + * + * @param x The x coordinate, from -128 to 127. + * @param y The y coordinate, from -128 to 127. + * @param direction The facing of the cursor, from 0 to 15. + * @param type The type (color/style) of the map cursor. + * @param visible Whether the cursor is visible by default. + * @param caption cursor caption + * @deprecated Magic value + */ + @Deprecated + public MapCursor(byte x, byte y, byte direction, byte type, boolean visible, @Nullable String caption) { + this.x = x; + this.y = y; + setDirection(direction); + setRawType(type); + this.visible = visible; + this.caption = caption; + } + + /** + * Initialize the map cursor. + * + * @param x The x coordinate, from -128 to 127. + * @param y The y coordinate, from -128 to 127. + * @param direction The facing of the cursor, from 0 to 15. + * @param type The type (color/style) of the map cursor. + * @param visible Whether the cursor is visible by default. + * @param caption cursor caption + */ + public MapCursor(byte x, byte y, byte direction, @NotNull Type type, boolean visible, @Nullable String caption) { + this.x = x; + this.y = y; + setDirection(direction); + setType(type); + this.visible = visible; + this.caption = caption; + } + + /** + * Get the X position of this cursor. + * + * @return The X coordinate. + */ + public byte getX() { + return x; + } + + /** + * Get the Y position of this cursor. + * + * @return The Y coordinate. + */ + public byte getY() { + return y; + } + + /** + * Get the direction of this cursor. + * + * @return The facing of the cursor, from 0 to 15. + */ + public byte getDirection() { + return direction; + } + + /** + * Get the type of this cursor. + * + * @return The type (color/style) of the map cursor. + */ + @NotNull + public Type getType() { + // It should be impossible to set type to something without appropriate Type, so this shouldn't return null + return Type.byValue(type); + } + + /** + * Get the type of this cursor. + * + * @return The type (color/style) of the map cursor. + * @deprecated Magic value + */ + @Deprecated + public byte getRawType() { + return type; + } + + /** + * Get the visibility status of this cursor. + * + * @return True if visible, false otherwise. + */ + public boolean isVisible() { + return visible; + } + + /** + * Set the X position of this cursor. + * + * @param x The X coordinate. + */ + public void setX(byte x) { + this.x = x; + } + + /** + * Set the Y position of this cursor. + * + * @param y The Y coordinate. + */ + public void setY(byte y) { + this.y = y; + } + + /** + * Set the direction of this cursor. + * + * @param direction The facing of the cursor, from 0 to 15. + */ + public void setDirection(byte direction) { + if (direction < 0 || direction > 15) { + throw new IllegalArgumentException("Direction must be in the range 0-15"); + } + this.direction = direction; + } + + /** + * Set the type of this cursor. + * + * @param type The type (color/style) of the map cursor. + */ + public void setType(@NotNull Type type) { + setRawType(type.value); + } + + /** + * Set the type of this cursor. + * + * @param type The type (color/style) of the map cursor. + * @deprecated Magic value + */ + @Deprecated + public void setRawType(byte type) { + if (type < 0 || type > 26) { + throw new IllegalArgumentException("Type must be in the range 0-26"); + } + this.type = type; + } + + /** + * Set the visibility status of this cursor. + * + * @param visible True if visible. + */ + public void setVisible(boolean visible) { + this.visible = visible; + } + + /** + * Gets the caption on this cursor. + * + * @return caption + */ + @Nullable + public String getCaption() { + return caption; + } + + /** + * Sets the caption on this cursor. + * + * @param caption new caption + */ + public void setCaption(@Nullable String caption) { + this.caption = caption; + } + + /** + * Represents the standard types of map cursors. More may be made + * available by resource packs - the value is used by the client as an + * index in the file './misc/mapicons.png' from minecraft.jar or from a + * resource pack. + */ + public enum Type { + WHITE_POINTER(0), + GREEN_POINTER(1), + RED_POINTER(2), + BLUE_POINTER(3), + WHITE_CROSS(4), + RED_MARKER(5), + WHITE_CIRCLE(6), + SMALL_WHITE_CIRCLE(7), + MANSION(8), + TEMPLE(9), + BANNER_WHITE(10), + BANNER_ORANGE(11), + BANNER_MAGENTA(12), + BANNER_LIGHT_BLUE(13), + BANNER_YELLOW(14), + BANNER_LIME(15), + BANNER_PINK(16), + BANNER_GRAY(17), + BANNER_LIGHT_GRAY(18), + BANNER_CYAN(19), + BANNER_PURPLE(20), + BANNER_BLUE(21), + BANNER_BROWN(22), + BANNER_GREEN(23), + BANNER_RED(24), + BANNER_BLACK(25), + RED_X(26); + + private byte value; + + private Type(int value) { + this.value = (byte) value; + } + + /** + * + * @return the value + * @deprecated Magic value + */ + @Deprecated + public byte getValue() { + return value; + } + + /** + * + * @param value the value + * @return the matching type + * @deprecated Magic value + */ + @Deprecated + @Nullable + public static Type byValue(byte value) { + for (Type t : values()) { + if (t.value == value) return t; + } + return null; + } + } + +} diff --git a/api/src/main/java/org/bukkit/map/MapCursorCollection.java b/api/src/main/java/org/bukkit/map/MapCursorCollection.java new file mode 100644 index 000000000..be0683ba6 --- /dev/null +++ b/api/src/main/java/org/bukkit/map/MapCursorCollection.java @@ -0,0 +1,121 @@ +package org.bukkit.map; + +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +import java.util.ArrayList; +import java.util.List; + +/** + * Represents all the map cursors on a {@link MapCanvas}. Like MapCanvas, a + * MapCursorCollection is linked to a specific {@link MapRenderer}. + */ +public final class MapCursorCollection { + private List cursors = new ArrayList(); + + /** + * Get the amount of cursors in this collection. + * + * @return The size of this collection. + */ + public int size() { + return cursors.size(); + } + + /** + * Get a cursor from this collection. + * + * @param index The index of the cursor. + * @return The MapCursor. + */ + @NotNull + public MapCursor getCursor(int index) { + return cursors.get(index); + } + + /** + * Remove a cursor from the collection. + * + * @param cursor The MapCursor to remove. + * @return Whether the cursor was removed successfully. + */ + public boolean removeCursor(@NotNull MapCursor cursor) { + return cursors.remove(cursor); + } + + /** + * Add a cursor to the collection. + * + * @param cursor The MapCursor to add. + * @return The MapCursor that was passed. + */ + @NotNull + public MapCursor addCursor(@NotNull MapCursor cursor) { + cursors.add(cursor); + return cursor; + } + + /** + * Add a cursor to the collection. + * + * @param x The x coordinate, from -128 to 127. + * @param y The y coordinate, from -128 to 127. + * @param direction The facing of the cursor, from 0 to 15. + * @return The newly added MapCursor. + */ + @NotNull + public MapCursor addCursor(int x, int y, byte direction) { + return addCursor(x, y, direction, (byte) 0, true); + } + + /** + * Add a cursor to the collection. + * + * @param x The x coordinate, from -128 to 127. + * @param y The y coordinate, from -128 to 127. + * @param direction The facing of the cursor, from 0 to 15. + * @param type The type (color/style) of the map cursor. + * @return The newly added MapCursor. + * @deprecated Magic value + */ + @Deprecated + @NotNull + public MapCursor addCursor(int x, int y, byte direction, byte type) { + return addCursor(x, y, direction, type, true); + } + + /** + * Add a cursor to the collection. + * + * @param x The x coordinate, from -128 to 127. + * @param y The y coordinate, from -128 to 127. + * @param direction The facing of the cursor, from 0 to 15. + * @param type The type (color/style) of the map cursor. + * @param visible Whether the cursor is visible. + * @return The newly added MapCursor. + * @deprecated Magic value + */ + @Deprecated + @NotNull + public MapCursor addCursor(int x, int y, byte direction, byte type, boolean visible) { + return addCursor(new MapCursor((byte) x, (byte) y, direction, type, visible)); + } + + /** + * Add a cursor to the collection. + * + * @param x The x coordinate, from -128 to 127. + * @param y The y coordinate, from -128 to 127. + * @param direction The facing of the cursor, from 0 to 15. + * @param type The type (color/style) of the map cursor. + * @param visible Whether the cursor is visible. + * @param caption banner caption + * @return The newly added MapCursor. + * @deprecated Magic value + */ + @Deprecated + @NotNull + public MapCursor addCursor(int x, int y, byte direction, byte type, boolean visible, @Nullable String caption) { + return addCursor(new MapCursor((byte) x, (byte) y, direction, type, visible, caption)); + } +} diff --git a/api/src/main/java/org/bukkit/map/MapFont.java b/api/src/main/java/org/bukkit/map/MapFont.java new file mode 100644 index 000000000..2d599c8f3 --- /dev/null +++ b/api/src/main/java/org/bukkit/map/MapFont.java @@ -0,0 +1,150 @@ +package org.bukkit.map; + +import java.util.HashMap; +import org.bukkit.ChatColor; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +/** + * Represents a bitmap font drawable to a map. + */ +public class MapFont { + + private final HashMap chars = new HashMap(); + private int height = 0; + protected boolean malleable = true; + + /** + * Set the sprite for a given character. + * + * @param ch The character to set the sprite for. + * @param sprite The CharacterSprite to set. + * @throws IllegalStateException if this font is static. + */ + public void setChar(char ch, @NotNull CharacterSprite sprite) { + if (!malleable) { + throw new IllegalStateException("this font is not malleable"); + } + + chars.put(ch, sprite); + if (sprite.getHeight() > height) { + height = sprite.getHeight(); + } + } + + /** + * Get the sprite for a given character. + * + * @param ch The character to get the sprite for. + * @return The CharacterSprite associated with the character, or null if + * there is none. + */ + @Nullable + public CharacterSprite getChar(char ch) { + return chars.get(ch); + } + + /** + * Get the width of the given text as it would be rendered using this + * font. + * + * @param text The text. + * @return The width in pixels. + */ + public int getWidth(@NotNull String text) { + if (!isValid(text)) { + throw new IllegalArgumentException("text contains invalid characters"); + } + + if (text.length() == 0) { + return 0; + } + + int result = 0; + for (int i = 0; i < text.length(); ++i) { + char ch = text.charAt(i); + if (ch == ChatColor.COLOR_CHAR) continue; + result += chars.get(ch).getWidth(); + } + result += text.length() - 1; // Account for 1px spacing between characters + + return result; + } + + /** + * Get the height of this font. + * + * @return The height of the font. + */ + public int getHeight() { + return height; + } + + /** + * Check whether the given text is valid. + * + * @param text The text. + * @return True if the string contains only defined characters, false + * otherwise. + */ + public boolean isValid(@NotNull String text) { + for (int i = 0; i < text.length(); ++i) { + char ch = text.charAt(i); + if (ch == ChatColor.COLOR_CHAR || ch == '\n') continue; + if (chars.get(ch) == null) return false; + } + return true; + } + + /** + * Represents the graphics for a single character in a MapFont. + */ + public static class CharacterSprite { + + private final int width; + private final int height; + private final boolean[] data; + + public CharacterSprite(int width, int height, @NotNull boolean[] data) { + this.width = width; + this.height = height; + this.data = data; + + if (data.length != width * height) { + throw new IllegalArgumentException("size of data does not match dimensions"); + } + } + + /** + * Get the value of a pixel of the character. + * + * @param row The row, in the range [0,8). + * @param col The column, in the range [0,8). + * @return True if the pixel is solid, false if transparent. + */ + public boolean get(int row, int col) { + if (row < 0 || col < 0 || row >= height || col >= width) return false; + return data[row * width + col]; + } + + /** + * Get the width of the character sprite. + * + * @return The width of the character. + */ + public int getWidth() { + return width; + } + + /** + * Get the height of the character sprite. + * + * @return The height of the character. + */ + public int getHeight() { + return height; + } + + } + +} diff --git a/api/src/main/java/org/bukkit/map/MapPalette.java b/api/src/main/java/org/bukkit/map/MapPalette.java new file mode 100644 index 000000000..afe727e24 --- /dev/null +++ b/api/src/main/java/org/bukkit/map/MapPalette.java @@ -0,0 +1,264 @@ +package org.bukkit.map; + +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +import java.awt.Color; +import java.awt.Graphics2D; +import java.awt.Image; +import java.awt.image.BufferedImage; + +/** + * Represents the palette that map items use. + *

+ * These fields are hee base color ranges. Each entry corresponds to four + * colors of varying shades with values entry to entry + 3. + */ +public final class MapPalette { + // Internal mechanisms + private MapPalette() {} + + @NotNull + private static Color c(int r, int g, int b) { + return new Color(r, g, b); + } + + private static double getDistance(@NotNull Color c1, @NotNull Color c2) { + double rmean = (c1.getRed() + c2.getRed()) / 2.0; + double r = c1.getRed() - c2.getRed(); + double g = c1.getGreen() - c2.getGreen(); + int b = c1.getBlue() - c2.getBlue(); + double weightR = 2 + rmean / 256.0; + double weightG = 4.0; + double weightB = 2 + (255 - rmean) / 256.0; + return weightR * r * r + weightG * g * g + weightB * b * b; + } + + @NotNull + static final Color[] colors = { + c(0, 0, 0), c(0, 0, 0), c(0, 0, 0), c(0, 0, 0), + c(89, 125, 39), c(109, 153, 48), c(127, 178, 56), c(67, 94, 29), + c(174, 164, 115), c(213, 201, 140), c(247, 233, 163), c(130, 123, 86), + c(140, 140, 140), c(171, 171, 171), c(199, 199, 199), c(105, 105, 105), + c(180, 0, 0), c(220, 0, 0), c(255, 0, 0), c(135, 0, 0), + c(112, 112, 180), c(138, 138, 220), c(160, 160, 255), c(84, 84, 135), + c(117, 117, 117), c(144, 144, 144), c(167, 167, 167), c(88, 88, 88), + c(0, 87, 0), c(0, 106, 0), c(0, 124, 0), c(0, 65, 0), + c(180, 180, 180), c(220, 220, 220), c(255, 255, 255), c(135, 135, 135), + c(115, 118, 129), c(141, 144, 158), c(164, 168, 184), c(86, 88, 97), + c(106, 76, 54), c(130, 94, 66), c(151, 109, 77), c(79, 57, 40), + c(79, 79, 79), c(96, 96, 96), c(112, 112, 112), c(59, 59, 59), + c(45, 45, 180), c(55, 55, 220), c(64, 64, 255), c(33, 33, 135), + c(100, 84, 50), c(123, 102, 62), c(143, 119, 72), c(75, 63, 38), + c(180, 177, 172), c(220, 217, 211), c(255, 252, 245), c(135, 133, 129), + c(152, 89, 36), c(186, 109, 44), c(216, 127, 51), c(114, 67, 27), + c(125, 53, 152), c(153, 65, 186), c(178, 76, 216), c(94, 40, 114), + c(72, 108, 152), c(88, 132, 186), c(102, 153, 216), c(54, 81, 114), + c(161, 161, 36), c(197, 197, 44), c(229, 229, 51), c(121, 121, 27), + c(89, 144, 17), c(109, 176, 21), c(127, 204, 25), c(67, 108, 13), + c(170, 89, 116), c(208, 109, 142), c(242, 127, 165), c(128, 67, 87), + c(53, 53, 53), c(65, 65, 65), c(76, 76, 76), c(40, 40, 40), + c(108, 108, 108), c(132, 132, 132), c(153, 153, 153), c(81, 81, 81), + c(53, 89, 108), c(65, 109, 132), c(76, 127, 153), c(40, 67, 81), + c(89, 44, 125), c(109, 54, 153), c(127, 63, 178), c(67, 33, 94), + c(36, 53, 125), c(44, 65, 153), c(51, 76, 178), c(27, 40, 94), + c(72, 53, 36), c(88, 65, 44), c(102, 76, 51), c(54, 40, 27), + c(72, 89, 36), c(88, 109, 44), c(102, 127, 51), c(54, 67, 27), + c(108, 36, 36), c(132, 44, 44), c(153, 51, 51), c(81, 27, 27), + c(17, 17, 17), c(21, 21, 21), c(25, 25, 25), c(13, 13, 13), + c(176, 168, 54), c(215, 205, 66), c(250, 238, 77), c(132, 126, 40), + c(64, 154, 150), c(79, 188, 183), c(92, 219, 213), c(48, 115, 112), + c(52, 90, 180), c(63, 110, 220), c(74, 128, 255), c(39, 67, 135), + c(0, 153, 40), c(0, 187, 50), c(0, 217, 58), c(0, 114, 30), + c(91, 60, 34), c(111, 74, 42), c(129, 86, 49), c(68, 45, 25), + c(79, 1, 0), c(96, 1, 0), c(112, 2, 0), c(59, 1, 0), + c(147, 124, 113), c(180, 152, 138), c(209, 177, 161), c(110, 93, 85), + c(112, 57, 25), c(137, 70, 31), c(159, 82, 36), c(84, 43, 19), + c(105, 61, 76), c(128, 75, 93), c(149, 87, 108), c(78, 46, 57), + c(79, 76, 97), c(96, 93, 119), c(112, 108, 138), c(59, 57, 73), + c(131, 93, 25), c(160, 114, 31), c(186, 133, 36), c(98, 70, 19), + c(72, 82, 37), c(88, 100, 45), c(103, 117, 53), c(54, 61, 28), + c(112, 54, 55), c(138, 66, 67), c(160, 77, 78), c(84, 40, 41), + c(40, 28, 24), c(49, 35, 30), c(57, 41, 35), c(30, 21, 18), + c(95, 75, 69), c(116, 92, 84), c(135, 107, 98), c(71, 56, 51), + c(61, 64, 64), c(75, 79, 79), c(87, 92, 92), c(46, 48, 48), + c(86, 51, 62), c(105, 62, 75), c(122, 73, 88), c(64, 38, 46), + c(53, 43, 64), c(65, 53, 79), c(76, 62, 92), c(40, 32, 48), + c(53, 35, 24), c(65, 43, 30), c(76, 50, 35), c(40, 26, 18), + c(53, 57, 29), c(65, 70, 36), c(76, 82, 42), c(40, 43, 22), + c(100, 42, 32), c(122, 51, 39), c(142, 60, 46), c(75, 31, 24), + c(26, 15, 11), c(31, 18, 13), c(37, 22, 16), c(19, 11, 8) + }; + + // Interface + /** + * @deprecated Magic value + */ + @Deprecated + public static final byte TRANSPARENT = 0; + /** + * @deprecated Magic value + */ + @Deprecated + public static final byte LIGHT_GREEN = 4; + /** + * @deprecated Magic value + */ + @Deprecated + public static final byte LIGHT_BROWN = 8; + /** + * @deprecated Magic value + */ + @Deprecated + public static final byte GRAY_1 = 12; + /** + * @deprecated Magic value + */ + @Deprecated + public static final byte RED = 16; + /** + * @deprecated Magic value + */ + @Deprecated + public static final byte PALE_BLUE = 20; + /** + * @deprecated Magic value + */ + @Deprecated + public static final byte GRAY_2 = 24; + /** + * @deprecated Magic value + */ + @Deprecated + public static final byte DARK_GREEN = 28; + /** + * @deprecated Magic value + */ + @Deprecated + public static final byte WHITE = 32; + /** + * @deprecated Magic value + */ + @Deprecated + public static final byte LIGHT_GRAY = 36; + /** + * @deprecated Magic value + */ + @Deprecated + public static final byte BROWN = 40; + /** + * @deprecated Magic value + */ + @Deprecated + public static final byte DARK_GRAY = 44; + /** + * @deprecated Magic value + */ + @Deprecated + public static final byte BLUE = 48; + /** + * @deprecated Magic value + */ + @Deprecated + public static final byte DARK_BROWN = 52; + + /** + * Resize an image to 128x128. + * + * @param image The image to resize. + * @return The resized image. + */ + @NotNull + public static BufferedImage resizeImage(@Nullable Image image) { + BufferedImage result = new BufferedImage(128, 128, BufferedImage.TYPE_INT_ARGB); + Graphics2D graphics = result.createGraphics(); + graphics.drawImage(image, 0, 0, 128, 128, null); + graphics.dispose(); + return result; + } + + /** + * Convert an Image to a byte[] using the palette. + * + * @param image The image to convert. + * @return A byte[] containing the pixels of the image. + * @deprecated Magic value + */ + @Deprecated + @NotNull + public static byte[] imageToBytes(@NotNull Image image) { + BufferedImage temp = new BufferedImage(image.getWidth(null), image.getHeight(null), BufferedImage.TYPE_INT_ARGB); + Graphics2D graphics = temp.createGraphics(); + graphics.drawImage(image, 0, 0, null); + graphics.dispose(); + + int[] pixels = new int[temp.getWidth() * temp.getHeight()]; + temp.getRGB(0, 0, temp.getWidth(), temp.getHeight(), pixels, 0, temp.getWidth()); + + byte[] result = new byte[temp.getWidth() * temp.getHeight()]; + for (int i = 0; i < pixels.length; i++) { + result[i] = matchColor(new Color(pixels[i], true)); + } + return result; + } + + /** + * Get the index of the closest matching color in the palette to the given + * color. + * + * @param r The red component of the color. + * @param b The blue component of the color. + * @param g The green component of the color. + * @return The index in the palette. + * @deprecated Magic value + */ + @Deprecated + public static byte matchColor(int r, int g, int b) { + return matchColor(new Color(r, g, b)); + } + + /** + * Get the index of the closest matching color in the palette to the given + * color. + * + * @param color The Color to match. + * @return The index in the palette. + * @deprecated Magic value + */ + @Deprecated + public static byte matchColor(@NotNull Color color) { + if (color.getAlpha() < 128) return 0; + + int index = 0; + double best = -1; + + for (int i = 4; i < colors.length; i++) { + double distance = getDistance(color, colors[i]); + if (distance < best || best == -1) { + best = distance; + index = i; + } + } + + // Minecraft has 143 colors, some of which have negative byte representations + return (byte) (index < 128 ? index : -129 + (index - 127)); + } + + /** + * Get the value of the given color in the palette. + * + * @param index The index in the palette. + * @return The Color of the palette entry. + * @deprecated Magic value + */ + @Deprecated + @NotNull + public static Color getColor(byte index) { + if ((index > -49 && index < 0) || index > 127) { + throw new IndexOutOfBoundsException(); + } else { + // Minecraft has 143 colors, some of which have negative byte representations + return colors[index >= 0 ? index : index + 256]; + } + } +} diff --git a/api/src/main/java/org/bukkit/map/MapRenderer.java b/api/src/main/java/org/bukkit/map/MapRenderer.java new file mode 100644 index 000000000..93aeaf7e5 --- /dev/null +++ b/api/src/main/java/org/bukkit/map/MapRenderer.java @@ -0,0 +1,57 @@ +package org.bukkit.map; + +import org.bukkit.entity.Player; +import org.jetbrains.annotations.NotNull; + +/** + * Represents a renderer for a map. + */ +public abstract class MapRenderer { + + private boolean contextual; + + /** + * Initialize the map renderer base to be non-contextual. See {@link + * #isContextual()}. + */ + public MapRenderer() { + this(false); + } + + /** + * Initialize the map renderer base with the given contextual status. + * + * @param contextual Whether the renderer is contextual. See {@link + * #isContextual()}. + */ + public MapRenderer(boolean contextual) { + this.contextual = contextual; + } + + /** + * Get whether the renderer is contextual, i.e. has different canvases for + * different players. + * + * @return True if contextual, false otherwise. + */ + final public boolean isContextual() { + return contextual; + } + + /** + * Initialize this MapRenderer for the given map. + * + * @param map The MapView being initialized. + */ + public void initialize(@NotNull MapView map) {} + + /** + * Render to the given map. + * + * @param map The MapView being rendered to. + * @param canvas The canvas to use for rendering. + * @param player The player who triggered the rendering. + */ + abstract public void render(@NotNull MapView map, @NotNull MapCanvas canvas, @NotNull Player player); + +} diff --git a/api/src/main/java/org/bukkit/map/MapView.java b/api/src/main/java/org/bukkit/map/MapView.java new file mode 100644 index 000000000..29dd573ac --- /dev/null +++ b/api/src/main/java/org/bukkit/map/MapView.java @@ -0,0 +1,176 @@ +package org.bukkit.map; + +import java.util.List; +import org.bukkit.World; +import org.bukkit.inventory.meta.MapMeta; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +/** + * Represents a map item. + */ +public interface MapView { + + /** + * An enum representing all possible scales a map can be set to. + */ + public static enum Scale { + CLOSEST(0), + CLOSE(1), + NORMAL(2), + FAR(3), + FARTHEST(4); + + private byte value; + + private Scale(int value) { + this.value = (byte) value; + } + + /** + * Get the scale given the raw value. + * + * @param value The raw scale + * @return The enum scale, or null for an invalid input + * @deprecated Magic value + */ + @Deprecated + @Nullable + public static Scale valueOf(byte value) { + switch (value) { + case 0: return CLOSEST; + case 1: return CLOSE; + case 2: return NORMAL; + case 3: return FAR; + case 4: return FARTHEST; + default: return null; + } + } + + /** + * Get the raw value of this scale level. + * + * @return The scale value + * @deprecated Magic value + */ + @Deprecated + public byte getValue() { + return value; + } + } + + /** + * Get the ID of this map item for use with {@link MapMeta}. + * + * @return The ID of the map. + */ + public int getId(); + + /** + * Check whether this map is virtual. A map is virtual if its lowermost + * MapRenderer is plugin-provided. + * + * @return Whether the map is virtual. + */ + public boolean isVirtual(); + + /** + * Get the scale of this map. + * + * @return The scale of the map. + */ + @NotNull + public Scale getScale(); + + /** + * Set the scale of this map. + * + * @param scale The scale to set. + */ + public void setScale(@NotNull Scale scale); + + /** + * Get the center X position of this map. + * + * @return The center X position. + */ + public int getCenterX(); + + /** + * Get the center Z position of this map. + * + * @return The center Z position. + */ + public int getCenterZ(); + + /** + * Set the center X position of this map. + * + * @param x The center X position. + */ + public void setCenterX(int x); + + /** + * Set the center Z position of this map. + * + * @param z The center Z position. + */ + public void setCenterZ(int z); + + /** + * Get the world that this map is associated with. Primarily used by the + * internal renderer, but may be used by external renderers. May return + * null if the world the map is associated with is not loaded. + * + * @return The World this map is associated with. + */ + @Nullable + public World getWorld(); + + /** + * Set the world that this map is associated with. The world is used by + * the internal renderer, and may also be used by external renderers. + * + * @param world The World to associate this map with. + */ + public void setWorld(@NotNull World world); + + /** + * Get a list of MapRenderers currently in effect. + * + * @return A {@code List} containing each map renderer. + */ + @NotNull + public List getRenderers(); + + /** + * Add a renderer to this map. + * + * @param renderer The MapRenderer to add. + */ + public void addRenderer(@NotNull MapRenderer renderer); + + /** + * Remove a renderer from this map. + * + * @param renderer The MapRenderer to remove. + * @return True if the renderer was successfully removed. + */ + public boolean removeRenderer(@Nullable MapRenderer renderer); + + /** + * Whether the map will show a smaller position cursor (true), or no + * position cursor (false) when cursor is outside of map's range. + * + * @return unlimited tracking state + */ + boolean isUnlimitedTracking(); + + /** + * Whether the map will show a smaller position cursor (true), or no + * position cursor (false) when cursor is outside of map's range. + * + * @param unlimited tracking state + */ + void setUnlimitedTracking(boolean unlimited); +} diff --git a/api/src/main/java/org/bukkit/map/MinecraftFont.java b/api/src/main/java/org/bukkit/map/MinecraftFont.java new file mode 100644 index 000000000..e1c8acc77 --- /dev/null +++ b/api/src/main/java/org/bukkit/map/MinecraftFont.java @@ -0,0 +1,331 @@ +package org.bukkit.map; + +import org.jetbrains.annotations.NotNull; + +/** + * Represents the built-in Minecraft font. + */ +public class MinecraftFont extends MapFont { + + private static final int spaceSize = 2; + + private static final String fontChars = + " !\"#$%&'()*+,-./0123456789:;<=>?" + + "@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\\]^_" + + "'abcdefghijklmnopqrstuvwxyz{|}~\u007F" + + "\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\u0191" + // ÿÖÜø£Ø×ƒ + "\u00E1\u00ED\u00F3\u00FA\u00F1\u00D1\u00AA\u00BA" + // áíóúñѪº + "\u00BF\u00AE\u00AC\u00BD\u00BC\u00A1\u00AB\u00BB"; // ¿®¬½¼¡«» + + private static final int[][] fontData = new int[][] { + /* null */ {0,0,0,0,0,0,0,0}, + /* 1 */ {126,129,165,129,189,153,129,126}, + /* 2 */ {126,255,219,255,195,231,255,126}, + /* 3 */ {54,127,127,127,62,28,8,0}, + /* 4 */ {8,28,62,127,62,28,8,0}, + /* 5 */ {28,62,28,127,127,62,28,62}, + /* 6 */ {8,8,28,62,127,62,28,62}, + /* 7 */ {0,0,24,60,60,24,0,0}, + /* 8 */ {255,255,231,195,195,231,255,255}, + /* 9 */ {0,60,102,66,66,102,60,0}, + /* 10 */ {255,195,153,189,189,153,195,255}, + /* 11 */ {240,224,240,190,51,51,51,30}, + /* 12 */ {60,102,102,102,60,24,126,24}, + /* 13 */ {252,204,252,12,12,14,15,7}, + /* 14 */ {254,198,254,198,198,230,103,3}, + /* 15 */ {153,90,60,231,231,60,90,153}, + /* 16 */ {1,7,31,127,31,7,1,0}, + /* 17 */ {64,112,124,127,124,112,64,0}, + /* 18 */ {24,60,126,24,24,126,60,24}, + /* 19 */ {102,102,102,102,102,0,102,0}, + /* 20 */ {254,219,219,222,216,216,216,0}, + /* 21 */ {124,198,28,54,54,28,51,30}, + /* 22 */ {0,0,0,0,126,126,126,0}, + /* 23 */ {24,60,126,24,126,60,24,255}, + /* 24 */ {24,60,126,24,24,24,24,0}, + /* 25 */ {24,24,24,24,126,60,24,0}, + /* 26 */ {0,24,48,127,48,24,0,0}, + /* 27 */ {0,12,6,127,6,12,0,0}, + /* 28 */ {0,0,3,3,3,127,0,0}, + /* 29 */ {0,36,102,255,102,36,0,0}, + /* 30 */ {0,24,60,126,255,255,0,0}, + /* 31 */ {0,255,255,126,60,24,0,0}, + /* */ {0,0,0,0,0,0,0,0}, + /* ! */ {1,1,1,1,1,0,1,0}, + /* " */ {10,10,5,0,0,0,0,0}, + /* # */ {10,10,31,10,31,10,10,0}, + /* $ */ {4,30,1,14,16,15,4,0}, + /* % */ {17,9,8,4,2,18,17,0}, + /* & */ {4,10,4,22,13,9,22,0}, + /* ' */ {2,2,1,0,0,0,0,0}, + /* ( */ {12,2,1,1,1,2,12,0}, + /* ) */ {3,4,8,8,8,4,3,0}, + /* * */ {0,0,9,6,9,0,0,0}, + /* + */ {0,4,4,31,4,4,0,0}, + /* , */ {0,0,0,0,0,1,1,1}, + /* - */ {0,0,0,31,0,0,0,0}, + /* . */ {0,0,0,0,0,1,1,0}, + /* / */ {16,8,8,4,2,2,1,0}, + /* 0 */ {14,17,25,21,19,17,14,0}, + /* 1 */ {4,6,4,4,4,4,31,0}, + /* 2 */ {14,17,16,12,2,17,31,0}, + /* 3 */ {14,17,16,12,16,17,14,0}, + /* 4 */ {24,20,18,17,31,16,16,0}, + /* 5 */ {31,1,15,16,16,17,14,0}, + /* 6 */ {12,2,1,15,17,17,14,0}, + /* 7 */ {31,17,16,8,4,4,4,0}, + /* 8 */ {14,17,17,14,17,17,14,0}, + /* 9 */ {14,17,17,30,16,8,6,0}, + /* : */ {0,1,1,0,0,1,1,0}, + /* ; */ {0,1,1,0,0,1,1,1}, + /* < */ {8,4,2,1,2,4,8,0}, + /* = */ {0,0,31,0,0,31,0,0}, + /* > */ {1,2,4,8,4,2,1,0}, + /* ? */ {14,17,16,8,4,0,4,0}, + /* @ */ {30,33,45,45,61,1,30,0}, + /* A */ {14,17,31,17,17,17,17,0}, + /* B */ {15,17,15,17,17,17,15,0}, + /* C */ {14,17,1,1,1,17,14,0}, + /* D */ {15,17,17,17,17,17,15,0}, + /* E */ {31,1,7,1,1,1,31,0}, + /* F */ {31,1,7,1,1,1,1,0}, + /* G */ {30,1,25,17,17,17,14,0}, + /* H */ {17,17,31,17,17,17,17,0}, + /* I */ {7,2,2,2,2,2,7,0}, + /* J */ {16,16,16,16,16,17,14,0}, + /* K */ {17,9,7,9,17,17,17,0}, + /* L */ {1,1,1,1,1,1,31,0}, + /* M */ {17,27,21,17,17,17,17,0}, + /* N */ {17,19,21,25,17,17,17,0}, + /* O */ {14,17,17,17,17,17,14,0}, + /* P */ {15,17,15,1,1,1,1,0}, + /* Q */ {14,17,17,17,17,9,22,0}, + /* R */ {15,17,15,17,17,17,17,0}, + /* S */ {30,1,14,16,16,17,14,0}, + /* T */ {31,4,4,4,4,4,4,0}, + /* U */ {17,17,17,17,17,17,14,0}, + /* V */ {17,17,17,17,10,10,4,0}, + /* W */ {17,17,17,17,21,27,17,0}, + /* X */ {17,10,4,10,17,17,17,0}, + /* Y */ {17,10,4,4,4,4,4,0}, + /* Z */ {31,16,8,4,2,1,31,0}, + /* [ */ {7,1,1,1,1,1,7,0}, + /* \ */ {1,2,2,4,8,8,16,0}, + /* ] */ {7,4,4,4,4,4,7,0}, + /* ^ */ {4,10,17,0,0,0,0,0}, + /* _ */ {0,0,0,0,0,0,0,31}, + /* ` */ {1,1,2,0,0,0,0,0}, + /* a */ {0,0,14,16,30,17,30,0}, + /* b */ {1,1,13,19,17,17,15,0}, + /* c */ {0,0,14,17,1,17,14,0}, + /* d */ {16,16,22,25,17,17,30,0}, + /* e */ {0,0,14,17,31,1,30,0}, + /* f */ {12,2,15,2,2,2,2,0}, + /* g */ {0,0,30,17,17,30,16,15}, + /* h */ {1,1,13,19,17,17,17,0}, + /* i */ {1,0,1,1,1,1,1,0}, + /* j */ {16,0,16,16,16,17,17,14}, + /* k */ {1,1,9,5,3,5,9,0}, + /* l */ {1,1,1,1,1,1,2,0}, + /* m */ {0,0,11,21,21,17,17,0}, + /* n */ {0,0,15,17,17,17,17,0}, + /* o */ {0,0,14,17,17,17,14,0}, + /* p */ {0,0,13,19,17,15,1,1}, + /* q */ {0,0,22,25,17,30,16,16}, + /* r */ {0,0,13,19,1,1,1,0}, + /* s */ {0,0,30,1,14,16,15,0}, + /* t */ {2,2,7,2,2,2,4,0}, + /* u */ {0,0,17,17,17,17,30,0}, + /* v */ {0,0,17,17,17,10,4,0}, + /* w */ {0,0,17,17,21,21,30,0}, + /* x */ {0,0,17,10,4,10,17,0}, + /* y */ {0,0,17,17,17,30,16,15}, + /* z */ {0,0,31,8,4,2,31,0}, + /* { */ {12,2,2,1,2,2,12,0}, + /* | */ {1,1,1,0,1,1,1,0}, + /* } */ {3,4,4,8,4,4,3,0}, + /* ~ */ {38,25,0,0,0,0,0,0}, + /* ⌂ */ {0,0,4,10,17,17,31,0}, + /* Ç */ {14,17,1,1,17,14,16,12}, + /* ü */ {10,0,17,17,17,17,30,0}, + /* é */ {24,0,14,17,31,1,30,0}, + /* â */ {14,17,14,16,30,17,30,0}, + /* ä */ {10,0,14,16,30,17,30,0}, + /* à */ {3,0,14,16,30,17,30,0}, + /* å */ {4,0,14,16,30,17,30,0}, + /* ç */ {0,14,17,1,17,14,16,12}, + /* ê */ {14,17,14,17,31,1,30,0}, + /* ë */ {10,0,14,17,31,1,30,0}, + /* è */ {3,0,14,17,31,1,30,0}, + /* ï */ {5,0,2,2,2,2,2,0}, + /* î */ {14,17,4,4,4,4,4,0}, + /* ì */ {3,0,2,2,2,2,2,0}, + /* Ä */ {17,14,17,31,17,17,17,0}, + /* Å */ {4,0,14,17,31,17,17,0}, + /* É */ {24,0,31,1,7,1,31,0}, + /* æ */ {0,0,10,20,30,5,30,0}, + /* Æ */ {30,5,15,5,5,5,29,0}, + /* ô */ {14,17,14,17,17,17,14,0}, + /* ö */ {10,0,14,17,17,17,14,0}, + /* ò */ {3,0,14,17,17,17,14,0}, + /* û */ {14,17,0,17,17,17,30,0}, + /* ù */ {3,0,17,17,17,17,30,0}, + /* ÿ */ {10,0,17,17,17,30,16,15}, + /* Ö */ {17,14,17,17,17,17,14,0}, + /* Ü */ {17,0,17,17,17,17,14,0}, + /* ø */ {0,0,14,25,21,19,14,4}, + /* £ */ {12,18,2,15,2,2,31,0}, + /* Ø */ {14,17,25,21,19,17,14,0}, + /* × */ {0,0,5,2,5,0,0,0}, + /* ƒ */ {8,20,4,14,4,4,5,2}, + /* á */ {24,0,14,16,30,17,30,0}, + /* í */ {3,0,1,1,1,1,1,0}, + /* ó */ {24,0,14,17,17,17,14,0}, + /* ú */ {24,0,17,17,17,17,30,0}, + /* ñ */ {31,0,15,17,17,17,17,0}, + /* Ñ */ {31,0,17,19,21,25,17,0}, + /* ª */ {14,16,31,30,0,31,0,0}, + /* º */ {14,17,17,14,0,31,0,0}, + /* ¿ */ {4,0,4,2,1,17,14,0}, + /* ® */ {0,30,45,37,43,30,0,0}, + /* ¬ */ {0,0,0,31,16,16,0,0}, + /* ½ */ {17,9,8,4,18,10,25,0}, + /* ¼ */ {17,9,8,4,26,26,17,0}, + /* ¡ */ {0,1,0,1,1,1,1,0}, + /* « */ {0,20,10,5,10,20,0,0}, + /* » */ {0,5,10,20,10,5,0,0}, + /* 176 */ {68,17,68,17,68,17,68,17}, + /* 177 */ {170,85,170,85,170,85,170,85}, + /* 178 */ {219,238,219,119,219,238,219,119}, + /* 179 */ {24,24,24,24,24,24,24,24}, + /* 180 */ {24,24,24,24,31,24,24,24}, + /* 181 */ {24,24,31,24,31,24,24,24}, + /* 182 */ {108,108,108,108,111,108,108,108}, + /* 183 */ {0,0,0,0,127,108,108,108}, + /* 184 */ {0,0,31,24,31,24,24,24}, + /* 185 */ {108,108,111,96,111,108,108,108}, + /* 186 */ {108,108,108,108,108,108,108,108}, + /* 187 */ {0,0,127,96,111,108,108,108}, + /* 188 */ {108,108,111,96,127,0,0,0}, + /* 189 */ {108,108,108,108,127,0,0,0}, + /* 190 */ {24,24,31,24,31,0,0,0}, + /* 191 */ {0,0,0,0,31,24,24,24}, + /* 192 */ {24,24,24,24,248,0,0,0}, + /* 193 */ {24,24,24,24,255,0,0,0}, + /* 194 */ {0,0,0,0,255,24,24,24}, + /* 195 */ {24,24,24,24,248,24,24,24}, + /* 196 */ {0,0,0,0,255,0,0,0}, + /* 197 */ {24,24,24,24,255,24,24,24}, + /* 198 */ {24,24,248,24,248,24,24,24}, + /* 199 */ {108,108,108,108,236,108,108,108}, + /* 200 */ {108,108,236,12,252,0,0,0}, + /* 201 */ {0,0,252,12,236,108,108,108}, + /* 202 */ {108,108,239,0,255,0,0,0}, + /* 203 */ {0,0,255,0,239,108,108,108}, + /* 204 */ {108,108,236,12,236,108,108,108}, + /* 205 */ {0,0,255,0,255,0,0,0}, + /* 206 */ {108,108,239,0,239,108,108,108}, + /* 207 */ {24,24,255,0,255,0,0,0}, + /* 208 */ {108,108,108,108,255,0,0,0}, + /* 209 */ {0,0,255,0,255,24,24,24}, + /* 210 */ {0,0,0,0,255,108,108,108}, + /* 211 */ {108,108,108,108,252,0,0,0}, + /* 212 */ {24,24,248,24,248,0,0,0}, + /* 213 */ {0,0,248,24,248,24,24,24}, + /* 214 */ {0,0,0,0,252,108,108,108}, + /* 215 */ {108,108,108,108,255,108,108,108}, + /* 216 */ {24,24,255,24,255,24,24,24}, + /* 217 */ {24,24,24,24,31,0,0,0}, + /* 218 */ {0,0,0,0,248,24,24,24}, + /* 219 */ {255,255,255,255,255,255,255,255}, + /* 220 */ {0,0,0,0,255,255,255,255}, + /* 221 */ {15,15,15,15,15,15,15,15}, + /* 222 */ {240,240,240,240,240,240,240,240}, + /* 223 */ {255,255,255,255,0,0,0,0}, + /* 224 */ {0,0,110,59,19,59,110,0}, + /* 225 */ {0,30,51,31,51,31,3,3}, + /* 226 */ {0,63,51,3,3,3,3,0}, + /* 227 */ {0,127,54,54,54,54,54,0}, + /* 228 */ {63,51,6,12,6,51,63,0}, + /* 229 */ {0,0,126,27,27,27,14,0}, + /* 230 */ {0,102,102,102,102,62,6,3}, + /* 231 */ {0,110,59,24,24,24,24,0}, + /* 232 */ {63,12,30,51,51,30,12,63}, + /* 233 */ {28,54,99,127,99,54,28,0}, + /* 234 */ {28,54,99,99,54,54,119,0}, + /* 235 */ {56,12,24,62,51,51,30,0}, + /* 236 */ {0,0,126,219,219,126,0,0}, + /* 237 */ {96,48,126,219,219,126,6,3}, + /* 238 */ {28,6,3,31,3,6,28,0}, + /* 239 */ {30,51,51,51,51,51,51,0}, + /* 240 */ {0,63,0,63,0,63,0,0}, + /* 241 */ {12,12,63,12,12,0,63,0}, + /* 242 */ {6,12,24,12,6,0,63,0}, + /* 243 */ {24,12,6,12,24,0,63,0}, + /* 244 */ {112,216,216,24,24,24,24,24}, + /* 245 */ {24,24,24,24,24,27,27,14}, + /* 246 */ {12,12,0,63,0,12,12,0}, + /* 247 */ {0,110,59,0,110,59,0,0}, + /* 248 */ {28,54,54,28,0,0,0,0}, + /* 249 */ {0,0,0,24,24,0,0,0}, + /* 250 */ {0,0,0,0,24,0,0,0}, + /* 251 */ {240,48,48,48,55,54,60,56}, + /* 252 */ {30,54,54,54,54,0,0,0}, + /* 253 */ {14,24,12,6,30,0,0,0}, + /* 254 */ {0,0,60,60,60,60,0,0}, + /* 255 */ {0,0,0,0,0,0,0,0}, + }; + + /** + * A static non-malleable MinecraftFont. + */ + @NotNull + public static final MinecraftFont Font = new MinecraftFont(false); + + /** + * Initialize a new MinecraftFont. + */ + public MinecraftFont() { + this(true); + } + + private MinecraftFont(boolean malleable) { + for (int i = 1; i < fontData.length; ++i) { + char ch = (char) i; + if (i >= 32 && i < 32 + fontChars.length()) { + ch = fontChars.charAt(i - 32); + } + + if (ch == ' ') { + setChar(ch, new CharacterSprite(spaceSize, 8, new boolean[spaceSize * 8])); + continue; + } + + int[] rows = fontData[i]; + int width = 0; + for (int r = 0; r < 8; ++r) { + for (int c = 0; c < 8; ++c) { + if ((rows[r] & (1 << c)) != 0 && c > width) { + width = c; + } + } + } + ++width; + + boolean[] data = new boolean[width * 8]; + for (int r = 0; r < 8; ++r) { + for (int c = 0; c < width; ++c) { + data[r * width + c] = (rows[r] & (1 << c)) != 0; + } + } + + setChar(ch, new CharacterSprite(width, 8, data)); + } + + this.malleable = malleable; + } + +} diff --git a/api/src/main/java/org/bukkit/material/Attachable.java b/api/src/main/java/org/bukkit/material/Attachable.java new file mode 100644 index 000000000..16abdbf92 --- /dev/null +++ b/api/src/main/java/org/bukkit/material/Attachable.java @@ -0,0 +1,18 @@ +package org.bukkit.material; + +import org.bukkit.block.BlockFace; +import org.jetbrains.annotations.NotNull; + +/** + * Indicates that a block can be attached to another block + */ +public interface Attachable extends Directional { + + /** + * Gets the face that this block is attached on + * + * @return BlockFace attached to + */ + @NotNull + public BlockFace getAttachedFace(); +} diff --git a/api/src/main/java/org/bukkit/material/Banner.java b/api/src/main/java/org/bukkit/material/Banner.java new file mode 100644 index 000000000..5f1843f9f --- /dev/null +++ b/api/src/main/java/org/bukkit/material/Banner.java @@ -0,0 +1,216 @@ +package org.bukkit.material; + +import org.bukkit.Material; +import org.bukkit.block.BlockFace; + +public class Banner extends MaterialData implements Attachable { + + public Banner() { + super(Material.LEGACY_BANNER); + } + + public Banner(Material type) { + super(type); + } + + /** + * + * @param type the type + * @param data the raw data value + * @deprecated Magic value + */ + @Deprecated + public Banner(Material type, byte data) { + super(type, data); + } + + public boolean isWallBanner() { + return getItemType() == Material.LEGACY_WALL_BANNER; + } + + public BlockFace getAttachedFace() { + if (isWallBanner()) { + byte data = getData(); + + switch (data) { + case 0x2: + return BlockFace.SOUTH; + + case 0x3: + return BlockFace.NORTH; + + case 0x4: + return BlockFace.EAST; + + case 0x5: + return BlockFace.WEST; + } + + return null; + } else { + return BlockFace.DOWN; + } + } + + public BlockFace getFacing() { + byte data = getData(); + + if (!isWallBanner()) { + switch (data) { + case 0x0: + return BlockFace.SOUTH; + + case 0x1: + return BlockFace.SOUTH_SOUTH_WEST; + + case 0x2: + return BlockFace.SOUTH_WEST; + + case 0x3: + return BlockFace.WEST_SOUTH_WEST; + + case 0x4: + return BlockFace.WEST; + + case 0x5: + return BlockFace.WEST_NORTH_WEST; + + case 0x6: + return BlockFace.NORTH_WEST; + + case 0x7: + return BlockFace.NORTH_NORTH_WEST; + + case 0x8: + return BlockFace.NORTH; + + case 0x9: + return BlockFace.NORTH_NORTH_EAST; + + case 0xA: + return BlockFace.NORTH_EAST; + + case 0xB: + return BlockFace.EAST_NORTH_EAST; + + case 0xC: + return BlockFace.EAST; + + case 0xD: + return BlockFace.EAST_SOUTH_EAST; + + case 0xE: + return BlockFace.SOUTH_EAST; + + case 0xF: + return BlockFace.SOUTH_SOUTH_EAST; + } + + return null; + } else { + return getAttachedFace().getOppositeFace(); + } + } + + public void setFacingDirection(BlockFace face) { + byte data; + + if (isWallBanner()) { + switch (face) { + case NORTH: + data = 0x2; + break; + + case SOUTH: + data = 0x3; + break; + + case WEST: + data = 0x4; + break; + + case EAST: + default: + data = 0x5; + } + } else { + switch (face) { + case SOUTH: + data = 0x0; + break; + + case SOUTH_SOUTH_WEST: + data = 0x1; + break; + + case SOUTH_WEST: + data = 0x2; + break; + + case WEST_SOUTH_WEST: + data = 0x3; + break; + + case WEST: + data = 0x4; + break; + + case WEST_NORTH_WEST: + data = 0x5; + break; + + case NORTH_WEST: + data = 0x6; + break; + + case NORTH_NORTH_WEST: + data = 0x7; + break; + + case NORTH: + data = 0x8; + break; + + case NORTH_NORTH_EAST: + data = 0x9; + break; + + case NORTH_EAST: + data = 0xA; + break; + + case EAST_NORTH_EAST: + data = 0xB; + break; + + case EAST: + data = 0xC; + break; + + case EAST_SOUTH_EAST: + data = 0xD; + break; + + case SOUTH_SOUTH_EAST: + data = 0xF; + break; + + case SOUTH_EAST: + default: + data = 0xE; + } + } + + setData(data); + } + + @Override + public String toString() { + return super.toString() + " facing " + getFacing(); + } + + @Override + public Banner clone() { + return (Banner) super.clone(); + } +} diff --git a/api/src/main/java/org/bukkit/material/Bed.java b/api/src/main/java/org/bukkit/material/Bed.java new file mode 100644 index 000000000..63965682d --- /dev/null +++ b/api/src/main/java/org/bukkit/material/Bed.java @@ -0,0 +1,125 @@ +package org.bukkit.material; + +import org.bukkit.Material; +import org.bukkit.block.BlockFace; + +/** + * Represents a bed. + */ +public class Bed extends MaterialData implements Directional { + + /** + * Default constructor for a bed. + */ + public Bed() { + super(Material.LEGACY_BED_BLOCK); + } + + /** + * Instantiate a bed facing in a particular direction. + * + * @param direction the direction the bed's head is facing + */ + public Bed(BlockFace direction) { + this(); + setFacingDirection(direction); + } + + public Bed(final Material type) { + super(type); + } + + /** + * @param type the type + * @param data the raw data value + * @deprecated Magic value + */ + @Deprecated + public Bed(final Material type, final byte data) { + super(type, data); + } + + /** + * Determine if this block represents the head of the bed + * + * @return true if this is the head of the bed, false if it is the foot + */ + public boolean isHeadOfBed() { + return (getData() & 0x8) == 0x8; + } + + /** + * Configure this to be either the head or the foot of the bed + * + * @param isHeadOfBed True to make it the head. + */ + public void setHeadOfBed(boolean isHeadOfBed) { + setData((byte) (isHeadOfBed ? (getData() | 0x8) : (getData() & ~0x8))); + } + + /** + * Set which direction the head of the bed is facing. Note that this will + * only affect one of the two blocks the bed is made of. + */ + public void setFacingDirection(BlockFace face) { + byte data; + + switch (face) { + case SOUTH: + data = 0x0; + break; + + case WEST: + data = 0x1; + break; + + case NORTH: + data = 0x2; + break; + + case EAST: + default: + data = 0x3; + } + + if (isHeadOfBed()) { + data |= 0x8; + } + + setData(data); + } + + /** + * Get the direction that this bed's head is facing toward + * + * @return the direction the head of the bed is facing + */ + public BlockFace getFacing() { + byte data = (byte) (getData() & 0x7); + + switch (data) { + case 0x0: + return BlockFace.SOUTH; + + case 0x1: + return BlockFace.WEST; + + case 0x2: + return BlockFace.NORTH; + + case 0x3: + default: + return BlockFace.EAST; + } + } + + @Override + public String toString() { + return (isHeadOfBed() ? "HEAD" : "FOOT") + " of " + super.toString() + " facing " + getFacing(); + } + + @Override + public Bed clone() { + return (Bed) super.clone(); + } +} diff --git a/api/src/main/java/org/bukkit/material/Button.java b/api/src/main/java/org/bukkit/material/Button.java new file mode 100644 index 000000000..62c499a10 --- /dev/null +++ b/api/src/main/java/org/bukkit/material/Button.java @@ -0,0 +1,123 @@ +package org.bukkit.material; + +import org.bukkit.block.BlockFace; +import org.bukkit.Material; + +/** + * Represents a button + */ +public class Button extends SimpleAttachableMaterialData implements Redstone { + public Button() { + super(Material.LEGACY_STONE_BUTTON); + } + + public Button(final Material type) { + super(type); + } + + /** + * @param type the type + * @param data the raw data value + * @deprecated Magic value + */ + @Deprecated + public Button(final Material type, final byte data) { + super(type, data); + } + + /** + * Gets the current state of this Material, indicating if it's powered or + * unpowered + * + * @return true if powered, otherwise false + */ + public boolean isPowered() { + return (getData() & 0x8) == 0x8; + } + + /** + * Sets the current state of this button + * + * @param bool + * whether or not the button is powered + */ + public void setPowered(boolean bool) { + setData((byte) (bool ? (getData() | 0x8) : (getData() & ~0x8))); + } + + /** + * Gets the face that this block is attached on + * + * @return BlockFace attached to + */ + public BlockFace getAttachedFace() { + byte data = (byte) (getData() & 0x7); + + switch (data) { + case 0x0: + return BlockFace.UP; + + case 0x1: + return BlockFace.WEST; + + case 0x2: + return BlockFace.EAST; + + case 0x3: + return BlockFace.NORTH; + + case 0x4: + return BlockFace.SOUTH; + + case 0x5: + return BlockFace.DOWN; + } + + return null; + } + + /** + * Sets the direction this button is pointing toward + */ + public void setFacingDirection(BlockFace face) { + byte data = (byte) (getData() & 0x8); + + switch (face) { + case DOWN: + data |= 0x0; + break; + + case EAST: + data |= 0x1; + break; + + case WEST: + data |= 0x2; + break; + + case SOUTH: + data |= 0x3; + break; + + case NORTH: + data |= 0x4; + break; + + case UP: + data |= 0x5; + break; + } + + setData(data); + } + + @Override + public String toString() { + return super.toString() + " " + (isPowered() ? "" : "NOT ") + "POWERED"; + } + + @Override + public Button clone() { + return (Button) super.clone(); + } +} diff --git a/api/src/main/java/org/bukkit/material/Cake.java b/api/src/main/java/org/bukkit/material/Cake.java new file mode 100644 index 000000000..25e3db486 --- /dev/null +++ b/api/src/main/java/org/bukkit/material/Cake.java @@ -0,0 +1,74 @@ +package org.bukkit.material; + +import org.bukkit.Material; + +public class Cake extends MaterialData { + public Cake() { + super(Material.LEGACY_CAKE_BLOCK); + } + + public Cake(Material type) { + super(type); + } + + /** + * @param type the type + * @param data the raw data value + * @deprecated Magic value + */ + @Deprecated + public Cake(Material type, byte data) { + super(type, data); + } + + /** + * Gets the number of slices eaten from this cake + * + * @return The number of slices eaten + */ + public int getSlicesEaten() { + return getData(); + } + + /** + * Gets the number of slices remaining on this cake + * + * @return The number of slices remaining + */ + public int getSlicesRemaining() { + return 6 - getData(); + } + + /** + * Sets the number of slices eaten from this cake + * + * @param n The number of slices eaten + */ + public void setSlicesEaten(int n) { + if (n < 6) { + setData((byte) n); + } // TODO: else destroy the block? Probably not possible though + } + + /** + * Sets the number of slices remaining on this cake + * + * @param n The number of slices remaining + */ + public void setSlicesRemaining(int n) { + if (n > 6) { + n = 6; + } + setData((byte) (6 - n)); + } + + @Override + public String toString() { + return super.toString() + " " + getSlicesEaten() + "/" + getSlicesRemaining() + " slices eaten/remaining"; + } + + @Override + public Cake clone() { + return (Cake) super.clone(); + } +} diff --git a/api/src/main/java/org/bukkit/material/Cauldron.java b/api/src/main/java/org/bukkit/material/Cauldron.java new file mode 100644 index 000000000..90e7a5aff --- /dev/null +++ b/api/src/main/java/org/bukkit/material/Cauldron.java @@ -0,0 +1,64 @@ +package org.bukkit.material; + +import org.bukkit.Material; + +/** + * Represents a cauldron + */ +public class Cauldron extends MaterialData { + private static final int CAULDRON_FULL = 3; + private static final int CAULDRON_EMPTY = 0; + + public Cauldron() { + super(Material.LEGACY_CAULDRON); + } + + /** + * + * @param type the type + * @param data the raw data value + * @deprecated Magic value + */ + @Deprecated + public Cauldron(final Material type, final byte data) { + super(type, data); + } + + /** + * + * @param data the raw data value + * @deprecated Magic value + */ + @Deprecated + public Cauldron(byte data) { + super(Material.LEGACY_CAULDRON, data); + } + + /** + * Check if the cauldron is full. + * + * @return True if it is full. + */ + public boolean isFull() { + return getData() >= CAULDRON_FULL; + } + + /** + * Check if the cauldron is empty. + * + * @return True if it is empty. + */ + public boolean isEmpty() { + return getData() <= CAULDRON_EMPTY; + } + + @Override + public String toString() { + return (isEmpty() ? "EMPTY" : (isFull() ? "FULL" : getData() + "/3 FULL")) + " CAULDRON"; + } + + @Override + public Cauldron clone() { + return (Cauldron) super.clone(); + } +} diff --git a/api/src/main/java/org/bukkit/material/Chest.java b/api/src/main/java/org/bukkit/material/Chest.java new file mode 100644 index 000000000..48ef0b8cc --- /dev/null +++ b/api/src/main/java/org/bukkit/material/Chest.java @@ -0,0 +1,43 @@ +package org.bukkit.material; + +import org.bukkit.Material; +import org.bukkit.block.BlockFace; + +/** + * Represents a chest + */ +public class Chest extends DirectionalContainer { + + public Chest() { + super(Material.LEGACY_CHEST); + } + + /** + * Instantiate a chest facing in a particular direction. + * + * @param direction the direction the chest's lit opens towards + */ + public Chest(BlockFace direction) { + this(); + setFacingDirection(direction); + } + + public Chest(final Material type) { + super(type); + } + + /** + * @param type the type + * @param data the raw data value + * @deprecated Magic value + */ + @Deprecated + public Chest(final Material type, final byte data) { + super(type, data); + } + + @Override + public Chest clone() { + return (Chest) super.clone(); + } +} diff --git a/api/src/main/java/org/bukkit/material/Coal.java b/api/src/main/java/org/bukkit/material/Coal.java new file mode 100644 index 000000000..1ac6faaf6 --- /dev/null +++ b/api/src/main/java/org/bukkit/material/Coal.java @@ -0,0 +1,60 @@ +package org.bukkit.material; + +import org.bukkit.CoalType; +import org.bukkit.Material; + +/** + * Represents the different types of coals. + */ +public class Coal extends MaterialData { + public Coal() { + super(Material.LEGACY_COAL); + } + + public Coal(CoalType type) { + this(); + setType(type); + } + + public Coal(final Material type) { + super(type); + } + + /** + * @param type the type + * @param data the raw data value + * @deprecated Magic value + */ + @Deprecated + public Coal(final Material type, final byte data) { + super(type, data); + } + + /** + * Gets the current type of this coal + * + * @return CoalType of this coal + */ + public CoalType getType() { + return CoalType.getByData(getData()); + } + + /** + * Sets the type of this coal + * + * @param type New type of this coal + */ + public void setType(CoalType type) { + setData(type.getData()); + } + + @Override + public String toString() { + return getType() + " " + super.toString(); + } + + @Override + public Coal clone() { + return (Coal) super.clone(); + } +} diff --git a/api/src/main/java/org/bukkit/material/CocoaPlant.java b/api/src/main/java/org/bukkit/material/CocoaPlant.java new file mode 100644 index 000000000..298222006 --- /dev/null +++ b/api/src/main/java/org/bukkit/material/CocoaPlant.java @@ -0,0 +1,124 @@ +package org.bukkit.material; + +import org.bukkit.Material; +import org.bukkit.block.BlockFace; + +/** + * Represents the cocoa plant + */ +public class CocoaPlant extends MaterialData implements Directional, Attachable { + + public enum CocoaPlantSize { + SMALL, + MEDIUM, + LARGE + } + + public CocoaPlant() { + super(Material.LEGACY_COCOA); + } + + /** + * @param type the type + * @param data the raw data value + * @deprecated Magic value + */ + @Deprecated + public CocoaPlant(final Material type, final byte data) { + super(type, data); + } + + public CocoaPlant(CocoaPlantSize sz) { + this(); + setSize(sz); + } + + public CocoaPlant(CocoaPlantSize sz, BlockFace dir) { + this(); + setSize(sz); + setFacingDirection(dir); + } + + /** + * Get size of plant + * + * @return size + */ + public CocoaPlantSize getSize() { + switch (getData() & 0xC) { + case 0: + return CocoaPlantSize.SMALL; + case 4: + return CocoaPlantSize.MEDIUM; + default: + return CocoaPlantSize.LARGE; + } + } + + /** + * Set size of plant + * + * @param sz - size of plant + */ + public void setSize(CocoaPlantSize sz) { + int dat = getData() & 0x3; + switch (sz) { + case SMALL: + break; + case MEDIUM: + dat |= 0x4; + break; + case LARGE: + dat |= 0x8; + break; + } + setData((byte) dat); + } + + public BlockFace getAttachedFace() { + return getFacing().getOppositeFace(); + } + + public void setFacingDirection(BlockFace face) { + int dat = getData() & 0xC; + switch (face) { + default: + case SOUTH: + break; + case WEST: + dat |= 0x1; + break; + case NORTH: + dat |= 0x2; + break; + case EAST: + dat |= 0x3; + break; + } + setData((byte) dat); + } + + public BlockFace getFacing() { + switch (getData() & 0x3) { + case 0: + return BlockFace.SOUTH; + case 1: + return BlockFace.WEST; + case 2: + return BlockFace.NORTH; + case 3: + return BlockFace.EAST; + } + return null; + } + + @Override + public CocoaPlant clone() { + return (CocoaPlant) super.clone(); + } + + @Override + public String toString() { + return super.toString() + " facing " + getFacing() + " " + getSize(); + } +} diff --git a/api/src/main/java/org/bukkit/material/Colorable.java b/api/src/main/java/org/bukkit/material/Colorable.java new file mode 100644 index 000000000..7f495e052 --- /dev/null +++ b/api/src/main/java/org/bukkit/material/Colorable.java @@ -0,0 +1,34 @@ +package org.bukkit.material; + +import org.bukkit.DyeColor; +import org.bukkit.UndefinedNullability; +import org.jetbrains.annotations.Nullable; + +/** + * An object that can be colored. + */ +public interface Colorable { + + /** + * Gets the color of this object. + *
+ * This may be null to represent the default color of an object, if the + * object has a special default color (e.g Shulkers). + * + * @return The DyeColor of this object. + */ + @Nullable + public DyeColor getColor(); + + /** + * Sets the color of this object to the specified DyeColor. + *
+ * This may be null to represent the default color of an object, if the + * object has a special default color (e.g Shulkers). + * + * @param color The color of the object, as a DyeColor. + * @throws NullPointerException if argument is null and this implementation does not support null + */ + public void setColor(@UndefinedNullability("defined by subclass") DyeColor color); + +} diff --git a/api/src/main/java/org/bukkit/material/Command.java b/api/src/main/java/org/bukkit/material/Command.java new file mode 100644 index 000000000..7f6a88d5d --- /dev/null +++ b/api/src/main/java/org/bukkit/material/Command.java @@ -0,0 +1,56 @@ +package org.bukkit.material; + +import org.bukkit.Material; + +/** + * Represents a command block + */ +public class Command extends MaterialData implements Redstone { + public Command() { + super(Material.LEGACY_COMMAND); + } + + public Command(final Material type) { + super(type); + } + + /** + * @param type the type + * @param data the raw data value + * @deprecated Magic value + */ + @Deprecated + public Command(final Material type, final byte data) { + super(type, data); + } + + /** + * Gets the current state of this Material, indicating if it's powered or + * unpowered + * + * @return true if powered, otherwise false + */ + public boolean isPowered() { + return (getData() & 1) != 0; + } + + /** + * Sets the current state of this Material + * + * @param bool + * whether or not the command block is powered + */ + public void setPowered(boolean bool) { + setData((byte) (bool ? (getData() | 1) : (getData() & -2))); + } + + @Override + public String toString() { + return super.toString() + " " + (isPowered() ? "" : "NOT ") + "POWERED"; + } + + @Override + public Command clone() { + return (Command) super.clone(); + } +} diff --git a/api/src/main/java/org/bukkit/material/Comparator.java b/api/src/main/java/org/bukkit/material/Comparator.java new file mode 100644 index 000000000..3464e8b70 --- /dev/null +++ b/api/src/main/java/org/bukkit/material/Comparator.java @@ -0,0 +1,181 @@ +package org.bukkit.material; + +import org.bukkit.Material; +import org.bukkit.block.BlockFace; + +/** + * Represents a comparator in the on or off state, in normal or subtraction mode and facing in a specific direction. + * + * @see Material#LEGACY_REDSTONE_COMPARATOR_OFF + * @see Material#LEGACY_REDSTONE_COMPARATOR_ON + */ +public class Comparator extends MaterialData implements Directional, Redstone { + protected static final BlockFace DEFAULT_DIRECTION = BlockFace.NORTH; + protected static final boolean DEFAULT_SUBTRACTION_MODE = false; + protected static final boolean DEFAULT_STATE = false; + + /** + * Constructs a comparator switched off, with the default mode (normal) and facing the default direction (north). + */ + public Comparator() { + this(DEFAULT_DIRECTION, DEFAULT_SUBTRACTION_MODE, false); + } + + /** + * Constructs a comparator switched off, with the default mode (normal) and facing the specified direction. + * + * @param facingDirection the direction the comparator is facing + * + * @see BlockFace + */ + public Comparator(BlockFace facingDirection) { + this(facingDirection, DEFAULT_SUBTRACTION_MODE, DEFAULT_STATE); + } + + /** + * Constructs a comparator switched off, with the specified mode and facing the specified direction. + * + * @param facingDirection the direction the comparator is facing + * @param isSubtraction True if the comparator is in subtraction mode, false for normal comparator operation + * + * @see BlockFace + */ + public Comparator(BlockFace facingDirection, boolean isSubtraction) { + this(facingDirection, isSubtraction, DEFAULT_STATE); + } + + /** + * Constructs a comparator switched on or off, with the specified mode and facing the specified direction. + * + * @param facingDirection the direction the comparator is facing + * @param isSubtraction True if the comparator is in subtraction mode, false for normal comparator operation + * @param state True if the comparator is in the on state + * + * @see BlockFace + */ + public Comparator(BlockFace facingDirection, boolean isSubtraction, boolean state) { + super(state ? Material.LEGACY_REDSTONE_COMPARATOR_ON : Material.LEGACY_REDSTONE_COMPARATOR_OFF); + setFacingDirection(facingDirection); + setSubtractionMode(isSubtraction); + } + + public Comparator(Material type) { + super(type); + } + + /** + * @param type the type + * @param data the raw data value + * @deprecated Magic value + */ + @Deprecated + public Comparator(Material type, byte data) { + super(type, data); + } + + /** + * Sets whether the comparator is in subtraction mode. + * + * @param isSubtraction True if the comparator is in subtraction mode, false for normal comparator operation + */ + public void setSubtractionMode(boolean isSubtraction) { + setData((byte) (getData() & 0xB | (isSubtraction ? 0x4 : 0x0))); + } + + /** + * Checks whether the comparator is in subtraction mode + * + * @return True if the comparator is in subtraction mode, false if normal comparator operation + */ + public boolean isSubtractionMode() { + return (getData() & 0x4) != 0; + } + + /** + * Sets the direction this comparator is facing + * + * @param face The direction to set this comparator to + * + * @see BlockFace + */ + @Override + public void setFacingDirection(BlockFace face) { + int data = getData() & 0xC; + + switch (face) { + case EAST: + data |= 0x1; + break; + + case SOUTH: + data |= 0x2; + break; + + case WEST: + data |= 0x3; + break; + + case NORTH: + default: + data |= 0x0; + } + + setData((byte) data); + } + + /** + * Gets the direction this comparator is facing + * + * @return The direction this comparator is facing + * + * @see BlockFace + */ + @Override + public BlockFace getFacing() { + byte data = (byte) (getData() & 0x3); + + switch (data) { + case 0x0: + default: + return BlockFace.NORTH; + + case 0x1: + return BlockFace.EAST; + + case 0x2: + return BlockFace.SOUTH; + + case 0x3: + return BlockFace.WEST; + } + } + + @Override + public String toString() { + return super.toString() + " facing " + getFacing() + " in " + (isSubtractionMode() ? "subtraction" : "comparator") + " mode"; + } + + @Override + public Comparator clone() { + return (Comparator) super.clone(); + } + + /** + * Checks if the comparator is powered + * + * @return true if the comparator is powered + */ + @Override + public boolean isPowered() { + return getItemType() == Material.LEGACY_REDSTONE_COMPARATOR_ON; + } + + /** + * Checks if the comparator is being powered + * + * @return true if the comparator is being powered + */ + public boolean isBeingPowered() { + return (getData() & 0x8) != 0; + } +} diff --git a/api/src/main/java/org/bukkit/material/Crops.java b/api/src/main/java/org/bukkit/material/Crops.java new file mode 100644 index 000000000..0706f7a2e --- /dev/null +++ b/api/src/main/java/org/bukkit/material/Crops.java @@ -0,0 +1,132 @@ +package org.bukkit.material; + +import org.bukkit.CropState; +import org.bukkit.Material; + +/** + * Represents the different types of crops in different states of growth. + * + * @see Material#LEGACY_CROPS + * @see Material#LEGACY_CARROT + * @see Material#LEGACY_POTATO + * @see Material#LEGACY_BEETROOT_BLOCK + * @see Material#LEGACY_NETHER_WARTS + */ +public class Crops extends MaterialData { + protected static final Material DEFAULT_TYPE = Material.LEGACY_CROPS; + protected static final CropState DEFAULT_STATE = CropState.SEEDED; + + /** + * Constructs a wheat crop block in the seeded state. + */ + public Crops() { + this(DEFAULT_TYPE, DEFAULT_STATE); + } + + /** + * Constructs a wheat crop block in the given growth state + * + * @param state The growth state of the crops + */ + public Crops(CropState state) { + this(DEFAULT_TYPE, state); + setState(state); + } + + /** + * Constructs a crop block of the given type and in the given growth state + * + * @param type The type of crops + * @param state The growth state of the crops + */ + public Crops(final Material type, final CropState state) { + super(type); + setState(state); + } + + /** + * Constructs a crop block of the given type and in the seeded state + * + * @param type The type of crops + */ + public Crops(final Material type) { + this(type, DEFAULT_STATE); + } + + /** + * @param type the type + * @param data the raw data value + * @deprecated Magic value + */ + @Deprecated + public Crops(final Material type, final byte data) { + super(type, data); + } + + /** + * Gets the current growth state of this crop + * + * For crops with only four growth states such as beetroot, only the values SEEDED, SMALL, TALL and RIPE will be + * returned. + * + * @return CropState of this crop + */ + public CropState getState() { + switch (getItemType()) { + case LEGACY_CROPS: + case LEGACY_CARROT: + case LEGACY_POTATO: + // Mask the data just in case top bit set + return CropState.getByData((byte) (getData() & 0x7)); + case LEGACY_BEETROOT_BLOCK: + case LEGACY_NETHER_WARTS: + // Mask the data just in case top bits are set + // Will return SEEDED, SMALL, TALL, RIPE for the three growth data values + return CropState.getByData((byte) (((getData() & 0x3) * 7 + 2) / 3)); + default: + throw new IllegalArgumentException("Block type is not a crop"); + } + } + + /** + * Sets the growth state of this crop + * + * For crops with only four growth states such as beetroot, the 8 CropStates are mapped into four states: + * + * SEEDED, SMALL, TALL and RIPE + * + * GERMINATED will change to SEEDED + * VERY_SMALL will change to SMALL + * MEDIUM will change to TALL + * VERY_TALL will change to RIPE + * + * @param state New growth state of this crop + */ + public void setState(CropState state) { + switch (getItemType()) { + case LEGACY_CROPS: + case LEGACY_CARROT: + case LEGACY_POTATO: + // Preserve the top bit in case it is set + setData((byte) ((getData() & 0x8) | state.getData())); + break; + case LEGACY_NETHER_WARTS: + case LEGACY_BEETROOT_BLOCK: + // Preserve the top bits in case they are set + setData((byte) ((getData() & 0xC) | (state.getData() >> 1))); + break; + default: + throw new IllegalArgumentException("Block type is not a crop"); + } + } + + @Override + public String toString() { + return getState() + " " + super.toString(); + } + + @Override + public Crops clone() { + return (Crops) super.clone(); + } +} diff --git a/api/src/main/java/org/bukkit/material/DetectorRail.java b/api/src/main/java/org/bukkit/material/DetectorRail.java new file mode 100644 index 000000000..32d5db523 --- /dev/null +++ b/api/src/main/java/org/bukkit/material/DetectorRail.java @@ -0,0 +1,39 @@ +package org.bukkit.material; + +import org.bukkit.Material; + +/** + * Represents a detector rail + */ +public class DetectorRail extends ExtendedRails implements PressureSensor { + public DetectorRail() { + super(Material.LEGACY_DETECTOR_RAIL); + } + + public DetectorRail(final Material type) { + super(type); + } + + /** + * @param type the type + * @param data the raw data value + * @deprecated Magic value + */ + @Deprecated + public DetectorRail(final Material type, final byte data) { + super(type, data); + } + + public boolean isPressed() { + return (getData() & 0x8) == 0x8; + } + + public void setPressed(boolean isPressed) { + setData((byte) (isPressed ? (getData() | 0x8) : (getData() & ~0x8))); + } + + @Override + public DetectorRail clone() { + return (DetectorRail) super.clone(); + } +} diff --git a/api/src/main/java/org/bukkit/material/Diode.java b/api/src/main/java/org/bukkit/material/Diode.java new file mode 100644 index 000000000..9b7d02ef6 --- /dev/null +++ b/api/src/main/java/org/bukkit/material/Diode.java @@ -0,0 +1,190 @@ +package org.bukkit.material; + +import org.bukkit.Material; +import org.bukkit.block.BlockFace; + +/** + * Represents a diode/repeater in the on or off state, with a delay and facing + * in a specific direction. + * + * @see Material#LEGACY_DIODE_BLOCK_OFF + * @see Material#LEGACY_DIODE_BLOCK_ON + */ +public class Diode extends MaterialData implements Directional, Redstone { + + protected static final BlockFace DEFAULT_DIRECTION = BlockFace.NORTH; + protected static final int DEFAULT_DELAY = 1; + protected static final boolean DEFAULT_STATE = false; + + /** + * Constructs a diode switched on, with a delay of 1 and facing the default + * direction (north). + * + * By default this constructor creates a diode that is switched on for + * backwards compatibility with past implementations. + */ + public Diode() { + this(DEFAULT_DIRECTION, DEFAULT_DELAY, true); + } + + /** + * Constructs a diode switched off, with a delay of 1 and facing the + * specified direction. + * + * @param facingDirection the direction the diode is facing + * + * @see BlockFace + */ + public Diode(BlockFace facingDirection) { + this(facingDirection, DEFAULT_DELAY, DEFAULT_STATE); + } + + /** + * Constructs a diode switched off, with the specified delay and facing the + * specified direction. + * + * @param facingDirection the direction the diode is facing + * @param delay The number of ticks (1-4) before the diode turns on after + * being powered + * + * @see BlockFace + */ + public Diode(BlockFace facingDirection, int delay) { + this(facingDirection, delay, DEFAULT_STATE); + } + + /** + * Constructs a diode switched on or off, with the specified delay and + * facing the specified direction. + * + * @param facingDirection the direction the diode is facing + * @param delay The number of ticks (1-4) before the diode turns on after + * being powered + * @param state True if the diode is in the on state + * + * @see BlockFace + */ + public Diode(BlockFace facingDirection, int delay, boolean state) { + super(state ? Material.LEGACY_DIODE_BLOCK_ON : Material.LEGACY_DIODE_BLOCK_OFF); + setFacingDirection(facingDirection); + setDelay(delay); + } + + public Diode(Material type) { + super(type); + } + + /** + * @param type the type + * @param data the raw data value + * @deprecated Magic value + */ + @Deprecated + public Diode(Material type, byte data) { + super(type, data); + } + + /** + * Sets the delay of the repeater. + * + * @param delay The new delay (1-4) + */ + public void setDelay(int delay) { + if (delay > 4) { + delay = 4; + } + if (delay < 1) { + delay = 1; + } + byte newData = (byte) (getData() & 0x3); + + setData((byte) (newData | ((delay - 1) << 2))); + } + + /** + * Gets the delay of the repeater in ticks. + * + * @return The delay (1-4) + */ + public int getDelay() { + return (getData() >> 2) + 1; + } + + /** + * Sets the direction this diode is facing. + * + * @param face The direction to set this diode to + * + * @see BlockFace + */ + @Override + public void setFacingDirection(BlockFace face) { + int delay = getDelay(); + byte data; + + switch (face) { + case EAST: + data = 0x1; + break; + case SOUTH: + data = 0x2; + break; + case WEST: + data = 0x3; + break; + case NORTH: + default: + data = 0x0; + } + + setData(data); + setDelay(delay); + } + + /** + * Gets the direction this diode is facing + * + * @return The direction this diode is facing + * + * @see BlockFace + */ + @Override + public BlockFace getFacing() { + byte data = (byte) (getData() & 0x3); + + switch (data) { + case 0x0: + default: + return BlockFace.NORTH; + + case 0x1: + return BlockFace.EAST; + + case 0x2: + return BlockFace.SOUTH; + + case 0x3: + return BlockFace.WEST; + } + } + + @Override + public String toString() { + return super.toString() + " facing " + getFacing() + " with " + getDelay() + " ticks delay"; + } + + @Override + public Diode clone() { + return (Diode) super.clone(); + } + + /** + * Checks if the diode is powered. + * + * @return true if the diode is powered + */ + @Override + public boolean isPowered() { + return getItemType() == Material.LEGACY_DIODE_BLOCK_ON; + } +} diff --git a/api/src/main/java/org/bukkit/material/Directional.java b/api/src/main/java/org/bukkit/material/Directional.java new file mode 100644 index 000000000..8c1c7b0a2 --- /dev/null +++ b/api/src/main/java/org/bukkit/material/Directional.java @@ -0,0 +1,22 @@ +package org.bukkit.material; + +import org.bukkit.block.BlockFace; +import org.jetbrains.annotations.NotNull; + +public interface Directional { + + /** + * Sets the direction that this block is facing in + * + * @param face The facing direction + */ + public void setFacingDirection(@NotNull BlockFace face); + + /** + * Gets the direction this block is facing + * + * @return the direction this block is facing + */ + @NotNull + public BlockFace getFacing(); +} diff --git a/api/src/main/java/org/bukkit/material/DirectionalContainer.java b/api/src/main/java/org/bukkit/material/DirectionalContainer.java new file mode 100644 index 000000000..514336318 --- /dev/null +++ b/api/src/main/java/org/bukkit/material/DirectionalContainer.java @@ -0,0 +1,77 @@ +package org.bukkit.material; + +import org.bukkit.Material; +import org.bukkit.block.BlockFace; + +/** + * Represents a furnace or a dispenser. + */ +public class DirectionalContainer extends MaterialData implements Directional { + + public DirectionalContainer(final Material type) { + super(type); + } + + /** + * @param type the type + * @param data the raw data value + * @deprecated Magic value + */ + @Deprecated + public DirectionalContainer(final Material type, final byte data) { + super(type, data); + } + + public void setFacingDirection(BlockFace face) { + byte data; + + switch (face) { + case NORTH: + data = 0x2; + break; + + case SOUTH: + data = 0x3; + break; + + case WEST: + data = 0x4; + break; + + case EAST: + default: + data = 0x5; + } + + setData(data); + } + + public BlockFace getFacing() { + byte data = getData(); + + switch (data) { + case 0x2: + return BlockFace.NORTH; + + case 0x3: + return BlockFace.SOUTH; + + case 0x4: + return BlockFace.WEST; + + case 0x5: + default: + return BlockFace.EAST; + } + } + + @Override + public String toString() { + return super.toString() + " facing " + getFacing(); + } + + @Override + public DirectionalContainer clone() { + return (DirectionalContainer) super.clone(); + } +} diff --git a/api/src/main/java/org/bukkit/material/Dispenser.java b/api/src/main/java/org/bukkit/material/Dispenser.java new file mode 100644 index 000000000..d078664a6 --- /dev/null +++ b/api/src/main/java/org/bukkit/material/Dispenser.java @@ -0,0 +1,95 @@ +package org.bukkit.material; + +import org.bukkit.Material; +import org.bukkit.block.BlockFace; + +/** + * Represents a dispenser. + */ +public class Dispenser extends FurnaceAndDispenser { + + public Dispenser() { + super(Material.LEGACY_DISPENSER); + } + + public Dispenser(BlockFace direction) { + this(); + setFacingDirection(direction); + } + + public Dispenser(final Material type) { + super(type); + } + + /** + * @param type the type + * @param data the raw data value + * @deprecated Magic value + */ + @Deprecated + public Dispenser(final Material type, final byte data) { + super(type, data); + } + + public void setFacingDirection(BlockFace face) { + byte data; + + switch (face) { + case DOWN: + data = 0x0; + break; + + case UP: + data = 0x1; + break; + + case NORTH: + data = 0x2; + break; + + case SOUTH: + data = 0x3; + break; + + case WEST: + data = 0x4; + break; + + case EAST: + default: + data = 0x5; + } + + setData(data); + } + + public BlockFace getFacing() { + int data = getData() & 0x7; + + switch (data) { + case 0x0: + return BlockFace.DOWN; + + case 0x1: + return BlockFace.UP; + + case 0x2: + return BlockFace.NORTH; + + case 0x3: + return BlockFace.SOUTH; + + case 0x4: + return BlockFace.WEST; + + case 0x5: + default: + return BlockFace.EAST; + } + } + + @Override + public Dispenser clone() { + return (Dispenser) super.clone(); + } +} diff --git a/api/src/main/java/org/bukkit/material/Door.java b/api/src/main/java/org/bukkit/material/Door.java new file mode 100644 index 000000000..15f2928e6 --- /dev/null +++ b/api/src/main/java/org/bukkit/material/Door.java @@ -0,0 +1,319 @@ +package org.bukkit.material; + +import org.bukkit.Material; +import org.bukkit.TreeSpecies; +import org.bukkit.block.BlockFace; + +/** + * Represents a door. + * + * This class was previously deprecated, but has been retrofitted to + * work with modern doors. Some methods are undefined dependant on isTopHalf() + * due to Minecraft's internal representation of doors. + * + * @see Material#LEGACY_WOODEN_DOOR + * @see Material#LEGACY_IRON_DOOR_BLOCK + * @see Material#LEGACY_SPRUCE_DOOR + * @see Material#LEGACY_BIRCH_DOOR + * @see Material#LEGACY_JUNGLE_DOOR + * @see Material#LEGACY_ACACIA_DOOR + * @see Material#LEGACY_DARK_OAK_DOOR + */ +public class Door extends MaterialData implements Directional, Openable { + + // This class breaks API contracts on Directional and Openable because + // of the way doors are currently implemented. Beware! + + /** + * @deprecated Artifact of old API, equivalent to new Door(Material.LEGACY_WOODEN_DOOR); + */ + @Deprecated + public Door() { + super(Material.LEGACY_WOODEN_DOOR); + } + + public Door(final Material type) { + super(type); + } + + /** + * Constructs the bottom half of a door of the given material type, facing the specified direction and set to closed + * + * @param type The type of material this door is made of. This must match the type of the block above. + * @param face The direction the door is facing. + * + * @see Material#LEGACY_WOODEN_DOOR + * @see Material#LEGACY_IRON_DOOR_BLOCK + * @see Material#LEGACY_SPRUCE_DOOR + * @see Material#LEGACY_BIRCH_DOOR + * @see Material#LEGACY_JUNGLE_DOOR + * @see Material#LEGACY_ACACIA_DOOR + * @see Material#LEGACY_DARK_OAK_DOOR + * + * @see BlockFace#WEST + * @see BlockFace#NORTH + * @see BlockFace#EAST + * @see BlockFace#SOUTH + */ + public Door(final Material type, BlockFace face) { + this(type, face, false); + } + + /** + * Constructs the bottom half of a door of the given material type, facing the specified direction and set to open + * or closed + * + * @param type The type of material this door is made of. This must match the type of the block above. + * @param face The direction the door is facing. + * @param isOpen Whether the door is currently opened. + * + * @see Material#LEGACY_WOODEN_DOOR + * @see Material#LEGACY_IRON_DOOR_BLOCK + * @see Material#LEGACY_SPRUCE_DOOR + * @see Material#LEGACY_BIRCH_DOOR + * @see Material#LEGACY_JUNGLE_DOOR + * @see Material#LEGACY_ACACIA_DOOR + * @see Material#LEGACY_DARK_OAK_DOOR + * + * @see BlockFace#WEST + * @see BlockFace#NORTH + * @see BlockFace#EAST + * @see BlockFace#SOUTH + */ + public Door(final Material type, BlockFace face, boolean isOpen) { + super(type); + setTopHalf(false); + setFacingDirection(face); + setOpen(isOpen); + } + + /** + * Constructs the top half of door of the given material type and with the hinge on the left or right + * + * @param type The type of material this door is made of. This must match the type of the block below. + * @param isHingeRight True if the hinge is on the right hand side, false if the hinge is on the left hand side. + * + * @see Material#LEGACY_WOODEN_DOOR + * @see Material#LEGACY_IRON_DOOR_BLOCK + * @see Material#LEGACY_SPRUCE_DOOR + * @see Material#LEGACY_BIRCH_DOOR + * @see Material#LEGACY_JUNGLE_DOOR + * @see Material#LEGACY_ACACIA_DOOR + * @see Material#LEGACY_DARK_OAK_DOOR + */ + public Door(final Material type, boolean isHingeRight) { + super(type); + setTopHalf(true); + setHinge(isHingeRight); + } + + /** + * Constructs the bottom half of a wooden door of the given species, facing the specified direction and set to + * closed + * + * @param species The species this wooden door is made of. This must match the species of the block above. + * @param face The direction the door is facing. + * + * @see TreeSpecies + * + * @see BlockFace#WEST + * @see BlockFace#NORTH + * @see BlockFace#EAST + * @see BlockFace#SOUTH + */ + public Door(final TreeSpecies species, BlockFace face) { + this(getWoodDoorOfSpecies(species), face, false); + } + + /** + * Constructs the bottom half of a wooden door of the given species, facing the specified direction and set to open + * or closed + * + * @param species The species this wooden door is made of. This must match the species of the block above. + * @param face The direction the door is facing. + * @param isOpen Whether the door is currently opened. + * + * @see TreeSpecies + * + * @see BlockFace#WEST + * @see BlockFace#NORTH + * @see BlockFace#EAST + * @see BlockFace#SOUTH + */ + public Door(final TreeSpecies species, BlockFace face, boolean isOpen) { + this(getWoodDoorOfSpecies(species), face, isOpen); + } + + /** + * Constructs the top half of a wooden door of the given species and with the hinge on the left or right + * + * @param species The species this wooden door is made of. This must match the species of the block below. + * @param isHingeRight True if the hinge is on the right hand side, false if the hinge is on the left hand side. + * + * @see TreeSpecies + */ + public Door(final TreeSpecies species, boolean isHingeRight) { + this(getWoodDoorOfSpecies(species), isHingeRight); + } + + /** + * @param type the type + * @param data the raw data value + * @deprecated Magic value + */ + @Deprecated + public Door(final Material type, final byte data) { + super(type, data); + } + + /** + * Returns the item type of a wooden door for the given tree species. + * + * @param species The species of wood door required. + * @return The item type for the given species. + * + * @see Material#LEGACY_WOODEN_DOOR + * @see Material#LEGACY_SPRUCE_DOOR + * @see Material#LEGACY_BIRCH_DOOR + * @see Material#LEGACY_JUNGLE_DOOR + * @see Material#LEGACY_ACACIA_DOOR + * @see Material#LEGACY_DARK_OAK_DOOR + */ + public static Material getWoodDoorOfSpecies(TreeSpecies species) { + switch (species) { + default: + case GENERIC: + return Material.LEGACY_WOODEN_DOOR; + case BIRCH: + return Material.LEGACY_BIRCH_DOOR; + case REDWOOD: + return Material.LEGACY_SPRUCE_DOOR; + case JUNGLE: + return Material.LEGACY_JUNGLE_DOOR; + case ACACIA: + return Material.LEGACY_ACACIA_DOOR; + case DARK_OAK: + return Material.LEGACY_DARK_OAK_DOOR; + } + } + + /** + * Result is undefined if isTopHalf() is true. + */ + public boolean isOpen() { + return ((getData() & 0x4) == 0x4); + } + + /** + * Set whether the door is open. Undefined if isTopHalf() is true. + */ + public void setOpen(boolean isOpen) { + setData((byte) (isOpen ? (getData() | 0x4) : (getData() & ~0x4))); + } + + /** + * @return whether this is the top half of the door + */ + public boolean isTopHalf() { + return ((getData() & 0x8) == 0x8); + } + + /** + * Configure this part of the door to be either the top or the bottom half + * + * @param isTopHalf True to make it the top half. + */ + public void setTopHalf(boolean isTopHalf) { + setData((byte) (isTopHalf ? (getData() | 0x8) : (getData() & ~0x8))); + } + + /** + * @return BlockFace.SELF + * @deprecated This method should not be used; use hinge and facing accessors instead. + */ + @Deprecated + public BlockFace getHingeCorner() { + return BlockFace.SELF; + } + + @Override + public String toString() { + return (isTopHalf() ? "TOP" : "BOTTOM") + " half of " + super.toString(); + } + + /** + * Set the direction that this door should is facing. + * + * Undefined if isTopHalf() is true. + * + * @param face the direction + */ + public void setFacingDirection(BlockFace face) { + byte data = (byte) (getData() & 0xC); + switch (face) { + case WEST: + data |= 0x0; + break; + case NORTH: + data |= 0x1; + break; + case EAST: + data |= 0x2; + break; + case SOUTH: + data |= 0x3; + break; + } + setData(data); + } + + /** + * Get the direction that this door is facing. + * + * Undefined if isTopHalf() is true. + * + * @return the direction + */ + public BlockFace getFacing() { + byte data = (byte) (getData() & 0x3); + switch (data) { + case 0: + return BlockFace.WEST; + case 1: + return BlockFace.NORTH; + case 2: + return BlockFace.EAST; + case 3: + return BlockFace.SOUTH; + default: + throw new IllegalStateException("Unknown door facing (data: " + data + ")"); + } + } + + /** + * Returns the side of the door the hinge is on. + * + * Undefined if isTopHalf() is false. + * + * @return false for left hinge, true for right hinge + */ + public boolean getHinge() { + return (getData() & 0x1) == 1; + } + + /** + * Set whether the hinge is on the left or right side. Left is false, right is true. + * + * Undefined if isTopHalf() is false. + * + * @param isHingeRight True if the hinge is on the right hand side, false if the hinge is on the left hand side. + */ + public void setHinge(boolean isHingeRight) { + setData((byte) (isHingeRight ? (getData() | 0x1) : (getData() & ~0x1))); + } + + @Override + public Door clone() { + return (Door) super.clone(); + } +} diff --git a/api/src/main/java/org/bukkit/material/Dye.java b/api/src/main/java/org/bukkit/material/Dye.java new file mode 100644 index 000000000..5752be0ee --- /dev/null +++ b/api/src/main/java/org/bukkit/material/Dye.java @@ -0,0 +1,62 @@ +package org.bukkit.material; + +import org.bukkit.DyeColor; +import org.bukkit.Material; + +/** + * Represents dye + */ +public class Dye extends MaterialData implements Colorable { + public Dye() { + super(Material.LEGACY_INK_SACK); + } + + public Dye(final Material type) { + super(type); + } + + /** + * @param type the type + * @param data the raw data value + * @deprecated Magic value + */ + @Deprecated + public Dye(final Material type, final byte data) { + super(type, data); + } + + /** + * @param color color of the dye + */ + public Dye(final DyeColor color) { + super(Material.LEGACY_INK_SACK, color.getDyeData()); + } + + /** + * Gets the current color of this dye + * + * @return DyeColor of this dye + */ + public DyeColor getColor() { + return DyeColor.getByDyeData(getData()); + } + + /** + * Sets the color of this dye + * + * @param color New color of this dye + */ + public void setColor(DyeColor color) { + setData(color.getDyeData()); + } + + @Override + public String toString() { + return getColor() + " DYE(" + getData() + ")"; + } + + @Override + public Dye clone() { + return (Dye) super.clone(); + } +} diff --git a/api/src/main/java/org/bukkit/material/EnderChest.java b/api/src/main/java/org/bukkit/material/EnderChest.java new file mode 100644 index 000000000..c03eb018f --- /dev/null +++ b/api/src/main/java/org/bukkit/material/EnderChest.java @@ -0,0 +1,43 @@ +package org.bukkit.material; + +import org.bukkit.Material; +import org.bukkit.block.BlockFace; + +/** + * Represents an ender chest + */ +public class EnderChest extends DirectionalContainer { + + public EnderChest() { + super(Material.LEGACY_ENDER_CHEST); + } + + /** + * Instantiate an ender chest facing in a particular direction. + * + * @param direction the direction the ender chest's lid opens towards + */ + public EnderChest(BlockFace direction) { + this(); + setFacingDirection(direction); + } + + public EnderChest(final Material type) { + super(type); + } + + /** + * @param type the type + * @param data the raw data value + * @deprecated Magic value + */ + @Deprecated + public EnderChest(final Material type, final byte data) { + super(type, data); + } + + @Override + public EnderChest clone() { + return (EnderChest) super.clone(); + } +} diff --git a/api/src/main/java/org/bukkit/material/ExtendedRails.java b/api/src/main/java/org/bukkit/material/ExtendedRails.java new file mode 100644 index 000000000..9d7906c11 --- /dev/null +++ b/api/src/main/java/org/bukkit/material/ExtendedRails.java @@ -0,0 +1,57 @@ +package org.bukkit.material; + +import org.bukkit.Material; +import org.bukkit.block.BlockFace; + +/** + * This is the superclass for the {@link DetectorRail} and {@link PoweredRail} + * classes + */ +public class ExtendedRails extends Rails { + + public ExtendedRails(final Material type) { + super(type); + } + + /** + * @param type the type + * @param data the raw data value + * @deprecated Magic value + */ + @Deprecated + public ExtendedRails(final Material type, final byte data) { + super(type, data); + } + + @Override + public boolean isCurve() { + return false; + } + + /** + * + * @deprecated Magic value + */ + @Deprecated + @Override + protected byte getConvertedData() { + return (byte) (getData() & 0x7); + } + + @Override + public void setDirection(BlockFace face, boolean isOnSlope) { + boolean extraBitSet = (getData() & 0x8) == 0x8; + + if (face != BlockFace.WEST && face != BlockFace.EAST && face != BlockFace.NORTH && face != BlockFace.SOUTH) { + throw new IllegalArgumentException("Detector rails and powered rails cannot be set on a curve!"); + } + + super.setDirection(face, isOnSlope); + setData((byte) (extraBitSet ? (getData() | 0x8) : (getData() & ~0x8))); + } + + @Override + public ExtendedRails clone() { + return (ExtendedRails) super.clone(); + } +} diff --git a/api/src/main/java/org/bukkit/material/FlowerPot.java b/api/src/main/java/org/bukkit/material/FlowerPot.java new file mode 100644 index 000000000..8aa6918e6 --- /dev/null +++ b/api/src/main/java/org/bukkit/material/FlowerPot.java @@ -0,0 +1,122 @@ +package org.bukkit.material; + +import org.bukkit.GrassSpecies; +import org.bukkit.Material; +import org.bukkit.TreeSpecies; + +/** + * Represents a flower pot. + * + * @deprecated Flower pots are now tile entities, use + * {@link org.bukkit.block.FlowerPot}. + */ +@Deprecated +public class FlowerPot extends MaterialData { + + /** + * Default constructor for a flower pot. + */ + public FlowerPot() { + super(Material.LEGACY_FLOWER_POT); + } + + public FlowerPot(final Material type) { + super(type); + } + + /** + * @param type the type + * @param data the raw data value + * @deprecated Magic value + */ + @Deprecated + public FlowerPot(final Material type, final byte data) { + super(type, data); + } + + /** + * Get the material in the flower pot + * + * @return material MaterialData for the block currently in the flower pot + * or null if empty + */ + public MaterialData getContents() { + switch (getData()) { + case 1: + return new MaterialData(Material.LEGACY_RED_ROSE); + case 2: + return new MaterialData(Material.LEGACY_YELLOW_FLOWER); + case 3: + return new Tree(TreeSpecies.GENERIC); + case 4: + return new Tree(TreeSpecies.REDWOOD); + case 5: + return new Tree(TreeSpecies.BIRCH); + case 6: + return new Tree(TreeSpecies.JUNGLE); + case 7: + return new MaterialData(Material.LEGACY_RED_MUSHROOM); + case 8: + return new MaterialData(Material.LEGACY_BROWN_MUSHROOM); + case 9: + return new MaterialData(Material.LEGACY_CACTUS); + case 10: + return new MaterialData(Material.LEGACY_DEAD_BUSH); + case 11: + return new LongGrass(GrassSpecies.FERN_LIKE); + default: + return null; + } + } + + /** + * Set the contents of the flower pot + * + * @param materialData MaterialData of the block to put in the flower pot. + */ + public void setContents(MaterialData materialData) { + Material mat = materialData.getItemType(); + + if (mat == Material.LEGACY_RED_ROSE) { + setData((byte) 1); + } else if (mat == Material.LEGACY_YELLOW_FLOWER) { + setData((byte) 2); + } else if (mat == Material.LEGACY_RED_MUSHROOM) { + setData((byte) 7); + } else if (mat == Material.LEGACY_BROWN_MUSHROOM) { + setData((byte) 8); + } else if (mat == Material.LEGACY_CACTUS) { + setData((byte) 9); + } else if (mat == Material.LEGACY_DEAD_BUSH) { + setData((byte) 10); + } else if (mat == Material.LEGACY_SAPLING) { + TreeSpecies species = ((Tree) materialData).getSpecies(); + + if (species == TreeSpecies.GENERIC) { + setData((byte) 3); + } else if (species == TreeSpecies.REDWOOD) { + setData((byte) 4); + } else if (species == TreeSpecies.BIRCH) { + setData((byte) 5); + } else { + setData((byte) 6); + } + } else if (mat == Material.LEGACY_LONG_GRASS) { + GrassSpecies species = ((LongGrass) materialData).getSpecies(); + + if (species == GrassSpecies.FERN_LIKE) { + setData((byte) 11); + } + } + } + + @Override + public String toString() { + return super.toString() + " containing " + getContents(); + } + + @Override + public FlowerPot clone() { + return (FlowerPot) super.clone(); + } +} diff --git a/api/src/main/java/org/bukkit/material/Furnace.java b/api/src/main/java/org/bukkit/material/Furnace.java new file mode 100644 index 000000000..1a9190fe8 --- /dev/null +++ b/api/src/main/java/org/bukkit/material/Furnace.java @@ -0,0 +1,43 @@ +package org.bukkit.material; + +import org.bukkit.Material; +import org.bukkit.block.BlockFace; + +/** + * Represents a furnace. + */ +public class Furnace extends FurnaceAndDispenser { + + public Furnace() { + super(Material.LEGACY_FURNACE); + } + + /** + * Instantiate a furnace facing in a particular direction. + * + * @param direction the direction the furnace's "opening" is facing + */ + public Furnace(BlockFace direction) { + this(); + setFacingDirection(direction); + } + + public Furnace(final Material type) { + super(type); + } + + /** + * @param type the type + * @param data the raw data value + * @deprecated Magic value + */ + @Deprecated + public Furnace(final Material type, final byte data) { + super(type, data); + } + + @Override + public Furnace clone() { + return (Furnace) super.clone(); + } +} diff --git a/api/src/main/java/org/bukkit/material/FurnaceAndDispenser.java b/api/src/main/java/org/bukkit/material/FurnaceAndDispenser.java new file mode 100644 index 000000000..fbe408308 --- /dev/null +++ b/api/src/main/java/org/bukkit/material/FurnaceAndDispenser.java @@ -0,0 +1,28 @@ +package org.bukkit.material; + +import org.bukkit.Material; + +/** + * Represents a furnace or dispenser, two types of directional containers + */ +public class FurnaceAndDispenser extends DirectionalContainer { + + public FurnaceAndDispenser(final Material type) { + super(type); + } + + /** + * @param type the type + * @param data the raw data value + * @deprecated Magic value + */ + @Deprecated + public FurnaceAndDispenser(final Material type, final byte data) { + super(type, data); + } + + @Override + public FurnaceAndDispenser clone() { + return (FurnaceAndDispenser) super.clone(); + } +} diff --git a/api/src/main/java/org/bukkit/material/Gate.java b/api/src/main/java/org/bukkit/material/Gate.java new file mode 100644 index 000000000..ff0420610 --- /dev/null +++ b/api/src/main/java/org/bukkit/material/Gate.java @@ -0,0 +1,97 @@ +package org.bukkit.material; + +import org.bukkit.Material; +import org.bukkit.block.BlockFace; + +/** + * Represents a fence gate + */ +public class Gate extends MaterialData implements Directional, Openable { + private static final byte OPEN_BIT = 0x4; + private static final byte DIR_BIT = 0x3; + private static final byte GATE_SOUTH = 0x0; + private static final byte GATE_WEST = 0x1; + private static final byte GATE_NORTH = 0x2; + private static final byte GATE_EAST = 0x3; + + public Gate() { + super(Material.LEGACY_FENCE_GATE); + } + + /** + * @param type the type + * @param data the raw data value + * @deprecated Magic value + */ + @Deprecated + public Gate(final Material type, final byte data) { + super(type, data); + } + + public Gate(byte data) { + super(Material.LEGACY_FENCE_GATE, data); + } + + public void setFacingDirection(BlockFace face) { + byte data = (byte) (getData() & ~DIR_BIT); + + switch (face) { + default: + case EAST: + data |= GATE_SOUTH; + break; + case SOUTH: + data |= GATE_WEST; + break; + case WEST: + data |= GATE_NORTH; + break; + case NORTH: + data |= GATE_EAST; + break; + } + + setData(data); + } + + public BlockFace getFacing() { + switch (getData() & DIR_BIT) { + case GATE_SOUTH: + return BlockFace.EAST; + case GATE_WEST: + return BlockFace.SOUTH; + case GATE_NORTH: + return BlockFace.WEST; + case GATE_EAST: + return BlockFace.NORTH; + } + + return BlockFace.EAST; + } + + public boolean isOpen() { + return (getData() & OPEN_BIT) > 0; + } + + public void setOpen(boolean isOpen) { + byte data = getData(); + + if (isOpen) { + data |= OPEN_BIT; + } else { + data &= ~OPEN_BIT; + } + + setData(data); + } + + @Override + public String toString() { + return (isOpen() ? "OPEN " : "CLOSED ") + " facing and opening " + getFacing(); + } + + @Override + public Gate clone() { + return (Gate) super.clone(); + } +} diff --git a/api/src/main/java/org/bukkit/material/Hopper.java b/api/src/main/java/org/bukkit/material/Hopper.java new file mode 100644 index 000000000..832d148a7 --- /dev/null +++ b/api/src/main/java/org/bukkit/material/Hopper.java @@ -0,0 +1,162 @@ +package org.bukkit.material; + +import org.bukkit.Material; +import org.bukkit.block.BlockFace; + +/** + * Represents a hopper in an active or deactivated state and facing in a + * specific direction. + * + * @see Material#HOPPER + */ +public class Hopper extends MaterialData implements Directional, Redstone { + + protected static final BlockFace DEFAULT_DIRECTION = BlockFace.DOWN; + protected static final boolean DEFAULT_ACTIVE = true; + + /** + * Constructs a hopper facing the default direction (down) and initially + * active. + */ + public Hopper() { + this(DEFAULT_DIRECTION, DEFAULT_ACTIVE); + } + + /** + * Constructs a hopper facing the specified direction and initially active. + * + * @param facingDirection the direction the hopper is facing + * + * @see BlockFace + */ + public Hopper(BlockFace facingDirection) { + this(facingDirection, DEFAULT_ACTIVE); + } + + /** + * Constructs a hopper facing the specified direction and either active or + * not. + * + * @param facingDirection the direction the hopper is facing + * @param isActive True if the hopper is initially active, false if + * deactivated + * + * @see BlockFace + */ + public Hopper(BlockFace facingDirection, boolean isActive) { + super(Material.LEGACY_HOPPER); + setFacingDirection(facingDirection); + setActive(isActive); + } + + public Hopper(Material type) { + super(type); + } + + /** + * @param type the type + * @param data the raw data value + * @deprecated Magic value + */ + @Deprecated + public Hopper(Material type, byte data) { + super(type, data); + } + + /** + * Sets whether the hopper is active or not. + * + * @param isActive True if the hopper is active, false if deactivated as if + * powered by redstone + */ + public void setActive(boolean isActive) { + setData((byte) (getData() & 0x7 | (isActive ? 0x0 : 0x8))); + } + + /** + * Checks whether the hopper is active or not. + * + * @return True if the hopper is active, false if deactivated + */ + public boolean isActive() { + return (getData() & 0x8) == 0; + } + + /** + * Sets the direction this hopper is facing + * + * @param face The direction to set this hopper to + * + * @see BlockFace + */ + @Override + public void setFacingDirection(BlockFace face) { + int data = getData() & 0x8; + + switch (face) { + case DOWN: + data |= 0x0; + break; + case NORTH: + data |= 0x2; + break; + case SOUTH: + data |= 0x3; + break; + case WEST: + data |= 0x4; + break; + case EAST: + data |= 0x5; + break; + } + + setData((byte) data); + } + + /** + * Gets the direction this hopper is facing + * + * @return The direction this hopper is facing + * + * @see BlockFace + */ + @Override + public BlockFace getFacing() { + byte data = (byte) (getData() & 0x7); + + switch (data) { + default: + case 0x0: + return BlockFace.DOWN; + case 0x2: + return BlockFace.NORTH; + case 0x3: + return BlockFace.SOUTH; + case 0x4: + return BlockFace.WEST; + case 0x5: + return BlockFace.EAST; + } + } + + @Override + public String toString() { + return super.toString() + " facing " + getFacing(); + } + + @Override + public Hopper clone() { + return (Hopper) super.clone(); + } + + /** + * Checks if the hopper is powered. + * + * @return true if the hopper is powered + */ + @Override + public boolean isPowered() { + return (getData() & 0x8) != 0; + } +} diff --git a/api/src/main/java/org/bukkit/material/Ladder.java b/api/src/main/java/org/bukkit/material/Ladder.java new file mode 100644 index 000000000..b4f9db90f --- /dev/null +++ b/api/src/main/java/org/bukkit/material/Ladder.java @@ -0,0 +1,85 @@ +package org.bukkit.material; + +import org.bukkit.block.BlockFace; +import org.bukkit.Material; + +/** + * Represents Ladder data + */ +public class Ladder extends SimpleAttachableMaterialData { + public Ladder() { + super(Material.LEGACY_LADDER); + } + + public Ladder(final Material type) { + super(type); + } + + /** + * @param type the type + * @param data the raw data value + * @deprecated Magic value + */ + @Deprecated + public Ladder(final Material type, final byte data) { + super(type, data); + } + + /** + * Gets the face that this block is attached on + * + * @return BlockFace attached to + */ + public BlockFace getAttachedFace() { + byte data = getData(); + + switch (data) { + case 0x2: + return BlockFace.SOUTH; + + case 0x3: + return BlockFace.NORTH; + + case 0x4: + return BlockFace.EAST; + + case 0x5: + return BlockFace.WEST; + } + + return null; + } + + /** + * Sets the direction this ladder is facing + */ + public void setFacingDirection(BlockFace face) { + byte data = (byte) 0x0; + + switch (face) { + case SOUTH: + data = 0x2; + break; + + case NORTH: + data = 0x3; + break; + + case EAST: + data = 0x4; + break; + + case WEST: + data = 0x5; + break; + } + + setData(data); + + } + + @Override + public Ladder clone() { + return (Ladder) super.clone(); + } +} diff --git a/api/src/main/java/org/bukkit/material/Leaves.java b/api/src/main/java/org/bukkit/material/Leaves.java new file mode 100644 index 000000000..f118fbab1 --- /dev/null +++ b/api/src/main/java/org/bukkit/material/Leaves.java @@ -0,0 +1,137 @@ +package org.bukkit.material; + +import org.bukkit.Material; +import org.bukkit.TreeSpecies; + +/** + * Represents the different types of leaf block that may be permanent or can + * decay when too far from a log. + * + * @see Material#LEGACY_LEAVES + * @see Material#LEGACY_LEAVES_2 + */ +public class Leaves extends Wood { + protected static final Material DEFAULT_TYPE = Material.LEGACY_LEAVES; + protected static final boolean DEFAULT_DECAYABLE = true; + + /** + * Constructs a leaf block. + */ + public Leaves() { + this(DEFAULT_TYPE, DEFAULT_SPECIES, DEFAULT_DECAYABLE); + } + + /** + * Constructs a leaf block of the given tree species. + * + * @param species the species of the wood block + */ + public Leaves(TreeSpecies species) { + this(DEFAULT_TYPE, species, DEFAULT_DECAYABLE); + } + + /** + * Constructs a leaf block of the given tree species and flag for whether + * this leaf block will disappear when too far from a log. + * + * @param species the species of the wood block + * @param isDecayable whether the block is permanent or can disappear + */ + public Leaves(TreeSpecies species, boolean isDecayable) { + this(DEFAULT_TYPE, species, isDecayable); + } + + /** + * Constructs a leaf block of the given type. + * + * @param type the type of leaf block + */ + public Leaves(final Material type) { + this(type, DEFAULT_SPECIES, DEFAULT_DECAYABLE); + } + + /** + * Constructs a leaf block of the given type and tree species. + * + * @param type the type of leaf block + * @param species the species of the wood block + */ + public Leaves(final Material type, TreeSpecies species) { + this(type, species, DEFAULT_DECAYABLE); + } + + /** + * Constructs a leaf block of the given type and tree species and flag for + * whether this leaf block will disappear when too far from a log. + * + * @param type the type of leaf block + * @param species the species of the wood block + * @param isDecayable whether the block is permanent or can disappear + */ + public Leaves(final Material type, TreeSpecies species, boolean isDecayable) { + super(type, species); + setDecayable(isDecayable); + } + + /** + * @param type the type + * @param data the raw data value + * @deprecated Magic value + */ + @Deprecated + public Leaves(final Material type, final byte data) { + super(type, data); + } + + /** + * Checks if this leaf block is in the process of decaying + * + * @return true if the leaf block is in the process of decaying + */ + public boolean isDecaying() { + return (getData() & 0x8) != 0; + } + + /** + * Set whether this leaf block is in the process of decaying + * + * @param isDecaying whether the block is decaying or not + */ + public void setDecaying(boolean isDecaying) { + setData((byte) ((getData() & 0x3) | (isDecaying + ? 0x8 // Clear the permanent flag to make this a decayable flag and set the decaying flag + : (getData() & 0x4)))); // Only persist the decayable flag if this is not a decaying block + } + + /** + * Checks if this leaf block is permanent or can decay when too far from a + * log + * + * @return true if the leaf block is permanent or can decay when too far + * from a log + */ + public boolean isDecayable() { + return (getData() & 0x4) == 0; + } + + /** + * Set whether this leaf block will disappear when too far from a log + * + * @param isDecayable whether the block is permanent or can disappear + */ + public void setDecayable(boolean isDecayable) { + setData((byte) ((getData() & 0x3) | (isDecayable + ? (getData() & 0x8) // Only persist the decaying flag if this is a decayable block + : 0x4))); + } + + @Override + public String toString() { + return getSpecies() + (isDecayable() ? " DECAYABLE " : " PERMANENT ") + (isDecaying() ? " DECAYING " : " ") + super.toString(); + } + + @Override + public Leaves clone() { + return (Leaves) super.clone(); + } +} diff --git a/api/src/main/java/org/bukkit/material/Lever.java b/api/src/main/java/org/bukkit/material/Lever.java new file mode 100644 index 000000000..5bc835332 --- /dev/null +++ b/api/src/main/java/org/bukkit/material/Lever.java @@ -0,0 +1,143 @@ +package org.bukkit.material; + +import org.bukkit.block.BlockFace; +import org.bukkit.Material; + +/** + * Represents a lever + */ +public class Lever extends SimpleAttachableMaterialData implements Redstone { + public Lever() { + super(Material.LEGACY_LEVER); + } + + public Lever(final Material type) { + super(type); + } + + /** + * @param type the type + * @param data the raw data value + * @deprecated Magic value + */ + @Deprecated + public Lever(final Material type, final byte data) { + super(type, data); + } + + /** + * Gets the current state of this Material, indicating if it's powered or + * unpowered + * + * @return true if powered, otherwise false + */ + public boolean isPowered() { + return (getData() & 0x8) == 0x8; + } + + /** + * Set this lever to be powered or not. + * + * @param isPowered whether the lever should be powered or not + */ + public void setPowered(boolean isPowered) { + setData((byte) (isPowered ? (getData() | 0x8) : (getData() & ~0x8))); + } + + /** + * Gets the face that this block is attached on + * + * @return BlockFace attached to + */ + public BlockFace getAttachedFace() { + byte data = (byte) (getData() & 0x7); + + switch (data) { + case 0x1: + return BlockFace.WEST; + + case 0x2: + return BlockFace.EAST; + + case 0x3: + return BlockFace.NORTH; + + case 0x4: + return BlockFace.SOUTH; + + case 0x5: + case 0x6: + return BlockFace.DOWN; + + case 0x0: + case 0x7: + return BlockFace.UP; + + } + + return null; + } + + /** + * Sets the direction this lever is pointing in + */ + public void setFacingDirection(BlockFace face) { + byte data = (byte) (getData() & 0x8); + BlockFace attach = getAttachedFace(); + + if (attach == BlockFace.DOWN) { + switch (face) { + case SOUTH: + case NORTH: + data |= 0x5; + break; + + case EAST: + case WEST: + data |= 0x6; + break; + } + } else if (attach == BlockFace.UP) { + switch (face) { + case SOUTH: + case NORTH: + data |= 0x7; + break; + + case EAST: + case WEST: + data |= 0x0; + break; + } + } else { + switch (face) { + case EAST: + data |= 0x1; + break; + + case WEST: + data |= 0x2; + break; + + case SOUTH: + data |= 0x3; + break; + + case NORTH: + data |= 0x4; + break; + } + } + setData(data); + } + + @Override + public String toString() { + return super.toString() + " facing " + getFacing() + " " + (isPowered() ? "" : "NOT ") + "POWERED"; + } + + @Override + public Lever clone() { + return (Lever) super.clone(); + } +} diff --git a/api/src/main/java/org/bukkit/material/LongGrass.java b/api/src/main/java/org/bukkit/material/LongGrass.java new file mode 100644 index 000000000..7592ca7ff --- /dev/null +++ b/api/src/main/java/org/bukkit/material/LongGrass.java @@ -0,0 +1,60 @@ +package org.bukkit.material; + +import org.bukkit.GrassSpecies; +import org.bukkit.Material; + +/** + * Represents the different types of long grasses. + */ +public class LongGrass extends MaterialData { + public LongGrass() { + super(Material.LEGACY_LONG_GRASS); + } + + public LongGrass(GrassSpecies species) { + this(); + setSpecies(species); + } + + public LongGrass(final Material type) { + super(type); + } + + /** + * @param type the type + * @param data the raw data value + * @deprecated Magic value + */ + @Deprecated + public LongGrass(final Material type, final byte data) { + super(type, data); + } + + /** + * Gets the current species of this grass + * + * @return GrassSpecies of this grass + */ + public GrassSpecies getSpecies() { + return GrassSpecies.getByData(getData()); + } + + /** + * Sets the species of this grass + * + * @param species New species of this grass + */ + public void setSpecies(GrassSpecies species) { + setData(species.getData()); + } + + @Override + public String toString() { + return getSpecies() + " " + super.toString(); + } + + @Override + public LongGrass clone() { + return (LongGrass) super.clone(); + } +} diff --git a/api/src/main/java/org/bukkit/material/MaterialData.java b/api/src/main/java/org/bukkit/material/MaterialData.java new file mode 100644 index 000000000..9f6a0ca66 --- /dev/null +++ b/api/src/main/java/org/bukkit/material/MaterialData.java @@ -0,0 +1,115 @@ +package org.bukkit.material; + +import org.bukkit.inventory.ItemStack; +import org.bukkit.Material; +import org.bukkit.block.data.BlockData; + +/** + * Handles specific metadata for certain items or blocks + * + * @deprecated all usage of MaterialData is deprecated and subject to removal. + * Use {@link BlockData}. + */ +@Deprecated +public class MaterialData implements Cloneable { + private final Material type; + private byte data = 0; + + public MaterialData(final Material type) { + this(type, (byte) 0); + } + + /** + * @param type the type + * @param data the raw data value + * @deprecated Magic value + */ + @Deprecated + public MaterialData(final Material type, final byte data) { + this.type = type; + this.data = data; + } + + /** + * Gets the raw data in this material + * + * @return Raw data + * @deprecated Magic value + */ + @Deprecated + public byte getData() { + return data; + } + + /** + * Sets the raw data of this material + * + * @param data New raw data + * @deprecated Magic value + */ + @Deprecated + public void setData(byte data) { + this.data = data; + } + + /** + * Gets the Material that this MaterialData represents + * + * @return Material represented by this MaterialData + */ + public Material getItemType() { + return type; + } + + /** + * Creates a new ItemStack based on this MaterialData + * + * @return New ItemStack containing a copy of this MaterialData + * @deprecated this method creates an ItemStack of size 0 which is not + * generally useful. Consider {@link #toItemStack(int)}. + */ + @Deprecated + public ItemStack toItemStack() { + return new ItemStack(type, 0, data); + } + + /** + * Creates a new ItemStack based on this MaterialData + * + * @param amount The stack size of the new stack + * @return New ItemStack containing a copy of this MaterialData + */ + public ItemStack toItemStack(int amount) { + return new ItemStack(type, amount, data); + } + + @Override + public String toString() { + return getItemType() + "(" + getData() + ")"; + } + + @Override + public int hashCode() { + return ((getItemType().hashCode() << 8) ^ getData()); + } + + @Override + public boolean equals(Object obj) { + if (obj != null && obj instanceof MaterialData) { + MaterialData md = (MaterialData) obj; + + return (md.getItemType() == getItemType() && md.getData() == getData()); + } else { + return false; + } + } + + @Override + public MaterialData clone() { + try { + return (MaterialData) super.clone(); + } catch (CloneNotSupportedException e) { + throw new Error(e); + } + } +} diff --git a/api/src/main/java/org/bukkit/material/MonsterEggs.java b/api/src/main/java/org/bukkit/material/MonsterEggs.java new file mode 100644 index 000000000..3e483fdf1 --- /dev/null +++ b/api/src/main/java/org/bukkit/material/MonsterEggs.java @@ -0,0 +1,50 @@ +package org.bukkit.material; + +import java.util.ArrayList; +import java.util.List; + +import org.bukkit.Material; + +/** + * Represents the different types of monster eggs + */ +public class MonsterEggs extends TexturedMaterial { + + private static final List textures = new ArrayList(); + static { + textures.add(Material.LEGACY_STONE); + textures.add(Material.LEGACY_COBBLESTONE); + textures.add(Material.LEGACY_SMOOTH_BRICK); + } + + public MonsterEggs() { + super(Material.LEGACY_MONSTER_EGGS); + } + + public MonsterEggs(final Material type) { + super((textures.contains(type)) ? Material.LEGACY_MONSTER_EGGS : type); + if (textures.contains(type)) { + setMaterial(type); + } + } + + /** + * @param type the type + * @param data the raw data value + * @deprecated Magic value + */ + @Deprecated + public MonsterEggs(final Material type, final byte data) { + super(type, data); + } + + @Override + public List getTextures() { + return textures; + } + + @Override + public MonsterEggs clone() { + return (MonsterEggs) super.clone(); + } +} diff --git a/api/src/main/java/org/bukkit/material/Mushroom.java b/api/src/main/java/org/bukkit/material/Mushroom.java new file mode 100644 index 000000000..cbc7d9bf4 --- /dev/null +++ b/api/src/main/java/org/bukkit/material/Mushroom.java @@ -0,0 +1,283 @@ +package org.bukkit.material; + +import java.util.EnumSet; +import java.util.Set; + +import org.apache.commons.lang.Validate; +import org.bukkit.Material; +import org.bukkit.block.BlockFace; +import org.bukkit.material.types.MushroomBlockTexture; + +/** + * Represents a huge mushroom block with certain combinations of faces set to + * cap, pores or stem. + * + * @see Material#LEGACY_HUGE_MUSHROOM_1 + * @see Material#LEGACY_HUGE_MUSHROOM_2 + */ +public class Mushroom extends MaterialData { + private static final byte NORTH_LIMIT = 4; + private static final byte SOUTH_LIMIT = 6; + private static final byte EAST_WEST_LIMIT = 3; + private static final byte EAST_REMAINDER = 0; + private static final byte WEST_REMAINDER = 1; + private static final byte NORTH_SOUTH_MOD = 3; + private static final byte EAST_WEST_MOD = 1; + + /** + * Constructs a brown/red mushroom block with all sides set to pores. + * + * @param shroom A brown or red mushroom material type. + * + * @see Material#LEGACY_HUGE_MUSHROOM_1 + * @see Material#LEGACY_HUGE_MUSHROOM_2 + */ + public Mushroom(Material shroom) { + super(shroom); + Validate.isTrue(shroom == Material.LEGACY_HUGE_MUSHROOM_1 || shroom == Material.LEGACY_HUGE_MUSHROOM_2, "Not a mushroom!"); + } + + /** + * Constructs a brown/red mushroom cap block with the specified face or + * faces set to cap texture. + * + * Setting any of the four sides will also set the top to cap. + * + * To set two side faces at once use e.g. north-west. + * + * Specify self to set all six faces at once. + * + * @param shroom A brown or red mushroom material type. + * @param capFace The face or faces to set to mushroom cap texture. + * + * @see Material#LEGACY_HUGE_MUSHROOM_1 + * @see Material#LEGACY_HUGE_MUSHROOM_2 + * @see BlockFace + */ + public Mushroom(Material shroom, BlockFace capFace) { + this(shroom, MushroomBlockTexture.getCapByFace(capFace)); + } + + /** + * Constructs a brown/red mushroom block with the specified textures. + * + * @param shroom A brown or red mushroom material type. + * @param texture The textured mushroom faces. + * + * @see Material#LEGACY_HUGE_MUSHROOM_1 + * @see Material#LEGACY_HUGE_MUSHROOM_2 + */ + public Mushroom(Material shroom, MushroomBlockTexture texture) { + this(shroom, texture.getData()); + } + + /** + * @param shroom the type + * @param data the raw data value + * @deprecated Magic value + */ + @Deprecated + public Mushroom(Material shroom, byte data) { + super(shroom, data); + Validate.isTrue(shroom == Material.LEGACY_HUGE_MUSHROOM_1 || shroom == Material.LEGACY_HUGE_MUSHROOM_2, "Not a mushroom!"); + } + + /** + * @return Whether this is a mushroom stem. + */ + public boolean isStem() { + return getData() == MushroomBlockTexture.STEM_SIDES.getData() || getData() == MushroomBlockTexture.ALL_STEM.getData(); + } + + /** + * Sets this to be a mushroom stem. + * + * @see MushroomBlockTexture#STEM_SIDES + * @see MushroomBlockTexture#ALL_STEM + * + * @deprecated Use + * {@link #setBlockTexture(org.bukkit.material.types.MushroomBlockTexture)} + * with {@link MushroomBlockTexture#STEM_SIDES } or + * {@link MushroomBlockTexture#ALL_STEM} + */ + @Deprecated + public void setStem() { + setData((byte) MushroomBlockTexture.STEM_SIDES.getData()); + } + + /** + * Gets the mushroom texture of this block. + * + * @return The mushroom texture of this block + */ + public MushroomBlockTexture getBlockTexture() { + return MushroomBlockTexture.getByData(getData()); + } + + /** + * Sets the mushroom texture of this block. + * + * @param texture The mushroom texture to set + */ + public void setBlockTexture(MushroomBlockTexture texture) { + setData(texture.getData()); + } + + /** + * Checks whether a face of the block is painted with cap texture. + * + * @param face The face to check. + * @return True if it is painted. + */ + public boolean isFacePainted(BlockFace face) { + byte data = getData(); + + if (data == MushroomBlockTexture.ALL_PORES.getData() || data == MushroomBlockTexture.STEM_SIDES.getData() + || data == MushroomBlockTexture.ALL_STEM.getData()) { + return false; + } + + switch (face) { + case WEST: + return data < NORTH_LIMIT; + case EAST: + return data > SOUTH_LIMIT; + case NORTH: + return data % EAST_WEST_LIMIT == EAST_REMAINDER; + case SOUTH: + return data % EAST_WEST_LIMIT == WEST_REMAINDER; + case UP: + return true; + case DOWN: + case SELF: + return data == MushroomBlockTexture.ALL_CAP.getData(); + default: + return false; + } + } + + /** + * Set a face of the block to be painted or not. Note that due to the + * nature of how the data is stored, setting a face painted or not is not + * guaranteed to leave the other faces unchanged. + * + * @param face The face to paint or unpaint. + * @param painted True if you want to paint it, false if you want the + * pores to show. + * + * @deprecated Use MushroomBlockType cap options + */ + @Deprecated + public void setFacePainted(BlockFace face, boolean painted) { + if (painted == isFacePainted(face)) { + return; + } + + byte data = getData(); + + if (data == MushroomBlockTexture.ALL_PORES.getData() || isStem()) { + data = MushroomBlockTexture.CAP_TOP.getData(); + } + if (data == MushroomBlockTexture.ALL_CAP.getData() && !painted) { + data = MushroomBlockTexture.CAP_TOP.getData(); + face = face.getOppositeFace(); + painted = true; + } + + switch (face) { + case WEST: + if (painted) { + data -= NORTH_SOUTH_MOD; + } else { + data += NORTH_SOUTH_MOD; + } + + break; + case EAST: + if (painted) { + data += NORTH_SOUTH_MOD; + } else { + data -= NORTH_SOUTH_MOD; + } + + break; + case NORTH: + if (painted) { + data += EAST_WEST_MOD; + } else { + data -= EAST_WEST_MOD; + } + + break; + case SOUTH: + if (painted) { + data -= EAST_WEST_MOD; + } else { + data += EAST_WEST_MOD; + } + + break; + case UP: + if (!painted) { + data = MushroomBlockTexture.ALL_PORES.getData(); + } + break; + case SELF: + case DOWN: + if (painted) { + data = MushroomBlockTexture.ALL_CAP.getData(); + } else { + data = MushroomBlockTexture.ALL_PORES.getData(); + } + break; + default: + throw new IllegalArgumentException("Can't paint that face of a mushroom!"); + } + + setData(data); + } + + /** + * @return A set of all faces that are currently painted (an empty set if + * it is a stem) + */ + public Set getPaintedFaces() { + EnumSet faces = EnumSet.noneOf(BlockFace.class); + + if (isFacePainted(BlockFace.WEST)) { + faces.add(BlockFace.WEST); + } + + if (isFacePainted(BlockFace.NORTH)) { + faces.add(BlockFace.NORTH); + } + + if (isFacePainted(BlockFace.SOUTH)) { + faces.add(BlockFace.SOUTH); + } + + if (isFacePainted(BlockFace.EAST)) { + faces.add(BlockFace.EAST); + } + + if (isFacePainted(BlockFace.UP)) { + faces.add(BlockFace.UP); + } + + if (isFacePainted(BlockFace.DOWN)) { + faces.add(BlockFace.DOWN); + } + + return faces; + } + + @Override + public String toString() { + return getItemType() + (isStem() ? " STEM " : " CAP ") + getPaintedFaces(); + } + + @Override + public Mushroom clone() { + return (Mushroom) super.clone(); + } +} diff --git a/api/src/main/java/org/bukkit/material/NetherWarts.java b/api/src/main/java/org/bukkit/material/NetherWarts.java new file mode 100644 index 000000000..09d5a9f89 --- /dev/null +++ b/api/src/main/java/org/bukkit/material/NetherWarts.java @@ -0,0 +1,82 @@ +package org.bukkit.material; + +import org.bukkit.Material; +import org.bukkit.NetherWartsState; + +/** + * Represents nether wart + */ +public class NetherWarts extends MaterialData { + public NetherWarts() { + super(Material.LEGACY_NETHER_WARTS); + } + + public NetherWarts(NetherWartsState state) { + this(); + setState(state); + } + + public NetherWarts(final Material type) { + super(type); + } + + /** + * @param type the type + * @param data the raw data value + * @deprecated Magic value + */ + @Deprecated + public NetherWarts(final Material type, final byte data) { + super(type, data); + } + + /** + * Gets the current growth state of this nether wart + * + * @return NetherWartsState of this nether wart + */ + public NetherWartsState getState() { + switch (getData()) { + case 0: + return NetherWartsState.SEEDED; + case 1: + return NetherWartsState.STAGE_ONE; + case 2: + return NetherWartsState.STAGE_TWO; + default: + return NetherWartsState.RIPE; + } + } + + /** + * Sets the growth state of this nether wart + * + * @param state New growth state of this nether wart + */ + public void setState(NetherWartsState state) { + switch (state) { + case SEEDED: + setData((byte) 0x0); + return; + case STAGE_ONE: + setData((byte) 0x1); + return; + case STAGE_TWO: + setData((byte) 0x2); + return; + case RIPE: + setData((byte) 0x3); + return; + } + } + + @Override + public String toString() { + return getState() + " " + super.toString(); + } + + @Override + public NetherWarts clone() { + return (NetherWarts) super.clone(); + } +} diff --git a/api/src/main/java/org/bukkit/material/Observer.java b/api/src/main/java/org/bukkit/material/Observer.java new file mode 100644 index 000000000..afedb9304 --- /dev/null +++ b/api/src/main/java/org/bukkit/material/Observer.java @@ -0,0 +1,98 @@ +package org.bukkit.material; + +import org.bukkit.Material; +import org.bukkit.block.BlockFace; + +/** + * Represents an observer. + */ +public class Observer extends MaterialData implements Directional, Redstone { + + public Observer() { + super(Material.LEGACY_OBSERVER); + } + + public Observer(BlockFace direction) { + this(); + setFacingDirection(direction); + } + + public Observer(final Material type) { + super(type); + } + + /** + * @param type the type + * @param data the raw data value + * @deprecated Magic value + */ + @Deprecated + public Observer(final Material type, final byte data) { + super(type, data); + } + + @Override + public boolean isPowered() { + return (getData() & 0x8) == 0x8; + } + + @Override + public void setFacingDirection(BlockFace face) { + byte data = (byte) (getData() & 0x8); + + switch (face) { + case DOWN: + data |= 0x0; + break; + case UP: + data |= 0x1; + break; + case SOUTH: + data |= 0x2; + break; + case NORTH: + data |= 0x3; + break; + case EAST: + data |= 0x4; + break; + case WEST: + data |= 0x5; + break; + } + + setData(data); + } + + @Override + public BlockFace getFacing() { + int data = getData() & 0x7; + + switch (data) { + case 0x0: + return BlockFace.DOWN; + case 0x1: + return BlockFace.UP; + case 0x2: + return BlockFace.SOUTH; + case 0x3: + return BlockFace.NORTH; + case 0x4: + return BlockFace.EAST; + case 0x5: + return BlockFace.WEST; + default: + throw new IllegalArgumentException("Illegal facing direction " + data); + } + } + + @Override + public String toString() { + return super.toString() + " facing " + getFacing(); + } + + @Override + public Observer clone() { + return (Observer) super.clone(); + } +} diff --git a/api/src/main/java/org/bukkit/material/Openable.java b/api/src/main/java/org/bukkit/material/Openable.java new file mode 100644 index 000000000..0ae54f973 --- /dev/null +++ b/api/src/main/java/org/bukkit/material/Openable.java @@ -0,0 +1,18 @@ +package org.bukkit.material; + +public interface Openable { + + /** + * Check to see if the door is open. + * + * @return true if the door has swung counterclockwise around its hinge. + */ + boolean isOpen(); + + /** + * Configure this door to be either open or closed; + * + * @param isOpen True to open the door. + */ + void setOpen(boolean isOpen); +} diff --git a/api/src/main/java/org/bukkit/material/PistonBaseMaterial.java b/api/src/main/java/org/bukkit/material/PistonBaseMaterial.java new file mode 100644 index 000000000..805f9e57c --- /dev/null +++ b/api/src/main/java/org/bukkit/material/PistonBaseMaterial.java @@ -0,0 +1,100 @@ +package org.bukkit.material; + +import org.bukkit.Material; +import org.bukkit.block.BlockFace; + +/** + * Material data for the piston base block + */ +public class PistonBaseMaterial extends MaterialData implements Directional, Redstone { + + public PistonBaseMaterial(final Material type) { + super(type); + } + + /** + * Constructs a PistonBaseMaterial. + * + * @param type the material type to use + * @param data the raw data value + * @deprecated Magic value + */ + @Deprecated + public PistonBaseMaterial(final Material type, final byte data) { + super(type, data); + } + + @Override + public void setFacingDirection(BlockFace face) { + byte data = (byte) (getData() & 0x8); + + switch (face) { + case UP: + data |= 1; + break; + case NORTH: + data |= 2; + break; + case SOUTH: + data |= 3; + break; + case WEST: + data |= 4; + break; + case EAST: + data |= 5; + break; + } + setData(data); + } + + @Override + public BlockFace getFacing() { + byte dir = (byte) (getData() & 7); + + switch (dir) { + case 0: + return BlockFace.DOWN; + case 1: + return BlockFace.UP; + case 2: + return BlockFace.NORTH; + case 3: + return BlockFace.SOUTH; + case 4: + return BlockFace.WEST; + case 5: + return BlockFace.EAST; + default: + return BlockFace.SELF; + } + } + + @Override + public boolean isPowered() { + return (getData() & 0x8) == 0x8; + } + + /** + * Sets the current state of this piston + * + * @param powered true if the piston is extended {@literal &} powered, or false + */ + public void setPowered(boolean powered) { + setData((byte) (powered ? (getData() | 0x8) : (getData() & ~0x8))); + } + + /** + * Checks if this piston base is sticky, and returns true if so + * + * @return true if this piston is "sticky", or false + */ + public boolean isSticky() { + return this.getItemType() == Material.LEGACY_PISTON_STICKY_BASE; + } + + @Override + public PistonBaseMaterial clone() { + return (PistonBaseMaterial) super.clone(); + } +} diff --git a/api/src/main/java/org/bukkit/material/PistonExtensionMaterial.java b/api/src/main/java/org/bukkit/material/PistonExtensionMaterial.java new file mode 100644 index 000000000..f293b9ca3 --- /dev/null +++ b/api/src/main/java/org/bukkit/material/PistonExtensionMaterial.java @@ -0,0 +1,95 @@ +package org.bukkit.material; + +import org.bukkit.Material; +import org.bukkit.block.BlockFace; + +/** + * Material data for the piston extension block + */ +public class PistonExtensionMaterial extends MaterialData implements Attachable { + + public PistonExtensionMaterial(final Material type) { + super(type); + } + + /** + * @param type the type + * @param data the raw data value + * @deprecated Magic value + */ + @Deprecated + public PistonExtensionMaterial(final Material type, final byte data) { + super(type, data); + } + + public void setFacingDirection(BlockFace face) { + byte data = (byte) (getData() & 0x8); + + switch (face) { + case UP: + data |= 1; + break; + case NORTH: + data |= 2; + break; + case SOUTH: + data |= 3; + break; + case WEST: + data |= 4; + break; + case EAST: + data |= 5; + break; + } + setData(data); + } + + public BlockFace getFacing() { + byte dir = (byte) (getData() & 7); + + switch (dir) { + case 0: + return BlockFace.DOWN; + case 1: + return BlockFace.UP; + case 2: + return BlockFace.NORTH; + case 3: + return BlockFace.SOUTH; + case 4: + return BlockFace.WEST; + case 5: + return BlockFace.EAST; + default: + return BlockFace.SELF; + } + } + + /** + * Checks if this piston extension is sticky, and returns true if so + * + * @return true if this piston is "sticky", or false + */ + public boolean isSticky() { + return (getData() & 8) == 8; + } + + /** + * Sets whether or not this extension is sticky + * + * @param sticky true if sticky, otherwise false + */ + public void setSticky(boolean sticky) { + setData((byte) (sticky ? (getData() | 0x8) : (getData() & ~0x8))); + } + + public BlockFace getAttachedFace() { + return getFacing().getOppositeFace(); + } + + @Override + public PistonExtensionMaterial clone() { + return (PistonExtensionMaterial) super.clone(); + } +} diff --git a/api/src/main/java/org/bukkit/material/PoweredRail.java b/api/src/main/java/org/bukkit/material/PoweredRail.java new file mode 100644 index 000000000..bdccff944 --- /dev/null +++ b/api/src/main/java/org/bukkit/material/PoweredRail.java @@ -0,0 +1,44 @@ +package org.bukkit.material; + +import org.bukkit.Material; + +/** + * Represents a powered rail + */ +public class PoweredRail extends ExtendedRails implements Redstone { + public PoweredRail() { + super(Material.LEGACY_POWERED_RAIL); + } + + public PoweredRail(final Material type) { + super(type); + } + + /** + * @param type the type + * @param data the raw data value + * @deprecated Magic value + */ + @Deprecated + public PoweredRail(final Material type, final byte data) { + super(type, data); + } + + public boolean isPowered() { + return (getData() & 0x8) == 0x8; + } + + /** + * Set whether this PoweredRail should be powered or not. + * + * @param isPowered whether or not the rail is powered + */ + public void setPowered(boolean isPowered) { + setData((byte) (isPowered ? (getData() | 0x8) : (getData() & ~0x8))); + } + + @Override + public PoweredRail clone() { + return (PoweredRail) super.clone(); + } +} diff --git a/api/src/main/java/org/bukkit/material/PressurePlate.java b/api/src/main/java/org/bukkit/material/PressurePlate.java new file mode 100644 index 000000000..95ce6648f --- /dev/null +++ b/api/src/main/java/org/bukkit/material/PressurePlate.java @@ -0,0 +1,40 @@ +package org.bukkit.material; + +import org.bukkit.Material; + +/** + * Represents a pressure plate + */ +public class PressurePlate extends MaterialData implements PressureSensor { + public PressurePlate() { + super(Material.LEGACY_WOOD_PLATE); + } + + public PressurePlate(Material type) { + super(type); + } + + /** + * @param type the type + * @param data the raw data value + * @deprecated Magic value + */ + @Deprecated + public PressurePlate(Material type, byte data) { + super(type, data); + } + + public boolean isPressed() { + return getData() == 0x1; + } + + @Override + public String toString() { + return super.toString() + (isPressed() ? " PRESSED" : ""); + } + + @Override + public PressurePlate clone() { + return (PressurePlate) super.clone(); + } +} diff --git a/api/src/main/java/org/bukkit/material/PressureSensor.java b/api/src/main/java/org/bukkit/material/PressureSensor.java new file mode 100644 index 000000000..de20bd39c --- /dev/null +++ b/api/src/main/java/org/bukkit/material/PressureSensor.java @@ -0,0 +1,5 @@ +package org.bukkit.material; + +public interface PressureSensor { + public boolean isPressed(); +} diff --git a/api/src/main/java/org/bukkit/material/Pumpkin.java b/api/src/main/java/org/bukkit/material/Pumpkin.java new file mode 100644 index 000000000..1fad246bf --- /dev/null +++ b/api/src/main/java/org/bukkit/material/Pumpkin.java @@ -0,0 +1,95 @@ +package org.bukkit.material; + +import org.bukkit.Material; +import org.bukkit.block.BlockFace; + +/** + * Represents a pumpkin. + */ +public class Pumpkin extends MaterialData implements Directional { + + public Pumpkin() { + super(Material.LEGACY_PUMPKIN); + } + + /** + * Instantiate a pumpkin facing in a particular direction. + * + * @param direction the direction the pumkin's face is facing + */ + public Pumpkin(BlockFace direction) { + this(); + setFacingDirection(direction); + } + + public Pumpkin(final Material type) { + super(type); + } + + /** + * @param type the type + * @param data the raw data value + * @deprecated Magic value + */ + @Deprecated + public Pumpkin(final Material type, final byte data) { + super(type, data); + } + + public boolean isLit() { + return getItemType() == Material.LEGACY_JACK_O_LANTERN; + } + + public void setFacingDirection(BlockFace face) { + byte data; + + switch (face) { + case NORTH: + data = 0x0; + break; + + case EAST: + data = 0x1; + break; + + case SOUTH: + data = 0x2; + break; + + case WEST: + default: + data = 0x3; + } + + setData(data); + } + + public BlockFace getFacing() { + byte data = getData(); + + switch (data) { + case 0x0: + return BlockFace.NORTH; + + case 0x1: + return BlockFace.EAST; + + case 0x2: + return BlockFace.SOUTH; + + case 0x3: + default: + return BlockFace.EAST; + } + } + + @Override + public String toString() { + return super.toString() + " facing " + getFacing() + " " + (isLit() ? "" : "NOT ") + "LIT"; + } + + @Override + public Pumpkin clone() { + return (Pumpkin) super.clone(); + } +} diff --git a/api/src/main/java/org/bukkit/material/Rails.java b/api/src/main/java/org/bukkit/material/Rails.java new file mode 100644 index 000000000..2c2420efe --- /dev/null +++ b/api/src/main/java/org/bukkit/material/Rails.java @@ -0,0 +1,159 @@ +package org.bukkit.material; + +import org.bukkit.Material; +import org.bukkit.block.BlockFace; + +/** + * Represents minecart rails. + */ +public class Rails extends MaterialData { + + public Rails() { + super(Material.LEGACY_RAILS); + } + + public Rails(final Material type) { + super(type); + } + + /** + * @param type the type + * @param data the raw data value + * @deprecated Magic value + */ + @Deprecated + public Rails(final Material type, final byte data) { + super(type, data); + } + + /** + * @return the whether this track is set on a slope + */ + public boolean isOnSlope() { + byte d = getConvertedData(); + + return (d == 0x2 || d == 0x3 || d == 0x4 || d == 0x5); + } + + /** + * @return the whether this track is set as a curve + */ + public boolean isCurve() { + byte d = getConvertedData(); + + return (d == 0x6 || d == 0x7 || d == 0x8 || d == 0x9); + } + + /** + * @return the direction these tracks are set + *

+ * Note that tracks are bidirectional and that the direction returned + * is the ascending direction if the track is set on a slope. If it is + * set as a curve, the corner of the track is returned. + */ + public BlockFace getDirection() { + byte d = getConvertedData(); + + switch (d) { + case 0x0: + default: + return BlockFace.SOUTH; + + case 0x1: + return BlockFace.EAST; + + case 0x2: + return BlockFace.EAST; + + case 0x3: + return BlockFace.WEST; + + case 0x4: + return BlockFace.NORTH; + + case 0x5: + return BlockFace.SOUTH; + + case 0x6: + return BlockFace.NORTH_WEST; + + case 0x7: + return BlockFace.NORTH_EAST; + + case 0x8: + return BlockFace.SOUTH_EAST; + + case 0x9: + return BlockFace.SOUTH_WEST; + } + } + + @Override + public String toString() { + return super.toString() + " facing " + getDirection() + (isCurve() ? " on a curve" : (isOnSlope() ? " on a slope" : "")); + } + + /** + * Return the data without the extended properties used by {@link + * PoweredRail} and {@link DetectorRail}. Overridden in {@link + * ExtendedRails} + * + * @return the data without the extended part + * @deprecated Magic value + */ + @Deprecated + protected byte getConvertedData() { + return getData(); + } + + /** + * Set the direction of these tracks + *

+ * Note that tracks are bidirectional and that the direction returned is + * the ascending direction if the track is set on a slope. If it is set as + * a curve, the corner of the track should be supplied. + * + * @param face the direction the track should be facing + * @param isOnSlope whether or not the track should be on a slope + */ + public void setDirection(BlockFace face, boolean isOnSlope) { + switch (face) { + case EAST: + setData((byte) (isOnSlope ? 0x2 : 0x1)); + break; + + case WEST: + setData((byte) (isOnSlope ? 0x3 : 0x1)); + break; + + case NORTH: + setData((byte) (isOnSlope ? 0x4 : 0x0)); + break; + + case SOUTH: + setData((byte) (isOnSlope ? 0x5 : 0x0)); + break; + + case NORTH_WEST: + setData((byte) 0x6); + break; + + case NORTH_EAST: + setData((byte) 0x7); + break; + + case SOUTH_EAST: + setData((byte) 0x8); + break; + + case SOUTH_WEST: + setData((byte) 0x9); + break; + } + } + + @Override + public Rails clone() { + return (Rails) super.clone(); + } +} diff --git a/api/src/main/java/org/bukkit/material/Redstone.java b/api/src/main/java/org/bukkit/material/Redstone.java new file mode 100644 index 000000000..3e46603f8 --- /dev/null +++ b/api/src/main/java/org/bukkit/material/Redstone.java @@ -0,0 +1,15 @@ +package org.bukkit.material; + +/** + * Indicated a Material that may carry or create a Redstone current + */ +public interface Redstone { + + /** + * Gets the current state of this Material, indicating if it's powered or + * unpowered + * + * @return true if powered, otherwise false + */ + public boolean isPowered(); +} diff --git a/api/src/main/java/org/bukkit/material/RedstoneTorch.java b/api/src/main/java/org/bukkit/material/RedstoneTorch.java new file mode 100644 index 000000000..b42ec940d --- /dev/null +++ b/api/src/main/java/org/bukkit/material/RedstoneTorch.java @@ -0,0 +1,46 @@ +package org.bukkit.material; + +import org.bukkit.Material; + +/** + * Represents a redstone torch + */ +public class RedstoneTorch extends Torch implements Redstone { + public RedstoneTorch() { + super(Material.LEGACY_REDSTONE_TORCH_ON); + } + + public RedstoneTorch(final Material type) { + super(type); + } + + /** + * @param type the type + * @param data the raw data value + * @deprecated Magic value + */ + @Deprecated + public RedstoneTorch(final Material type, final byte data) { + super(type, data); + } + + /** + * Gets the current state of this Material, indicating if it's powered or + * unpowered + * + * @return true if powered, otherwise false + */ + public boolean isPowered() { + return getItemType() == Material.LEGACY_REDSTONE_TORCH_ON; + } + + @Override + public String toString() { + return super.toString() + " " + (isPowered() ? "" : "NOT ") + "POWERED"; + } + + @Override + public RedstoneTorch clone() { + return (RedstoneTorch) super.clone(); + } +} diff --git a/api/src/main/java/org/bukkit/material/RedstoneWire.java b/api/src/main/java/org/bukkit/material/RedstoneWire.java new file mode 100644 index 000000000..709c395a5 --- /dev/null +++ b/api/src/main/java/org/bukkit/material/RedstoneWire.java @@ -0,0 +1,46 @@ +package org.bukkit.material; + +import org.bukkit.Material; + +/** + * Represents redstone wire + */ +public class RedstoneWire extends MaterialData implements Redstone { + public RedstoneWire() { + super(Material.LEGACY_REDSTONE_WIRE); + } + + public RedstoneWire(final Material type) { + super(type); + } + + /** + * @param type the type + * @param data the raw data value + * @deprecated Magic value + */ + @Deprecated + public RedstoneWire(final Material type, final byte data) { + super(type, data); + } + + /** + * Gets the current state of this Material, indicating if it's powered or + * unpowered + * + * @return true if powered, otherwise false + */ + public boolean isPowered() { + return getData() > 0; + } + + @Override + public String toString() { + return super.toString() + " " + (isPowered() ? "" : "NOT ") + "POWERED"; + } + + @Override + public RedstoneWire clone() { + return (RedstoneWire) super.clone(); + } +} diff --git a/api/src/main/java/org/bukkit/material/Sandstone.java b/api/src/main/java/org/bukkit/material/Sandstone.java new file mode 100644 index 000000000..c2f882c22 --- /dev/null +++ b/api/src/main/java/org/bukkit/material/Sandstone.java @@ -0,0 +1,60 @@ +package org.bukkit.material; + +import org.bukkit.Material; +import org.bukkit.SandstoneType; + +/** + * Represents the different types of sandstone. + */ +public class Sandstone extends MaterialData { + public Sandstone() { + super(Material.LEGACY_SANDSTONE); + } + + public Sandstone(SandstoneType type) { + this(); + setType(type); + } + + public Sandstone(final Material type) { + super(type); + } + + /** + * @param type the type + * @param data the raw data value + * @deprecated Magic value + */ + @Deprecated + public Sandstone(final Material type, final byte data) { + super(type, data); + } + + /** + * Gets the current type of this sandstone + * + * @return SandstoneType of this sandstone + */ + public SandstoneType getType() { + return SandstoneType.getByData(getData()); + } + + /** + * Sets the type of this sandstone + * + * @param type New type of this sandstone + */ + public void setType(SandstoneType type) { + setData(type.getData()); + } + + @Override + public String toString() { + return getType() + " " + super.toString(); + } + + @Override + public Sandstone clone() { + return (Sandstone) super.clone(); + } +} diff --git a/api/src/main/java/org/bukkit/material/Sapling.java b/api/src/main/java/org/bukkit/material/Sapling.java new file mode 100644 index 000000000..db2bae9ff --- /dev/null +++ b/api/src/main/java/org/bukkit/material/Sapling.java @@ -0,0 +1,111 @@ +package org.bukkit.material; + +import org.bukkit.Material; +import org.bukkit.TreeSpecies; + +/** + * Represents the different types of Tree block that face a direction. + * + * @see Material#LEGACY_SAPLING + */ +public class Sapling extends Wood { + + /** + * Constructs a sapling. + */ + public Sapling() { + this(DEFAULT_SPECIES); + } + + /** + * Constructs a sapling of the given tree species. + * + * @param species the species of the sapling + */ + public Sapling(TreeSpecies species) { + this(species, false); + } + + /** + * Constructs a sapling of the given tree species and if is it instant + * growable + * + * @param species the species of the tree block + * @param isInstantGrowable true if the Sapling should grow when next ticked with bonemeal + */ + public Sapling(TreeSpecies species, boolean isInstantGrowable) { + this(Material.LEGACY_SAPLING, species, isInstantGrowable); + } + + /** + * Constructs a sapling of the given type. + * + * @param type the type of tree block + */ + public Sapling(final Material type) { + this(type, DEFAULT_SPECIES, false); + } + + /** + * Constructs a sapling of the given type and tree species. + * + * @param type the type of sapling + * @param species the species of the sapling + */ + public Sapling(final Material type, TreeSpecies species) { + this(type, species, false); + } + + /** + * Constructs a sapling of the given type and tree species and if is it + * instant growable + * + * @param type the type of sapling + * @param species the species of the sapling + * @param isInstantGrowable true if the Sapling should grow when next ticked + * with bonemeal + */ + public Sapling(final Material type, TreeSpecies species, boolean isInstantGrowable) { + super(type, species); + setIsInstantGrowable(isInstantGrowable); + } + + /** + * @param type the type + * @param data the raw data value + * @deprecated Magic value + */ + @Deprecated + public Sapling(final Material type, final byte data) { + super(type, data); + } + + /** + * Checks if the Sapling would grow when next ticked with bonemeal + * + * @return true if the Sapling would grow when next ticked with bonemeal + */ + public boolean isInstantGrowable() { + return (getData() & 0x8) == 0x8; + } + + /** + * Set whether this sapling will grow when next ticked with bonemeal + * + * @param isInstantGrowable true if the Sapling should grow when next ticked + * with bonemeal + */ + public void setIsInstantGrowable(boolean isInstantGrowable) { + setData(isInstantGrowable ? (byte) ((getData() & 0x7) | 0x8) : (byte) (getData() & 0x7)); + } + + @Override + public String toString() { + return getSpecies() + " " + (isInstantGrowable() ? " IS_INSTANT_GROWABLE " : "") + " " + super.toString(); + } + + @Override + public Sapling clone() { + return (Sapling) super.clone(); + } +} diff --git a/api/src/main/java/org/bukkit/material/Sign.java b/api/src/main/java/org/bukkit/material/Sign.java new file mode 100644 index 000000000..d33b8ed21 --- /dev/null +++ b/api/src/main/java/org/bukkit/material/Sign.java @@ -0,0 +1,233 @@ +package org.bukkit.material; + +import org.bukkit.block.BlockFace; +import org.bukkit.Material; + +/** + * MaterialData for signs + */ +public class Sign extends MaterialData implements Attachable { + public Sign() { + super(Material.LEGACY_SIGN_POST); + } + + public Sign(final Material type) { + super(type); + } + + /** + * @param type the raw type id + * @param data the raw data value + * @deprecated Magic value + */ + @Deprecated + public Sign(final Material type, final byte data) { + super(type, data); + } + + /** + * Check if this sign is attached to a wall + * + * @return true if this sign is attached to a wall, false if set on top of + * a block + */ + public boolean isWallSign() { + return getItemType() == Material.LEGACY_WALL_SIGN; + } + + /** + * Gets the face that this block is attached on + * + * @return BlockFace attached to + */ + public BlockFace getAttachedFace() { + if (isWallSign()) { + byte data = getData(); + + switch (data) { + case 0x2: + return BlockFace.SOUTH; + + case 0x3: + return BlockFace.NORTH; + + case 0x4: + return BlockFace.EAST; + + case 0x5: + return BlockFace.WEST; + } + + return null; + } else { + return BlockFace.DOWN; + } + } + + /** + * Gets the direction that this sign is currently facing + * + * @return BlockFace indicating where this sign is facing + */ + public BlockFace getFacing() { + byte data = getData(); + + if (!isWallSign()) { + switch (data) { + case 0x0: + return BlockFace.SOUTH; + + case 0x1: + return BlockFace.SOUTH_SOUTH_WEST; + + case 0x2: + return BlockFace.SOUTH_WEST; + + case 0x3: + return BlockFace.WEST_SOUTH_WEST; + + case 0x4: + return BlockFace.WEST; + + case 0x5: + return BlockFace.WEST_NORTH_WEST; + + case 0x6: + return BlockFace.NORTH_WEST; + + case 0x7: + return BlockFace.NORTH_NORTH_WEST; + + case 0x8: + return BlockFace.NORTH; + + case 0x9: + return BlockFace.NORTH_NORTH_EAST; + + case 0xA: + return BlockFace.NORTH_EAST; + + case 0xB: + return BlockFace.EAST_NORTH_EAST; + + case 0xC: + return BlockFace.EAST; + + case 0xD: + return BlockFace.EAST_SOUTH_EAST; + + case 0xE: + return BlockFace.SOUTH_EAST; + + case 0xF: + return BlockFace.SOUTH_SOUTH_EAST; + } + + return null; + } else { + return getAttachedFace().getOppositeFace(); + } + } + + public void setFacingDirection(BlockFace face) { + byte data; + + if (isWallSign()) { + switch (face) { + case NORTH: + data = 0x2; + break; + + case SOUTH: + data = 0x3; + break; + + case WEST: + data = 0x4; + break; + + case EAST: + default: + data = 0x5; + } + } else { + switch (face) { + case SOUTH: + data = 0x0; + break; + + case SOUTH_SOUTH_WEST: + data = 0x1; + break; + + case SOUTH_WEST: + data = 0x2; + break; + + case WEST_SOUTH_WEST: + data = 0x3; + break; + + case WEST: + data = 0x4; + break; + + case WEST_NORTH_WEST: + data = 0x5; + break; + + case NORTH_WEST: + data = 0x6; + break; + + case NORTH_NORTH_WEST: + data = 0x7; + break; + + case NORTH: + data = 0x8; + break; + + case NORTH_NORTH_EAST: + data = 0x9; + break; + + case NORTH_EAST: + data = 0xA; + break; + + case EAST_NORTH_EAST: + data = 0xB; + break; + + case EAST: + data = 0xC; + break; + + case EAST_SOUTH_EAST: + data = 0xD; + break; + + case SOUTH_SOUTH_EAST: + data = 0xF; + break; + + case SOUTH_EAST: + default: + data = 0xE; + } + } + + setData(data); + } + + @Override + public String toString() { + return super.toString() + " facing " + getFacing(); + } + + @Override + public Sign clone() { + return (Sign) super.clone(); + } +} diff --git a/api/src/main/java/org/bukkit/material/SimpleAttachableMaterialData.java b/api/src/main/java/org/bukkit/material/SimpleAttachableMaterialData.java new file mode 100644 index 000000000..fb92f6587 --- /dev/null +++ b/api/src/main/java/org/bukkit/material/SimpleAttachableMaterialData.java @@ -0,0 +1,44 @@ +package org.bukkit.material; + +import org.bukkit.Material; +import org.bukkit.block.BlockFace; + +/** + * Simple utility class for attachable MaterialData subclasses + */ +public abstract class SimpleAttachableMaterialData extends MaterialData implements Attachable { + + public SimpleAttachableMaterialData(Material type, BlockFace direction) { + this(type); + setFacingDirection(direction); + } + + public SimpleAttachableMaterialData(Material type) { + super(type); + } + + /** + * @param type the type + * @param data the raw data value + * @deprecated Magic value + */ + @Deprecated + public SimpleAttachableMaterialData(Material type, byte data) { + super(type, data); + } + + public BlockFace getFacing() { + BlockFace attachedFace = getAttachedFace(); + return attachedFace == null ? null : attachedFace.getOppositeFace(); + } + + @Override + public String toString() { + return super.toString() + " facing " + getFacing(); + } + + @Override + public SimpleAttachableMaterialData clone() { + return (SimpleAttachableMaterialData) super.clone(); + } +} diff --git a/api/src/main/java/org/bukkit/material/Skull.java b/api/src/main/java/org/bukkit/material/Skull.java new file mode 100644 index 000000000..a0abe7483 --- /dev/null +++ b/api/src/main/java/org/bukkit/material/Skull.java @@ -0,0 +1,97 @@ +package org.bukkit.material; + +import org.bukkit.Material; +import org.bukkit.block.BlockFace; + +/** + * Represents a skull. + */ +public class Skull extends MaterialData implements Directional { + public Skull() { + super(Material.LEGACY_SKULL); + } + + /** + * Instantiate a skull facing in a particular direction. + * + * @param direction the direction the skull's face is facing + */ + public Skull(BlockFace direction) { + this(); + setFacingDirection(direction); + } + + public Skull(final Material type) { + super(type); + } + + /** + * @param type the type + * @param data the raw data value + * @deprecated Magic value + */ + @Deprecated + public Skull(final Material type, final byte data) { + super(type, data); + } + + public void setFacingDirection(BlockFace face) { + int data; + + switch (face) { + case SELF: + default: + data = 0x1; + break; + + case NORTH: + data = 0x2; + break; + + case WEST: + data = 0x4; + break; + + case SOUTH: + data = 0x3; + break; + + case EAST: + data = 0x5; + } + + setData((byte) data); + } + + public BlockFace getFacing() { + int data = getData(); + + switch (data) { + case 0x1: + default: + return BlockFace.SELF; + + case 0x2: + return BlockFace.NORTH; + + case 0x3: + return BlockFace.SOUTH; + + case 0x4: + return BlockFace.WEST; + + case 0x5: + return BlockFace.EAST; + } + } + + @Override + public String toString() { + return super.toString() + " facing " + getFacing(); + } + + @Override + public Skull clone() { + return (Skull) super.clone(); + } +} diff --git a/api/src/main/java/org/bukkit/material/SmoothBrick.java b/api/src/main/java/org/bukkit/material/SmoothBrick.java new file mode 100644 index 000000000..3e4a8525a --- /dev/null +++ b/api/src/main/java/org/bukkit/material/SmoothBrick.java @@ -0,0 +1,51 @@ +package org.bukkit.material; + +import java.util.ArrayList; +import java.util.List; + +import org.bukkit.Material; + +/** + * Represents the different types of smooth bricks. + */ +public class SmoothBrick extends TexturedMaterial { + + private static final List textures = new ArrayList(); + static { + textures.add(Material.LEGACY_STONE); + textures.add(Material.LEGACY_MOSSY_COBBLESTONE); + textures.add(Material.LEGACY_COBBLESTONE); + textures.add(Material.LEGACY_SMOOTH_BRICK); + } + + public SmoothBrick() { + super(Material.LEGACY_SMOOTH_BRICK); + } + + public SmoothBrick(final Material type) { + super((textures.contains(type)) ? Material.LEGACY_SMOOTH_BRICK : type); + if (textures.contains(type)) { + setMaterial(type); + } + } + + /** + * @param type the type + * @param data the raw data value + * @deprecated Magic value + */ + @Deprecated + public SmoothBrick(final Material type, final byte data) { + super(type, data); + } + + @Override + public List getTextures() { + return textures; + } + + @Override + public SmoothBrick clone() { + return (SmoothBrick) super.clone(); + } +} diff --git a/api/src/main/java/org/bukkit/material/SpawnEgg.java b/api/src/main/java/org/bukkit/material/SpawnEgg.java new file mode 100644 index 000000000..a177021d2 --- /dev/null +++ b/api/src/main/java/org/bukkit/material/SpawnEgg.java @@ -0,0 +1,73 @@ +package org.bukkit.material; + +import org.bukkit.Material; +import org.bukkit.entity.EntityType; +import org.bukkit.inventory.meta.SpawnEggMeta; + +/** + * Represents a spawn egg that can be used to spawn mobs + * @deprecated use {@link SpawnEggMeta} + */ +@Deprecated +public class SpawnEgg extends MaterialData { + + public SpawnEgg() { + super(Material.LEGACY_MONSTER_EGG); + } + + /** + * @param type the type + * @param data the raw data value + * @deprecated Magic value + */ + @Deprecated + public SpawnEgg(final Material type, final byte data) { + super(type, data); + } + + /** + * @param data the raw data value + * @deprecated Magic value + */ + @Deprecated + public SpawnEgg(byte data) { + super(Material.LEGACY_MONSTER_EGG, data); + } + + public SpawnEgg(EntityType type) { + this(); + setSpawnedType(type); + } + + /** + * Get the type of entity this egg will spawn. + * + * @return The entity type. + * @deprecated This is now stored in {@link SpawnEggMeta}. + */ + @Deprecated + public EntityType getSpawnedType() { + return EntityType.fromId(getData()); + } + + /** + * Set the type of entity this egg will spawn. + * + * @param type The entity type. + * @deprecated This is now stored in {@link SpawnEggMeta}. + */ + @Deprecated + public void setSpawnedType(EntityType type) { + setData((byte) type.getTypeId()); + } + + @Override + public String toString() { + return "SPAWN EGG{" + getSpawnedType() + "}"; + } + + @Override + public SpawnEgg clone() { + return (SpawnEgg) super.clone(); + } +} diff --git a/api/src/main/java/org/bukkit/material/Stairs.java b/api/src/main/java/org/bukkit/material/Stairs.java new file mode 100644 index 000000000..29ef66c4f --- /dev/null +++ b/api/src/main/java/org/bukkit/material/Stairs.java @@ -0,0 +1,121 @@ +package org.bukkit.material; + +import org.bukkit.Material; +import org.bukkit.block.BlockFace; + +/** + * Represents stairs. + */ +public class Stairs extends MaterialData implements Directional { + + public Stairs(final Material type) { + super(type); + } + + /** + * @param type the type + * @param data the raw data value + * @deprecated Magic value + */ + @Deprecated + public Stairs(final Material type, final byte data) { + super(type, data); + } + + /** + * @return the direction the stairs ascend towards + */ + public BlockFace getAscendingDirection() { + byte data = getData(); + + switch (data & 0x3) { + case 0x0: + default: + return BlockFace.EAST; + + case 0x1: + return BlockFace.WEST; + + case 0x2: + return BlockFace.SOUTH; + + case 0x3: + return BlockFace.NORTH; + } + } + + /** + * @return the direction the stairs descend towards + */ + public BlockFace getDescendingDirection() { + return getAscendingDirection().getOppositeFace(); + } + + /** + * Set the direction the stair part of the block is facing + */ + public void setFacingDirection(BlockFace face) { + byte data; + + switch (face) { + case NORTH: + data = 0x3; + break; + + case SOUTH: + data = 0x2; + break; + + case EAST: + default: + data = 0x0; + break; + + case WEST: + data = 0x1; + break; + } + + setData((byte) ((getData() & 0xC) | data)); + } + + /** + * @return the direction the stair part of the block is facing + */ + public BlockFace getFacing() { + return getDescendingDirection(); + } + + /** + * Test if step is inverted + * + * @return true if inverted (top half), false if normal (bottom half) + */ + public boolean isInverted() { + return ((getData() & 0x4) != 0); + } + + /** + * Set step inverted state + * + * @param inv - true if step is inverted (top half), false if step is + * normal (bottom half) + */ + public void setInverted(boolean inv) { + int dat = getData() & 0x3; + if (inv) { + dat |= 0x4; + } + setData((byte) dat); + } + + @Override + public String toString() { + return super.toString() + " facing " + getFacing() + (isInverted() ? " inverted" : ""); + } + + @Override + public Stairs clone() { + return (Stairs) super.clone(); + } +} diff --git a/api/src/main/java/org/bukkit/material/Step.java b/api/src/main/java/org/bukkit/material/Step.java new file mode 100644 index 000000000..8c255ba06 --- /dev/null +++ b/api/src/main/java/org/bukkit/material/Step.java @@ -0,0 +1,102 @@ +package org.bukkit.material; + +import java.util.ArrayList; +import java.util.List; + +import org.bukkit.Material; + +/** + * Represents the different types of steps. + */ +public class Step extends TexturedMaterial { + private static final List textures = new ArrayList(); + static { + textures.add(Material.LEGACY_STONE); + textures.add(Material.LEGACY_SANDSTONE); + textures.add(Material.LEGACY_WOOD); + textures.add(Material.LEGACY_COBBLESTONE); + textures.add(Material.LEGACY_BRICK); + textures.add(Material.LEGACY_SMOOTH_BRICK); + textures.add(Material.LEGACY_NETHER_BRICK); + textures.add(Material.LEGACY_QUARTZ_BLOCK); + } + + public Step() { + super(Material.LEGACY_STEP); + } + + public Step(final Material type) { + super((textures.contains(type)) ? Material.LEGACY_STEP : type); + if (textures.contains(type)) { + setMaterial(type); + } + } + + /** + * @param type the type + * @param data the raw data value + * @deprecated Magic value + */ + @Deprecated + public Step(final Material type, final byte data) { + super(type, data); + } + + @Override + public List getTextures() { + return textures; + } + + /** + * Test if step is inverted + * + * @return true if inverted (top half), false if normal (bottom half) + */ + public boolean isInverted() { + return ((getData() & 0x8) != 0); + } + + /** + * Set step inverted state + * + * @param inv - true if step is inverted (top half), false if step is + * normal (bottom half) + */ + public void setInverted(boolean inv) { + int dat = getData() & 0x7; + if (inv) { + dat |= 0x8; + } + setData((byte) dat); + } + + /** + * + * @deprecated Magic value + */ + @Deprecated + @Override + protected int getTextureIndex() { + return getData() & 0x7; + } + + /** + * + * @deprecated Magic value + */ + @Deprecated + @Override + protected void setTextureIndex(int idx) { + setData((byte) ((getData() & 0x8) | idx)); + } + + @Override + public Step clone() { + return (Step) super.clone(); + } + + @Override + public String toString() { + return super.toString() + (isInverted() ? "inverted" : ""); + } +} diff --git a/api/src/main/java/org/bukkit/material/TexturedMaterial.java b/api/src/main/java/org/bukkit/material/TexturedMaterial.java new file mode 100644 index 000000000..ea3433ac8 --- /dev/null +++ b/api/src/main/java/org/bukkit/material/TexturedMaterial.java @@ -0,0 +1,93 @@ +package org.bukkit.material; + +import java.util.List; + +import org.bukkit.Material; + +/** + * Represents textured materials like steps and smooth bricks + */ +public abstract class TexturedMaterial extends MaterialData { + + public TexturedMaterial(Material m) { + super(m); + } + + /** + * @param type the type + * @param data the raw data value + * @deprecated Magic value + */ + @Deprecated + public TexturedMaterial(final Material type, final byte data) { + super(type, data); + } + + /** + * Retrieve a list of possible textures. The first element of the list + * will be used as a default. + * + * @return a list of possible textures for this block + */ + public abstract List getTextures(); + + /** + * Gets the current Material this block is made of + * + * @return Material of this block + */ + public Material getMaterial() { + int n = getTextureIndex(); + if (n > getTextures().size() - 1) { + n = 0; + } + + return getTextures().get(n); + } + + /** + * Sets the material this block is made of + * + * @param material + * New material of this block + */ + public void setMaterial(Material material) { + if (getTextures().contains(material)) { + setTextureIndex(getTextures().indexOf(material)); + } else { + setTextureIndex(0x0); + } + } + + /** + * Get material index from data + * + * @return index of data in textures list + * @deprecated Magic value + */ + @Deprecated + protected int getTextureIndex() { + return getData(); // Default to using all bits - override for other mappings + } + + /** + * Set material index + * + * @param idx - index of data in textures list + * @deprecated Magic value + */ + @Deprecated + protected void setTextureIndex(int idx) { + setData((byte) idx); // Default to using all bits - override for other mappings + } + + @Override + public String toString() { + return getMaterial() + " " + super.toString(); + } + + @Override + public TexturedMaterial clone() { + return (TexturedMaterial) super.clone(); + } +} diff --git a/api/src/main/java/org/bukkit/material/Torch.java b/api/src/main/java/org/bukkit/material/Torch.java new file mode 100644 index 000000000..0c34004d1 --- /dev/null +++ b/api/src/main/java/org/bukkit/material/Torch.java @@ -0,0 +1,87 @@ +package org.bukkit.material; + +import org.bukkit.block.BlockFace; +import org.bukkit.Material; + +/** + * MaterialData for torches + */ +public class Torch extends SimpleAttachableMaterialData { + public Torch() { + super(Material.LEGACY_TORCH); + } + + public Torch(final Material type) { + super(type); + } + + /** + * @param type the type + * @param data the raw data value + * @deprecated Magic value + */ + @Deprecated + public Torch(final Material type, final byte data) { + super(type, data); + } + + /** + * Gets the face that this block is attached on + * + * @return BlockFace attached to + */ + public BlockFace getAttachedFace() { + byte data = getData(); + + switch (data) { + case 0x1: + return BlockFace.WEST; + + case 0x2: + return BlockFace.EAST; + + case 0x3: + return BlockFace.NORTH; + + case 0x4: + return BlockFace.SOUTH; + + case 0x5: + default: + return BlockFace.DOWN; + } + } + + public void setFacingDirection(BlockFace face) { + byte data; + + switch (face) { + case EAST: + data = 0x1; + break; + + case WEST: + data = 0x2; + break; + + case SOUTH: + data = 0x3; + break; + + case NORTH: + data = 0x4; + break; + + case UP: + default: + data = 0x5; + } + + setData(data); + } + + @Override + public Torch clone() { + return (Torch) super.clone(); + } +} diff --git a/api/src/main/java/org/bukkit/material/TrapDoor.java b/api/src/main/java/org/bukkit/material/TrapDoor.java new file mode 100644 index 000000000..2bbfe2b31 --- /dev/null +++ b/api/src/main/java/org/bukkit/material/TrapDoor.java @@ -0,0 +1,114 @@ +package org.bukkit.material; + +import org.bukkit.Material; +import org.bukkit.block.BlockFace; + +/** + * Represents a trap door + */ +public class TrapDoor extends SimpleAttachableMaterialData implements Openable { + public TrapDoor() { + super(Material.LEGACY_TRAP_DOOR); + } + + public TrapDoor(final Material type) { + super(type); + } + + /** + * @param type the type + * @param data the raw data value + * @deprecated Magic value + */ + @Deprecated + public TrapDoor(final Material type, final byte data) { + super(type, data); + } + + public boolean isOpen() { + return ((getData() & 0x4) == 0x4); + } + + public void setOpen(boolean isOpen) { + byte data = getData(); + + if (isOpen) { + data |= 0x4; + } else { + data &= ~0x4; + } + + setData(data); + } + + /** + * Test if trapdoor is inverted + * + * @return true if inverted (top half), false if normal (bottom half) + */ + public boolean isInverted() { + return ((getData() & 0x8) != 0); + } + + /** + * Set trapdoor inverted state + * + * @param inv - true if inverted (top half), false if normal (bottom half) + */ + public void setInverted(boolean inv) { + int dat = getData() & 0x7; + if (inv) { + dat |= 0x8; + } + setData((byte) dat); + } + + public BlockFace getAttachedFace() { + byte data = (byte) (getData() & 0x3); + + switch (data) { + case 0x0: + return BlockFace.SOUTH; + + case 0x1: + return BlockFace.NORTH; + + case 0x2: + return BlockFace.EAST; + + case 0x3: + return BlockFace.WEST; + } + + return null; + + } + + public void setFacingDirection(BlockFace face) { + byte data = (byte) (getData() & 0xC); + + switch (face) { + case SOUTH: + data |= 0x1; + break; + case WEST: + data |= 0x2; + break; + case EAST: + data |= 0x3; + break; + } + + setData(data); + } + + @Override + public String toString() { + return (isOpen() ? "OPEN " : "CLOSED ") + super.toString() + " with hinges set " + getAttachedFace() + (isInverted() ? " inverted" : ""); + } + + @Override + public TrapDoor clone() { + return (TrapDoor) super.clone(); + } +} diff --git a/api/src/main/java/org/bukkit/material/Tree.java b/api/src/main/java/org/bukkit/material/Tree.java new file mode 100644 index 000000000..2c4b0c40d --- /dev/null +++ b/api/src/main/java/org/bukkit/material/Tree.java @@ -0,0 +1,150 @@ +package org.bukkit.material; + +import org.bukkit.Material; +import org.bukkit.TreeSpecies; +import org.bukkit.block.BlockFace; + +/** + * Represents the different types of Tree block that face a direction. + * + * @see Material#LEGACY_LOG + * @see Material#LEGACY_LOG_2 + */ +public class Tree extends Wood { + protected static final Material DEFAULT_TYPE = Material.LEGACY_LOG; + protected static final BlockFace DEFAULT_DIRECTION = BlockFace.UP; + + /** + * Constructs a tree block. + */ + public Tree() { + this(DEFAULT_TYPE, DEFAULT_SPECIES, DEFAULT_DIRECTION); + } + + /** + * Constructs a tree block of the given tree species. + * + * @param species the species of the tree block + */ + public Tree(TreeSpecies species) { + this(DEFAULT_TYPE, species, DEFAULT_DIRECTION); + } + + /** + * Constructs a tree block of the given tree species, and facing the given + * direction. + * + * @param species the species of the tree block + * @param dir the direction the tree block is facing + */ + public Tree(TreeSpecies species, BlockFace dir) { + this(DEFAULT_TYPE, species, dir); + } + + /** + * Constructs a tree block of the given type. + * + * @param type the type of tree block + */ + public Tree(final Material type) { + this(type, DEFAULT_SPECIES, DEFAULT_DIRECTION); + } + + /** + * Constructs a tree block of the given type and tree species. + * + * @param type the type of tree block + * @param species the species of the tree block + */ + public Tree(final Material type, TreeSpecies species) { + this(type, species, DEFAULT_DIRECTION); + } + + /** + * Constructs a tree block of the given type and tree species, and facing + * the given direction. + * + * @param type the type of tree block + * @param species the species of the tree block + * @param dir the direction the tree block is facing + */ + public Tree(final Material type, TreeSpecies species, BlockFace dir) { + super(type, species); + setDirection(dir); + } + + /** + * @param type the type + * @param data the raw data value + * @deprecated Magic value + */ + @Deprecated + public Tree(final Material type, final byte data) { + super(type, data); + } + + /** + * Get direction of the log + * + * @return one of: + *

    + *
  • BlockFace.TOP for upright (default) + *
  • BlockFace.NORTH (east-west) + *
  • BlockFace.WEST (north-south) + *
  • BlockFace.SELF (directionless) + *
+ */ + @SuppressWarnings("deprecation") + public BlockFace getDirection() { + switch ((getData() >> 2) & 0x3) { + case 0: // Up-down + default: + return BlockFace.UP; + case 1: // North-south + return BlockFace.WEST; + case 2: // East-west + return BlockFace.NORTH; + case 3: // Directionless (bark on all sides) + return BlockFace.SELF; + } + } + + /** + * Set direction of the log + * + * @param dir - direction of end of log (BlockFace.SELF for no direction) + */ + @SuppressWarnings("deprecation") + public void setDirection(BlockFace dir) { + int dat; + switch (dir) { + case UP: + case DOWN: + default: + dat = 0; + break; + case WEST: + case EAST: + dat = 4; // 1<<2 + break; + case NORTH: + case SOUTH: + dat = 8; // 2<<2 + break; + case SELF: + dat = 12; // 3<<2 + break; + } + setData((byte) ((getData() & 0x3) | dat)); + } + + @Override + public String toString() { + return getSpecies() + " " + getDirection() + " " + super.toString(); + } + + @Override + public Tree clone() { + return (Tree) super.clone(); + } +} diff --git a/api/src/main/java/org/bukkit/material/Tripwire.java b/api/src/main/java/org/bukkit/material/Tripwire.java new file mode 100644 index 000000000..89b85a614 --- /dev/null +++ b/api/src/main/java/org/bukkit/material/Tripwire.java @@ -0,0 +1,77 @@ +package org.bukkit.material; + +import org.bukkit.Material; + +/** + * Represents the tripwire + */ +public class Tripwire extends MaterialData { + + public Tripwire() { + super(Material.LEGACY_TRIPWIRE); + } + + /** + * @param type the type + * @param data the raw data value + * @deprecated Magic value + */ + @Deprecated + public Tripwire(final Material type, final byte data) { + super(type, data); + } + + /** + * Test if tripwire is currently activated + * + * @return true if activated, false if not + */ + public boolean isActivated() { + return (getData() & 0x4) != 0; + } + + /** + * Set tripwire activated state + * + * @param act - true if activated, false if not + */ + public void setActivated(boolean act) { + int dat = getData() & (0x8 | 0x3); + if (act) { + dat |= 0x4; + } + setData((byte) dat); + } + + /** + * Test if object triggering this tripwire directly + * + * @return true if object activating tripwire, false if not + */ + public boolean isObjectTriggering() { + return (getData() & 0x1) != 0; + } + + /** + * Set object triggering state for this tripwire + * + * @param trig - true if object activating tripwire, false if not + */ + public void setObjectTriggering(boolean trig) { + int dat = getData() & 0xE; + if (trig) { + dat |= 0x1; + } + setData((byte) dat); + } + + @Override + public Tripwire clone() { + return (Tripwire) super.clone(); + } + + @Override + public String toString() { + return super.toString() + (isActivated() ? " Activated" : "") + (isObjectTriggering() ? " Triggered" : ""); + } +} diff --git a/api/src/main/java/org/bukkit/material/TripwireHook.java b/api/src/main/java/org/bukkit/material/TripwireHook.java new file mode 100644 index 000000000..97f97eab8 --- /dev/null +++ b/api/src/main/java/org/bukkit/material/TripwireHook.java @@ -0,0 +1,120 @@ +package org.bukkit.material; + +import org.bukkit.Material; +import org.bukkit.block.BlockFace; + +/** + * Represents the tripwire hook + */ +public class TripwireHook extends SimpleAttachableMaterialData implements Redstone { + + public TripwireHook() { + super(Material.LEGACY_TRIPWIRE_HOOK); + } + + /** + * @param type the type + * @param data the raw data value + * @deprecated Magic value + */ + @Deprecated + public TripwireHook(final Material type, final byte data) { + super(type, data); + } + + public TripwireHook(BlockFace dir) { + this(); + setFacingDirection(dir); + } + + /** + * Test if tripwire is connected + * + * @return true if connected, false if not + */ + public boolean isConnected() { + return (getData() & 0x4) != 0; + } + + /** + * Set tripwire connection state + * + * @param connected - true if connected, false if not + */ + public void setConnected(boolean connected) { + int dat = getData() & (0x8 | 0x3); + if (connected) { + dat |= 0x4; + } + setData((byte) dat); + } + + /** + * Test if hook is currently activated + * + * @return true if activated, false if not + */ + public boolean isActivated() { + return (getData() & 0x8) != 0; + } + + /** + * Set hook activated state + * + * @param act - true if activated, false if not + */ + public void setActivated(boolean act) { + int dat = getData() & (0x4 | 0x3); + if (act) { + dat |= 0x8; + } + setData((byte) dat); + } + + public void setFacingDirection(BlockFace face) { + int dat = getData() & 0xC; + switch (face) { + case WEST: + dat |= 0x1; + break; + case NORTH: + dat |= 0x2; + break; + case EAST: + dat |= 0x3; + break; + case SOUTH: + default: + break; + } + setData((byte) dat); + } + + public BlockFace getAttachedFace() { + switch (getData() & 0x3) { + case 0: + return BlockFace.NORTH; + case 1: + return BlockFace.EAST; + case 2: + return BlockFace.SOUTH; + case 3: + return BlockFace.WEST; + } + return null; + } + + public boolean isPowered() { + return isActivated(); + } + + @Override + public TripwireHook clone() { + return (TripwireHook) super.clone(); + } + + @Override + public String toString() { + return super.toString() + " facing " + getFacing() + (isActivated() ? " Activated" : "") + (isConnected() ? " Connected" : ""); + } +} diff --git a/api/src/main/java/org/bukkit/material/Vine.java b/api/src/main/java/org/bukkit/material/Vine.java new file mode 100644 index 000000000..abfc211c9 --- /dev/null +++ b/api/src/main/java/org/bukkit/material/Vine.java @@ -0,0 +1,197 @@ +package org.bukkit.material; + +import java.util.Arrays; +import java.util.EnumSet; + +import org.bukkit.Material; +import org.bukkit.block.BlockFace; + +/** + * Represents a vine + */ +public class Vine extends MaterialData { + private static final int VINE_NORTH = 0x4; + private static final int VINE_EAST = 0x8; + private static final int VINE_WEST = 0x2; + private static final int VINE_SOUTH = 0x1; + private static final EnumSet possibleFaces = EnumSet.of(BlockFace.WEST, BlockFace.NORTH, BlockFace.SOUTH, BlockFace.EAST); + + public Vine() { + super(Material.LEGACY_VINE); + } + + /** + * @param type the type + * @param data the raw data value + * @deprecated Magic value + */ + @Deprecated + public Vine(final Material type, final byte data) { + super(type, data); + } + + /** + * @param data the raw data value + * @deprecated Magic value + */ + @Deprecated + public Vine(byte data) { + super(Material.LEGACY_VINE, data); + } + + public Vine(BlockFace... faces) { + this(EnumSet.copyOf(Arrays.asList(faces))); + } + + public Vine(EnumSet faces) { + this((byte) 0); + faces.retainAll(possibleFaces); + + byte data = 0; + + if (faces.contains(BlockFace.WEST)) { + data |= VINE_WEST; + } + + if (faces.contains(BlockFace.NORTH)) { + data |= VINE_NORTH; + } + + if (faces.contains(BlockFace.SOUTH)) { + data |= VINE_SOUTH; + } + + if (faces.contains(BlockFace.EAST)) { + data |= VINE_EAST; + } + + setData(data); + } + + /** + * Check if the vine is attached to the specified face of an adjacent + * block. You can check two faces at once by passing e.g. {@link + * BlockFace#NORTH_EAST}. + * + * @param face The face to check. + * @return Whether it is attached to that face. + */ + public boolean isOnFace(BlockFace face) { + switch (face) { + case WEST: + return (getData() & VINE_WEST) == VINE_WEST; + case NORTH: + return (getData() & VINE_NORTH) == VINE_NORTH; + case SOUTH: + return (getData() & VINE_SOUTH) == VINE_SOUTH; + case EAST: + return (getData() & VINE_EAST) == VINE_EAST; + case NORTH_EAST: + return isOnFace(BlockFace.EAST) && isOnFace(BlockFace.NORTH); + case NORTH_WEST: + return isOnFace(BlockFace.WEST) && isOnFace(BlockFace.NORTH); + case SOUTH_EAST: + return isOnFace(BlockFace.EAST) && isOnFace(BlockFace.SOUTH); + case SOUTH_WEST: + return isOnFace(BlockFace.WEST) && isOnFace(BlockFace.SOUTH); + case UP: // It's impossible to be accurate with this since it's contextual + return true; + default: + return false; + } + } + + /** + * Attach the vine to the specified face of an adjacent block. + * + * @param face The face to attach. + */ + public void putOnFace(BlockFace face) { + switch (face) { + case WEST: + setData((byte) (getData() | VINE_WEST)); + break; + case NORTH: + setData((byte) (getData() | VINE_NORTH)); + break; + case SOUTH: + setData((byte) (getData() | VINE_SOUTH)); + break; + case EAST: + setData((byte) (getData() | VINE_EAST)); + break; + case NORTH_WEST: + putOnFace(BlockFace.WEST); + putOnFace(BlockFace.NORTH); + break; + case SOUTH_WEST: + putOnFace(BlockFace.WEST); + putOnFace(BlockFace.SOUTH); + break; + case NORTH_EAST: + putOnFace(BlockFace.EAST); + putOnFace(BlockFace.NORTH); + break; + case SOUTH_EAST: + putOnFace(BlockFace.EAST); + putOnFace(BlockFace.SOUTH); + break; + case UP: + break; + default: + throw new IllegalArgumentException("Vines can't go on face " + face.toString()); + } + } + + /** + * Detach the vine from the specified face of an adjacent block. + * + * @param face The face to detach. + */ + public void removeFromFace(BlockFace face) { + switch (face) { + case WEST: + setData((byte) (getData() & ~VINE_WEST)); + break; + case NORTH: + setData((byte) (getData() & ~VINE_NORTH)); + break; + case SOUTH: + setData((byte) (getData() & ~VINE_SOUTH)); + break; + case EAST: + setData((byte) (getData() & ~VINE_EAST)); + break; + case NORTH_WEST: + removeFromFace(BlockFace.WEST); + removeFromFace(BlockFace.NORTH); + break; + case SOUTH_WEST: + removeFromFace(BlockFace.WEST); + removeFromFace(BlockFace.SOUTH); + break; + case NORTH_EAST: + removeFromFace(BlockFace.EAST); + removeFromFace(BlockFace.NORTH); + break; + case SOUTH_EAST: + removeFromFace(BlockFace.EAST); + removeFromFace(BlockFace.SOUTH); + break; + case UP: + break; + default: + throw new IllegalArgumentException("Vines can't go on face " + face.toString()); + } + } + + @Override + public String toString() { + return "VINE"; + } + + @Override + public Vine clone() { + return (Vine) super.clone(); + } +} diff --git a/api/src/main/java/org/bukkit/material/Wood.java b/api/src/main/java/org/bukkit/material/Wood.java new file mode 100644 index 000000000..c77664ee8 --- /dev/null +++ b/api/src/main/java/org/bukkit/material/Wood.java @@ -0,0 +1,177 @@ +package org.bukkit.material; + +import org.bukkit.Material; +import org.bukkit.TreeSpecies; + +/** + * Represents wood blocks of different species. + * + * @see Material#LEGACY_WOOD + * @see Material#LEGACY_SAPLING + * @see Material#LEGACY_WOOD_DOUBLE_STEP + */ +public class Wood extends MaterialData { + protected static final Material DEFAULT_TYPE = Material.LEGACY_WOOD; + protected static final TreeSpecies DEFAULT_SPECIES = TreeSpecies.GENERIC; + + /** + * Constructs a wood block. + */ + public Wood() { + this(DEFAULT_TYPE, DEFAULT_SPECIES); + } + + /** + * Constructs a wood block of the given tree species. + * + * @param species the species of the wood block + */ + public Wood(TreeSpecies species) { + this(DEFAULT_TYPE, species); + } + + /** + * Constructs a wood block of the given type. + * + * @param type the type of wood block + */ + public Wood(final Material type) { + this(type, DEFAULT_SPECIES); + } + + /** + * Constructs a wood block of the given type and tree species. + * + * @param type the type of wood block + * @param species the species of the wood block + */ + public Wood(final Material type, final TreeSpecies species) { + // Ensure only valid species-type combinations + super(getSpeciesType(type, species)); + setSpecies(species); + } + + /** + * @param type the type + * @param data the raw data value + * @deprecated Magic value + */ + @Deprecated + public Wood(final Material type, final byte data) { + super(type, data); + } + + /** + * Gets the current species of this wood block + * + * @return TreeSpecies of this wood block + */ + public TreeSpecies getSpecies() { + switch (getItemType()) { + case LEGACY_WOOD: + case LEGACY_WOOD_DOUBLE_STEP: + return TreeSpecies.getByData((byte) getData()); + case LEGACY_LOG: + case LEGACY_LEAVES: + return TreeSpecies.getByData((byte) (getData() & 0x3)); + case LEGACY_LOG_2: + case LEGACY_LEAVES_2: + return TreeSpecies.getByData((byte) ((getData() & 0x3) | 0x4)); + case LEGACY_SAPLING: + case LEGACY_WOOD_STEP: + return TreeSpecies.getByData((byte) (getData() & 0x7)); + default: + throw new IllegalArgumentException("Invalid block type for tree species"); + } + } + + /** + * Correct the block type for certain species-type combinations. + * + * @param type The desired type + * @param species The required species + * @return The actual type for this species given the desired type + */ + private static Material getSpeciesType(Material type, TreeSpecies species) { + switch (species) { + case GENERIC: + case REDWOOD: + case BIRCH: + case JUNGLE: + switch (type) { + case LEGACY_LOG_2: + return Material.LEGACY_LOG; + case LEGACY_LEAVES_2: + return Material.LEGACY_LEAVES; + default: + } + break; + case ACACIA: + case DARK_OAK: + switch (type) { + case LEGACY_LOG: + return Material.LEGACY_LOG_2; + case LEGACY_LEAVES: + return Material.LEGACY_LEAVES_2; + default: + } + break; + } + return type; + } + + /** + * Sets the species of this wood block + * + * @param species New species of this wood block + */ + public void setSpecies(final TreeSpecies species) { + boolean firstType = false; + switch (getItemType()) { + case LEGACY_WOOD: + case LEGACY_WOOD_DOUBLE_STEP: + setData(species.getData()); + break; + case LEGACY_LOG: + case LEGACY_LEAVES: + firstType = true; + // fall through to next switch statement below + case LEGACY_LOG_2: + case LEGACY_LEAVES_2: + switch (species) { + case GENERIC: + case REDWOOD: + case BIRCH: + case JUNGLE: + if (!firstType) { + throw new IllegalArgumentException("Invalid tree species for block type, use block type 2 instead"); + } + break; + case ACACIA: + case DARK_OAK: + if (firstType) { + throw new IllegalArgumentException("Invalid tree species for block type 2, use block type instead"); + } + break; + } + setData((byte) ((getData() & 0xC) | (species.getData() & 0x3))); + break; + case LEGACY_SAPLING: + case LEGACY_WOOD_STEP: + setData((byte) ((getData() & 0x8) | species.getData())); + break; + default: + throw new IllegalArgumentException("Invalid block type for tree species"); + } + } + + @Override + public String toString() { + return getSpecies() + " " + super.toString(); + } + + @Override + public Wood clone() { + return (Wood) super.clone(); + } +} diff --git a/api/src/main/java/org/bukkit/material/WoodenStep.java b/api/src/main/java/org/bukkit/material/WoodenStep.java new file mode 100644 index 000000000..34886cfcb --- /dev/null +++ b/api/src/main/java/org/bukkit/material/WoodenStep.java @@ -0,0 +1,87 @@ +package org.bukkit.material; + +import org.bukkit.Material; +import org.bukkit.TreeSpecies; + +/** + * Represents the different types of wooden steps. + * + * @see Material#LEGACY_WOOD_STEP + */ +public class WoodenStep extends Wood { + protected static final Material DEFAULT_TYPE = Material.LEGACY_WOOD_STEP; + protected static final boolean DEFAULT_INVERTED = false; + + /** + * Constructs a wooden step. + */ + public WoodenStep() { + this(DEFAULT_SPECIES, DEFAULT_INVERTED); + } + + /** + * Constructs a wooden step of the given tree species. + * + * @param species the species of the wooden step + */ + public WoodenStep(TreeSpecies species) { + this(species, DEFAULT_INVERTED); + } + + /** + * Constructs a wooden step of the given type and tree species, either + * inverted or not. + * + * @param species the species of the wooden step + * @param inv true the step is at the top of the block + */ + public WoodenStep(final TreeSpecies species, boolean inv) { + super(DEFAULT_TYPE, species); + setInverted(inv); + } + + /** + * @param type the type + * @param data the raw data value + * @deprecated Magic value + */ + @Deprecated + public WoodenStep(final Material type, final byte data) { + super(type, data); + } + + /** + * Test if step is inverted + * + * @return true if inverted (top half), false if normal (bottom half) + */ + @SuppressWarnings("deprecation") + public boolean isInverted() { + return ((getData() & 0x8) != 0); + } + + /** + * Set step inverted state + * + * @param inv - true if step is inverted (top half), false if step is normal + * (bottom half) + */ + @SuppressWarnings("deprecation") + public void setInverted(boolean inv) { + int dat = getData() & 0x7; + if (inv) { + dat |= 0x8; + } + setData((byte) dat); + } + + @Override + public WoodenStep clone() { + return (WoodenStep) super.clone(); + } + + @Override + public String toString() { + return super.toString() + " " + getSpecies() + (isInverted() ? " inverted" : ""); + } +} diff --git a/api/src/main/java/org/bukkit/material/Wool.java b/api/src/main/java/org/bukkit/material/Wool.java new file mode 100644 index 000000000..87f35f588 --- /dev/null +++ b/api/src/main/java/org/bukkit/material/Wool.java @@ -0,0 +1,60 @@ +package org.bukkit.material; + +import org.bukkit.DyeColor; +import org.bukkit.Material; + +/** + * Represents a Wool/Cloth block + */ +public class Wool extends MaterialData implements Colorable { + public Wool() { + super(Material.LEGACY_WOOL); + } + + public Wool(DyeColor color) { + this(); + setColor(color); + } + + public Wool(final Material type) { + super(type); + } + + /** + * @param type the type + * @param data the raw data value + * @deprecated Magic value + */ + @Deprecated + public Wool(final Material type, final byte data) { + super(type, data); + } + + /** + * Gets the current color of this dye + * + * @return DyeColor of this dye + */ + public DyeColor getColor() { + return DyeColor.getByWoolData(getData()); + } + + /** + * Sets the color of this dye + * + * @param color New color of this dye + */ + public void setColor(DyeColor color) { + setData(color.getWoolData()); + } + + @Override + public String toString() { + return getColor() + " " + super.toString(); + } + + @Override + public Wool clone() { + return (Wool) super.clone(); + } +} diff --git a/api/src/main/java/org/bukkit/material/types/MushroomBlockTexture.java b/api/src/main/java/org/bukkit/material/types/MushroomBlockTexture.java new file mode 100644 index 000000000..b36a10bfb --- /dev/null +++ b/api/src/main/java/org/bukkit/material/types/MushroomBlockTexture.java @@ -0,0 +1,135 @@ +package org.bukkit.material.types; + +import java.util.Map; + +import org.bukkit.block.BlockFace; + +import com.google.common.collect.Maps; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +/** + * Represents the different textured blocks of mushroom. + */ +public enum MushroomBlockTexture { + + /** + * Pores on all faces. + */ + ALL_PORES(0, null), + /** + * Cap texture on the top, north and west faces, pores on remaining sides. + */ + CAP_NORTH_WEST(1, BlockFace.NORTH_WEST), + /** + * Cap texture on the top and north faces, pores on remaining sides. + */ + CAP_NORTH(2, BlockFace.NORTH), + /** + * Cap texture on the top, north and east faces, pores on remaining sides. + */ + CAP_NORTH_EAST(3, BlockFace.NORTH_EAST), + /** + * Cap texture on the top and west faces, pores on remaining sides. + */ + CAP_WEST(4, BlockFace.WEST), + /** + * Cap texture on the top face, pores on remaining sides. + */ + CAP_TOP(5, BlockFace.UP), + /** + * Cap texture on the top and east faces, pores on remaining sides. + */ + CAP_EAST(6, BlockFace.EAST), + /** + * Cap texture on the top, south and west faces, pores on remaining sides. + */ + CAP_SOUTH_WEST(7, BlockFace.SOUTH_WEST), + /** + * Cap texture on the top and south faces, pores on remaining sides. + */ + CAP_SOUTH(8, BlockFace.SOUTH), + /** + * Cap texture on the top, south and east faces, pores on remaining sides. + */ + CAP_SOUTH_EAST(9, BlockFace.SOUTH_EAST), + /** + * Stem texture on the north, east, south and west faces, pores on top and + * bottom. + */ + STEM_SIDES(10, null), + /** + * Cap texture on all faces. + */ + ALL_CAP(14, BlockFace.SELF), + /** + * Stem texture on all faces. + */ + ALL_STEM(15, null); + private final static Map BY_DATA = Maps.newHashMap(); + private final static Map BY_BLOCKFACE = Maps.newHashMap(); + + private final Byte data; + private final BlockFace capFace; + + private MushroomBlockTexture(final int data, /*@Nullable*/ final BlockFace capFace) { + this.data = (byte) data; + this.capFace = capFace; + } + + /** + * Gets the associated data value representing this mushroom block face. + * + * @return A byte containing the data value of this mushroom block face + * @deprecated Magic value + */ + @Deprecated + public byte getData() { + return data; + } + + /** + * Gets the face that has cap texture. + * + * @return The cap face + */ + @Nullable + public BlockFace getCapFace() { + return capFace; + } + + /** + * Gets the MushroomBlockType with the given data value. + * + * @param data Data value to fetch + * @return The {@link MushroomBlockTexture} representing the given value, or + * null if it doesn't exist + * @deprecated Magic value + */ + @Deprecated + @Nullable + public static MushroomBlockTexture getByData(final byte data) { + return BY_DATA.get(data); + } + + /** + * Gets the MushroomBlockType with cap texture on the given block face. + * + * @param face the required block face with cap texture + * @return The {@link MushroomBlockTexture} representing the given block + * face, or null if it doesn't exist + * + * @see BlockFace + */ + @Nullable + public static MushroomBlockTexture getCapByFace(@Nullable final BlockFace face) { + return BY_BLOCKFACE.get(face); + } + + static { + for (MushroomBlockTexture type : values()) { + BY_DATA.put(type.data, type); + BY_BLOCKFACE.put(type.capFace, type); + } + } +} diff --git a/api/src/main/java/org/bukkit/metadata/FixedMetadataValue.java b/api/src/main/java/org/bukkit/metadata/FixedMetadataValue.java new file mode 100644 index 000000000..4ded65531 --- /dev/null +++ b/api/src/main/java/org/bukkit/metadata/FixedMetadataValue.java @@ -0,0 +1,44 @@ +package org.bukkit.metadata; + +import org.bukkit.plugin.Plugin; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +/** + * A FixedMetadataValue is a special case metadata item that contains the same + * value forever after initialization. Invalidating a FixedMetadataValue has + * no effect. + *

+ * This class extends LazyMetadataValue for historical reasons, even though it + * overrides all the implementation methods. it is possible that in the future + * that the inheritance hierarchy may change. + */ +public class FixedMetadataValue extends LazyMetadataValue { + + /** + * Store the internal value that is represented by this fixed value. + */ + private final Object internalValue; + + /** + * Initializes a FixedMetadataValue with an Object + * + * @param owningPlugin the {@link Plugin} that created this metadata value + * @param value the value assigned to this metadata value + */ + public FixedMetadataValue(@NotNull Plugin owningPlugin, @Nullable final Object value) { + super(owningPlugin); + this.internalValue = value; + } + + @Override + public void invalidate() { + + } + + @Nullable + @Override + public Object value() { + return internalValue; + } +} diff --git a/api/src/main/java/org/bukkit/metadata/LazyMetadataValue.java b/api/src/main/java/org/bukkit/metadata/LazyMetadataValue.java new file mode 100644 index 000000000..820b22ca9 --- /dev/null +++ b/api/src/main/java/org/bukkit/metadata/LazyMetadataValue.java @@ -0,0 +1,125 @@ +package org.bukkit.metadata; + +import java.lang.ref.SoftReference; +import java.util.concurrent.Callable; + +import org.apache.commons.lang.Validate; +import org.bukkit.plugin.Plugin; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +/** + * The LazyMetadataValue class implements a type of metadata that is not + * computed until another plugin asks for it. + *

+ * By making metadata values lazy, no computation is done by the providing + * plugin until absolutely necessary (if ever). Additionally, + * LazyMetadataValue objects cache their values internally unless overridden + * by a {@link CacheStrategy} or invalidated at the individual or plugin + * level. Once invalidated, the LazyMetadataValue will recompute its value + * when asked. + */ +public class LazyMetadataValue extends MetadataValueAdapter { + private Callable lazyValue; + private CacheStrategy cacheStrategy; + private SoftReference internalValue; + private static final Object ACTUALLY_NULL = new Object(); + + /** + * Initialized a LazyMetadataValue object with the default + * CACHE_AFTER_FIRST_EVAL cache strategy. + * + * @param owningPlugin the {@link Plugin} that created this metadata + * value. + * @param lazyValue the lazy value assigned to this metadata value. + */ + public LazyMetadataValue(@NotNull Plugin owningPlugin, @NotNull Callable lazyValue) { + this(owningPlugin, CacheStrategy.CACHE_AFTER_FIRST_EVAL, lazyValue); + } + + /** + * Initializes a LazyMetadataValue object with a specific cache strategy. + * + * @param owningPlugin the {@link Plugin} that created this metadata + * value. + * @param cacheStrategy determines the rules for caching this metadata + * value. + * @param lazyValue the lazy value assigned to this metadata value. + */ + public LazyMetadataValue(@NotNull Plugin owningPlugin, @NotNull CacheStrategy cacheStrategy, @NotNull Callable lazyValue) { + super(owningPlugin); + Validate.notNull(cacheStrategy, "cacheStrategy cannot be null"); + Validate.notNull(lazyValue, "lazyValue cannot be null"); + this.internalValue = new SoftReference(null); + this.lazyValue = lazyValue; + this.cacheStrategy = cacheStrategy; + } + + /** + * Protected special constructor used by FixedMetadataValue to bypass + * standard setup. + * + * @param owningPlugin the owning plugin + */ + protected LazyMetadataValue(@NotNull Plugin owningPlugin) { + super(owningPlugin); + } + + @Nullable + public Object value() { + eval(); + Object value = internalValue.get(); + if (value == ACTUALLY_NULL) { + return null; + } + return value; + } + + /** + * Lazily evaluates the value of this metadata item. + * + * @throws MetadataEvaluationException if computing the metadata value + * fails. + */ + private synchronized void eval() throws MetadataEvaluationException { + if (cacheStrategy == CacheStrategy.NEVER_CACHE || internalValue.get() == null) { + try { + Object value = lazyValue.call(); + if (value == null) { + value = ACTUALLY_NULL; + } + internalValue = new SoftReference(value); + } catch (Exception e) { + throw new MetadataEvaluationException(e); + } + } + } + + public synchronized void invalidate() { + if (cacheStrategy != CacheStrategy.CACHE_ETERNALLY) { + internalValue.clear(); + } + } + + /** + * Describes possible caching strategies for metadata. + */ + public enum CacheStrategy { + /** + * Once the metadata value has been evaluated, do not re-evaluate the + * value until it is manually invalidated. + */ + CACHE_AFTER_FIRST_EVAL, + + /** + * Re-evaluate the metadata item every time it is requested + */ + NEVER_CACHE, + + /** + * Once the metadata value has been evaluated, do not re-evaluate the + * value in spite of manual invalidation. + */ + CACHE_ETERNALLY + } +} diff --git a/api/src/main/java/org/bukkit/metadata/MetadataConversionException.java b/api/src/main/java/org/bukkit/metadata/MetadataConversionException.java new file mode 100644 index 000000000..a3def46a4 --- /dev/null +++ b/api/src/main/java/org/bukkit/metadata/MetadataConversionException.java @@ -0,0 +1,13 @@ +package org.bukkit.metadata; + +/** + * A MetadataConversionException is thrown any time a {@link + * LazyMetadataValue} attempts to convert a metadata value to an inappropriate + * data type. + */ +@SuppressWarnings("serial") +public class MetadataConversionException extends RuntimeException { + MetadataConversionException(String message) { + super(message); + } +} diff --git a/api/src/main/java/org/bukkit/metadata/MetadataEvaluationException.java b/api/src/main/java/org/bukkit/metadata/MetadataEvaluationException.java new file mode 100644 index 000000000..918e7c839 --- /dev/null +++ b/api/src/main/java/org/bukkit/metadata/MetadataEvaluationException.java @@ -0,0 +1,13 @@ +package org.bukkit.metadata; + +/** + * A MetadataEvaluationException is thrown any time a {@link + * LazyMetadataValue} fails to evaluate its value due to an exception. The + * originating exception will be included as this exception's cause. + */ +@SuppressWarnings("serial") +public class MetadataEvaluationException extends RuntimeException { + MetadataEvaluationException(Throwable cause) { + super(cause); + } +} diff --git a/api/src/main/java/org/bukkit/metadata/MetadataStore.java b/api/src/main/java/org/bukkit/metadata/MetadataStore.java new file mode 100644 index 000000000..1ebba41f4 --- /dev/null +++ b/api/src/main/java/org/bukkit/metadata/MetadataStore.java @@ -0,0 +1,62 @@ +package org.bukkit.metadata; + +import org.bukkit.plugin.Plugin; +import org.jetbrains.annotations.NotNull; + +import java.util.List; + +public interface MetadataStore { + /** + * Adds a metadata value to an object. + * + * @param subject The object receiving the metadata. + * @param metadataKey A unique key to identify this metadata. + * @param newMetadataValue The metadata value to apply. + * @throws IllegalArgumentException If value is null, or the owning plugin + * is null + */ + public void setMetadata(@NotNull T subject, @NotNull String metadataKey, @NotNull MetadataValue newMetadataValue); + + /** + * Returns all metadata values attached to an object. If multiple plugins + * have attached metadata, each will value will be included. + * + * @param subject the object being interrogated. + * @param metadataKey the unique metadata key being sought. + * @return A list of values, one for each plugin that has set the + * requested value. + */ + @NotNull + public List getMetadata(@NotNull T subject, @NotNull String metadataKey); + + /** + * Tests to see if a metadata attribute has been set on an object. + * + * @param subject the object upon which the has-metadata test is + * performed. + * @param metadataKey the unique metadata key being queried. + * @return the existence of the metadataKey within subject. + */ + public boolean hasMetadata(@NotNull T subject, @NotNull String metadataKey); + + /** + * Removes a metadata item owned by a plugin from a subject. + * + * @param subject the object to remove the metadata from. + * @param metadataKey the unique metadata key identifying the metadata to + * remove. + * @param owningPlugin the plugin attempting to remove a metadata item. + * @throws IllegalArgumentException If plugin is null + */ + public void removeMetadata(@NotNull T subject, @NotNull String metadataKey, @NotNull Plugin owningPlugin); + + /** + * Invalidates all metadata in the metadata store that originates from the + * given plugin. Doing this will force each invalidated metadata item to + * be recalculated the next time it is accessed. + * + * @param owningPlugin the plugin requesting the invalidation. + * @throws IllegalArgumentException If plugin is null + */ + public void invalidateAll(@NotNull Plugin owningPlugin); +} diff --git a/api/src/main/java/org/bukkit/metadata/MetadataStoreBase.java b/api/src/main/java/org/bukkit/metadata/MetadataStoreBase.java new file mode 100644 index 000000000..b634b124e --- /dev/null +++ b/api/src/main/java/org/bukkit/metadata/MetadataStoreBase.java @@ -0,0 +1,164 @@ +package org.bukkit.metadata; + +import org.apache.commons.lang.Validate; +import org.bukkit.plugin.Plugin; +import org.jetbrains.annotations.NotNull; + +import java.util.*; +import java.util.concurrent.ConcurrentHashMap; + +public abstract class MetadataStoreBase { + private Map> metadataMap = new ConcurrentHashMap>(); // Paper + + /** + * Adds a metadata value to an object. Each metadata value is owned by a + * specific {@link Plugin}. If a plugin has already added a metadata value + * to an object, that value will be replaced with the value of {@code + * newMetadataValue}. Multiple plugins can set independent values for the + * same {@code metadataKey} without conflict. + *

+ * Implementation note: I considered using a {@link + * java.util.concurrent.locks.ReadWriteLock} for controlling access to + * {@code metadataMap}, but decided that the added overhead wasn't worth + * the finer grained access control. + *

+ * Bukkit is almost entirely single threaded so locking overhead shouldn't + * pose a problem. + * + * @param subject The object receiving the metadata. + * @param metadataKey A unique key to identify this metadata. + * @param newMetadataValue The metadata value to apply. + * @see MetadataStore#setMetadata(Object, String, MetadataValue) + * @throws IllegalArgumentException If value is null, or the owning plugin + * is null + */ + public synchronized void setMetadata(@NotNull T subject, @NotNull String metadataKey, @NotNull MetadataValue newMetadataValue) { + Validate.notNull(newMetadataValue, "Value cannot be null"); + Plugin owningPlugin = newMetadataValue.getOwningPlugin(); + Validate.notNull(owningPlugin, "Plugin cannot be null"); + String key = disambiguate(subject, metadataKey); + Map entry = metadataMap.get(key); + if (entry == null) { + entry = new WeakHashMap(1); + metadataMap.put(key, entry); + } + synchronized (entry) { + entry.put(owningPlugin, newMetadataValue); + } + } + + /** + * Returns all metadata values attached to an object. If multiple + * have attached metadata, each will value will be included. + * + * @param subject the object being interrogated. + * @param metadataKey the unique metadata key being sought. + * @return A list of values, one for each plugin that has set the + * requested value. + * @see MetadataStore#getMetadata(Object, String) + */ + @NotNull + public List getMetadata(@NotNull T subject, @NotNull String metadataKey) { // Paper + String key = disambiguate(subject, metadataKey); + Map entry = metadataMap.get(key); + if (entry != null) { + Collection values = entry.values(); + return Collections.unmodifiableList(new ArrayList(values)); + } else { + return Collections.emptyList(); + } + } + + /** + * Tests to see if a metadata attribute has been set on an object. + * + * @param subject the object upon which the has-metadata test is + * performed. + * @param metadataKey the unique metadata key being queried. + * @return the existence of the metadataKey within subject. + */ + public boolean hasMetadata(@NotNull T subject, @NotNull String metadataKey) { // Paper + String key = disambiguate(subject, metadataKey); + return metadataMap.containsKey(key); + } + + /** + * Removes a metadata item owned by a plugin from a subject. + * + * @param subject the object to remove the metadata from. + * @param metadataKey the unique metadata key identifying the metadata to + * remove. + * @param owningPlugin the plugin attempting to remove a metadata item. + * @see MetadataStore#removeMetadata(Object, String, + * org.bukkit.plugin.Plugin) + * @throws IllegalArgumentException If plugin is null + */ + public void removeMetadata(@NotNull T subject, @NotNull String metadataKey, @NotNull Plugin owningPlugin) { // Paper + Validate.notNull(owningPlugin, "Plugin cannot be null"); + String key = disambiguate(subject, metadataKey); + Map entry = metadataMap.get(key); + if (entry == null) { + return; + } + synchronized (entry) { + entry.remove(owningPlugin); + if (entry.isEmpty()) { + metadataMap.remove(key); + } + } + } + + /** + * Invalidates all metadata in the metadata store that originates from the + * given plugin. Doing this will force each invalidated metadata item to + * be recalculated the next time it is accessed. + * + * @param owningPlugin the plugin requesting the invalidation. + * @see MetadataStore#invalidateAll(org.bukkit.plugin.Plugin) + * @throws IllegalArgumentException If plugin is null + */ + public void invalidateAll(@NotNull Plugin owningPlugin) { // Paper + Validate.notNull(owningPlugin, "Plugin cannot be null"); + for (Map values : metadataMap.values()) { + if (values.containsKey(owningPlugin)) { + values.get(owningPlugin).invalidate(); + } + } + } + + /** + * Removes all metadata in the metadata store that originates from the + * given plugin. + * + * @param owningPlugin the plugin requesting the invalidation. + * @throws IllegalArgumentException If plugin is null + */ + public void removeAll(@NotNull Plugin owningPlugin) { + Validate.notNull(owningPlugin, "Plugin cannot be null"); + for (Iterator> iterator = metadataMap.values().iterator(); iterator.hasNext(); ) { + Map values = iterator.next(); + if (values.containsKey(owningPlugin)) { + values.remove(owningPlugin); + } + if (values.isEmpty()) { + iterator.remove(); + } + } + } + + /** + * Creates a unique name for the object receiving metadata by combining + * unique data from the subject with a metadataKey. + *

+ * The name created must be globally unique for the given object and any + * two equivalent objects must generate the same unique name. For example, + * two Player objects must generate the same string if they represent the + * same player, even if the objects would fail a reference equality test. + * + * @param subject The object for which this key is being generated. + * @param metadataKey The name identifying the metadata value. + * @return a unique metadata key for the given subject. + */ + @NotNull + protected abstract String disambiguate(@NotNull T subject, @NotNull String metadataKey); +} diff --git a/api/src/main/java/org/bukkit/metadata/MetadataValue.java b/api/src/main/java/org/bukkit/metadata/MetadataValue.java new file mode 100644 index 000000000..4b4d57924 --- /dev/null +++ b/api/src/main/java/org/bukkit/metadata/MetadataValue.java @@ -0,0 +1,87 @@ +package org.bukkit.metadata; + +import org.bukkit.plugin.Plugin; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +public interface MetadataValue { + + /** + * Fetches the value of this metadata item. + * + * @return the metadata value. + */ + @Nullable + public Object value(); + + /** + * Attempts to convert the value of this metadata item into an int. + * + * @return the value as an int. + */ + public int asInt(); + + /** + * Attempts to convert the value of this metadata item into a float. + * + * @return the value as a float. + */ + public float asFloat(); + + /** + * Attempts to convert the value of this metadata item into a double. + * + * @return the value as a double. + */ + public double asDouble(); + + /** + * Attempts to convert the value of this metadata item into a long. + * + * @return the value as a long. + */ + public long asLong(); + + /** + * Attempts to convert the value of this metadata item into a short. + * + * @return the value as a short. + */ + public short asShort(); + + /** + * Attempts to convert the value of this metadata item into a byte. + * + * @return the value as a byte. + */ + public byte asByte(); + + /** + * Attempts to convert the value of this metadata item into a boolean. + * + * @return the value as a boolean. + */ + public boolean asBoolean(); + + /** + * Attempts to convert the value of this metadata item into a string. + * + * @return the value as a string. + */ + @NotNull + public String asString(); + + /** + * Returns the {@link Plugin} that created this metadata item. + * + * @return the plugin that owns this metadata value. Could be null if the plugin was already unloaded. + */ + @Nullable + public Plugin getOwningPlugin(); + + /** + * Invalidates this metadata item, forcing it to recompute when next + * accessed. + */ + public void invalidate(); +} diff --git a/api/src/main/java/org/bukkit/metadata/MetadataValueAdapter.java b/api/src/main/java/org/bukkit/metadata/MetadataValueAdapter.java new file mode 100644 index 000000000..f88299103 --- /dev/null +++ b/api/src/main/java/org/bukkit/metadata/MetadataValueAdapter.java @@ -0,0 +1,82 @@ +package org.bukkit.metadata; + +import java.lang.ref.WeakReference; + +import org.apache.commons.lang.Validate; +import org.bukkit.plugin.Plugin; +import org.bukkit.util.NumberConversions; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +/** + * Optional base class for facilitating MetadataValue implementations. + *

+ * This provides all the conversion functions for MetadataValue so that + * writing an implementation of MetadataValue is as simple as implementing + * value() and invalidate(). + */ +public abstract class MetadataValueAdapter implements MetadataValue { + protected final WeakReference owningPlugin; + + protected MetadataValueAdapter(@NotNull Plugin owningPlugin) { + Validate.notNull(owningPlugin, "owningPlugin cannot be null"); + this.owningPlugin = new WeakReference(owningPlugin); + } + + @Nullable + public Plugin getOwningPlugin() { + return owningPlugin.get(); + } + + public int asInt() { + return NumberConversions.toInt(value()); + } + + public float asFloat() { + return NumberConversions.toFloat(value()); + } + + public double asDouble() { + return NumberConversions.toDouble(value()); + } + + public long asLong() { + return NumberConversions.toLong(value()); + } + + public short asShort() { + return NumberConversions.toShort(value()); + } + + public byte asByte() { + return NumberConversions.toByte(value()); + } + + public boolean asBoolean() { + Object value = value(); + if (value instanceof Boolean) { + return (Boolean) value; + } + + if (value instanceof Number) { + return ((Number) value).intValue() != 0; + } + + if (value instanceof String) { + return Boolean.parseBoolean((String) value); + } + + return value != null; + } + + @NotNull + public String asString() { + Object value = value(); + + if (value == null) { + return ""; + } + return value.toString(); + } + +} diff --git a/api/src/main/java/org/bukkit/metadata/Metadatable.java b/api/src/main/java/org/bukkit/metadata/Metadatable.java new file mode 100644 index 000000000..891e51608 --- /dev/null +++ b/api/src/main/java/org/bukkit/metadata/Metadatable.java @@ -0,0 +1,54 @@ +package org.bukkit.metadata; + +import org.bukkit.plugin.Plugin; +import org.jetbrains.annotations.NotNull; + +import java.util.List; + +/** + * This interface is implemented by all objects that can provide metadata + * about themselves. + */ +public interface Metadatable { + /** + * Sets a metadata value in the implementing object's metadata store. + * + * @param metadataKey A unique key to identify this metadata. + * @param newMetadataValue The metadata value to apply. + * @throws IllegalArgumentException If value is null, or the owning plugin + * is null + */ + public void setMetadata(@NotNull String metadataKey, @NotNull MetadataValue newMetadataValue); + + /** + * Returns a list of previously set metadata values from the implementing + * object's metadata store. + * + * @param metadataKey the unique metadata key being sought. + * @return A list of values, one for each plugin that has set the + * requested value. + */ + @NotNull + public List getMetadata(@NotNull String metadataKey); + + /** + * Tests to see whether the implementing object contains the given + * metadata value in its metadata store. + * + * @param metadataKey the unique metadata key being queried. + * @return the existence of the metadataKey within subject. + */ + public boolean hasMetadata(@NotNull String metadataKey); + + /** + * Removes the given metadata value from the implementing object's + * metadata store. + * + * @param metadataKey the unique metadata key identifying the metadata to + * remove. + * @param owningPlugin This plugin's metadata value will be removed. All + * other values will be left untouched. + * @throws IllegalArgumentException If plugin is null + */ + public void removeMetadata(@NotNull String metadataKey, @NotNull Plugin owningPlugin); +} diff --git a/api/src/main/java/org/bukkit/permissions/Permissible.java b/api/src/main/java/org/bukkit/permissions/Permissible.java new file mode 100644 index 000000000..228421154 --- /dev/null +++ b/api/src/main/java/org/bukkit/permissions/Permissible.java @@ -0,0 +1,129 @@ +package org.bukkit.permissions; + +import java.util.Set; +import org.bukkit.plugin.Plugin; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +/** + * Represents an object that may be assigned permissions + */ +public interface Permissible extends ServerOperator { + + /** + * Checks if this object contains an override for the specified + * permission, by fully qualified name + * + * @param name Name of the permission + * @return true if the permission is set, otherwise false + */ + public boolean isPermissionSet(@NotNull String name); + + /** + * Checks if this object contains an override for the specified {@link + * Permission} + * + * @param perm Permission to check + * @return true if the permission is set, otherwise false + */ + public boolean isPermissionSet(@NotNull Permission perm); + + /** + * Gets the value of the specified permission, if set. + *

+ * If a permission override is not set on this object, the default value + * of the permission will be returned. + * + * @param name Name of the permission + * @return Value of the permission + */ + public boolean hasPermission(@NotNull String name); + + /** + * Gets the value of the specified permission, if set. + *

+ * If a permission override is not set on this object, the default value + * of the permission will be returned + * + * @param perm Permission to get + * @return Value of the permission + */ + public boolean hasPermission(@NotNull Permission perm); + + /** + * Adds a new {@link PermissionAttachment} with a single permission by + * name and value + * + * @param plugin Plugin responsible for this attachment, may not be null + * or disabled + * @param name Name of the permission to attach + * @param value Value of the permission + * @return The PermissionAttachment that was just created + */ + @NotNull + public PermissionAttachment addAttachment(@NotNull Plugin plugin, @NotNull String name, boolean value); + + /** + * Adds a new empty {@link PermissionAttachment} to this object + * + * @param plugin Plugin responsible for this attachment, may not be null + * or disabled + * @return The PermissionAttachment that was just created + */ + @NotNull + public PermissionAttachment addAttachment(@NotNull Plugin plugin); + + /** + * Temporarily adds a new {@link PermissionAttachment} with a single + * permission by name and value + * + * @param plugin Plugin responsible for this attachment, may not be null + * or disabled + * @param name Name of the permission to attach + * @param value Value of the permission + * @param ticks Amount of ticks to automatically remove this attachment + * after + * @return The PermissionAttachment that was just created + */ + @Nullable + public PermissionAttachment addAttachment(@NotNull Plugin plugin, @NotNull String name, boolean value, int ticks); + + /** + * Temporarily adds a new empty {@link PermissionAttachment} to this + * object + * + * @param plugin Plugin responsible for this attachment, may not be null + * or disabled + * @param ticks Amount of ticks to automatically remove this attachment + * after + * @return The PermissionAttachment that was just created + */ + @Nullable + public PermissionAttachment addAttachment(@NotNull Plugin plugin, int ticks); + + /** + * Removes the given {@link PermissionAttachment} from this object + * + * @param attachment Attachment to remove + * @throws IllegalArgumentException Thrown when the specified attachment + * isn't part of this object + */ + public void removeAttachment(@NotNull PermissionAttachment attachment); + + /** + * Recalculates the permissions for this object, if the attachments have + * changed values. + *

+ * This should very rarely need to be called from a plugin. + */ + public void recalculatePermissions(); + + /** + * Gets a set containing all of the permissions currently in effect by + * this object + * + * @return Set of currently effective permissions + */ + @NotNull + public Set getEffectivePermissions(); +} diff --git a/api/src/main/java/org/bukkit/permissions/PermissibleBase.java b/api/src/main/java/org/bukkit/permissions/PermissibleBase.java new file mode 100644 index 000000000..72fff64e0 --- /dev/null +++ b/api/src/main/java/org/bukkit/permissions/PermissibleBase.java @@ -0,0 +1,259 @@ +package org.bukkit.permissions; + +import java.util.HashMap; +import java.util.HashSet; +import java.util.LinkedList; +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.logging.Level; +import org.bukkit.Bukkit; +import org.bukkit.plugin.Plugin; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +/** + * Base Permissible for use in any Permissible object via proxy or extension + */ +public class PermissibleBase implements Permissible { + private ServerOperator opable; + private Permissible parent = this; + private final List attachments = new LinkedList(); + private final Map permissions = new HashMap(); + + public PermissibleBase(@Nullable ServerOperator opable) { + this.opable = opable; + + if (opable instanceof Permissible) { + this.parent = (Permissible) opable; + } + + recalculatePermissions(); + } + + public boolean isOp() { + if (opable == null) { + return false; + } else { + return opable.isOp(); + } + } + + public void setOp(boolean value) { + if (opable == null) { + throw new UnsupportedOperationException("Cannot change op value as no ServerOperator is set"); + } else { + opable.setOp(value); + } + } + + public boolean isPermissionSet(@NotNull String name) { + if (name == null) { + throw new IllegalArgumentException("Permission name cannot be null"); + } + + return permissions.containsKey(name.toLowerCase(java.util.Locale.ENGLISH)); + } + + public boolean isPermissionSet(@NotNull Permission perm) { + if (perm == null) { + throw new IllegalArgumentException("Permission cannot be null"); + } + + return isPermissionSet(perm.getName()); + } + + public boolean hasPermission(@NotNull String inName) { + if (inName == null) { + throw new IllegalArgumentException("Permission name cannot be null"); + } + + String name = inName.toLowerCase(java.util.Locale.ENGLISH); + + // Paper start + PermissionAttachmentInfo info = permissions.get(name); + if (info != null) { + return info.getValue(); + // Paper end + } else { + Permission perm = Bukkit.getServer().getPluginManager().getPermission(name); + + if (perm != null) { + return perm.getDefault().getValue(isOp()); + } else { + return Permission.DEFAULT_PERMISSION.getValue(isOp()); + } + } + } + + public boolean hasPermission(@NotNull Permission perm) { + if (perm == null) { + throw new IllegalArgumentException("Permission cannot be null"); + } + + String name = perm.getName().toLowerCase(java.util.Locale.ENGLISH); + + // Paper start + PermissionAttachmentInfo info = permissions.get(name); + if (info != null) { + return info.getValue(); + } + // Paper end + return perm.getDefault().getValue(isOp()); + } + + @NotNull + public synchronized PermissionAttachment addAttachment(@NotNull Plugin plugin, @NotNull String name, boolean value) { // Paper - synchronized + if (name == null) { + throw new IllegalArgumentException("Permission name cannot be null"); + } else if (plugin == null) { + throw new IllegalArgumentException("Plugin cannot be null"); + } else if (!plugin.isEnabled()) { + throw new IllegalArgumentException("Plugin " + plugin.getDescription().getFullName() + " is disabled"); + } + + PermissionAttachment result = addAttachment(plugin); + result.setPermission(name, value); + + recalculatePermissions(); + + return result; + } + + @NotNull + public synchronized PermissionAttachment addAttachment(@NotNull Plugin plugin) { // Paper - synchronized + if (plugin == null) { + throw new IllegalArgumentException("Plugin cannot be null"); + } else if (!plugin.isEnabled()) { + throw new IllegalArgumentException("Plugin " + plugin.getDescription().getFullName() + " is disabled"); + } + + PermissionAttachment result = new PermissionAttachment(plugin, parent); + + attachments.add(result); + recalculatePermissions(); + + return result; + } + + public synchronized void removeAttachment(@NotNull PermissionAttachment attachment) { // Paper - synchronized + if (attachment == null) { + throw new IllegalArgumentException("Attachment cannot be null"); + } + + if (attachments.contains(attachment)) { + attachments.remove(attachment); + PermissionRemovedExecutor ex = attachment.getRemovalCallback(); + + if (ex != null) { + ex.attachmentRemoved(attachment); + } + + recalculatePermissions(); + } else { + throw new IllegalArgumentException("Given attachment is not part of Permissible object " + parent); + } + } + + public synchronized void recalculatePermissions() { // Paper - synchronized + clearPermissions(); + Set defaults = Bukkit.getServer().getPluginManager().getDefaultPermissions(isOp()); + Bukkit.getServer().getPluginManager().subscribeToDefaultPerms(isOp(), parent); + + for (Permission perm : defaults) { + String name = perm.getName().toLowerCase(java.util.Locale.ENGLISH); + permissions.put(name, new PermissionAttachmentInfo(parent, name, null, true)); + Bukkit.getServer().getPluginManager().subscribeToPermission(name, parent); + calculateChildPermissions(perm.getChildren(), false, null); + } + + for (PermissionAttachment attachment : attachments) { + calculateChildPermissions(attachment.getPermissions(), false, attachment); + } + } + + public synchronized void clearPermissions() { + Set perms = permissions.keySet(); + + for (String name : perms) { + Bukkit.getServer().getPluginManager().unsubscribeFromPermission(name, parent); + } + + Bukkit.getServer().getPluginManager().unsubscribeFromDefaultPerms(false, parent); + Bukkit.getServer().getPluginManager().unsubscribeFromDefaultPerms(true, parent); + + permissions.clear(); + } + + private void calculateChildPermissions(@NotNull Map children, boolean invert, @Nullable PermissionAttachment attachment) { + for (Map.Entry entry : children.entrySet()) { + String name = entry.getKey(); + + Permission perm = Bukkit.getServer().getPluginManager().getPermission(name); + boolean value = entry.getValue() ^ invert; + String lname = name.toLowerCase(java.util.Locale.ENGLISH); + + permissions.put(lname, new PermissionAttachmentInfo(parent, lname, attachment, value)); + Bukkit.getServer().getPluginManager().subscribeToPermission(name, parent); + + if (perm != null) { + calculateChildPermissions(perm.getChildren(), !value, attachment); + } + } + } + + @Nullable + public synchronized PermissionAttachment addAttachment(@NotNull Plugin plugin, @NotNull String name, boolean value, int ticks) { // Paper + if (name == null) { + throw new IllegalArgumentException("Permission name cannot be null"); + } else if (plugin == null) { + throw new IllegalArgumentException("Plugin cannot be null"); + } else if (!plugin.isEnabled()) { + throw new IllegalArgumentException("Plugin " + plugin.getDescription().getFullName() + " is disabled"); + } + + PermissionAttachment result = addAttachment(plugin, ticks); + + if (result != null) { + result.setPermission(name, value); + } + + return result; + } + + @Nullable + public synchronized PermissionAttachment addAttachment(@NotNull Plugin plugin, int ticks) { // Paper - synchronized + if (plugin == null) { + throw new IllegalArgumentException("Plugin cannot be null"); + } else if (!plugin.isEnabled()) { + throw new IllegalArgumentException("Plugin " + plugin.getDescription().getFullName() + " is disabled"); + } + + PermissionAttachment result = addAttachment(plugin); + + if (Bukkit.getServer().getScheduler().scheduleSyncDelayedTask(plugin, new RemoveAttachmentRunnable(result), ticks) == -1) { + Bukkit.getServer().getLogger().log(Level.WARNING, "Could not add PermissionAttachment to " + parent + " for plugin " + plugin.getDescription().getFullName() + ": Scheduler returned -1"); + result.remove(); + return null; + } else { + return result; + } + } + + @NotNull + public synchronized Set getEffectivePermissions() { // Paper - synchronized + return new HashSet(permissions.values()); + } + + private static class RemoveAttachmentRunnable implements Runnable { + private PermissionAttachment attachment; + + public RemoveAttachmentRunnable(@NotNull PermissionAttachment attachment) { + this.attachment = attachment; + } + + public void run() { + attachment.remove(); + } + } +} diff --git a/api/src/main/java/org/bukkit/permissions/Permission.java b/api/src/main/java/org/bukkit/permissions/Permission.java new file mode 100644 index 000000000..09eb28cce --- /dev/null +++ b/api/src/main/java/org/bukkit/permissions/Permission.java @@ -0,0 +1,357 @@ +package org.bukkit.permissions; + +import java.util.ArrayList; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.logging.Level; + +import org.apache.commons.lang.Validate; +import org.bukkit.Bukkit; +import org.bukkit.plugin.PluginManager; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +/** + * Represents a unique permission that may be attached to a {@link + * Permissible} + */ +public class Permission { + public static final PermissionDefault DEFAULT_PERMISSION = PermissionDefault.OP; + + private final String name; + private final Map children = new LinkedHashMap(); + private PermissionDefault defaultValue = DEFAULT_PERMISSION; + private String description; + + public Permission(@NotNull String name) { + this(name, null, null, null); + } + + public Permission(@NotNull String name, @Nullable String description) { + this(name, description, null, null); + } + + public Permission(@NotNull String name, @Nullable PermissionDefault defaultValue) { + this(name, null, defaultValue, null); + } + + public Permission(@NotNull String name, @Nullable String description, @Nullable PermissionDefault defaultValue) { + this(name, description, defaultValue, null); + } + + public Permission(@NotNull String name, @Nullable Map children) { + this(name, null, null, children); + } + + public Permission(@NotNull String name, @Nullable String description, @Nullable Map children) { + this(name, description, null, children); + } + + public Permission(@NotNull String name, @Nullable PermissionDefault defaultValue, @Nullable Map children) { + this(name, null, defaultValue, children); + } + + public Permission(@NotNull String name, @Nullable String description, @Nullable PermissionDefault defaultValue, @Nullable Map children) { + Validate.notNull(name, "Name cannot be null"); + this.name = name; + this.description = (description == null) ? "" : description; + + if (defaultValue != null) { + this.defaultValue = defaultValue; + } + + if (children != null) { + this.children.putAll(children); + } + + recalculatePermissibles(); + } + + /** + * Returns the unique fully qualified name of this Permission + * + * @return Fully qualified name + */ + @NotNull + public String getName() { + return name; + } + + /** + * Gets the children of this permission. + *

+ * If you change this map in any form, you must call {@link + * #recalculatePermissibles()} to recalculate all {@link Permissible}s + * + * @return Permission children + */ + @NotNull + public Map getChildren() { + return children; + } + + /** + * Gets the default value of this permission. + * + * @return Default value of this permission. + */ + @NotNull + public PermissionDefault getDefault() { + return defaultValue; + } + + /** + * Sets the default value of this permission. + *

+ * This will not be saved to disk, and is a temporary operation until the + * server reloads permissions. Changing this default will cause all {@link + * Permissible}s that contain this permission to recalculate their + * permissions + * + * @param value The new default to set + */ + public void setDefault(@NotNull PermissionDefault value) { + if (defaultValue == null) { + throw new IllegalArgumentException("Default value cannot be null"); + } + + defaultValue = value; + recalculatePermissibles(); + } + + /** + * Gets a brief description of this permission, may be empty + * + * @return Brief description of this permission + */ + @NotNull + public String getDescription() { + return description; + } + + /** + * Sets the description of this permission. + *

+ * This will not be saved to disk, and is a temporary operation until the + * server reloads permissions. + * + * @param value The new description to set + */ + public void setDescription(@Nullable String value) { + if (value == null) { + description = ""; + } else { + description = value; + } + } + + /** + * Gets a set containing every {@link Permissible} that has this + * permission. + *

+ * This set cannot be modified. + * + * @return Set containing permissibles with this permission + */ + @NotNull + public Set getPermissibles() { + return Bukkit.getServer().getPluginManager().getPermissionSubscriptions(name); + } + + /** + * Recalculates all {@link Permissible}s that contain this permission. + *

+ * This should be called after modifying the children, and is + * automatically called after modifying the default value + */ + public void recalculatePermissibles() { + Set perms = getPermissibles(); + + Bukkit.getServer().getPluginManager().recalculatePermissionDefaults(this); + + for (Permissible p : perms) { + p.recalculatePermissions(); + } + } + + /** + * Adds this permission to the specified parent permission. + *

+ * If the parent permission does not exist, it will be created and + * registered. + * + * @param name Name of the parent permission + * @param value The value to set this permission to + * @return Parent permission it created or loaded + */ + @NotNull + public Permission addParent(@NotNull String name, boolean value) { + PluginManager pm = Bukkit.getServer().getPluginManager(); + String lname = name.toLowerCase(java.util.Locale.ENGLISH); + + Permission perm = pm.getPermission(lname); + + if (perm == null) { + perm = new Permission(lname); + pm.addPermission(perm); + } + + addParent(perm, value); + + return perm; + } + + /** + * Adds this permission to the specified parent permission. + * + * @param perm Parent permission to register with + * @param value The value to set this permission to + */ + public void addParent(@NotNull Permission perm, boolean value) { + perm.getChildren().put(getName(), value); + perm.recalculatePermissibles(); + } + + /** + * Loads a list of Permissions from a map of data, usually used from + * retrieval from a yaml file. + *

+ * The data may contain a list of name:data, where the data contains the + * following keys: + *

    + *
  • default: Boolean true or false. If not specified, false. + *
  • children: {@code Map} of child permissions. If not + * specified, empty list. + *
  • description: Short string containing a very small description of + * this description. If not specified, empty string. + *
+ * + * @param data Map of permissions + * @param error An error message to show if a permission is invalid. May contain "%s" format tag, which will be replaced with the name of invalid permission. + * @param def Default permission value to use if missing + * @return Permission object + */ + @NotNull + public static List loadPermissions(@NotNull Map data, @NotNull String error, @Nullable PermissionDefault def) { + List result = new ArrayList(); + + for (Map.Entry entry : data.entrySet()) { + try { + result.add(Permission.loadPermission(entry.getKey().toString(), (Map) entry.getValue(), def, result)); + } catch (Throwable ex) { + Bukkit.getServer().getLogger().log(Level.SEVERE, String.format(error, entry.getKey()), ex); + } + } + + return result; + } + + /** + * Loads a Permission from a map of data, usually used from retrieval from + * a yaml file. + *

+ * The data may contain the following keys: + *

    + *
  • default: Boolean true or false. If not specified, false. + *
  • children: {@code Map} of child permissions. If not + * specified, empty list. + *
  • description: Short string containing a very small description of + * this description. If not specified, empty string. + *
+ * + * @param name Name of the permission + * @param data Map of keys + * @return Permission object + */ + @NotNull + public static Permission loadPermission(@NotNull String name, @NotNull Map data) { + return loadPermission(name, data, DEFAULT_PERMISSION, null); + } + + /** + * Loads a Permission from a map of data, usually used from retrieval from + * a yaml file. + *

+ * The data may contain the following keys: + *

    + *
  • default: Boolean true or false. If not specified, false. + *
  • children: {@code Map} of child permissions. If not + * specified, empty list. + *
  • description: Short string containing a very small description of + * this description. If not specified, empty string. + *
+ * + * @param name Name of the permission + * @param data Map of keys + * @param def Default permission value to use if not set + * @param output A list to append any created child-Permissions to, may be null + * @return Permission object + */ + @NotNull + public static Permission loadPermission(@NotNull String name, @NotNull Map data, @Nullable PermissionDefault def, @Nullable List output) { + Validate.notNull(name, "Name cannot be null"); + Validate.notNull(data, "Data cannot be null"); + + String desc = null; + Map children = null; + + if (data.get("default") != null) { + PermissionDefault value = PermissionDefault.getByName(data.get("default").toString()); + if (value != null) { + def = value; + } else { + throw new IllegalArgumentException("'default' key contained unknown value"); + } + } + + if (data.get("children") != null) { + Object childrenNode = data.get("children"); + if (childrenNode instanceof Iterable) { + children = new LinkedHashMap(); + for (Object child : (Iterable) childrenNode) { + if (child != null) { + children.put(child.toString(), Boolean.TRUE); + } + } + } else if (childrenNode instanceof Map) { + children = extractChildren((Map) childrenNode, name, def, output); + } else { + throw new IllegalArgumentException("'children' key is of wrong type"); + } + } + + if (data.get("description") != null) { + desc = data.get("description").toString(); + } + + return new Permission(name, desc, def, children); + } + + @NotNull + private static Map extractChildren(@NotNull Map input, @NotNull String name, @Nullable PermissionDefault def, @Nullable List output) { + Map children = new LinkedHashMap(); + + for (Map.Entry entry : input.entrySet()) { + if ((entry.getValue() instanceof Boolean)) { + children.put(entry.getKey().toString(), (Boolean) entry.getValue()); + } else if ((entry.getValue() instanceof Map)) { + try { + Permission perm = loadPermission(entry.getKey().toString(), (Map) entry.getValue(), def, output); + children.put(perm.getName(), Boolean.TRUE); + + if (output != null) { + output.add(perm); + } + } catch (Throwable ex) { + throw new IllegalArgumentException("Permission node '" + entry.getKey().toString() + "' in child of " + name + " is invalid", ex); + } + } else { + throw new IllegalArgumentException("Child '" + entry.getKey().toString() + "' contains invalid value"); + } + } + + return children; + } +} diff --git a/api/src/main/java/org/bukkit/permissions/PermissionAttachment.java b/api/src/main/java/org/bukkit/permissions/PermissionAttachment.java new file mode 100644 index 000000000..cd8ac371a --- /dev/null +++ b/api/src/main/java/org/bukkit/permissions/PermissionAttachment.java @@ -0,0 +1,145 @@ +package org.bukkit.permissions; + +import java.util.LinkedHashMap; +import java.util.Map; +import org.bukkit.plugin.Plugin; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +/** + * Holds information about a permission attachment on a {@link Permissible} + * object + */ +public class PermissionAttachment { + private PermissionRemovedExecutor removed; + private final Map permissions = new LinkedHashMap(); + private final Permissible permissible; + private final Plugin plugin; + + public PermissionAttachment(@NotNull Plugin plugin, @NotNull Permissible permissible) { + if (plugin == null) { + throw new IllegalArgumentException("Plugin cannot be null"); + } else if (!plugin.isEnabled()) { + throw new IllegalArgumentException("Plugin " + plugin.getDescription().getFullName() + " is disabled"); + } + + this.permissible = permissible; + this.plugin = plugin; + } + + /** + * Gets the plugin responsible for this attachment + * + * @return Plugin responsible for this permission attachment + */ + @NotNull + public Plugin getPlugin() { + return plugin; + } + + /** + * Sets an object to be called for when this attachment is removed from a + * {@link Permissible}. May be null. + * + * @param ex Object to be called when this is removed + */ + public void setRemovalCallback(@Nullable PermissionRemovedExecutor ex) { + removed = ex; + } + + /** + * Gets the class that was previously set to be called when this + * attachment was removed from a {@link Permissible}. May be null. + * + * @return Object to be called when this is removed + */ + @Nullable + public PermissionRemovedExecutor getRemovalCallback() { + return removed; + } + + /** + * Gets the Permissible that this is attached to + * + * @return Permissible containing this attachment + */ + @NotNull + public Permissible getPermissible() { + return permissible; + } + + /** + * Gets a copy of all set permissions and values contained within this + * attachment. + *

+ * This map may be modified but will not affect the attachment, as it is a + * copy. + * + * @return Copy of all permissions and values expressed by this attachment + */ + @NotNull + public Map getPermissions() { + return new LinkedHashMap(permissions); + } + + /** + * Sets a permission to the given value, by its fully qualified name + * + * @param name Name of the permission + * @param value New value of the permission + */ + public void setPermission(@NotNull String name, boolean value) { + permissions.put(name.toLowerCase(java.util.Locale.ENGLISH), value); + permissible.recalculatePermissions(); + } + + /** + * Sets a permission to the given value + * + * @param perm Permission to set + * @param value New value of the permission + */ + public void setPermission(@NotNull Permission perm, boolean value) { + setPermission(perm.getName(), value); + } + + /** + * Removes the specified permission from this attachment. + *

+ * If the permission does not exist in this attachment, nothing will + * happen. + * + * @param name Name of the permission to remove + */ + public void unsetPermission(@NotNull String name) { + permissions.remove(name.toLowerCase(java.util.Locale.ENGLISH)); + permissible.recalculatePermissions(); + } + + /** + * Removes the specified permission from this attachment. + *

+ * If the permission does not exist in this attachment, nothing will + * happen. + * + * @param perm Permission to remove + */ + public void unsetPermission(@NotNull Permission perm) { + unsetPermission(perm.getName()); + } + + /** + * Removes this attachment from its registered {@link Permissible} + * + * @return true if the permissible was removed successfully, false if it + * did not exist + */ + public boolean remove() { + try { + permissible.removeAttachment(this); + return true; + } catch (IllegalArgumentException ex) { + return false; + } + } +} diff --git a/api/src/main/java/org/bukkit/permissions/PermissionAttachmentInfo.java b/api/src/main/java/org/bukkit/permissions/PermissionAttachmentInfo.java new file mode 100644 index 000000000..a7f4cd37c --- /dev/null +++ b/api/src/main/java/org/bukkit/permissions/PermissionAttachmentInfo.java @@ -0,0 +1,68 @@ +package org.bukkit.permissions; + +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +/** + * Holds information on a permission and which {@link PermissionAttachment} + * provides it + */ +public class PermissionAttachmentInfo { + private final Permissible permissible; + private final String permission; + private final PermissionAttachment attachment; + private final boolean value; + + public PermissionAttachmentInfo(@NotNull Permissible permissible, @NotNull String permission, @Nullable PermissionAttachment attachment, boolean value) { + if (permissible == null) { + throw new IllegalArgumentException("Permissible may not be null"); + } else if (permission == null) { + throw new IllegalArgumentException("Permission may not be null"); + } + + this.permissible = permissible; + this.permission = permission; + this.attachment = attachment; + this.value = value; + } + + /** + * Gets the permissible this is attached to + * + * @return Permissible this permission is for + */ + @NotNull + public Permissible getPermissible() { + return permissible; + } + + /** + * Gets the permission being set + * + * @return Name of the permission + */ + @NotNull + public String getPermission() { + return permission; + } + + /** + * Gets the attachment providing this permission. This may be null for + * default permissions (usually parent permissions). + * + * @return Attachment + */ + @Nullable + public PermissionAttachment getAttachment() { + return attachment; + } + + /** + * Gets the value of this permission + * + * @return Value of the permission + */ + public boolean getValue() { + return value; + } +} diff --git a/api/src/main/java/org/bukkit/permissions/PermissionDefault.java b/api/src/main/java/org/bukkit/permissions/PermissionDefault.java new file mode 100644 index 000000000..c36f52f95 --- /dev/null +++ b/api/src/main/java/org/bukkit/permissions/PermissionDefault.java @@ -0,0 +1,70 @@ +package org.bukkit.permissions; + +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +import java.util.HashMap; +import java.util.Map; + +/** + * Represents the possible default values for permissions + */ +public enum PermissionDefault { + TRUE("true"), + FALSE("false"), + OP("op", "isop", "operator", "isoperator", "admin", "isadmin"), + NOT_OP("!op", "notop", "!operator", "notoperator", "!admin", "notadmin"); + + private final String[] names; + private final static Map lookup = new HashMap(); + + private PermissionDefault(/*@NotNull*/ String... names) { + this.names = names; + } + + /** + * Calculates the value of this PermissionDefault for the given operator + * value + * + * @param op If the target is op + * @return True if the default should be true, or false + */ + public boolean getValue(boolean op) { + switch (this) { + case TRUE: + return true; + case FALSE: + return false; + case OP: + return op; + case NOT_OP: + return !op; + default: + return false; + } + } + + /** + * Looks up a PermissionDefault by name + * + * @param name Name of the default + * @return Specified value, or null if not found + */ + @Nullable + public static PermissionDefault getByName(@NotNull String name) { + return lookup.get(name.toLowerCase(java.util.Locale.ENGLISH).replaceAll("[^a-z!]", "")); + } + + @Override + public String toString() { + return names[0]; + } + + static { + for (PermissionDefault value : values()) { + for (String name : value.names) { + lookup.put(name, value); + } + } + } +} diff --git a/api/src/main/java/org/bukkit/permissions/PermissionRemovedExecutor.java b/api/src/main/java/org/bukkit/permissions/PermissionRemovedExecutor.java new file mode 100644 index 000000000..4cc9e6471 --- /dev/null +++ b/api/src/main/java/org/bukkit/permissions/PermissionRemovedExecutor.java @@ -0,0 +1,18 @@ +package org.bukkit.permissions; + +import org.jetbrains.annotations.NotNull; + +/** + * Represents a class which is to be notified when a {@link + * PermissionAttachment} is removed from a {@link Permissible} + */ +public interface PermissionRemovedExecutor { + + /** + * Called when a {@link PermissionAttachment} is removed from a {@link + * Permissible} + * + * @param attachment Attachment which was removed + */ + public void attachmentRemoved(@NotNull PermissionAttachment attachment); +} diff --git a/api/src/main/java/org/bukkit/permissions/ServerOperator.java b/api/src/main/java/org/bukkit/permissions/ServerOperator.java new file mode 100644 index 000000000..26ed24307 --- /dev/null +++ b/api/src/main/java/org/bukkit/permissions/ServerOperator.java @@ -0,0 +1,24 @@ +package org.bukkit.permissions; + +import org.bukkit.entity.Player; + +/** + * Represents an object that may become a server operator, such as a {@link + * Player} + */ +public interface ServerOperator { + + /** + * Checks if this object is a server operator + * + * @return true if this is an operator, otherwise false + */ + public boolean isOp(); + + /** + * Sets the operator status of this object + * + * @param value New operator value + */ + public void setOp(boolean value); +} diff --git a/api/src/main/java/org/bukkit/plugin/AuthorNagException.java b/api/src/main/java/org/bukkit/plugin/AuthorNagException.java new file mode 100644 index 000000000..6565a4414 --- /dev/null +++ b/api/src/main/java/org/bukkit/plugin/AuthorNagException.java @@ -0,0 +1,20 @@ +package org.bukkit.plugin; + +@SuppressWarnings("serial") +public class AuthorNagException extends RuntimeException { + private final String message; + + /** + * Constructs a new AuthorNagException based on the given Exception + * + * @param message Brief message explaining the cause of the exception + */ + public AuthorNagException(final String message) { + this.message = message; + } + + @Override + public String getMessage() { + return message; + } +} diff --git a/api/src/main/java/org/bukkit/plugin/EventExecutor.java b/api/src/main/java/org/bukkit/plugin/EventExecutor.java new file mode 100644 index 000000000..9026e108c --- /dev/null +++ b/api/src/main/java/org/bukkit/plugin/EventExecutor.java @@ -0,0 +1,79 @@ +package org.bukkit.plugin; + +import org.bukkit.event.Event; +import org.bukkit.event.EventException; +import org.bukkit.event.Listener; +import org.jetbrains.annotations.NotNull; + +// Paper start +import java.lang.reflect.Method; +import java.lang.reflect.Modifier; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.ConcurrentMap; +import java.util.function.Function; + +import com.destroystokyo.paper.event.executor.MethodHandleEventExecutor; +import com.destroystokyo.paper.event.executor.StaticMethodHandleEventExecutor; +import com.destroystokyo.paper.event.executor.asm.ASMEventExecutorGenerator; +import com.destroystokyo.paper.event.executor.asm.ClassDefiner; +import com.google.common.base.Preconditions; +// Paper end + +/** + * Interface which defines the class for event call backs to plugins + */ +public interface EventExecutor { + public void execute(@NotNull Listener listener, @NotNull Event event) throws EventException; + + // Paper start + ConcurrentMap> eventExecutorMap = new ConcurrentHashMap>() { + @NotNull + @Override + public Class computeIfAbsent(@NotNull Method key, @NotNull Function> mappingFunction) { + Class executorClass = get(key); + if (executorClass != null) + return executorClass; + + //noinspection SynchronizationOnLocalVariableOrMethodParameter + synchronized (key) { + executorClass = get(key); + if (executorClass != null) + return executorClass; + + return super.computeIfAbsent(key, mappingFunction); + } + } + }; + + @NotNull + public static EventExecutor create(@NotNull Method m, @NotNull Class eventClass) { + Preconditions.checkNotNull(m, "Null method"); + Preconditions.checkArgument(m.getParameterCount() != 0, "Incorrect number of arguments %s", m.getParameterCount()); + Preconditions.checkArgument(m.getParameterTypes()[0] == eventClass, "First parameter %s doesn't match event class %s", m.getParameterTypes()[0], eventClass); + ClassDefiner definer = ClassDefiner.getInstance(); + if (Modifier.isStatic(m.getModifiers())) { + return new StaticMethodHandleEventExecutor(eventClass, m); + } else if (definer.isBypassAccessChecks() || Modifier.isPublic(m.getDeclaringClass().getModifiers()) && Modifier.isPublic(m.getModifiers())) { + // get the existing generated EventExecutor class for the Method or generate one + Class executorClass = eventExecutorMap.computeIfAbsent(m, (__) -> { + String name = ASMEventExecutorGenerator.generateName(); + byte[] classData = ASMEventExecutorGenerator.generateEventExecutor(m, name); + return definer.defineClass(m.getDeclaringClass().getClassLoader(), name, classData).asSubclass(EventExecutor.class); + }); + + try { + EventExecutor asmExecutor = executorClass.newInstance(); + // Define a wrapper to conform to bukkit stupidity (passing in events that don't match and wrapper exception) + return (listener, event) -> { + if (!eventClass.isInstance(event)) return; + asmExecutor.execute(listener, event); + }; + } catch (InstantiationException | IllegalAccessException e) { + throw new AssertionError("Unable to initialize generated event executor", e); + } + } else { + return new MethodHandleEventExecutor(eventClass, m); + } + } + // Paper end +} diff --git a/api/src/main/java/org/bukkit/plugin/IllegalPluginAccessException.java b/api/src/main/java/org/bukkit/plugin/IllegalPluginAccessException.java new file mode 100644 index 000000000..b25447dcf --- /dev/null +++ b/api/src/main/java/org/bukkit/plugin/IllegalPluginAccessException.java @@ -0,0 +1,25 @@ +package org.bukkit.plugin; + +/** + * Thrown when a plugin attempts to interact with the server when it is not + * enabled + */ +@SuppressWarnings("serial") +public class IllegalPluginAccessException extends RuntimeException { + + /** + * Creates a new instance of IllegalPluginAccessException + * without detail message. + */ + public IllegalPluginAccessException() {} + + /** + * Constructs an instance of IllegalPluginAccessException + * with the specified detail message. + * + * @param msg the detail message. + */ + public IllegalPluginAccessException(String msg) { + super(msg); + } +} diff --git a/api/src/main/java/org/bukkit/plugin/InvalidDescriptionException.java b/api/src/main/java/org/bukkit/plugin/InvalidDescriptionException.java new file mode 100644 index 000000000..0a77c2e21 --- /dev/null +++ b/api/src/main/java/org/bukkit/plugin/InvalidDescriptionException.java @@ -0,0 +1,45 @@ +package org.bukkit.plugin; + +/** + * Thrown when attempting to load an invalid PluginDescriptionFile + */ +public class InvalidDescriptionException extends Exception { + private static final long serialVersionUID = 5721389122281775896L; + + /** + * Constructs a new InvalidDescriptionException based on the given + * Exception + * + * @param message Brief message explaining the cause of the exception + * @param cause Exception that triggered this Exception + */ + public InvalidDescriptionException(final Throwable cause, final String message) { + super(message, cause); + } + + /** + * Constructs a new InvalidDescriptionException based on the given + * Exception + * + * @param cause Exception that triggered this Exception + */ + public InvalidDescriptionException(final Throwable cause) { + super("Invalid plugin.yml", cause); + } + + /** + * Constructs a new InvalidDescriptionException with the given message + * + * @param message Brief message explaining the cause of the exception + */ + public InvalidDescriptionException(final String message) { + super(message); + } + + /** + * Constructs a new InvalidDescriptionException + */ + public InvalidDescriptionException() { + super("Invalid plugin.yml"); + } +} diff --git a/api/src/main/java/org/bukkit/plugin/InvalidPluginException.java b/api/src/main/java/org/bukkit/plugin/InvalidPluginException.java new file mode 100644 index 000000000..7ddf7b626 --- /dev/null +++ b/api/src/main/java/org/bukkit/plugin/InvalidPluginException.java @@ -0,0 +1,49 @@ +package org.bukkit.plugin; + +/** + * Thrown when attempting to load an invalid Plugin file + */ +public class InvalidPluginException extends Exception { + private static final long serialVersionUID = -8242141640709409544L; + + /** + * Constructs a new InvalidPluginException based on the given Exception + * + * @param cause Exception that triggered this Exception + */ + public InvalidPluginException(final Throwable cause) { + super(cause); + } + + /** + * Constructs a new InvalidPluginException + */ + public InvalidPluginException() { + + } + + /** + * Constructs a new InvalidPluginException with the specified detail + * message and cause. + * + * @param message the detail message (which is saved for later retrieval + * by the getMessage() method). + * @param cause the cause (which is saved for later retrieval by the + * getCause() method). (A null value is permitted, and indicates that + * the cause is nonexistent or unknown.) + */ + public InvalidPluginException(final String message, final Throwable cause) { + super(message, cause); + } + + /** + * Constructs a new InvalidPluginException with the specified detail + * message + * + * @param message TThe detail message is saved for later retrieval by the + * getMessage() method. + */ + public InvalidPluginException(final String message) { + super(message); + } +} diff --git a/api/src/main/java/org/bukkit/plugin/Plugin.java b/api/src/main/java/org/bukkit/plugin/Plugin.java new file mode 100644 index 000000000..b4882f48b --- /dev/null +++ b/api/src/main/java/org/bukkit/plugin/Plugin.java @@ -0,0 +1,187 @@ +package org.bukkit.plugin; + +import java.io.File; +import java.io.InputStream; +import java.util.logging.Logger; + +import org.bukkit.Server; +import org.bukkit.command.TabExecutor; +import org.bukkit.configuration.file.FileConfiguration; +import org.bukkit.generator.ChunkGenerator; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +/** + * Represents a Plugin + *

+ * The use of {@link PluginBase} is recommended for actual Implementation + */ +public interface Plugin extends TabExecutor { + /** + * Returns the folder that the plugin data's files are located in. The + * folder may not yet exist. + * + * @return The folder + */ + @NotNull + public File getDataFolder(); + + /** + * Returns the plugin.yaml file containing the details for this plugin + * + * @return Contents of the plugin.yaml file + */ + @NotNull + public PluginDescriptionFile getDescription(); + + /** + * Gets a {@link FileConfiguration} for this plugin, read through + * "config.yml" + *

+ * If there is a default config.yml embedded in this plugin, it will be + * provided as a default for this Configuration. + * + * @return Plugin configuration + */ + @NotNull + public FileConfiguration getConfig(); + + /** + * Gets an embedded resource in this plugin + * + * @param filename Filename of the resource + * @return File if found, otherwise null + */ + @Nullable + public InputStream getResource(@NotNull String filename); + + /** + * Saves the {@link FileConfiguration} retrievable by {@link #getConfig()}. + */ + public void saveConfig(); + + /** + * Saves the raw contents of the default config.yml file to the location + * retrievable by {@link #getConfig()}. + *

+ * This should fail silently if the config.yml already exists. + */ + public void saveDefaultConfig(); + + /** + * Saves the raw contents of any resource embedded with a plugin's .jar + * file assuming it can be found using {@link #getResource(String)}. + *

+ * The resource is saved into the plugin's data folder using the same + * hierarchy as the .jar file (subdirectories are preserved). + * + * @param resourcePath the embedded resource path to look for within the + * plugin's .jar file. (No preceding slash). + * @param replace if true, the embedded resource will overwrite the + * contents of an existing file. + * @throws IllegalArgumentException if the resource path is null, empty, + * or points to a nonexistent resource. + */ + public void saveResource(@NotNull String resourcePath, boolean replace); + + /** + * Discards any data in {@link #getConfig()} and reloads from disk. + */ + public void reloadConfig(); + + /** + * Gets the associated PluginLoader responsible for this plugin + * + * @return PluginLoader that controls this plugin + */ + @NotNull + public PluginLoader getPluginLoader(); + + /** + * Returns the Server instance currently running this plugin + * + * @return Server running this plugin + */ + @NotNull + public Server getServer(); + + /** + * Returns a value indicating whether or not this plugin is currently + * enabled + * + * @return true if this plugin is enabled, otherwise false + */ + public boolean isEnabled(); + + /** + * Called when this plugin is disabled + */ + public void onDisable(); + + /** + * Called after a plugin is loaded but before it has been enabled. + *

+ * When multiple plugins are loaded, the onLoad() for all plugins is + * called before any onEnable() is called. + */ + public void onLoad(); + + /** + * Called when this plugin is enabled + */ + public void onEnable(); + + /** + * Simple boolean if we can still nag to the logs about things + * + * @return boolean whether we can nag + */ + public boolean isNaggable(); + + /** + * Set naggable state + * + * @param canNag is this plugin still naggable? + */ + public void setNaggable(boolean canNag); + + /** + * Gets a {@link ChunkGenerator} for use in a default world, as specified + * in the server configuration + * + * @param worldName Name of the world that this will be applied to + * @param id Unique ID, if any, that was specified to indicate which + * generator was requested + * @return ChunkGenerator for use in the default world generation + */ + @Nullable + public ChunkGenerator getDefaultWorldGenerator(@NotNull String worldName, @Nullable String id); + + /** + * Returns the plugin logger associated with this server's logger. The + * returned logger automatically tags all log messages with the plugin's + * name. + * + * @return Logger associated with this plugin + */ + @NotNull + public Logger getLogger(); + + // Paper start - Add SLF4J logger + @NotNull + default org.slf4j.Logger getSLF4JLogger() { + return org.slf4j.LoggerFactory.getLogger(getLogger().getName()); + } + // Paper end + + /** + * Returns the name of the plugin. + *

+ * This should return the bare name of the plugin and should be used for + * comparison. + * + * @return name of the plugin + */ + @NotNull + public String getName(); +} diff --git a/api/src/main/java/org/bukkit/plugin/PluginAwareness.java b/api/src/main/java/org/bukkit/plugin/PluginAwareness.java new file mode 100644 index 000000000..3f535ed5d --- /dev/null +++ b/api/src/main/java/org/bukkit/plugin/PluginAwareness.java @@ -0,0 +1,28 @@ +package org.bukkit.plugin; + +import java.util.Set; + +/** + * Represents a concept that a plugin is aware of. + *

+ * The internal representation may be singleton, or be a parameterized + * instance, but must be immutable. + */ +public interface PluginAwareness { + /** + * Each entry here represents a particular plugin's awareness. These can + * be checked by using {@link PluginDescriptionFile#getAwareness()}.{@link + * Set#contains(Object) contains(flag)}. + */ + public enum Flags implements PluginAwareness { + /** + * This specifies that all (text) resources stored in a plugin's jar + * use UTF-8 encoding. + * + * @deprecated all plugins are now assumed to be UTF-8 aware. + */ + @Deprecated + UTF8, + ; + } +} diff --git a/api/src/main/java/org/bukkit/plugin/PluginBase.java b/api/src/main/java/org/bukkit/plugin/PluginBase.java new file mode 100644 index 000000000..70c53a24d --- /dev/null +++ b/api/src/main/java/org/bukkit/plugin/PluginBase.java @@ -0,0 +1,35 @@ +package org.bukkit.plugin; + +import org.jetbrains.annotations.NotNull; + +/** + * Represents a base {@link Plugin} + *

+ * Extend this class if your plugin is not a {@link + * org.bukkit.plugin.java.JavaPlugin} + */ +public abstract class PluginBase implements Plugin { + @Override + public final int hashCode() { + return getName().hashCode(); + } + + @Override + public final boolean equals(Object obj) { + if (this == obj) { + return true; + } + if (obj == null) { + return false; + } + if (!(obj instanceof Plugin)) { + return false; + } + return getName().equals(((Plugin) obj).getName()); + } + + @NotNull + public final String getName() { + return getDescription().getName(); + } +} diff --git a/api/src/main/java/org/bukkit/plugin/PluginDescriptionFile.java b/api/src/main/java/org/bukkit/plugin/PluginDescriptionFile.java new file mode 100644 index 000000000..4574a965e --- /dev/null +++ b/api/src/main/java/org/bukkit/plugin/PluginDescriptionFile.java @@ -0,0 +1,1144 @@ +package org.bukkit.plugin; + +import java.io.InputStream; +import java.io.Reader; +import java.io.Writer; +import java.util.HashMap; +import java.util.HashSet; +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.regex.Pattern; + +import org.bukkit.command.Command; +import org.bukkit.command.CommandExecutor; +import org.bukkit.command.CommandSender; +import org.bukkit.command.PluginCommand; +import org.bukkit.command.TabCompleter; +import org.bukkit.plugin.java.JavaPlugin; +import org.bukkit.permissions.Permissible; +import org.bukkit.permissions.Permission; +import org.bukkit.permissions.PermissionDefault; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; +import org.yaml.snakeyaml.Yaml; +import org.yaml.snakeyaml.constructor.AbstractConstruct; +import org.yaml.snakeyaml.constructor.SafeConstructor; +import org.yaml.snakeyaml.nodes.Node; +import org.yaml.snakeyaml.nodes.Tag; + +import com.google.common.collect.ImmutableList; +import com.google.common.collect.ImmutableMap; +import com.google.common.collect.ImmutableSet; + +/** + * This type is the runtime-container for the information in the plugin.yml. + * All plugins must have a respective plugin.yml. For plugins written in java + * using the standard plugin loader, this file must be in the root of the jar + * file. + *

+ * When Bukkit loads a plugin, it needs to know some basic information about + * it. It reads this information from a YAML file, 'plugin.yml'. This file + * consists of a set of attributes, each defined on a new line and with no + * indentation. + *

+ * Every (almost* every) method corresponds with a specific entry in the + * plugin.yml. These are the required entries for every plugin.yml: + *

    + *
  • {@link #getName()} - name + *
  • {@link #getVersion()} - version + *
  • {@link #getMain()} - main + *
+ *

+ * Failing to include any of these items will throw an exception and cause the + * server to ignore your plugin. + *

+ * This is a list of the possible yaml keys, with specific details included in + * the respective method documentations: + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + *
The description of the plugin.yml layout
NodeMethodSummary
name{@link #getName()}The unique name of plugin
version{@link #getVersion()}A plugin revision identifier
main{@link #getMain()}The plugin's initial class file
author
authors
{@link #getAuthors()}The plugin contributors
description{@link #getDescription()}Human readable plugin summary
website{@link #getWebsite()}The URL to the plugin's site
prefix{@link #getPrefix()}The token to prefix plugin log entries
load{@link #getLoad()}The phase of server-startup this plugin will load during
depend{@link #getDepend()}Other required plugins
softdepend{@link #getSoftDepend()}Other plugins that add functionality
loadbefore{@link #getLoadBefore()}The inverse softdepend
commands{@link #getCommands()}The commands the plugin will register
permissions{@link #getPermissions()}The permissions the plugin will register
default-permission{@link #getPermissionDefault()}The default {@link Permission#getDefault() default} permission + * state for defined {@link #getPermissions() permissions} the plugin + * will register
awareness{@link #getAwareness()}The concepts that the plugin acknowledges
api-version{@link #getAPIVersion()}The API version which this plugin was programmed against
+ *

+ * A plugin.yml example:

+ *name: Inferno
+ *version: 1.4.1
+ *description: This plugin is so 31337. You can set yourself on fire.
+ *# We could place every author in the authors list, but chose not to for illustrative purposes
+ *# Also, having an author distinguishes that person as the project lead, and ensures their
+ *# name is displayed first
+ *author: CaptainInflamo
+ *authors: [Cogito, verrier, EvilSeph]
+ *website: http://www.curse.com/server-mods/minecraft/myplugin
+ *
+ *main: com.captaininflamo.bukkit.inferno.Inferno
+ *depend: [NewFire, FlameWire]
+ *api-version: 1.13
+ *
+ *commands:
+ *  flagrate:
+ *    description: Set yourself on fire.
+ *    aliases: [combust_me, combustMe]
+ *    permission: inferno.flagrate
+ *    usage: Syntax error! Simply type /<command> to ignite yourself.
+ *  burningdeaths:
+ *    description: List how many times you have died by fire.
+ *    aliases: [burning_deaths, burningDeaths]
+ *    permission: inferno.burningdeaths
+ *    usage: |
+ *      /<command> [player]
+ *      Example: /<command> - see how many times you have burned to death
+ *      Example: /<command> CaptainIce - see how many times CaptainIce has burned to death
+ *
+ *permissions:
+ *  inferno.*:
+ *    description: Gives access to all Inferno commands
+ *    children:
+ *      inferno.flagrate: true
+ *      inferno.burningdeaths: true
+ *      inferno.burningdeaths.others: true
+ *  inferno.flagrate:
+ *    description: Allows you to ignite yourself
+ *    default: true
+ *  inferno.burningdeaths:
+ *    description: Allows you to see how many times you have burned to death
+ *    default: true
+ *  inferno.burningdeaths.others:
+ *    description: Allows you to see how many times others have burned to death
+ *    default: op
+ *    children:
+ *      inferno.burningdeaths: true
+ *
+ */ +public final class PluginDescriptionFile { + private static final Pattern VALID_NAME = Pattern.compile("^[A-Za-z0-9 _.-]+$"); + private static final ThreadLocal YAML = new ThreadLocal() { + @Override + @NotNull + protected Yaml initialValue() { + return new Yaml(new SafeConstructor() { + { + yamlConstructors.put(null, new AbstractConstruct() { + @NotNull + @Override + public Object construct(@NotNull final Node node) { + if (!node.getTag().startsWith("!@")) { + // Unknown tag - will fail + return SafeConstructor.undefinedConstructor.construct(node); + } + // Unknown awareness - provide a graceful substitution + return new PluginAwareness() { + @Override + public String toString() { + return node.toString(); + } + }; + } + }); + for (final PluginAwareness.Flags flag : PluginAwareness.Flags.values()) { + yamlConstructors.put(new Tag("!@" + flag.name()), new AbstractConstruct() { + @NotNull + @Override + public PluginAwareness.Flags construct(@NotNull final Node node) { + return flag; + } + }); + } + } + }); + } + }; + String rawName = null; + private String name = null; + private String main = null; + private String classLoaderOf = null; + private List depend = ImmutableList.of(); + private List softDepend = ImmutableList.of(); + private List loadBefore = ImmutableList.of(); + private String version = null; + private Map> commands = ImmutableMap.of(); + private String description = null; + private List authors = null; + private String website = null; + private String prefix = null; + private PluginLoadOrder order = PluginLoadOrder.POSTWORLD; + private List permissions = null; + private Map lazyPermissions = null; + private PermissionDefault defaultPerm = PermissionDefault.OP; + private Set awareness = ImmutableSet.of(); + private String apiVersion = null; + + public PluginDescriptionFile(@NotNull final InputStream stream) throws InvalidDescriptionException { + loadMap(asMap(YAML.get().load(stream))); + } + + /** + * Loads a PluginDescriptionFile from the specified reader + * + * @param reader The reader + * @throws InvalidDescriptionException If the PluginDescriptionFile is + * invalid + */ + public PluginDescriptionFile(@NotNull final Reader reader) throws InvalidDescriptionException { + loadMap(asMap(YAML.get().load(reader))); + } + + /** + * Creates a new PluginDescriptionFile with the given detailed + * + * @param pluginName Name of this plugin + * @param pluginVersion Version of this plugin + * @param mainClass Full location of the main class of this plugin + */ + public PluginDescriptionFile(@NotNull final String pluginName, @NotNull final String pluginVersion, @NotNull final String mainClass) { + name = rawName = pluginName; + + if (!VALID_NAME.matcher(name).matches()) { + throw new IllegalArgumentException("name '" + name + "' contains invalid characters."); + } + name = name.replace(' ', '_'); + version = pluginVersion; + main = mainClass; + } + + /** + * Gives the name of the plugin. This name is a unique identifier for + * plugins. + *
    + *
  • Must consist of all alphanumeric characters, underscores, hyphon, + * and period (a-z,A-Z,0-9, _.-). Any other character will cause the + * plugin.yml to fail loading. + *
  • Used to determine the name of the plugin's data folder. Data + * folders are placed in the ./plugins/ directory by default, but this + * behavior should not be relied on. {@link Plugin#getDataFolder()} + * should be used to reference the data folder. + *
  • It is good practice to name your jar the same as this, for example + * 'MyPlugin.jar'. + *
  • Case sensitive. + *
  • The is the token referenced in {@link #getDepend()}, {@link + * #getSoftDepend()}, and {@link #getLoadBefore()}. + *
  • Using spaces in the plugin's name is deprecated. + *
+ *

+ * In the plugin.yml, this entry is named name. + *

+ * Example:

name: MyPlugin
+ * + * @return the name of the plugin + */ + @NotNull + public String getName() { + return name; + } + + /** + * Gives the version of the plugin. + *
    + *
  • Version is an arbitrary string, however the most common format is + * MajorRelease.MinorRelease.Build (eg: 1.4.1). + *
  • Typically you will increment this every time you release a new + * feature or bug fix. + *
  • Displayed when a user types /version PluginName + *
+ *

+ * In the plugin.yml, this entry is named version. + *

+ * Example:

version: 1.4.1
+ * + * @return the version of the plugin + */ + @NotNull + public String getVersion() { + return version; + } + + /** + * Gives the fully qualified name of the main class for a plugin. The + * format should follow the {@link ClassLoader#loadClass(String)} syntax + * to successfully be resolved at runtime. For most plugins, this is the + * class that extends {@link JavaPlugin}. + *
    + *
  • This must contain the full namespace including the class file + * itself. + *
  • If your namespace is org.bukkit.plugin, and your class + * file is called MyPlugin then this must be + * org.bukkit.plugin.MyPlugin + *
  • No plugin can use org.bukkit. as a base package for + * any class, including the main class. + *
+ *

+ * In the plugin.yml, this entry is named main. + *

+ * Example: + *

main: org.bukkit.plugin.MyPlugin
+ * + * @return the fully qualified main class for the plugin + */ + @NotNull + public String getMain() { + return main; + } + + /** + * Gives a human-friendly description of the functionality the plugin + * provides. + *
    + *
  • The description can have multiple lines. + *
  • Displayed when a user types /version PluginName + *
+ *

+ * In the plugin.yml, this entry is named description. + *

+ * Example: + *

description: This plugin is so 31337. You can set yourself on fire.
+ * + * @return description of this plugin, or null if not specified + */ + @Nullable + public String getDescription() { + return description; + } + + /** + * Gives the phase of server startup that the plugin should be loaded. + *
    + *
  • Possible values are in {@link PluginLoadOrder}. + *
  • Defaults to {@link PluginLoadOrder#POSTWORLD}. + *
  • Certain caveats apply to each phase. + *
  • When different, {@link #getDepend()}, {@link #getSoftDepend()}, and + * {@link #getLoadBefore()} become relative in order loaded per-phase. + * If a plugin loads at STARTUP, but a dependency loads + * at POSTWORLD, the dependency will not be loaded before + * the plugin is loaded. + *
+ *

+ * In the plugin.yml, this entry is named load. + *

+ * Example:

load: STARTUP
+ * + * @return the phase when the plugin should be loaded + */ + @NotNull + public PluginLoadOrder getLoad() { + return order; + } + + /** + * Gives the list of authors for the plugin. + *
    + *
  • Gives credit to the developer. + *
  • Used in some server error messages to provide helpful feedback on + * who to contact when an error occurs. + *
  • A bukkit.org forum handle or email address is recommended. + *
  • Is displayed when a user types /version PluginName + *
  • authors must be in YAML list + * format. + *
+ *

+ * In the plugin.yml, this has two entries, author and + * authors. + *

+ * Single author example: + *

author: CaptainInflamo
+ * Multiple author example: + *
authors: [Cogito, verrier, EvilSeph]
+ * When both are specified, author will be the first entry in the list, so + * this example: + *
author: Grum
+     *authors:
+     *- feildmaster
+     *- amaranth
+ * Is equivilant to this example: + *
authors: [Grum, feildmaster, aramanth]
+ * + * @return an immutable list of the plugin's authors + */ + @NotNull + public List getAuthors() { + return authors; + } + + /** + * Gives the plugin's or plugin's author's website. + *
    + *
  • A link to the Curse page that includes documentation and downloads + * is highly recommended. + *
  • Displayed when a user types /version PluginName + *
+ *

+ * In the plugin.yml, this entry is named website. + *

+ * Example: + *

website: http://www.curse.com/server-mods/minecraft/myplugin
+ * + * @return description of this plugin, or null if not specified + */ + @Nullable + public String getWebsite() { + return website; + } + + /** + * Gives a list of other plugins that the plugin requires. + *
    + *
  • Use the value in the {@link #getName()} of the target plugin to + * specify the dependency. + *
  • If any plugin listed here is not found, your plugin will fail to + * load at startup. + *
  • If multiple plugins list each other in depend, + * creating a network with no individual plugin does not list another + * plugin in the network, + * all plugins in that network will fail. + *
  • depend must be in must be in YAML list + * format. + *
+ *

+ * In the plugin.yml, this entry is named depend. + *

+ * Example: + *

depend:
+     *- OnePlugin
+     *- AnotherPlugin
+ * + * @return immutable list of the plugin's dependencies + */ + @NotNull + public List getDepend() { + return depend; + } + + /** + * Gives a list of other plugins that the plugin requires for full + * functionality. The {@link PluginManager} will make best effort to treat + * all entries here as if they were a {@link #getDepend() dependency}, but + * will never fail because of one of these entries. + *
    + *
  • Use the value in the {@link #getName()} of the target plugin to + * specify the dependency. + *
  • When an unresolvable plugin is listed, it will be ignored and does + * not affect load order. + *
  • When a circular dependency occurs (a network of plugins depending + * or soft-dependending each other), it will arbitrarily choose a + * plugin that can be resolved when ignoring soft-dependencies. + *
  • softdepend must be in YAML list + * format. + *
+ *

+ * In the plugin.yml, this entry is named softdepend. + *

+ * Example: + *

softdepend: [OnePlugin, AnotherPlugin]
+ * + * @return immutable list of the plugin's preferred dependencies + */ + @NotNull + public List getSoftDepend() { + return softDepend; + } + + /** + * Gets the list of plugins that should consider this plugin a + * soft-dependency. + *
    + *
  • Use the value in the {@link #getName()} of the target plugin to + * specify the dependency. + *
  • The plugin should load before any other plugins listed here. + *
  • Specifying another plugin here is strictly equivalent to having the + * specified plugin's {@link #getSoftDepend()} include {@link + * #getName() this plugin}. + *
  • loadbefore must be in YAML list + * format. + *
+ *

+ * In the plugin.yml, this entry is named loadbefore. + *

+ * Example: + *

loadbefore:
+     *- OnePlugin
+     *- AnotherPlugin
+ * + * @return immutable list of plugins that should consider this plugin a + * soft-dependency + */ + @NotNull + public List getLoadBefore() { + return loadBefore; + } + + /** + * Gives the token to prefix plugin-specific logging messages with. + *
    + *
  • This includes all messages using {@link Plugin#getLogger()}. + *
  • If not specified, the server uses the plugin's {@link #getName() + * name}. + *
  • This should clearly indicate what plugin is being logged. + *
+ *

+ * In the plugin.yml, this entry is named prefix. + *

+ * Example:

prefix: ex-why-zee
+ * + * @return the prefixed logging token, or null if not specified + */ + @Nullable + public String getPrefix() { + return prefix; + } + + /** + * Gives the map of command-name to command-properties. Each entry in this + * map corresponds to a single command and the respective values are the + * properties of the command. Each property, with the exception of + * aliases, can be defined at runtime using methods in {@link + * PluginCommand} and are defined here only as a convenience. + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + *
The command section's description
NodeMethodTypeDescriptionExample
description{@link PluginCommand#setDescription(String)}StringA user-friendly description for a command. It is useful for + * documentation purposes as well as in-game help.
description: Set yourself on fire
aliases{@link PluginCommand#setAliases(List)}String or List of + * stringsAlternative command names, with special usefulness for commands + * that are already registered. Aliases are not effective when + * defined at runtime, so the plugin description file is the + * only way to have them properly defined. + *

+ * Note: Command aliases may not have a colon in them.

Single alias format: + *
aliases: combust_me
or + * multiple alias format: + *
aliases: [combust_me, combustMe]
permission{@link PluginCommand#setPermission(String)}StringThe name of the {@link Permission} required to use the command. + * A user without the permission will receive the specified + * message (see {@linkplain + * PluginCommand#setPermissionMessage(String) below}), or a + * standard one if no specific message is defined. Without the + * permission node, no {@link + * PluginCommand#setExecutor(CommandExecutor) CommandExecutor} or + * {@link PluginCommand#setTabCompleter(TabCompleter)} will be called.
permission: inferno.flagrate
permission-message{@link PluginCommand#setPermissionMessage(String)}String
    + *
  • Displayed to a player that attempts to use a command, but + * does not have the required permission. See {@link + * PluginCommand#getPermission() above}. + *
  • <permission> is a macro that is replaced with the + * permission node required to use the command. + *
  • Using empty quotes is a valid way to indicate nothing + * should be displayed to a player. + *
permission-message: You do not have /<permission>
usage{@link PluginCommand#setUsage(String)}StringThis message is displayed to a player when the {@link + * PluginCommand#setExecutor(CommandExecutor)} {@linkplain + * CommandExecutor#onCommand(CommandSender, Command, String, String[]) returns false}. + * <command> is a macro that is replaced the command issued.
usage: Syntax error! Perhaps you meant /<command> PlayerName?
+ * It is worth noting that to use a colon in a yaml, like + * `usage: Usage: /god [player]', you need to + * surround + * the message with double-quote: + *
usage: "Usage: /god [player]"
+ * The commands are structured as a hiearchy of nested mappings. + * The primary (top-level, no intendentation) node is + * `commands', while each individual command name is + * indented, indicating it maps to some value (in our case, the + * properties of the table above). + *

+ * Here is an example bringing together the piecemeal examples above, as + * well as few more definitions:

+     *commands:
+     *  flagrate:
+     *    description: Set yourself on fire.
+     *    aliases: [combust_me, combustMe]
+     *    permission: inferno.flagrate
+     *    permission-message: You do not have /<permission>
+     *    usage: Syntax error! Perhaps you meant /<command> PlayerName?
+     *  burningdeaths:
+     *    description: List how many times you have died by fire.
+     *    aliases:
+     *    - burning_deaths
+     *    - burningDeaths
+     *    permission: inferno.burningdeaths
+     *    usage: |
+     *      /<command> [player]
+     *      Example: /<command> - see how many times you have burned to death
+     *      Example: /<command> CaptainIce - see how many times CaptainIce has burned to death
+     *  # The next command has no description, aliases, etc. defined, but is still valid
+     *  # Having an empty declaration is useful for defining the description, permission, and messages from a configuration dynamically
+     *  apocalypse:
+     *
+ * Note: Command names may not have a colon in their name. + * + * @return the commands this plugin will register + */ + @NotNull + public Map> getCommands() { + return commands; + } + + /** + * Gives the list of permissions the plugin will register at runtime, + * immediately proceding enabling. The format for defining permissions is + * a map from permission name to properties. To represent a map without + * any specific property, empty curly-braces ( + * {} ) may be used (as a null value is not + * accepted, unlike the {@link #getCommands() commands} above). + *

+ * A list of optional properties for permissions: + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + *
The permission section's description
NodeDescriptionExample
descriptionPlaintext (user-friendly) description of what the permission + * is for.
description: Allows you to set yourself on fire
defaultThe default state for the permission, as defined by {@link + * Permission#getDefault()}. If not defined, it will be set to + * the value of {@link PluginDescriptionFile#getPermissionDefault()}. + *

+ * For reference:

    + *
  • true - Represents a positive assignment to + * {@link Permissible permissibles}. + *
  • false - Represents no assignment to {@link + * Permissible permissibles}. + *
  • op - Represents a positive assignment to + * {@link Permissible#isOp() operator permissibles}. + *
  • notop - Represents a positive assignment to + * {@link Permissible#isOp() non-operator permissibiles}. + *
default: true
childrenAllows other permissions to be set as a {@linkplain + * Permission#getChildren() relation} to the parent permission. + * When a parent permissions is assigned, child permissions are + * respectively assigned as well. + *
    + *
  • When a parent permission is assigned negatively, child + * permissions are assigned based on an inversion of their + * association. + *
  • When a parent permission is assigned positively, child + * permissions are assigned based on their association. + *
+ *

+ * Child permissions may be defined in a number of ways:

    + *
  • Children may be defined as a list of + * names. Using a list will treat all children associated + * positively to their parent. + *
  • Children may be defined as a map. Each permission name maps + * to either a boolean (representing the association), or a + * nested permission definition (just as another permission). + * Using a nested definition treats the child as a positive + * association. + *
  • A nested permission definition must be a map of these same + * properties. To define a valid nested permission without + * defining any specific property, empty curly-braces ( + * {} ) must be used. + *
  • A nested permission may carry it's own nested permissions + * as children, as they may also have nested permissions, and + * so forth. There is no direct limit to how deep the + * permission tree is defined. + *
As a list: + *
children: [inferno.flagrate, inferno.burningdeaths]
+ * Or as a mapping: + *
children:
+     *  inferno.flagrate: true
+     *  inferno.burningdeaths: true
+ * An additional example showing basic nested values can be seen + * here. + *
+ * The permissions are structured as a hiearchy of nested mappings. + * The primary (top-level, no intendentation) node is + * `permissions', while each individual permission name is + * indented, indicating it maps to some value (in our case, the + * properties of the table above). + *

+ * Here is an example using some of the properties:

+     *permissions:
+     *  inferno.*:
+     *    description: Gives access to all Inferno commands
+     *    children:
+     *      inferno.flagrate: true
+     *      inferno.burningdeaths: true
+     *  inferno.flagate:
+     *    description: Allows you to ignite yourself
+     *    default: true
+     *  inferno.burningdeaths:
+     *    description: Allows you to see how many times you have burned to death
+     *    default: true
+     *
+ * Another example, with nested definitions, can be found here. + * + * @return the permissions this plugin will register + */ + @NotNull + public List getPermissions() { + if (permissions == null) { + if (lazyPermissions == null) { + permissions = ImmutableList.of(); + } else { + permissions = ImmutableList.copyOf(Permission.loadPermissions(lazyPermissions, "Permission node '%s' in plugin description file for " + getFullName() + " is invalid", defaultPerm)); + lazyPermissions = null; + } + } + return permissions; + } + + /** + * Gives the default {@link Permission#getDefault() default} state of + * {@link #getPermissions() permissions} registered for the plugin. + *
    + *
  • If not specified, it will be {@link PermissionDefault#OP}. + *
  • It is matched using {@link PermissionDefault#getByName(String)} + *
  • It only affects permissions that do not define the + * default node. + *
  • It may be any value in {@link PermissionDefault}. + *
+ *

+ * In the plugin.yml, this entry is named default-permission. + *

+ * Example:

default-permission: NOT_OP
+ * + * @return the default value for the plugin's permissions + */ + @NotNull + public PermissionDefault getPermissionDefault() { + return defaultPerm; + } + + /** + * Gives a set of every {@link PluginAwareness} for a plugin. An awareness + * dictates something that a plugin developer acknowledges when the plugin + * is compiled. Some implementions may define extra awarenesses that are + * not included in the API. Any unrecognized + * awareness (one unsupported or in a future version) will cause a dummy + * object to be created instead of failing. + * + *
    + *
  • Currently only supports the enumerated values in {@link + * PluginAwareness.Flags}. + *
  • Each awareness starts the identifier with bang-at + * (!@). + *
  • Unrecognized (future / unimplemented) entries are quietly replaced + * by a generic object that implements PluginAwareness. + *
  • A type of awareness must be defined by the runtime and acknowledged + * by the API, effectively discluding any derived type from any + * plugin's classpath. + *
  • awareness must be in YAML list + * format. + *
+ *

+ * In the plugin.yml, this entry is named awareness. + *

+ * Example:

awareness:
+     *- !@UTF8
+ *

+ * Note: Although unknown versions of some future awareness are + * gracefully substituted, previous versions of Bukkit (ones prior to the + * first implementation of awareness) will fail to load a plugin that + * defines any awareness. + * + * @return a set containing every awareness for the plugin + */ + @NotNull + public Set getAwareness() { + return awareness; + } + + /** + * Returns the name of a plugin, including the version. This method is + * provided for convenience; it uses the {@link #getName()} and {@link + * #getVersion()} entries. + * + * @return a descriptive name of the plugin and respective version + */ + @NotNull + public String getFullName() { + return name + " v" + version; + } + + /** + * Gives the API version which this plugin is designed to support. No + * specific format is guaranteed. + *

    + *
  • Refer to release notes for supported API versions. + *
+ *

+ * In the plugin.yml, this entry is named api-version. + *

+ * Example:

api-version: 1.13
+ * + * @return the version of the plugin + */ + @Nullable + public String getAPIVersion() { + return apiVersion; + } + + /** + * @return unused + * @deprecated unused + */ + @Deprecated + @Nullable + public String getClassLoaderOf() { + return classLoaderOf; + } + + /** + * Saves this PluginDescriptionFile to the given writer + * + * @param writer Writer to output this file to + */ + public void save(@NotNull Writer writer) { + YAML.get().dump(saveMap(), writer); + } + + private void loadMap(@NotNull Map map) throws InvalidDescriptionException { + try { + name = rawName = map.get("name").toString(); + + if (!VALID_NAME.matcher(name).matches()) { + throw new InvalidDescriptionException("name '" + name + "' contains invalid characters."); + } + name = name.replace(' ', '_'); + } catch (NullPointerException ex) { + throw new InvalidDescriptionException(ex, "name is not defined"); + } catch (ClassCastException ex) { + throw new InvalidDescriptionException(ex, "name is of wrong type"); + } + + try { + version = map.get("version").toString(); + } catch (NullPointerException ex) { + throw new InvalidDescriptionException(ex, "version is not defined"); + } catch (ClassCastException ex) { + throw new InvalidDescriptionException(ex, "version is of wrong type"); + } + + try { + main = map.get("main").toString(); + if (main.startsWith("org.bukkit.")) { + throw new InvalidDescriptionException("main may not be within the org.bukkit namespace"); + } + } catch (NullPointerException ex) { + throw new InvalidDescriptionException(ex, "main is not defined"); + } catch (ClassCastException ex) { + throw new InvalidDescriptionException(ex, "main is of wrong type"); + } + + if (map.get("commands") != null) { + ImmutableMap.Builder> commandsBuilder = ImmutableMap.>builder(); + try { + for (Map.Entry command : ((Map) map.get("commands")).entrySet()) { + ImmutableMap.Builder commandBuilder = ImmutableMap.builder(); + if (command.getValue() != null) { + for (Map.Entry commandEntry : ((Map) command.getValue()).entrySet()) { + if (commandEntry.getValue() instanceof Iterable) { + // This prevents internal alias list changes + ImmutableList.Builder commandSubList = ImmutableList.builder(); + for (Object commandSubListItem : (Iterable) commandEntry.getValue()) { + if (commandSubListItem != null) { + commandSubList.add(commandSubListItem); + } + } + commandBuilder.put(commandEntry.getKey().toString(), commandSubList.build()); + } else if (commandEntry.getValue() != null) { + commandBuilder.put(commandEntry.getKey().toString(), commandEntry.getValue()); + } + } + } + commandsBuilder.put(command.getKey().toString(), commandBuilder.build()); + } + } catch (ClassCastException ex) { + throw new InvalidDescriptionException(ex, "commands are of wrong type"); + } + commands = commandsBuilder.build(); + } + + if (map.get("class-loader-of") != null) { + classLoaderOf = map.get("class-loader-of").toString(); + } + + depend = makePluginNameList(map, "depend"); + softDepend = makePluginNameList(map, "softdepend"); + loadBefore = makePluginNameList(map, "loadbefore"); + + if (map.get("website") != null) { + website = map.get("website").toString(); + } + + if (map.get("description") != null) { + description = map.get("description").toString(); + } + + if (map.get("load") != null) { + try { + order = PluginLoadOrder.valueOf(((String) map.get("load")).toUpperCase(java.util.Locale.ENGLISH).replaceAll("\\W", "")); + } catch (ClassCastException ex) { + throw new InvalidDescriptionException(ex, "load is of wrong type"); + } catch (IllegalArgumentException ex) { + throw new InvalidDescriptionException(ex, "load is not a valid choice"); + } + } + + if (map.get("authors") != null) { + ImmutableList.Builder authorsBuilder = ImmutableList.builder(); + if (map.get("author") != null) { + authorsBuilder.add(map.get("author").toString()); + } + try { + for (Object o : (Iterable) map.get("authors")) { + authorsBuilder.add(o.toString()); + } + } catch (ClassCastException ex) { + throw new InvalidDescriptionException(ex, "authors are of wrong type"); + } catch (NullPointerException ex) { + throw new InvalidDescriptionException(ex, "authors are improperly defined"); + } + authors = authorsBuilder.build(); + } else if (map.get("author") != null) { + authors = ImmutableList.of(map.get("author").toString()); + } else { + authors = ImmutableList.of(); + } + + if (map.get("default-permission") != null) { + try { + defaultPerm = PermissionDefault.getByName(map.get("default-permission").toString()); + } catch (ClassCastException ex) { + throw new InvalidDescriptionException(ex, "default-permission is of wrong type"); + } catch (IllegalArgumentException ex) { + throw new InvalidDescriptionException(ex, "default-permission is not a valid choice"); + } + } + + if (map.get("awareness") instanceof Iterable) { + Set awareness = new HashSet(); + try { + for (Object o : (Iterable) map.get("awareness")) { + awareness.add((PluginAwareness) o); + } + } catch (ClassCastException ex) { + throw new InvalidDescriptionException(ex, "awareness has wrong type"); + } + this.awareness = ImmutableSet.copyOf(awareness); + } + + if (map.get("api-version") != null) { + apiVersion = map.get("api-version").toString(); + } + + try { + lazyPermissions = (Map) map.get("permissions"); + } catch (ClassCastException ex) { + throw new InvalidDescriptionException(ex, "permissions are of the wrong type"); + } + + if (map.get("prefix") != null) { + prefix = map.get("prefix").toString(); + } + } + + @NotNull + private static List makePluginNameList(@NotNull final Map map, @NotNull final String key) throws InvalidDescriptionException { + final Object value = map.get(key); + if (value == null) { + return ImmutableList.of(); + } + + final ImmutableList.Builder builder = ImmutableList.builder(); + try { + for (final Object entry : (Iterable) value) { + builder.add(entry.toString().replace(' ', '_')); + } + } catch (ClassCastException ex) { + throw new InvalidDescriptionException(ex, key + " is of wrong type"); + } catch (NullPointerException ex) { + throw new InvalidDescriptionException(ex, "invalid " + key + " format"); + } + return builder.build(); + } + + @NotNull + private Map saveMap() { + Map map = new HashMap(); + + map.put("name", name); + map.put("main", main); + map.put("version", version); + map.put("order", order.toString()); + map.put("default-permission", defaultPerm.toString()); + + if (commands != null) { + map.put("command", commands); + } + if (depend != null) { + map.put("depend", depend); + } + if (softDepend != null) { + map.put("softdepend", softDepend); + } + if (website != null) { + map.put("website", website); + } + if (description != null) { + map.put("description", description); + } + + if (authors.size() == 1) { + map.put("author", authors.get(0)); + } else if (authors.size() > 1) { + map.put("authors", authors); + } + + if (apiVersion != null) { + map.put("api-version", apiVersion); + } + + if (classLoaderOf != null) { + map.put("class-loader-of", classLoaderOf); + } + + if (prefix != null) { + map.put("prefix", prefix); + } + + return map; + } + + @NotNull + private Map asMap(@NotNull Object object) throws InvalidDescriptionException { + if (object instanceof Map) { + return (Map) object; + } + throw new InvalidDescriptionException(object + " is not properly structured."); + } + + /** + * @return internal use + * @deprecated Internal use + */ + @Deprecated + @NotNull + public String getRawName() { + return rawName; + } +} diff --git a/api/src/main/java/org/bukkit/plugin/PluginLoadOrder.java b/api/src/main/java/org/bukkit/plugin/PluginLoadOrder.java new file mode 100644 index 000000000..b77436fdd --- /dev/null +++ b/api/src/main/java/org/bukkit/plugin/PluginLoadOrder.java @@ -0,0 +1,17 @@ +package org.bukkit.plugin; + +/** + * Represents the order in which a plugin should be initialized and enabled + */ +public enum PluginLoadOrder { + + /** + * Indicates that the plugin will be loaded at startup + */ + STARTUP, + /** + * Indicates that the plugin will be loaded after the first/default world + * was created + */ + POSTWORLD +} diff --git a/api/src/main/java/org/bukkit/plugin/PluginLoader.java b/api/src/main/java/org/bukkit/plugin/PluginLoader.java new file mode 100644 index 000000000..fbd65e8b0 --- /dev/null +++ b/api/src/main/java/org/bukkit/plugin/PluginLoader.java @@ -0,0 +1,95 @@ +package org.bukkit.plugin; + +import java.io.File; +import java.util.Map; +import java.util.Set; +import java.util.regex.Pattern; + +import org.bukkit.event.Event; +import org.bukkit.event.Listener; +import org.jetbrains.annotations.NotNull; + +/** + * Represents a plugin loader, which handles direct access to specific types + * of plugins + */ +public interface PluginLoader { + + /** + * Loads the plugin contained in the specified file + * + * @param file File to attempt to load + * @return Plugin that was contained in the specified file, or null if + * unsuccessful + * @throws InvalidPluginException Thrown when the specified file is not a + * plugin + * @throws UnknownDependencyException If a required dependency could not + * be found + */ + @NotNull + public Plugin loadPlugin(@NotNull File file) throws InvalidPluginException, UnknownDependencyException; + + /** + * Loads a PluginDescriptionFile from the specified file + * + * @param file File to attempt to load from + * @return A new PluginDescriptionFile loaded from the plugin.yml in the + * specified file + * @throws InvalidDescriptionException If the plugin description file + * could not be created + */ + @NotNull + public PluginDescriptionFile getPluginDescription(@NotNull File file) throws InvalidDescriptionException; + + /** + * Returns a list of all filename filters expected by this PluginLoader + * + * @return The filters + */ + @NotNull + public Pattern[] getPluginFileFilters(); + + /** + * Creates and returns registered listeners for the event classes used in + * this listener + * + * @param listener The object that will handle the eventual call back + * @param plugin The plugin to use when creating registered listeners + * @return The registered listeners. + */ + @NotNull + public Map, Set> createRegisteredListeners(@NotNull Listener listener, @NotNull Plugin plugin); + + /** + * Enables the specified plugin + *

+ * Attempting to enable a plugin that is already enabled will have no + * effect + * + * @param plugin Plugin to enable + */ + public void enablePlugin(@NotNull Plugin plugin); + + /** + * Disables the specified plugin + *

+ * Attempting to disable a plugin that is not enabled will have no effect + * + * @param plugin Plugin to disable + */ + public void disablePlugin(@NotNull Plugin plugin); + // Paper start - close Classloader on disable + /** + * Disables the specified plugin + *

+ * Attempting to disable a plugin that is not enabled will have no effect + * + * @param plugin Plugin to disable + * @param closeClassloader if the classloader for the Plugin should be closed + */ + // provide default to allow other PluginLoader implementations to work + default public void disablePlugin(@NotNull Plugin plugin, boolean closeClassloader) { + disablePlugin(plugin); + } + // Paper end - close Classloader on disable +} diff --git a/api/src/main/java/org/bukkit/plugin/PluginLogger.java b/api/src/main/java/org/bukkit/plugin/PluginLogger.java new file mode 100644 index 000000000..533f0b6f8 --- /dev/null +++ b/api/src/main/java/org/bukkit/plugin/PluginLogger.java @@ -0,0 +1,38 @@ +package org.bukkit.plugin; + +import org.jetbrains.annotations.NotNull; + +import java.util.logging.Level; +import java.util.logging.LogRecord; +import java.util.logging.Logger; + +/** + * The PluginLogger class is a modified {@link Logger} that prepends all + * logging calls with the name of the plugin doing the logging. The API for + * PluginLogger is exactly the same as {@link Logger}. + * + * @see Logger + */ +public class PluginLogger extends Logger { + private String pluginName; + + /** + * Creates a new PluginLogger that extracts the name from a plugin. + * + * @param context A reference to the plugin + */ + public PluginLogger(@NotNull Plugin context) { + super(context.getClass().getCanonicalName(), null); + String prefix = context.getDescription().getPrefix(); + pluginName = prefix != null ? new StringBuilder().append("[").append(prefix).append("] ").toString() : "[" + context.getDescription().getName() + "] "; + setParent(context.getServer().getLogger()); + setLevel(Level.ALL); + } + + @Override + public void log(@NotNull LogRecord logRecord) { + logRecord.setMessage(pluginName + logRecord.getMessage()); + super.log(logRecord); + } + +} diff --git a/api/src/main/java/org/bukkit/plugin/PluginManager.java b/api/src/main/java/org/bukkit/plugin/PluginManager.java new file mode 100644 index 000000000..ba4ed7ed7 --- /dev/null +++ b/api/src/main/java/org/bukkit/plugin/PluginManager.java @@ -0,0 +1,319 @@ +package org.bukkit.plugin; + +import java.io.File; +import java.util.Set; + +import org.bukkit.event.Event; +import org.bukkit.event.EventPriority; +import org.bukkit.event.Listener; +import org.bukkit.permissions.Permissible; +import org.bukkit.permissions.Permission; +import org.jetbrains.annotations.Contract; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +/** + * Handles all plugin management from the Server + */ +public interface PluginManager { + + /** + * Registers the specified plugin loader + * + * @param loader Class name of the PluginLoader to register + * @throws IllegalArgumentException Thrown when the given Class is not a + * valid PluginLoader + */ + public void registerInterface(@NotNull Class loader) throws IllegalArgumentException; + + /** + * Checks if the given plugin is loaded and returns it when applicable + *

+ * Please note that the name of the plugin is case-sensitive + * + * @param name Name of the plugin to check + * @return Plugin if it exists, otherwise null + */ + @Nullable + public Plugin getPlugin(@NotNull String name); + + /** + * Gets a list of all currently loaded plugins + * + * @return Array of Plugins + */ + @NotNull + public Plugin[] getPlugins(); + + /** + * Checks if the given plugin is enabled or not + *

+ * Please note that the name of the plugin is case-sensitive. + * + * @param name Name of the plugin to check + * @return true if the plugin is enabled, otherwise false + */ + public boolean isPluginEnabled(@NotNull String name); + + /** + * Checks if the given plugin is enabled or not + * + * @param plugin Plugin to check + * @return true if the plugin is enabled, otherwise false + */ + @Contract("null -> false") + public boolean isPluginEnabled(@Nullable Plugin plugin); + + /** + * Loads the plugin in the specified file + *

+ * File must be valid according to the current enabled Plugin interfaces + * + * @param file File containing the plugin to load + * @return The Plugin loaded, or null if it was invalid + * @throws InvalidPluginException Thrown when the specified file is not a + * valid plugin + * @throws InvalidDescriptionException Thrown when the specified file + * contains an invalid description + * @throws UnknownDependencyException If a required dependency could not + * be resolved + */ + @Nullable + public Plugin loadPlugin(@NotNull File file) throws InvalidPluginException, InvalidDescriptionException, UnknownDependencyException; + + /** + * Loads the plugins contained within the specified directory + * + * @param directory Directory to check for plugins + * @return A list of all plugins loaded + */ + @NotNull + public Plugin[] loadPlugins(@NotNull File directory); + + /** + * Disables all the loaded plugins + */ + public void disablePlugins(); + + /** + * Disables and removes all plugins + */ + public void clearPlugins(); + + /** + * Calls an event with the given details + * + * @param event Event details + * @throws IllegalStateException Thrown when an asynchronous event is + * fired from synchronous code. + *

+ * Note: This is best-effort basis, and should not be used to test + * synchronized state. This is an indicator for flawed flow logic. + */ + public void callEvent(@NotNull Event event) throws IllegalStateException; + + /** + * Registers all the events in the given listener class + * + * @param listener Listener to register + * @param plugin Plugin to register + */ + public void registerEvents(@NotNull Listener listener, @NotNull Plugin plugin); + + /** + * Registers the specified executor to the given event class + * + * @param event Event type to register + * @param listener Listener to register + * @param priority Priority to register this event at + * @param executor EventExecutor to register + * @param plugin Plugin to register + */ + public void registerEvent(@NotNull Class event, @NotNull Listener listener, @NotNull EventPriority priority, @NotNull EventExecutor executor, @NotNull Plugin plugin); + + /** + * Registers the specified executor to the given event class + * + * @param event Event type to register + * @param listener Listener to register + * @param priority Priority to register this event at + * @param executor EventExecutor to register + * @param plugin Plugin to register + * @param ignoreCancelled Whether to pass cancelled events or not + */ + public void registerEvent(@NotNull Class event, @NotNull Listener listener, @NotNull EventPriority priority, @NotNull EventExecutor executor, @NotNull Plugin plugin, boolean ignoreCancelled); + + /** + * Enables the specified plugin + *

+ * Attempting to enable a plugin that is already enabled will have no + * effect + * + * @param plugin Plugin to enable + */ + public void enablePlugin(@NotNull Plugin plugin); + + /** + * Disables the specified plugin + *

+ * Attempting to disable a plugin that is not enabled will have no effect + * + * @param plugin Plugin to disable + */ + public void disablePlugin(@NotNull Plugin plugin); + + // Paper start - close Classloader on disable + /** + * Disables the specified plugin + *

+ * Attempting to disable a plugin that is not enabled will have no effect + * + * @param plugin Plugin to disable + * @param closeClassloader if the classloader for the Plugin should be closed + */ + public void disablePlugin(@NotNull Plugin plugin, boolean closeClassloader); + // Paper end - close Classloader on disable + + /** + * Gets a {@link Permission} from its fully qualified name + * + * @param name Name of the permission + * @return Permission, or null if none + */ + @Nullable + public Permission getPermission(@NotNull String name); + + /** + * Adds a {@link Permission} to this plugin manager. + *

+ * If a permission is already defined with the given name of the new + * permission, an exception will be thrown. + * + * @param perm Permission to add + * @throws IllegalArgumentException Thrown when a permission with the same + * name already exists + */ + public void addPermission(@NotNull Permission perm); + + /** + * Removes a {@link Permission} registration from this plugin manager. + *

+ * If the specified permission does not exist in this plugin manager, + * nothing will happen. + *

+ * Removing a permission registration will not remove the + * permission from any {@link Permissible}s that have it. + * + * @param perm Permission to remove + */ + public void removePermission(@NotNull Permission perm); + + /** + * Removes a {@link Permission} registration from this plugin manager. + *

+ * If the specified permission does not exist in this plugin manager, + * nothing will happen. + *

+ * Removing a permission registration will not remove the + * permission from any {@link Permissible}s that have it. + * + * @param name Permission to remove + */ + public void removePermission(@NotNull String name); + + /** + * Gets the default permissions for the given op status + * + * @param op Which set of default permissions to get + * @return The default permissions + */ + @NotNull + public Set getDefaultPermissions(boolean op); + + /** + * Recalculates the defaults for the given {@link Permission}. + *

+ * This will have no effect if the specified permission is not registered + * here. + * + * @param perm Permission to recalculate + */ + public void recalculatePermissionDefaults(@NotNull Permission perm); + + /** + * Subscribes the given Permissible for information about the requested + * Permission, by name. + *

+ * If the specified Permission changes in any form, the Permissible will + * be asked to recalculate. + * + * @param permission Permission to subscribe to + * @param permissible Permissible subscribing + */ + public void subscribeToPermission(@NotNull String permission, @NotNull Permissible permissible); + + /** + * Unsubscribes the given Permissible for information about the requested + * Permission, by name. + * + * @param permission Permission to unsubscribe from + * @param permissible Permissible subscribing + */ + public void unsubscribeFromPermission(@NotNull String permission, @NotNull Permissible permissible); + + /** + * Gets a set containing all subscribed {@link Permissible}s to the given + * permission, by name + * + * @param permission Permission to query for + * @return Set containing all subscribed permissions + */ + @NotNull + public Set getPermissionSubscriptions(@NotNull String permission); + + /** + * Subscribes to the given Default permissions by operator status + *

+ * If the specified defaults change in any form, the Permissible will be + * asked to recalculate. + * + * @param op Default list to subscribe to + * @param permissible Permissible subscribing + */ + public void subscribeToDefaultPerms(boolean op, @NotNull Permissible permissible); + + /** + * Unsubscribes from the given Default permissions by operator status + * + * @param op Default list to unsubscribe from + * @param permissible Permissible subscribing + */ + public void unsubscribeFromDefaultPerms(boolean op, @NotNull Permissible permissible); + + /** + * Gets a set containing all subscribed {@link Permissible}s to the given + * default list, by op status + * + * @param op Default list to query for + * @return Set containing all subscribed permissions + */ + @NotNull + public Set getDefaultPermSubscriptions(boolean op); + + /** + * Gets a set of all registered permissions. + *

+ * This set is a copy and will not be modified live. + * + * @return Set containing all current registered permissions + */ + @NotNull + public Set getPermissions(); + + /** + * Returns whether or not timing code should be used for event calls + * + * @return True if event timings are to be used + */ + public boolean useTimings(); +} diff --git a/api/src/main/java/org/bukkit/plugin/RegisteredListener.java b/api/src/main/java/org/bukkit/plugin/RegisteredListener.java new file mode 100644 index 000000000..a3b3a056b --- /dev/null +++ b/api/src/main/java/org/bukkit/plugin/RegisteredListener.java @@ -0,0 +1,77 @@ +package org.bukkit.plugin; + +import org.bukkit.event.*; +import org.jetbrains.annotations.NotNull; + +/** + * Stores relevant information for plugin listeners + */ +public class RegisteredListener { + private final Listener listener; + private final EventPriority priority; + private final Plugin plugin; + private final EventExecutor executor; + private final boolean ignoreCancelled; + + public RegisteredListener(@NotNull final Listener listener, @NotNull final EventExecutor executor, @NotNull final EventPriority priority, @NotNull final Plugin plugin, final boolean ignoreCancelled) { + this.listener = listener; + this.priority = priority; + this.plugin = plugin; + this.executor = executor; + this.ignoreCancelled = ignoreCancelled; + } + + /** + * Gets the listener for this registration + * + * @return Registered Listener + */ + @NotNull + public Listener getListener() { + return listener; + } + + /** + * Gets the plugin for this registration + * + * @return Registered Plugin + */ + @NotNull + public Plugin getPlugin() { + return plugin; + } + + /** + * Gets the priority for this registration + * + * @return Registered Priority + */ + @NotNull + public EventPriority getPriority() { + return priority; + } + + /** + * Calls the event executor + * + * @param event The event + * @throws EventException If an event handler throws an exception. + */ + public void callEvent(@NotNull final Event event) throws EventException { + if (event instanceof Cancellable) { + if (((Cancellable) event).isCancelled() && isIgnoringCancelled()) { + return; + } + } + executor.execute(listener, event); + } + + /** + * Whether this listener accepts cancelled events + * + * @return True when ignoring cancelled events + */ + public boolean isIgnoringCancelled() { + return ignoreCancelled; + } +} diff --git a/api/src/main/java/org/bukkit/plugin/RegisteredServiceProvider.java b/api/src/main/java/org/bukkit/plugin/RegisteredServiceProvider.java new file mode 100644 index 000000000..cbcdadd49 --- /dev/null +++ b/api/src/main/java/org/bukkit/plugin/RegisteredServiceProvider.java @@ -0,0 +1,51 @@ +package org.bukkit.plugin; + +import org.jetbrains.annotations.NotNull; + +/** + * A registered service provider. + * + * @param Service + */ +public class RegisteredServiceProvider implements Comparable> { + + private Class service; + private Plugin plugin; + private T provider; + private ServicePriority priority; + + public RegisteredServiceProvider(@NotNull Class service, @NotNull T provider, @NotNull ServicePriority priority, @NotNull Plugin plugin) { + this.service = service; + this.plugin = plugin; + this.provider = provider; + this.priority = priority; + } + + @NotNull + public Class getService() { + return service; + } + + @NotNull + public Plugin getPlugin() { + return plugin; + } + + @NotNull + public T getProvider() { + return provider; + } + + @NotNull + public ServicePriority getPriority() { + return priority; + } + + public int compareTo(@NotNull RegisteredServiceProvider other) { + if (priority.ordinal() == other.getPriority().ordinal()) { + return 0; + } else { + return priority.ordinal() < other.getPriority().ordinal() ? 1 : -1; + } + } +} diff --git a/api/src/main/java/org/bukkit/plugin/ServicePriority.java b/api/src/main/java/org/bukkit/plugin/ServicePriority.java new file mode 100644 index 000000000..4afe0fb32 --- /dev/null +++ b/api/src/main/java/org/bukkit/plugin/ServicePriority.java @@ -0,0 +1,12 @@ +package org.bukkit.plugin; + +/** + * Represents various priorities of a provider. + */ +public enum ServicePriority { + Lowest, + Low, + Normal, + High, + Highest +} diff --git a/api/src/main/java/org/bukkit/plugin/ServicesManager.java b/api/src/main/java/org/bukkit/plugin/ServicesManager.java new file mode 100644 index 000000000..185b644b1 --- /dev/null +++ b/api/src/main/java/org/bukkit/plugin/ServicesManager.java @@ -0,0 +1,114 @@ +package org.bukkit.plugin; + +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +import java.util.Collection; +import java.util.List; + +/** + * Manages services and service providers. Services are an interface + * specifying a list of methods that a provider must implement. Providers are + * implementations of these services. A provider can be queried from the + * services manager in order to use a service (if one is available). If + * multiple plugins register a service, then the service with the highest + * priority takes precedence. + */ +public interface ServicesManager { + + /** + * Register a provider of a service. + * + * @param Provider + * @param service service class + * @param provider provider to register + * @param plugin plugin with the provider + * @param priority priority of the provider + */ + public void register(@NotNull Class service, @NotNull T provider, @NotNull Plugin plugin, @NotNull ServicePriority priority); + + /** + * Unregister all the providers registered by a particular plugin. + * + * @param plugin The plugin + */ + public void unregisterAll(@NotNull Plugin plugin); + + /** + * Unregister a particular provider for a particular service. + * + * @param service The service interface + * @param provider The service provider implementation + */ + public void unregister(@NotNull Class service, @NotNull Object provider); + + /** + * Unregister a particular provider. + * + * @param provider The service provider implementation + */ + public void unregister(@NotNull Object provider); + + /** + * Queries for a provider. This may return if no provider has been + * registered for a service. The highest priority provider is returned. + * + * @param The service interface + * @param service The service interface + * @return provider or null + */ + @Nullable + public T load(@NotNull Class service); + + /** + * Queries for a provider registration. This may return if no provider + * has been registered for a service. + * + * @param The service interface + * @param service The service interface + * @return provider registration or null + */ + @Nullable + public RegisteredServiceProvider getRegistration(@NotNull Class service); + + /** + * Get registrations of providers for a plugin. + * + * @param plugin The plugin + * @return provider registrations + */ + @NotNull + public List> getRegistrations(@NotNull Plugin plugin); + + /** + * Get registrations of providers for a service. The returned list is + * unmodifiable. + * + * @param The service interface + * @param service The service interface + * @return list of registrations + */ + @NotNull + public Collection> getRegistrations(@NotNull Class service); + + /** + * Get a list of known services. A service is known if it has registered + * providers for it. + * + * @return list of known services + */ + @NotNull + public Collection> getKnownServices(); + + /** + * Returns whether a provider has been registered for a service. Do not + * check this first only to call load(service) later, as that + * would be a non-thread safe situation. + * + * @param service + * @param service service to check + * @return whether there has been a registered provider + */ + public boolean isProvidedFor(@NotNull Class service); + +} diff --git a/src/api/main/java/org/bukkit/plugin/SimplePluginManager.java b/api/src/main/java/org/bukkit/plugin/SimplePluginManager.java similarity index 100% rename from src/api/main/java/org/bukkit/plugin/SimplePluginManager.java rename to api/src/main/java/org/bukkit/plugin/SimplePluginManager.java diff --git a/api/src/main/java/org/bukkit/plugin/SimpleServicesManager.java b/api/src/main/java/org/bukkit/plugin/SimpleServicesManager.java new file mode 100644 index 000000000..6514d4ef1 --- /dev/null +++ b/api/src/main/java/org/bukkit/plugin/SimpleServicesManager.java @@ -0,0 +1,313 @@ +package org.bukkit.plugin; + +import org.bukkit.Bukkit; +import org.bukkit.event.server.ServiceRegisterEvent; +import org.bukkit.event.server.ServiceUnregisterEvent; + +import com.google.common.collect.ImmutableList; +import com.google.common.collect.ImmutableSet; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.HashMap; +import java.util.Iterator; +import java.util.Map; +import java.util.List; +import java.util.NoSuchElementException; +import java.util.Set; + +/** + * A simple services manager. + */ +public class SimpleServicesManager implements ServicesManager { + + /** + * Map of providers. + */ + private final Map, List>> providers = new HashMap, List>>(); + + /** + * Register a provider of a service. + * + * @param Provider + * @param service service class + * @param provider provider to register + * @param plugin plugin with the provider + * @param priority priority of the provider + */ + public void register(@NotNull Class service, @NotNull T provider, @NotNull Plugin plugin, @NotNull ServicePriority priority) { + RegisteredServiceProvider registeredProvider = null; + synchronized (providers) { + List> registered = providers.get(service); + if (registered == null) { + registered = new ArrayList>(); + providers.put(service, registered); + } + + registeredProvider = new RegisteredServiceProvider(service, provider, priority, plugin); + + // Insert the provider into the collection, much more efficient big O than sort + int position = Collections.binarySearch(registered, registeredProvider); + if (position < 0) { + registered.add(-(position + 1), registeredProvider); + } else { + registered.add(position, registeredProvider); + } + + } + Bukkit.getServer().getPluginManager().callEvent(new ServiceRegisterEvent(registeredProvider)); + } + + /** + * Unregister all the providers registered by a particular plugin. + * + * @param plugin The plugin + */ + public void unregisterAll(@NotNull Plugin plugin) { + ArrayList unregisteredEvents = new ArrayList(); + synchronized (providers) { + Iterator, List>>> it = providers.entrySet().iterator(); + + try { + while (it.hasNext()) { + Map.Entry, List>> entry = it.next(); + Iterator> it2 = entry.getValue().iterator(); + + try { + // Removed entries that are from this plugin + + while (it2.hasNext()) { + RegisteredServiceProvider registered = it2.next(); + + if (registered.getPlugin().equals(plugin)) { + it2.remove(); + unregisteredEvents.add(new ServiceUnregisterEvent(registered)); + } + } + } catch (NoSuchElementException e) { // Why does Java suck + } + + // Get rid of the empty list + if (entry.getValue().size() == 0) { + it.remove(); + } + } + } catch (NoSuchElementException e) {} + } + for (ServiceUnregisterEvent event : unregisteredEvents) { + Bukkit.getServer().getPluginManager().callEvent(event); + } + } + + /** + * Unregister a particular provider for a particular service. + * + * @param service The service interface + * @param provider The service provider implementation + */ + public void unregister(@NotNull Class service, @NotNull Object provider) { + ArrayList unregisteredEvents = new ArrayList(); + synchronized (providers) { + Iterator, List>>> it = providers.entrySet().iterator(); + + try { + while (it.hasNext()) { + Map.Entry, List>> entry = it.next(); + + // We want a particular service + if (entry.getKey() != service) { + continue; + } + + Iterator> it2 = entry.getValue().iterator(); + + try { + // Removed entries that are from this plugin + + while (it2.hasNext()) { + RegisteredServiceProvider registered = it2.next(); + + if (registered.getProvider() == provider) { + it2.remove(); + unregisteredEvents.add(new ServiceUnregisterEvent(registered)); + } + } + } catch (NoSuchElementException e) { // Why does Java suck + } + + // Get rid of the empty list + if (entry.getValue().size() == 0) { + it.remove(); + } + } + } catch (NoSuchElementException e) {} + } + for (ServiceUnregisterEvent event : unregisteredEvents) { + Bukkit.getServer().getPluginManager().callEvent(event); + } + } + + /** + * Unregister a particular provider. + * + * @param provider The service provider implementation + */ + public void unregister(@NotNull Object provider) { + ArrayList unregisteredEvents = new ArrayList(); + synchronized (providers) { + Iterator, List>>> it = providers.entrySet().iterator(); + + try { + while (it.hasNext()) { + Map.Entry, List>> entry = it.next(); + Iterator> it2 = entry.getValue().iterator(); + + try { + // Removed entries that are from this plugin + + while (it2.hasNext()) { + RegisteredServiceProvider registered = it2.next(); + + if (registered.getProvider().equals(provider)) { + it2.remove(); + unregisteredEvents.add(new ServiceUnregisterEvent(registered)); + } + } + } catch (NoSuchElementException e) { // Why does Java suck + } + + // Get rid of the empty list + if (entry.getValue().size() == 0) { + it.remove(); + } + } + } catch (NoSuchElementException e) {} + } + for (ServiceUnregisterEvent event : unregisteredEvents) { + Bukkit.getServer().getPluginManager().callEvent(event); + } + } + + /** + * Queries for a provider. This may return if no provider has been + * registered for a service. The highest priority provider is returned. + * + * @param The service interface + * @param service The service interface + * @return provider or null + */ + @Nullable + public T load(@NotNull Class service) { + synchronized (providers) { + List> registered = providers.get(service); + + if (registered == null) { + return null; + } + + // This should not be null! + return service.cast(registered.get(0).getProvider()); + } + } + + /** + * Queries for a provider registration. This may return if no provider + * has been registered for a service. + * + * @param The service interface + * @param service The service interface + * @return provider registration or null + */ + @Nullable + @SuppressWarnings("unchecked") + public RegisteredServiceProvider getRegistration(@NotNull Class service) { + synchronized (providers) { + List> registered = providers.get(service); + + if (registered == null) { + return null; + } + + // This should not be null! + return (RegisteredServiceProvider) registered.get(0); + } + } + + /** + * Get registrations of providers for a plugin. + * + * @param plugin The plugin + * @return provider registrations + */ + @NotNull + public List> getRegistrations(@NotNull Plugin plugin) { + ImmutableList.Builder> ret = ImmutableList.>builder(); + synchronized (providers) { + for (List> registered : providers.values()) { + for (RegisteredServiceProvider provider : registered) { + if (provider.getPlugin().equals(plugin)) { + ret.add(provider); + } + } + } + } + return ret.build(); + } + + /** + * Get registrations of providers for a service. The returned list is + * an unmodifiable copy. + * + * @param The service interface + * @param service The service interface + * @return a copy of the list of registrations + */ + @NotNull + @SuppressWarnings("unchecked") + public List> getRegistrations(@NotNull Class service) { + ImmutableList.Builder> ret; + synchronized (providers) { + List> registered = providers.get(service); + + if (registered == null) { + return ImmutableList.>of(); + } + + ret = ImmutableList.>builder(); + + for (RegisteredServiceProvider provider : registered) { + ret.add((RegisteredServiceProvider) provider); + } + + } + return ret.build(); + } + + /** + * Get a list of known services. A service is known if it has registered + * providers for it. + * + * @return a copy of the set of known services + */ + @NotNull + public Set> getKnownServices() { + synchronized (providers) { + return ImmutableSet.>copyOf(providers.keySet()); + } + } + + /** + * Returns whether a provider has been registered for a service. + * + * @param service + * @param service service to check + * @return true if and only if there are registered providers + */ + public boolean isProvidedFor(@NotNull Class service) { + synchronized (providers) { + return providers.containsKey(service); + } + } +} diff --git a/api/src/main/java/org/bukkit/plugin/TimedRegisteredListener.java b/api/src/main/java/org/bukkit/plugin/TimedRegisteredListener.java new file mode 100644 index 000000000..1d76e30b8 --- /dev/null +++ b/api/src/main/java/org/bukkit/plugin/TimedRegisteredListener.java @@ -0,0 +1,101 @@ +package org.bukkit.plugin; + +import org.bukkit.event.Event; +import org.bukkit.event.EventException; +import org.bukkit.event.EventPriority; +import org.bukkit.event.Listener; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +/** + * Extends RegisteredListener to include timing information + */ +public class TimedRegisteredListener extends RegisteredListener { + private int count; + private long totalTime; + private Class eventClass; + private boolean multiple = false; + + public TimedRegisteredListener(@NotNull final Listener pluginListener, @NotNull final EventExecutor eventExecutor, @NotNull final EventPriority eventPriority, @NotNull final Plugin registeredPlugin, final boolean listenCancelled) { + super(pluginListener, eventExecutor, eventPriority, registeredPlugin, listenCancelled); + } + + @Override + public void callEvent(@NotNull Event event) throws EventException { + if (event.isAsynchronous()) { + super.callEvent(event); + return; + } + count++; + Class newEventClass = event.getClass(); + if (this.eventClass == null) { + this.eventClass = newEventClass; + } else if (!this.eventClass.equals(newEventClass)) { + multiple = true; + this.eventClass = getCommonSuperclass(newEventClass, this.eventClass).asSubclass(Event.class); + } + long start = System.nanoTime(); + super.callEvent(event); + totalTime += System.nanoTime() - start; + } + + @NotNull + private static Class getCommonSuperclass(@NotNull Class class1, @NotNull Class class2) { + while (!class1.isAssignableFrom(class2)) { + class1 = class1.getSuperclass(); + } + return class1; + } + + /** + * Resets the call count and total time for this listener + */ + public void reset() { + count = 0; + totalTime = 0; + } + + /** + * Gets the total times this listener has been called + * + * @return Times this listener has been called + */ + public int getCount() { + return count; + } + + /** + * Gets the total time calls to this listener have taken + * + * @return Total time for all calls of this listener + */ + public long getTotalTime() { + return totalTime; + } + + /** + * Gets the class of the events this listener handled. If it handled + * multiple classes of event, the closest shared superclass will be + * returned, such that for any event this listener has handled, + * this.getEventClass().isAssignableFrom(event.getClass()) + * and no class this.getEventClass().isAssignableFrom(clazz) + * {@literal && this.getEventClass() != clazz &&} + * event.getClass().isAssignableFrom(clazz) for all handled events. + * + * @return the event class handled by this RegisteredListener + */ + @Nullable + public Class getEventClass() { + return eventClass; + } + + /** + * Gets whether this listener has handled multiple events, such that for + * some two events, eventA.getClass() != eventB.getClass(). + * + * @return true if this listener has handled multiple events + */ + public boolean hasMultiple() { + return multiple; + } +} diff --git a/api/src/main/java/org/bukkit/plugin/UnknownDependencyException.java b/api/src/main/java/org/bukkit/plugin/UnknownDependencyException.java new file mode 100644 index 000000000..a80251eff --- /dev/null +++ b/api/src/main/java/org/bukkit/plugin/UnknownDependencyException.java @@ -0,0 +1,46 @@ +package org.bukkit.plugin; + +/** + * Thrown when attempting to load an invalid Plugin file + */ +public class UnknownDependencyException extends RuntimeException { + + private static final long serialVersionUID = 5721389371901775895L; + + /** + * Constructs a new UnknownDependencyException based on the given + * Exception + * + * @param throwable Exception that triggered this Exception + */ + public UnknownDependencyException(final Throwable throwable) { + super(throwable); + } + + /** + * Constructs a new UnknownDependencyException with the given message + * + * @param message Brief message explaining the cause of the exception + */ + public UnknownDependencyException(final String message) { + super(message); + } + + /** + * Constructs a new UnknownDependencyException based on the given + * Exception + * + * @param message Brief message explaining the cause of the exception + * @param throwable Exception that triggered this Exception + */ + public UnknownDependencyException(final Throwable throwable, final String message) { + super(message, throwable); + } + + /** + * Constructs a new UnknownDependencyException + */ + public UnknownDependencyException() { + + } +} diff --git a/api/src/main/java/org/bukkit/plugin/java/JavaPlugin.java b/api/src/main/java/org/bukkit/plugin/java/JavaPlugin.java new file mode 100644 index 000000000..96ebe72ca --- /dev/null +++ b/api/src/main/java/org/bukkit/plugin/java/JavaPlugin.java @@ -0,0 +1,431 @@ +package org.bukkit.plugin.java; + +import java.io.File; +import java.io.FileOutputStream; +import java.io.IOException; +import java.io.InputStream; +import java.io.InputStreamReader; +import java.io.OutputStream; +import java.io.Reader; +import java.net.URL; +import java.net.URLConnection; +import java.util.List; +import java.util.logging.Level; +import java.util.logging.Logger; + +import org.apache.commons.lang.Validate; +import org.bukkit.Server; +import org.bukkit.command.Command; +import org.bukkit.command.CommandSender; +import org.bukkit.command.PluginCommand; +import org.bukkit.configuration.file.FileConfiguration; +import org.bukkit.configuration.file.YamlConfiguration; +import org.bukkit.generator.ChunkGenerator; +import org.bukkit.plugin.PluginBase; +import org.bukkit.plugin.PluginDescriptionFile; +import org.bukkit.plugin.PluginLoader; +import org.bukkit.plugin.PluginLogger; + +import com.google.common.base.Charsets; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +/** + * Represents a Java plugin + */ +public abstract class JavaPlugin extends PluginBase { + private boolean isEnabled = false; + private PluginLoader loader = null; + private Server server = null; + private File file = null; + private PluginDescriptionFile description = null; + private File dataFolder = null; + private ClassLoader classLoader = null; + private boolean naggable = true; + private FileConfiguration newConfig = null; + private File configFile = null; + Logger logger = null; // Paper - PluginLogger -> Logger, package-private + + public JavaPlugin() { + final ClassLoader classLoader = this.getClass().getClassLoader(); + if (!(classLoader instanceof PluginClassLoader)) { + throw new IllegalStateException("JavaPlugin requires " + PluginClassLoader.class.getName()); + } + ((PluginClassLoader) classLoader).initialize(this); + } + + protected JavaPlugin(@NotNull final JavaPluginLoader loader, @NotNull final PluginDescriptionFile description, @NotNull final File dataFolder, @NotNull final File file) { + final ClassLoader classLoader = this.getClass().getClassLoader(); + if (classLoader instanceof PluginClassLoader) { + throw new IllegalStateException("Cannot use initialization constructor at runtime"); + } + init(loader, loader.server, description, dataFolder, file, classLoader); + } + + /** + * Returns the folder that the plugin data's files are located in. The + * folder may not yet exist. + * + * @return The folder. + */ + @NotNull + @Override + public final File getDataFolder() { + return dataFolder; + } + + /** + * Gets the associated PluginLoader responsible for this plugin + * + * @return PluginLoader that controls this plugin + */ + @NotNull + @Override + public final PluginLoader getPluginLoader() { + return loader; + } + + /** + * Returns the Server instance currently running this plugin + * + * @return Server running this plugin + */ + @NotNull + @Override + public final Server getServer() { + return server; + } + + /** + * Returns a value indicating whether or not this plugin is currently + * enabled + * + * @return true if this plugin is enabled, otherwise false + */ + @Override + public final boolean isEnabled() { + return isEnabled; + } + + /** + * Returns the file which contains this plugin + * + * @return File containing this plugin + */ + @NotNull + protected File getFile() { + return file; + } + + /** + * Returns the plugin.yaml file containing the details for this plugin + * + * @return Contents of the plugin.yaml file + */ + @NotNull + @Override + public final PluginDescriptionFile getDescription() { + return description; + } + + @NotNull + @Override + public FileConfiguration getConfig() { + if (newConfig == null) { + reloadConfig(); + } + return newConfig; + } + + /** + * Provides a reader for a text file located inside the jar. + *

+ * The returned reader will read text with the UTF-8 charset. + * + * @param file the filename of the resource to load + * @return null if {@link #getResource(String)} returns null + * @throws IllegalArgumentException if file is null + * @see ClassLoader#getResourceAsStream(String) + */ + @Nullable + protected final Reader getTextResource(@NotNull String file) { + final InputStream in = getResource(file); + + return in == null ? null : new InputStreamReader(in, Charsets.UTF_8); + } + + @Override + public void reloadConfig() { + newConfig = YamlConfiguration.loadConfiguration(configFile); + + final InputStream defConfigStream = getResource("config.yml"); + if (defConfigStream == null) { + return; + } + + newConfig.setDefaults(YamlConfiguration.loadConfiguration(new InputStreamReader(defConfigStream, Charsets.UTF_8))); + } + + @Override + public void saveConfig() { + try { + getConfig().save(configFile); + } catch (IOException ex) { + logger.log(Level.SEVERE, "Could not save config to " + configFile, ex); + } + } + + @Override + public void saveDefaultConfig() { + if (!configFile.exists()) { + saveResource("config.yml", false); + } + } + + @Override + public void saveResource(@NotNull String resourcePath, boolean replace) { + if (resourcePath == null || resourcePath.equals("")) { + throw new IllegalArgumentException("ResourcePath cannot be null or empty"); + } + + resourcePath = resourcePath.replace('\\', '/'); + InputStream in = getResource(resourcePath); + if (in == null) { + throw new IllegalArgumentException("The embedded resource '" + resourcePath + "' cannot be found in " + file); + } + + File outFile = new File(dataFolder, resourcePath); + int lastIndex = resourcePath.lastIndexOf('/'); + File outDir = new File(dataFolder, resourcePath.substring(0, lastIndex >= 0 ? lastIndex : 0)); + + if (!outDir.exists()) { + outDir.mkdirs(); + } + + try { + if (!outFile.exists() || replace) { + OutputStream out = new FileOutputStream(outFile); + byte[] buf = new byte[1024]; + int len; + while ((len = in.read(buf)) > 0) { + out.write(buf, 0, len); + } + out.close(); + in.close(); + } else { + logger.log(Level.WARNING, "Could not save " + outFile.getName() + " to " + outFile + " because " + outFile.getName() + " already exists."); + } + } catch (IOException ex) { + logger.log(Level.SEVERE, "Could not save " + outFile.getName() + " to " + outFile, ex); + } + } + + @Nullable + @Override + public InputStream getResource(@NotNull String filename) { + if (filename == null) { + throw new IllegalArgumentException("Filename cannot be null"); + } + + try { + URL url = getClassLoader().getResource(filename); + + if (url == null) { + return null; + } + + URLConnection connection = url.openConnection(); + connection.setUseCaches(false); + return connection.getInputStream(); + } catch (IOException ex) { + return null; + } + } + + /** + * Returns the ClassLoader which holds this plugin + * + * @return ClassLoader holding this plugin + */ + @NotNull + protected final ClassLoader getClassLoader() { + return classLoader; + } + + /** + * Sets the enabled state of this plugin + * + * @param enabled true if enabled, otherwise false + */ + protected final void setEnabled(final boolean enabled) { + if (isEnabled != enabled) { + isEnabled = enabled; + + if (isEnabled) { + onEnable(); + } else { + onDisable(); + } + } + } + + + final void init(@NotNull PluginLoader loader, @NotNull Server server, @NotNull PluginDescriptionFile description, @NotNull File dataFolder, @NotNull File file, @NotNull ClassLoader classLoader) { + this.loader = loader; + this.server = server; + this.file = file; + this.description = description; + this.dataFolder = dataFolder; + this.classLoader = classLoader; + this.configFile = new File(dataFolder, "config.yml"); + // Paper start + if (this.logger == null) { + this.logger = com.destroystokyo.paper.utils.PaperPluginLogger.getLogger(this.description); + } + // Paper end + } + + /** + * {@inheritDoc} + */ + @Override + public boolean onCommand(@NotNull CommandSender sender, @NotNull Command command, @NotNull String label, @NotNull String[] args) { + return false; + } + + /** + * {@inheritDoc} + */ + @Override + @Nullable + public List onTabComplete(@NotNull CommandSender sender, @NotNull Command command, @NotNull String alias, @NotNull String[] args) { + return null; + } + + /** + * Gets the command with the given name, specific to this plugin. Commands + * need to be registered in the {@link PluginDescriptionFile#getCommands() + * PluginDescriptionFile} to exist at runtime. + * + * @param name name or alias of the command + * @return the plugin command if found, otherwise null + */ + @Nullable + public PluginCommand getCommand(@NotNull String name) { + String alias = name.toLowerCase(java.util.Locale.ENGLISH); + PluginCommand command = getServer().getPluginCommand(alias); + + if (command == null || command.getPlugin() != this) { + command = getServer().getPluginCommand(description.getName().toLowerCase(java.util.Locale.ENGLISH) + ":" + alias); + } + + if (command != null && command.getPlugin() == this) { + return command; + } else { + return null; + } + } + + @Override + public void onLoad() {} + + @Override + public void onDisable() {} + + @Override + public void onEnable() {} + + @Nullable + @Override + public ChunkGenerator getDefaultWorldGenerator(@NotNull String worldName, @Nullable String id) { + return null; + } + + @Override + public final boolean isNaggable() { + return naggable; + } + + @Override + public final void setNaggable(boolean canNag) { + this.naggable = canNag; + } + + @NotNull + @Override + public Logger getLogger() { + return logger; + } + + @NotNull + @Override + public String toString() { + return description.getFullName(); + } + + /** + * This method provides fast access to the plugin that has {@link + * #getProvidingPlugin(Class) provided} the given plugin class, which is + * usually the plugin that implemented it. + *

+ * An exception to this would be if plugin's jar that contained the class + * does not extend the class, where the intended plugin would have + * resided in a different jar / classloader. + * + * @param a class that extends JavaPlugin + * @param clazz the class desired + * @return the plugin that provides and implements said class + * @throws IllegalArgumentException if clazz is null + * @throws IllegalArgumentException if clazz does not extend {@link + * JavaPlugin} + * @throws IllegalStateException if clazz was not provided by a plugin, + * for example, if called with + * JavaPlugin.getPlugin(JavaPlugin.class) + * @throws IllegalStateException if called from the static initializer for + * given JavaPlugin + * @throws ClassCastException if plugin that provided the class does not + * extend the class + */ + @NotNull + public static T getPlugin(@NotNull Class clazz) { + Validate.notNull(clazz, "Null class cannot have a plugin"); + if (!JavaPlugin.class.isAssignableFrom(clazz)) { + throw new IllegalArgumentException(clazz + " does not extend " + JavaPlugin.class); + } + final ClassLoader cl = clazz.getClassLoader(); + if (!(cl instanceof PluginClassLoader)) { + throw new IllegalArgumentException(clazz + " is not initialized by " + PluginClassLoader.class); + } + JavaPlugin plugin = ((PluginClassLoader) cl).plugin; + if (plugin == null) { + throw new IllegalStateException("Cannot get plugin for " + clazz + " from a static initializer"); + } + return clazz.cast(plugin); + } + + /** + * This method provides fast access to the plugin that has provided the + * given class. + * + * @param clazz a class belonging to a plugin + * @return the plugin that provided the class + * @throws IllegalArgumentException if the class is not provided by a + * JavaPlugin + * @throws IllegalArgumentException if class is null + * @throws IllegalStateException if called from the static initializer for + * given JavaPlugin + */ + @NotNull + public static JavaPlugin getProvidingPlugin(@NotNull Class clazz) { + Validate.notNull(clazz, "Null class cannot have a plugin"); + final ClassLoader cl = clazz.getClassLoader(); + if (!(cl instanceof PluginClassLoader)) { + throw new IllegalArgumentException(clazz + " is not provided by " + PluginClassLoader.class); + } + JavaPlugin plugin = ((PluginClassLoader) cl).plugin; + if (plugin == null) { + throw new IllegalStateException("Cannot get plugin for " + clazz + " from a static initializer"); + } + return plugin; + } +} diff --git a/api/src/main/java/org/bukkit/plugin/java/JavaPluginLoader.java b/api/src/main/java/org/bukkit/plugin/java/JavaPluginLoader.java new file mode 100644 index 000000000..e91cb2c78 --- /dev/null +++ b/api/src/main/java/org/bukkit/plugin/java/JavaPluginLoader.java @@ -0,0 +1,395 @@ +package org.bukkit.plugin.java; + +import java.io.File; +import java.io.FileNotFoundException; +import java.io.IOException; +import java.io.InputStream; +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; +import java.util.Arrays; +import java.util.HashMap; +import java.util.HashSet; +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.CopyOnWriteArrayList; +import java.util.jar.JarEntry; +import java.util.jar.JarFile; +import java.util.logging.Level; +import java.util.regex.Pattern; + +import org.apache.commons.lang.Validate; +import org.bukkit.Server; +import org.bukkit.Warning; +import org.bukkit.Warning.WarningState; +import org.bukkit.configuration.serialization.ConfigurationSerializable; +import org.bukkit.configuration.serialization.ConfigurationSerialization; +import org.bukkit.event.Event; +import org.bukkit.event.EventException; +import org.bukkit.event.EventHandler; +import org.bukkit.event.Listener; +import org.bukkit.event.server.PluginDisableEvent; +import org.bukkit.event.server.PluginEnableEvent; +import org.bukkit.plugin.AuthorNagException; +import org.bukkit.plugin.EventExecutor; +import org.bukkit.plugin.InvalidDescriptionException; +import org.bukkit.plugin.InvalidPluginException; +import org.bukkit.plugin.Plugin; +import org.bukkit.plugin.PluginDescriptionFile; +import org.bukkit.plugin.PluginLoader; +import org.bukkit.plugin.RegisteredListener; +import org.bukkit.plugin.TimedRegisteredListener; +import org.bukkit.plugin.UnknownDependencyException; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; +import org.spigotmc.CustomTimingsHandler; // Spigot +import org.yaml.snakeyaml.error.YAMLException; + +/** + * Represents a Java plugin loader, allowing plugins in the form of .jar + */ +public final class JavaPluginLoader implements PluginLoader { + final Server server; + private final Pattern[] fileFilters = new Pattern[] { Pattern.compile("\\.jar$"), }; + private final Map> classes = new ConcurrentHashMap>(); + private final List loaders = new CopyOnWriteArrayList(); + + /** + * This class was not meant to be constructed explicitly + * + * @param instance the server instance + */ + @Deprecated + public JavaPluginLoader(@NotNull Server instance) { + Validate.notNull(instance, "Server cannot be null"); + server = instance; + } + + @NotNull + public Plugin loadPlugin(@NotNull final File file) throws InvalidPluginException { + Validate.notNull(file, "File cannot be null"); + + if (!file.exists()) { + throw new InvalidPluginException(new FileNotFoundException(file.getPath() + " does not exist")); + } + + final PluginDescriptionFile description; + try { + description = getPluginDescription(file); + } catch (InvalidDescriptionException ex) { + throw new InvalidPluginException(ex); + } + + final File parentFile = file.getParentFile(); + final File dataFolder = new File(parentFile, description.getName()); + @SuppressWarnings("deprecation") + final File oldDataFolder = new File(parentFile, description.getRawName()); + + // Found old data folder + if (dataFolder.equals(oldDataFolder)) { + // They are equal -- nothing needs to be done! + } else if (dataFolder.isDirectory() && oldDataFolder.isDirectory()) { + server.getLogger().warning(String.format( + "While loading %s (%s) found old-data folder: `%s' next to the new one `%s'", + description.getFullName(), + file, + oldDataFolder, + dataFolder + )); + } else if (oldDataFolder.isDirectory() && !dataFolder.exists()) { + if (!oldDataFolder.renameTo(dataFolder)) { + throw new InvalidPluginException("Unable to rename old data folder: `" + oldDataFolder + "' to: `" + dataFolder + "'"); + } + server.getLogger().log(Level.INFO, String.format( + "While loading %s (%s) renamed data folder: `%s' to `%s'", + description.getFullName(), + file, + oldDataFolder, + dataFolder + )); + } + + if (dataFolder.exists() && !dataFolder.isDirectory()) { + throw new InvalidPluginException(String.format( + "Projected datafolder: `%s' for %s (%s) exists and is not a directory", + dataFolder, + description.getFullName(), + file + )); + } + + for (final String pluginName : description.getDepend()) { + Plugin current = server.getPluginManager().getPlugin(pluginName); + + if (current == null) { + throw new UnknownDependencyException(pluginName); + } + } + + server.getUnsafe().checkSupported(description); + + final PluginClassLoader loader; + try { + loader = new PluginClassLoader(this, getClass().getClassLoader(), description, dataFolder, file); + } catch (InvalidPluginException ex) { + throw ex; + } catch (Throwable ex) { + throw new InvalidPluginException(ex); + } + + loaders.add(loader); + + return loader.plugin; + } + + @NotNull + public PluginDescriptionFile getPluginDescription(@NotNull File file) throws InvalidDescriptionException { + Validate.notNull(file, "File cannot be null"); + + JarFile jar = null; + InputStream stream = null; + + try { + jar = new JarFile(file); + JarEntry entry = jar.getJarEntry("plugin.yml"); + + if (entry == null) { + throw new InvalidDescriptionException(new FileNotFoundException("Jar does not contain plugin.yml")); + } + + stream = jar.getInputStream(entry); + + return new PluginDescriptionFile(stream); + + } catch (IOException ex) { + throw new InvalidDescriptionException(ex); + } catch (YAMLException ex) { + throw new InvalidDescriptionException(ex); + } finally { + if (jar != null) { + try { + jar.close(); + } catch (IOException e) { + } + } + if (stream != null) { + try { + stream.close(); + } catch (IOException e) { + } + } + } + } + + @NotNull + public Pattern[] getPluginFileFilters() { + return fileFilters.clone(); + } + + @Nullable + Class getClassByName(final String name) { + Class cachedClass = classes.get(name); + + if (cachedClass != null) { + return cachedClass; + } else { + for (PluginClassLoader loader : loaders) { + try { + cachedClass = loader.findClass(name, false); + } catch (ClassNotFoundException cnfe) {} + if (cachedClass != null) { + return cachedClass; + } + } + } + return null; + } + + void setClass(@NotNull final String name, @NotNull final Class clazz) { + if (!classes.containsKey(name)) { + classes.put(name, clazz); + + if (ConfigurationSerializable.class.isAssignableFrom(clazz)) { + Class serializable = clazz.asSubclass(ConfigurationSerializable.class); + ConfigurationSerialization.registerClass(serializable); + } + } + } + + private void removeClass(@NotNull String name) { + Class clazz = classes.remove(name); + + try { + if ((clazz != null) && (ConfigurationSerializable.class.isAssignableFrom(clazz))) { + Class serializable = clazz.asSubclass(ConfigurationSerializable.class); + ConfigurationSerialization.unregisterClass(serializable); + } + } catch (NullPointerException ex) { + // Boggle! + // (Native methods throwing NPEs is not fun when you can't stop it before-hand) + } + } + + @NotNull + public Map, Set> createRegisteredListeners(@NotNull Listener listener, @NotNull final Plugin plugin) { + Validate.notNull(plugin, "Plugin can not be null"); + Validate.notNull(listener, "Listener can not be null"); + + boolean useTimings = server.getPluginManager().useTimings(); + Map, Set> ret = new HashMap, Set>(); + Set methods; + try { + Method[] publicMethods = listener.getClass().getMethods(); + Method[] privateMethods = listener.getClass().getDeclaredMethods(); + methods = new HashSet(publicMethods.length + privateMethods.length, 1.0f); + for (Method method : publicMethods) { + methods.add(method); + } + for (Method method : privateMethods) { + methods.add(method); + } + } catch (NoClassDefFoundError e) { + plugin.getLogger().severe("Plugin " + plugin.getDescription().getFullName() + " has failed to register events for " + listener.getClass() + " because " + e.getMessage() + " does not exist."); + return ret; + } + + for (final Method method : methods) { + final EventHandler eh = method.getAnnotation(EventHandler.class); + if (eh == null) continue; + // Do not register bridge or synthetic methods to avoid event duplication + // Fixes SPIGOT-893 + if (method.isBridge() || method.isSynthetic()) { + continue; + } + final Class checkClass; + if (method.getParameterTypes().length != 1 || !Event.class.isAssignableFrom(checkClass = method.getParameterTypes()[0])) { + plugin.getLogger().severe(plugin.getDescription().getFullName() + " attempted to register an invalid EventHandler method signature \"" + method.toGenericString() + "\" in " + listener.getClass()); + continue; + } + final Class eventClass = checkClass.asSubclass(Event.class); + method.setAccessible(true); + Set eventSet = ret.get(eventClass); + if (eventSet == null) { + eventSet = new HashSet(); + ret.put(eventClass, eventSet); + } + + for (Class clazz = eventClass; Event.class.isAssignableFrom(clazz); clazz = clazz.getSuperclass()) { + // This loop checks for extending deprecated events + if (clazz.getAnnotation(Deprecated.class) != null) { + Warning warning = clazz.getAnnotation(Warning.class); + WarningState warningState = server.getWarningState(); + if (!warningState.printFor(warning)) { + break; + } + plugin.getLogger().log( + Level.WARNING, + String.format( + "\"%s\" has registered a listener for %s on method \"%s\", but the event is Deprecated." + + " \"%s\"; please notify the authors %s.", + plugin.getDescription().getFullName(), + clazz.getName(), + method.toGenericString(), + (warning != null && warning.reason().length() != 0) ? warning.reason() : "Server performance will be affected", + Arrays.toString(plugin.getDescription().getAuthors().toArray())), + warningState == WarningState.ON ? new AuthorNagException(null) : null); + break; + } + } + + EventExecutor executor = new co.aikar.timings.TimedEventExecutor(EventExecutor.create(method, eventClass), plugin, method, eventClass); // Paper // Paper (Yes.) - Use factory method `EventExecutor.create()` + if (false) { // Spigot - RL handles useTimings check now + eventSet.add(new TimedRegisteredListener(listener, executor, eh.priority(), plugin, eh.ignoreCancelled())); + } else { + eventSet.add(new RegisteredListener(listener, executor, eh.priority(), plugin, eh.ignoreCancelled())); + } + } + return ret; + } + + public void enablePlugin(@NotNull final Plugin plugin) { + Validate.isTrue(plugin instanceof JavaPlugin, "Plugin is not associated with this PluginLoader"); + + if (!plugin.isEnabled()) { + // Paper start - Add an asterisk to legacy plugins (so admins are aware) + String enableMsg = "Enabling " + plugin.getDescription().getFullName(); + if (org.bukkit.UnsafeValues.isLegacyPlugin(plugin)) { + enableMsg += "*"; + } + + plugin.getLogger().info(enableMsg); + // Paper end + + JavaPlugin jPlugin = (JavaPlugin) plugin; + + PluginClassLoader pluginLoader = (PluginClassLoader) jPlugin.getClassLoader(); + + if (!loaders.contains(pluginLoader)) { + loaders.add(pluginLoader); + server.getLogger().log(Level.WARNING, "Enabled plugin with unregistered PluginClassLoader " + plugin.getDescription().getFullName()); + } + + try { + jPlugin.setEnabled(true); + } catch (Throwable ex) { + server.getLogger().log(Level.SEVERE, "Error occurred while enabling " + plugin.getDescription().getFullName() + " (Is it up to date?)", ex); + // Paper start - Disable plugins that fail to load + server.getPluginManager().disablePlugin(jPlugin, true); // Paper - close Classloader on disable - She's dead jim + return; + // Paper end + } + + // Perhaps abort here, rather than continue going, but as it stands, + // an abort is not possible the way it's currently written + server.getPluginManager().callEvent(new PluginEnableEvent(plugin)); + } + } + + public void disablePlugin(@NotNull Plugin plugin) { + // Paper start - close Classloader on disable + disablePlugin(plugin, false); // Retain old behavior unless requested + } + + public void disablePlugin(@NotNull Plugin plugin, boolean closeClassloader) { + // Paper end - close Class Loader on disable + Validate.isTrue(plugin instanceof JavaPlugin, "Plugin is not associated with this PluginLoader"); + + if (plugin.isEnabled()) { + String message = String.format("Disabling %s", plugin.getDescription().getFullName()); + plugin.getLogger().info(message); + + server.getPluginManager().callEvent(new PluginDisableEvent(plugin)); + + JavaPlugin jPlugin = (JavaPlugin) plugin; + ClassLoader cloader = jPlugin.getClassLoader(); + + try { + jPlugin.setEnabled(false); + } catch (Throwable ex) { + server.getLogger().log(Level.SEVERE, "Error occurred while disabling " + plugin.getDescription().getFullName() + " (Is it up to date?)", ex); + } + + if (cloader instanceof PluginClassLoader) { + PluginClassLoader loader = (PluginClassLoader) cloader; + loaders.remove(loader); + + Set names = loader.getClasses(); + + for (String name : names) { + removeClass(name); + } + // Paper start - close Class Loader on disable + try { + if (closeClassloader) { + loader.close(); + } + } catch (IOException e) { + server.getLogger().log(Level.WARNING, "Error closing the Plugin Class Loader for " + plugin.getDescription().getFullName()); + e.printStackTrace(); + } + // Paper end + } + } + } +} diff --git a/api/src/main/java/org/bukkit/plugin/java/PluginClassLoader.java b/api/src/main/java/org/bukkit/plugin/java/PluginClassLoader.java new file mode 100644 index 000000000..0df1926fa --- /dev/null +++ b/api/src/main/java/org/bukkit/plugin/java/PluginClassLoader.java @@ -0,0 +1,181 @@ +package org.bukkit.plugin.java; + +import com.google.common.io.ByteStreams; +import java.io.File; +import java.io.IOException; +import java.io.InputStream; +import java.net.MalformedURLException; +import java.net.URL; +import java.net.URLClassLoader; +import java.security.CodeSigner; +import java.security.CodeSource; +import java.util.Map; +import java.util.Set; +import java.util.concurrent.ConcurrentHashMap; +import java.util.jar.JarEntry; +import java.util.jar.JarFile; +import java.util.jar.Manifest; + +import org.apache.commons.lang.Validate; +import org.bukkit.plugin.InvalidPluginException; +import org.bukkit.plugin.PluginDescriptionFile; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +/** + * A ClassLoader for plugins, to allow shared classes across multiple plugins + */ +public final class PluginClassLoader extends URLClassLoader { // Spigot + public JavaPlugin getPlugin() { return plugin; } // Spigot + private final JavaPluginLoader loader; + private final Map> classes = new ConcurrentHashMap>(); + private final PluginDescriptionFile description; + private final File dataFolder; + private final File file; + private final JarFile jar; + private final Manifest manifest; + private final URL url; + final JavaPlugin plugin; + private JavaPlugin pluginInit; + private IllegalStateException pluginState; + private java.util.logging.Logger logger; // Paper - add field + + static { + ClassLoader.registerAsParallelCapable(); + } + + PluginClassLoader(@NotNull final JavaPluginLoader loader, @Nullable final ClassLoader parent, @NotNull final PluginDescriptionFile description, @NotNull final File dataFolder, @NotNull final File file) throws IOException, InvalidPluginException, MalformedURLException { + super(new URL[] {file.toURI().toURL()}, parent); + Validate.notNull(loader, "Loader cannot be null"); + + this.loader = loader; + this.description = description; + this.dataFolder = dataFolder; + this.file = file; + this.jar = new JarFile(file); + this.manifest = jar.getManifest(); + this.url = file.toURI().toURL(); + + this.logger = com.destroystokyo.paper.utils.PaperPluginLogger.getLogger(description); // Paper - Register logger early + + try { + Class jarClass; + try { + jarClass = Class.forName(description.getMain(), true, this); + } catch (ClassNotFoundException ex) { + throw new InvalidPluginException("Cannot find main class `" + description.getMain() + "'", ex); + } + + Class pluginClass; + try { + pluginClass = jarClass.asSubclass(JavaPlugin.class); + } catch (ClassCastException ex) { + throw new InvalidPluginException("main class `" + description.getMain() + "' does not extend JavaPlugin", ex); + } + + plugin = pluginClass.newInstance(); + } catch (IllegalAccessException ex) { + throw new InvalidPluginException("No public constructor", ex); + } catch (InstantiationException ex) { + throw new InvalidPluginException("Abnormal plugin type", ex); + } + } + + @Override + protected Class findClass(String name) throws ClassNotFoundException { + return findClass(name, true); + } + + Class findClass(@NotNull String name, boolean checkGlobal) throws ClassNotFoundException { + if (name.startsWith("org.bukkit.") || name.startsWith("net.minecraft.")) { + throw new ClassNotFoundException(name); + } + Class result = classes.get(name); + + if (result == null) { + if (checkGlobal) { + result = loader.getClassByName(name); + } + + if (result == null) { + String path = name.replace('.', '/').concat(".class"); + JarEntry entry = jar.getJarEntry(path); + + if (entry != null) { + byte[] classBytes; + + try (InputStream is = jar.getInputStream(entry)) { + classBytes = ByteStreams.toByteArray(is); + } catch (IOException ex) { + throw new ClassNotFoundException(name, ex); + } + + classBytes = loader.server.getUnsafe().processClass(description, path, classBytes); + + int dot = name.lastIndexOf('.'); + if (dot != -1) { + String pkgName = name.substring(0, dot); + if (getPackage(pkgName) == null) { + try { + if (manifest != null) { + definePackage(pkgName, manifest, url); + } else { + definePackage(pkgName, null, null, null, null, null, null, null); + } + } catch (IllegalArgumentException ex) { + if (getPackage(pkgName) == null) { + throw new IllegalStateException("Cannot find package " + pkgName); + } + } + } + } + + CodeSigner[] signers = entry.getCodeSigners(); + CodeSource source = new CodeSource(url, signers); + + result = defineClass(name, classBytes, 0, classBytes.length, source); + } + + if (result == null) { + result = super.findClass(name); + } + + if (result != null) { + loader.setClass(name, result); + } + } + + classes.put(name, result); + } + + return result; + } + + @Override + public void close() throws IOException { + try { + super.close(); + } finally { + jar.close(); + } + } + + @NotNull + Set getClasses() { + return classes.keySet(); + } + + synchronized void initialize(@NotNull JavaPlugin javaPlugin) { + Validate.notNull(javaPlugin, "Initializing plugin cannot be null"); + Validate.isTrue(javaPlugin.getClass().getClassLoader() == this, "Cannot initialize plugin outside of this class loader"); + if (this.plugin != null || this.pluginInit != null) { + throw new IllegalArgumentException("Plugin already initialized!", pluginState); + } + + pluginState = new IllegalStateException("Initial initialization"); + this.pluginInit = javaPlugin; + + javaPlugin.logger = this.logger; // Paper - set logger + javaPlugin.init(loader, loader.server, description, dataFolder, file, this); + } +} diff --git a/api/src/main/java/org/bukkit/plugin/messaging/ChannelNameTooLongException.java b/api/src/main/java/org/bukkit/plugin/messaging/ChannelNameTooLongException.java new file mode 100644 index 000000000..80ef8a2a3 --- /dev/null +++ b/api/src/main/java/org/bukkit/plugin/messaging/ChannelNameTooLongException.java @@ -0,0 +1,15 @@ +package org.bukkit.plugin.messaging; + +/** + * Thrown if a Plugin Channel is too long. + */ +@SuppressWarnings("serial") +public class ChannelNameTooLongException extends RuntimeException { + public ChannelNameTooLongException() { + super("Attempted to send a Plugin Message to a channel that was too large. The maximum length a channel may be is " + Messenger.MAX_CHANNEL_SIZE + " chars."); + } + + public ChannelNameTooLongException(String channel) { + super("Attempted to send a Plugin Message to a channel that was too large. The maximum length a channel may be is " + Messenger.MAX_CHANNEL_SIZE + " chars (attempted " + channel.length() + " - '" + channel + "."); + } +} diff --git a/api/src/main/java/org/bukkit/plugin/messaging/ChannelNotRegisteredException.java b/api/src/main/java/org/bukkit/plugin/messaging/ChannelNotRegisteredException.java new file mode 100644 index 000000000..2266f1764 --- /dev/null +++ b/api/src/main/java/org/bukkit/plugin/messaging/ChannelNotRegisteredException.java @@ -0,0 +1,15 @@ +package org.bukkit.plugin.messaging; + +/** + * Thrown if a Plugin attempts to send a message on an unregistered channel. + */ +@SuppressWarnings("serial") +public class ChannelNotRegisteredException extends RuntimeException { + public ChannelNotRegisteredException() { + this("Attempted to send a plugin message through an unregistered channel."); + } + + public ChannelNotRegisteredException(String channel) { + super("Attempted to send a plugin message through the unregistered channel `" + channel + "'."); + } +} diff --git a/api/src/main/java/org/bukkit/plugin/messaging/MessageTooLargeException.java b/api/src/main/java/org/bukkit/plugin/messaging/MessageTooLargeException.java new file mode 100644 index 000000000..61af8c4c1 --- /dev/null +++ b/api/src/main/java/org/bukkit/plugin/messaging/MessageTooLargeException.java @@ -0,0 +1,23 @@ +package org.bukkit.plugin.messaging; + +/** + * Thrown if a Plugin Message is sent that is too large to be sent. + */ +@SuppressWarnings("serial") +public class MessageTooLargeException extends RuntimeException { + public MessageTooLargeException() { + this("Attempted to send a plugin message that was too large. The maximum length a plugin message may be is " + Messenger.MAX_MESSAGE_SIZE + " bytes."); + } + + public MessageTooLargeException(byte[] message) { + this(message.length); + } + + public MessageTooLargeException(int length) { + this("Attempted to send a plugin message that was too large. The maximum length a plugin message may be is " + Messenger.MAX_MESSAGE_SIZE + " bytes (tried to send one that is " + length + " bytes long)."); + } + + public MessageTooLargeException(String msg) { + super(msg); + } +} diff --git a/api/src/main/java/org/bukkit/plugin/messaging/Messenger.java b/api/src/main/java/org/bukkit/plugin/messaging/Messenger.java new file mode 100644 index 000000000..ea9b525ff --- /dev/null +++ b/api/src/main/java/org/bukkit/plugin/messaging/Messenger.java @@ -0,0 +1,233 @@ +package org.bukkit.plugin.messaging; + +import java.util.Set; +import org.bukkit.NamespacedKey; +import org.bukkit.entity.Player; +import org.bukkit.plugin.Plugin; +import org.jetbrains.annotations.NotNull; + +/** + * A class responsible for managing the registrations of plugin channels and + * their listeners. + * + * Channel names must contain a colon separator and consist of only [a-z0-9/._-] + * - i.e. they MUST be valid {@link NamespacedKey}. The "BungeeCord" channel is + * an exception and may only take this form. + */ +public interface Messenger { + + /** + * Represents the largest size that an individual Plugin Message may be. + */ + public static final int MAX_MESSAGE_SIZE = 32766; + + /** + * Represents the largest size that a Plugin Channel may be. + */ + public static final int MAX_CHANNEL_SIZE = 32; + + /** + * Checks if the specified channel is a reserved name. + *
+ * All channels within the "minecraft" namespace except for + * "minecraft:brand" are reserved. + * + * @param channel Channel name to check. + * @return True if the channel is reserved, otherwise false. + * @throws IllegalArgumentException Thrown if channel is null. + */ + public boolean isReservedChannel(@NotNull String channel); + + /** + * Registers the specific plugin to the requested outgoing plugin channel, + * allowing it to send messages through that channel to any clients. + * + * @param plugin Plugin that wishes to send messages through the channel. + * @param channel Channel to register. + * @throws IllegalArgumentException Thrown if plugin or channel is null. + */ + public void registerOutgoingPluginChannel(@NotNull Plugin plugin, @NotNull String channel); + + /** + * Unregisters the specific plugin from the requested outgoing plugin + * channel, no longer allowing it to send messages through that channel to + * any clients. + * + * @param plugin Plugin that no longer wishes to send messages through the + * channel. + * @param channel Channel to unregister. + * @throws IllegalArgumentException Thrown if plugin or channel is null. + */ + public void unregisterOutgoingPluginChannel(@NotNull Plugin plugin, @NotNull String channel); + + /** + * Unregisters the specific plugin from all outgoing plugin channels, no + * longer allowing it to send any plugin messages. + * + * @param plugin Plugin that no longer wishes to send plugin messages. + * @throws IllegalArgumentException Thrown if plugin is null. + */ + public void unregisterOutgoingPluginChannel(@NotNull Plugin plugin); + + /** + * Registers the specific plugin for listening on the requested incoming + * plugin channel, allowing it to act upon any plugin messages. + * + * @param plugin Plugin that wishes to register to this channel. + * @param channel Channel to register. + * @param listener Listener to receive messages on. + * @return The resulting registration that was made as a result of this + * method. + * @throws IllegalArgumentException Thrown if plugin, channel or listener + * is null, or the listener is already registered for this channel. + */ + @NotNull + public PluginMessageListenerRegistration registerIncomingPluginChannel(@NotNull Plugin plugin, @NotNull String channel, @NotNull PluginMessageListener listener); + + /** + * Unregisters the specific plugin's listener from listening on the + * requested incoming plugin channel, no longer allowing it to act upon + * any plugin messages. + * + * @param plugin Plugin that wishes to unregister from this channel. + * @param channel Channel to unregister. + * @param listener Listener to stop receiving messages on. + * @throws IllegalArgumentException Thrown if plugin, channel or listener + * is null. + */ + public void unregisterIncomingPluginChannel(@NotNull Plugin plugin, @NotNull String channel, @NotNull PluginMessageListener listener); + + /** + * Unregisters the specific plugin from listening on the requested + * incoming plugin channel, no longer allowing it to act upon any plugin + * messages. + * + * @param plugin Plugin that wishes to unregister from this channel. + * @param channel Channel to unregister. + * @throws IllegalArgumentException Thrown if plugin or channel is null. + */ + public void unregisterIncomingPluginChannel(@NotNull Plugin plugin, @NotNull String channel); + + /** + * Unregisters the specific plugin from listening on all plugin channels + * through all listeners. + * + * @param plugin Plugin that wishes to unregister from this channel. + * @throws IllegalArgumentException Thrown if plugin is null. + */ + public void unregisterIncomingPluginChannel(@NotNull Plugin plugin); + + /** + * Gets a set containing all the outgoing plugin channels. + * + * @return List of all registered outgoing plugin channels. + */ + @NotNull + public Set getOutgoingChannels(); + + /** + * Gets a set containing all the outgoing plugin channels that the + * specified plugin is registered to. + * + * @param plugin Plugin to retrieve channels for. + * @return List of all registered outgoing plugin channels that a plugin + * is registered to. + * @throws IllegalArgumentException Thrown if plugin is null. + */ + @NotNull + public Set getOutgoingChannels(@NotNull Plugin plugin); + + /** + * Gets a set containing all the incoming plugin channels. + * + * @return List of all registered incoming plugin channels. + */ + @NotNull + public Set getIncomingChannels(); + + /** + * Gets a set containing all the incoming plugin channels that the + * specified plugin is registered for. + * + * @param plugin Plugin to retrieve channels for. + * @return List of all registered incoming plugin channels that the plugin + * is registered for. + * @throws IllegalArgumentException Thrown if plugin is null. + */ + @NotNull + public Set getIncomingChannels(@NotNull Plugin plugin); + + /** + * Gets a set containing all the incoming plugin channel registrations + * that the specified plugin has. + * + * @param plugin Plugin to retrieve registrations for. + * @return List of all registrations that the plugin has. + * @throws IllegalArgumentException Thrown if plugin is null. + */ + @NotNull + public Set getIncomingChannelRegistrations(@NotNull Plugin plugin); + + /** + * Gets a set containing all the incoming plugin channel registrations + * that are on the requested channel. + * + * @param channel Channel to retrieve registrations for. + * @return List of all registrations that are on the channel. + * @throws IllegalArgumentException Thrown if channel is null. + */ + @NotNull + public Set getIncomingChannelRegistrations(@NotNull String channel); + + /** + * Gets a set containing all the incoming plugin channel registrations + * that the specified plugin has on the requested channel. + * + * @param plugin Plugin to retrieve registrations for. + * @param channel Channel to filter registrations by. + * @return List of all registrations that the plugin has. + * @throws IllegalArgumentException Thrown if plugin or channel is null. + */ + @NotNull + public Set getIncomingChannelRegistrations(@NotNull Plugin plugin, @NotNull String channel); + + /** + * Checks if the specified plugin message listener registration is valid. + *

+ * A registration is considered valid if it has not be unregistered and + * that the plugin is still enabled. + * + * @param registration Registration to check. + * @return True if the registration is valid, otherwise false. + */ + public boolean isRegistrationValid(@NotNull PluginMessageListenerRegistration registration); + + /** + * Checks if the specified plugin has registered to receive incoming + * messages through the requested channel. + * + * @param plugin Plugin to check registration for. + * @param channel Channel to test for. + * @return True if the channel is registered, else false. + */ + public boolean isIncomingChannelRegistered(@NotNull Plugin plugin, @NotNull String channel); + + /** + * Checks if the specified plugin has registered to send outgoing messages + * through the requested channel. + * + * @param plugin Plugin to check registration for. + * @param channel Channel to test for. + * @return True if the channel is registered, else false. + */ + public boolean isOutgoingChannelRegistered(@NotNull Plugin plugin, @NotNull String channel); + + /** + * Dispatches the specified incoming message to any registered listeners. + * + * @param source Source of the message. + * @param channel Channel that the message was sent by. + * @param message Raw payload of the message. + */ + public void dispatchIncomingMessage(@NotNull Player source, @NotNull String channel, @NotNull byte[] message); +} diff --git a/api/src/main/java/org/bukkit/plugin/messaging/PluginChannelDirection.java b/api/src/main/java/org/bukkit/plugin/messaging/PluginChannelDirection.java new file mode 100644 index 000000000..3d7ec2e2e --- /dev/null +++ b/api/src/main/java/org/bukkit/plugin/messaging/PluginChannelDirection.java @@ -0,0 +1,17 @@ +package org.bukkit.plugin.messaging; + +/** + * Represents the different directions a plugin channel may go. + */ +public enum PluginChannelDirection { + + /** + * The plugin channel is being sent to the server from a client. + */ + INCOMING, + + /** + * The plugin channel is being sent to a client from the server. + */ + OUTGOING +} diff --git a/api/src/main/java/org/bukkit/plugin/messaging/PluginMessageListener.java b/api/src/main/java/org/bukkit/plugin/messaging/PluginMessageListener.java new file mode 100644 index 000000000..eb962efd5 --- /dev/null +++ b/api/src/main/java/org/bukkit/plugin/messaging/PluginMessageListener.java @@ -0,0 +1,21 @@ +package org.bukkit.plugin.messaging; + +import org.bukkit.entity.Player; +import org.jetbrains.annotations.NotNull; + +/** + * A listener for a specific Plugin Channel, which will receive notifications + * of messages sent from a client. + */ +public interface PluginMessageListener { + + /** + * A method that will be thrown when a PluginMessageSource sends a plugin + * message on a registered channel. + * + * @param channel Channel that the message was sent through. + * @param player Source of the message. + * @param message The raw message that was sent. + */ + public void onPluginMessageReceived(@NotNull String channel, @NotNull Player player, @NotNull byte[] message); +} diff --git a/api/src/main/java/org/bukkit/plugin/messaging/PluginMessageListenerRegistration.java b/api/src/main/java/org/bukkit/plugin/messaging/PluginMessageListenerRegistration.java new file mode 100644 index 000000000..1d0029672 --- /dev/null +++ b/api/src/main/java/org/bukkit/plugin/messaging/PluginMessageListenerRegistration.java @@ -0,0 +1,108 @@ +package org.bukkit.plugin.messaging; + +import org.bukkit.plugin.Plugin; +import org.jetbrains.annotations.NotNull; + +/** + * Contains information about a {@link Plugin}s registration to a plugin + * channel. + */ +public final class PluginMessageListenerRegistration { + private final Messenger messenger; + private final Plugin plugin; + private final String channel; + private final PluginMessageListener listener; + + public PluginMessageListenerRegistration(@NotNull Messenger messenger, @NotNull Plugin plugin, @NotNull String channel, @NotNull PluginMessageListener listener) { + if (messenger == null) { + throw new IllegalArgumentException("Messenger cannot be null!"); + } + if (plugin == null) { + throw new IllegalArgumentException("Plugin cannot be null!"); + } + if (channel == null) { + throw new IllegalArgumentException("Channel cannot be null!"); + } + if (listener == null) { + throw new IllegalArgumentException("Listener cannot be null!"); + } + + this.messenger = messenger; + this.plugin = plugin; + this.channel = channel; + this.listener = listener; + } + + /** + * Gets the plugin channel that this registration is about. + * + * @return Plugin channel. + */ + @NotNull + public String getChannel() { + return channel; + } + + /** + * Gets the registered listener described by this registration. + * + * @return Registered listener. + */ + @NotNull + public PluginMessageListener getListener() { + return listener; + } + + /** + * Gets the plugin that this registration is for. + * + * @return Registered plugin. + */ + @NotNull + public Plugin getPlugin() { + return plugin; + } + + /** + * Checks if this registration is still valid. + * + * @return True if this registration is still valid, otherwise false. + */ + public boolean isValid() { + return messenger.isRegistrationValid(this); + } + + @Override + public boolean equals(Object obj) { + if (obj == null) { + return false; + } + if (getClass() != obj.getClass()) { + return false; + } + final PluginMessageListenerRegistration other = (PluginMessageListenerRegistration) obj; + if (this.messenger != other.messenger && !this.messenger.equals(other.messenger)) { + return false; + } + if (this.plugin != other.plugin && !this.plugin.equals(other.plugin)) { + return false; + } + if (!this.channel.equals(other.channel)) { + return false; + } + if (this.listener != other.listener && !this.listener.equals(other.listener)) { + return false; + } + return true; + } + + @Override + public int hashCode() { + int hash = 7; + hash = 53 * hash + this.messenger.hashCode(); + hash = 53 * hash + this.plugin.hashCode(); + hash = 53 * hash + this.channel.hashCode(); + hash = 53 * hash + this.listener.hashCode(); + return hash; + } +} diff --git a/api/src/main/java/org/bukkit/plugin/messaging/PluginMessageRecipient.java b/api/src/main/java/org/bukkit/plugin/messaging/PluginMessageRecipient.java new file mode 100644 index 000000000..b84b37fe2 --- /dev/null +++ b/api/src/main/java/org/bukkit/plugin/messaging/PluginMessageRecipient.java @@ -0,0 +1,40 @@ +package org.bukkit.plugin.messaging; + +import java.util.Set; +import org.bukkit.plugin.Plugin; +import org.jetbrains.annotations.NotNull; + +/** + * Represents a possible recipient for a Plugin Message. + */ +public interface PluginMessageRecipient { + /** + * Sends this recipient a Plugin Message on the specified outgoing + * channel. + *

+ * The message may not be larger than {@link Messenger#MAX_MESSAGE_SIZE} + * bytes, and the plugin must be registered to send messages on the + * specified channel. + * + * @param source The plugin that sent this message. + * @param channel The channel to send this message on. + * @param message The raw message to send. + * @throws IllegalArgumentException Thrown if the source plugin is + * disabled. + * @throws IllegalArgumentException Thrown if source, channel or message + * is null. + * @throws MessageTooLargeException Thrown if the message is too big. + * @throws ChannelNotRegisteredException Thrown if the channel is not + * registered for this plugin. + */ + public void sendPluginMessage(@NotNull Plugin source, @NotNull String channel, @NotNull byte[] message); + + /** + * Gets a set containing all the Plugin Channels that this client is + * listening on. + * + * @return Set containing all the channels that this client may accept. + */ + @NotNull + public Set getListeningPluginChannels(); +} diff --git a/api/src/main/java/org/bukkit/plugin/messaging/ReservedChannelException.java b/api/src/main/java/org/bukkit/plugin/messaging/ReservedChannelException.java new file mode 100644 index 000000000..0221f049a --- /dev/null +++ b/api/src/main/java/org/bukkit/plugin/messaging/ReservedChannelException.java @@ -0,0 +1,16 @@ +package org.bukkit.plugin.messaging; + +/** + * Thrown if a plugin attempts to register for a reserved channel (such as + * "REGISTER") + */ +@SuppressWarnings("serial") +public class ReservedChannelException extends RuntimeException { + public ReservedChannelException() { + this("Attempted to register for a reserved channel name."); + } + + public ReservedChannelException(String name) { + super("Attempted to register for a reserved channel name ('" + name + "')"); + } +} diff --git a/api/src/main/java/org/bukkit/plugin/messaging/StandardMessenger.java b/api/src/main/java/org/bukkit/plugin/messaging/StandardMessenger.java new file mode 100644 index 000000000..cc750eb3e --- /dev/null +++ b/api/src/main/java/org/bukkit/plugin/messaging/StandardMessenger.java @@ -0,0 +1,532 @@ +package org.bukkit.plugin.messaging; + +import com.google.common.collect.ImmutableSet; +import com.google.common.collect.ImmutableSet.Builder; +import java.util.HashMap; +import java.util.HashSet; +import java.util.Locale; +import java.util.Map; +import java.util.Set; +import java.util.logging.Level; +import org.bukkit.entity.Player; +import org.bukkit.plugin.Plugin; +import org.jetbrains.annotations.NotNull; + +/** + * Standard implementation to {@link Messenger} + */ +public class StandardMessenger implements Messenger { + private final Map> incomingByChannel = new HashMap>(); + private final Map> incomingByPlugin = new HashMap>(); + private final Map> outgoingByChannel = new HashMap>(); + private final Map> outgoingByPlugin = new HashMap>(); + private final Object incomingLock = new Object(); + private final Object outgoingLock = new Object(); + + private void addToOutgoing(@NotNull Plugin plugin, @NotNull String channel) { + synchronized (outgoingLock) { + Set plugins = outgoingByChannel.get(channel); + Set channels = outgoingByPlugin.get(plugin); + + if (plugins == null) { + plugins = new HashSet(); + outgoingByChannel.put(channel, plugins); + } + + if (channels == null) { + channels = new HashSet(); + outgoingByPlugin.put(plugin, channels); + } + + plugins.add(plugin); + channels.add(channel); + } + } + + private void removeFromOutgoing(@NotNull Plugin plugin, @NotNull String channel) { + synchronized (outgoingLock) { + Set plugins = outgoingByChannel.get(channel); + Set channels = outgoingByPlugin.get(plugin); + + if (plugins != null) { + plugins.remove(plugin); + + if (plugins.isEmpty()) { + outgoingByChannel.remove(channel); + } + } + + if (channels != null) { + channels.remove(channel); + + if (channels.isEmpty()) { + outgoingByChannel.remove(channel); + } + } + } + } + + private void removeFromOutgoing(@NotNull Plugin plugin) { + synchronized (outgoingLock) { + Set channels = outgoingByPlugin.get(plugin); + + if (channels != null) { + String[] toRemove = channels.toArray(new String[channels.size()]); + + outgoingByPlugin.remove(plugin); + + for (String channel : toRemove) { + removeFromOutgoing(plugin, channel); + } + } + } + } + + private void addToIncoming(@NotNull PluginMessageListenerRegistration registration) { + synchronized (incomingLock) { + Set registrations = incomingByChannel.get(registration.getChannel()); + + if (registrations == null) { + registrations = new HashSet(); + incomingByChannel.put(registration.getChannel(), registrations); + } else { + if (registrations.contains(registration)) { + throw new IllegalArgumentException("This registration already exists"); + } + } + + registrations.add(registration); + + registrations = incomingByPlugin.get(registration.getPlugin()); + + if (registrations == null) { + registrations = new HashSet(); + incomingByPlugin.put(registration.getPlugin(), registrations); + } else { + if (registrations.contains(registration)) { + throw new IllegalArgumentException("This registration already exists"); + } + } + + registrations.add(registration); + } + } + + private void removeFromIncoming(@NotNull PluginMessageListenerRegistration registration) { + synchronized (incomingLock) { + Set registrations = incomingByChannel.get(registration.getChannel()); + + if (registrations != null) { + registrations.remove(registration); + + if (registrations.isEmpty()) { + incomingByChannel.remove(registration.getChannel()); + } + } + + registrations = incomingByPlugin.get(registration.getPlugin()); + + if (registrations != null) { + registrations.remove(registration); + + if (registrations.isEmpty()) { + incomingByPlugin.remove(registration.getPlugin()); + } + } + } + } + + private void removeFromIncoming(@NotNull Plugin plugin, @NotNull String channel) { + synchronized (incomingLock) { + Set registrations = incomingByPlugin.get(plugin); + + if (registrations != null) { + PluginMessageListenerRegistration[] toRemove = registrations.toArray(new PluginMessageListenerRegistration[registrations.size()]); + + for (PluginMessageListenerRegistration registration : toRemove) { + if (registration.getChannel().equals(channel)) { + removeFromIncoming(registration); + } + } + } + } + } + + private void removeFromIncoming(@NotNull Plugin plugin) { + synchronized (incomingLock) { + Set registrations = incomingByPlugin.get(plugin); + + if (registrations != null) { + PluginMessageListenerRegistration[] toRemove = registrations.toArray(new PluginMessageListenerRegistration[registrations.size()]); + + incomingByPlugin.remove(plugin); + + for (PluginMessageListenerRegistration registration : toRemove) { + removeFromIncoming(registration); + } + } + } + } + + public boolean isReservedChannel(@NotNull String channel) { + channel = validateAndCorrectChannel(channel); + + return channel.equals("minecraft:register") || channel.equals("minecraft:unregister"); // Paper + } + + public void registerOutgoingPluginChannel(@NotNull Plugin plugin, @NotNull String channel) { + if (plugin == null) { + throw new IllegalArgumentException("Plugin cannot be null"); + } + channel = validateAndCorrectChannel(channel); + if (isReservedChannel(channel)) { + throw new ReservedChannelException(channel); + } + + addToOutgoing(plugin, channel); + } + + public void unregisterOutgoingPluginChannel(@NotNull Plugin plugin, @NotNull String channel) { + if (plugin == null) { + throw new IllegalArgumentException("Plugin cannot be null"); + } + channel = validateAndCorrectChannel(channel); + + removeFromOutgoing(plugin, channel); + } + + public void unregisterOutgoingPluginChannel(@NotNull Plugin plugin) { + if (plugin == null) { + throw new IllegalArgumentException("Plugin cannot be null"); + } + + removeFromOutgoing(plugin); + } + + @NotNull + public PluginMessageListenerRegistration registerIncomingPluginChannel(@NotNull Plugin plugin, @NotNull String channel, @NotNull PluginMessageListener listener) { + if (plugin == null) { + throw new IllegalArgumentException("Plugin cannot be null"); + } + channel = validateAndCorrectChannel(channel); + if (isReservedChannel(channel)) { + throw new ReservedChannelException(channel); + } + if (listener == null) { + throw new IllegalArgumentException("Listener cannot be null"); + } + + PluginMessageListenerRegistration result = new PluginMessageListenerRegistration(this, plugin, channel, listener); + + addToIncoming(result); + + return result; + } + + public void unregisterIncomingPluginChannel(@NotNull Plugin plugin, @NotNull String channel, @NotNull PluginMessageListener listener) { + if (plugin == null) { + throw new IllegalArgumentException("Plugin cannot be null"); + } + if (listener == null) { + throw new IllegalArgumentException("Listener cannot be null"); + } + channel = validateAndCorrectChannel(channel); + + removeFromIncoming(new PluginMessageListenerRegistration(this, plugin, channel, listener)); + } + + public void unregisterIncomingPluginChannel(@NotNull Plugin plugin, @NotNull String channel) { + if (plugin == null) { + throw new IllegalArgumentException("Plugin cannot be null"); + } + channel = validateAndCorrectChannel(channel); + + removeFromIncoming(plugin, channel); + } + + public void unregisterIncomingPluginChannel(@NotNull Plugin plugin) { + if (plugin == null) { + throw new IllegalArgumentException("Plugin cannot be null"); + } + + removeFromIncoming(plugin); + } + + @NotNull + public Set getOutgoingChannels() { + synchronized (outgoingLock) { + Set keys = outgoingByChannel.keySet(); + return ImmutableSet.copyOf(keys); + } + } + + @NotNull + public Set getOutgoingChannels(@NotNull Plugin plugin) { + if (plugin == null) { + throw new IllegalArgumentException("Plugin cannot be null"); + } + + synchronized (outgoingLock) { + Set channels = outgoingByPlugin.get(plugin); + + if (channels != null) { + return ImmutableSet.copyOf(channels); + } else { + return ImmutableSet.of(); + } + } + } + + @NotNull + public Set getIncomingChannels() { + synchronized (incomingLock) { + Set keys = incomingByChannel.keySet(); + return ImmutableSet.copyOf(keys); + } + } + + @NotNull + public Set getIncomingChannels(@NotNull Plugin plugin) { + if (plugin == null) { + throw new IllegalArgumentException("Plugin cannot be null"); + } + + synchronized (incomingLock) { + Set registrations = incomingByPlugin.get(plugin); + + if (registrations != null) { + Builder builder = ImmutableSet.builder(); + + for (PluginMessageListenerRegistration registration : registrations) { + builder.add(registration.getChannel()); + } + + return builder.build(); + } else { + return ImmutableSet.of(); + } + } + } + + @NotNull + public Set getIncomingChannelRegistrations(@NotNull Plugin plugin) { + if (plugin == null) { + throw new IllegalArgumentException("Plugin cannot be null"); + } + + synchronized (incomingLock) { + Set registrations = incomingByPlugin.get(plugin); + + if (registrations != null) { + return ImmutableSet.copyOf(registrations); + } else { + return ImmutableSet.of(); + } + } + } + + @NotNull + public Set getIncomingChannelRegistrations(@NotNull String channel) { + channel = validateAndCorrectChannel(channel); + + synchronized (incomingLock) { + Set registrations = incomingByChannel.get(channel); + + if (registrations != null) { + return ImmutableSet.copyOf(registrations); + } else { + return ImmutableSet.of(); + } + } + } + + @NotNull + public Set getIncomingChannelRegistrations(@NotNull Plugin plugin, @NotNull String channel) { + if (plugin == null) { + throw new IllegalArgumentException("Plugin cannot be null"); + } + channel = validateAndCorrectChannel(channel); + + synchronized (incomingLock) { + Set registrations = incomingByPlugin.get(plugin); + + if (registrations != null) { + Builder builder = ImmutableSet.builder(); + + for (PluginMessageListenerRegistration registration : registrations) { + if (registration.getChannel().equals(channel)) { + builder.add(registration); + } + } + + return builder.build(); + } else { + return ImmutableSet.of(); + } + } + } + + public boolean isRegistrationValid(@NotNull PluginMessageListenerRegistration registration) { + if (registration == null) { + throw new IllegalArgumentException("Registration cannot be null"); + } + + synchronized (incomingLock) { + Set registrations = incomingByPlugin.get(registration.getPlugin()); + + if (registrations != null) { + return registrations.contains(registration); + } + + return false; + } + } + + public boolean isIncomingChannelRegistered(@NotNull Plugin plugin, @NotNull String channel) { + if (plugin == null) { + throw new IllegalArgumentException("Plugin cannot be null"); + } + channel = validateAndCorrectChannel(channel); + + synchronized (incomingLock) { + Set registrations = incomingByPlugin.get(plugin); + + if (registrations != null) { + for (PluginMessageListenerRegistration registration : registrations) { + if (registration.getChannel().equals(channel)) { + return true; + } + } + } + + return false; + } + } + + public boolean isOutgoingChannelRegistered(@NotNull Plugin plugin, @NotNull String channel) { + if (plugin == null) { + throw new IllegalArgumentException("Plugin cannot be null"); + } + channel = validateAndCorrectChannel(channel); + + synchronized (outgoingLock) { + Set channels = outgoingByPlugin.get(plugin); + + if (channels != null) { + return channels.contains(channel); + } + + return false; + } + } + + public void dispatchIncomingMessage(@NotNull Player source, @NotNull String channel, @NotNull byte[] message) { + if (source == null) { + throw new IllegalArgumentException("Player source cannot be null"); + } + if (message == null) { + throw new IllegalArgumentException("Message cannot be null"); + } + channel = validateAndCorrectChannel(channel); + + Set registrations = getIncomingChannelRegistrations(channel); + + for (PluginMessageListenerRegistration registration : registrations) { + try { + registration.getListener().onPluginMessageReceived(channel, source, message); + } catch (Throwable t) { + registration.getPlugin().getLogger().log(Level.WARNING, + String.format("Plugin %s generated an exception whilst handling plugin message", + registration.getPlugin().getDescription().getFullName() + ), t); + } + } + } + + /** + * Validates a Plugin Channel name. + * + * @param channel Channel name to validate. + * @deprecated not an API method + */ + @Deprecated + public static void validateChannel(@NotNull String channel) { + validateAndCorrectChannel(channel); + } + + /** + * Validates and corrects a Plugin Channel name. Method is not reentrant / idempotent. + * + * @param channel Channel name to validate. + * @return corrected channel name + * @deprecated not an API method + */ + @Deprecated + @NotNull + public static String validateAndCorrectChannel(@NotNull String channel) { + if (channel == null) { + throw new IllegalArgumentException("Channel cannot be null"); + } + // This will correct registrations / outgoing messages + // It is not legal to send "BungeeCord" incoming anymore so we are fine there, + // but we must make sure that none of the API methods repeatedly call validate + if (channel.equals("BungeeCord")) { + return "bungeecord:main"; + } + // And this will correct incoming messages. + if (channel.equals("bungeecord:main")) { + return "BungeeCord"; + } + if (channel.length() > Messenger.MAX_CHANNEL_SIZE) { + throw new ChannelNameTooLongException(channel); + } + if (channel.indexOf(':') == -1) { + throw new IllegalArgumentException("Channel must contain : separator (attempted to use " + channel + ")"); + } + if (!channel.toLowerCase(Locale.ROOT).equals(channel)) { + // TODO: use NamespacedKey validation here + throw new IllegalArgumentException("Channel must be entirely lowercase (attempted to use " + channel + ")"); + } + return channel; + } + + /** + * Validates the input of a Plugin Message, ensuring the arguments are all + * valid. + * + * @param messenger Messenger to use for validation. + * @param source Source plugin of the Message. + * @param channel Plugin Channel to send the message by. + * @param message Raw message payload to send. + * @throws IllegalArgumentException Thrown if the source plugin is + * disabled. + * @throws IllegalArgumentException Thrown if source, channel or message + * is null. + * @throws MessageTooLargeException Thrown if the message is too big. + * @throws ChannelNameTooLongException Thrown if the channel name is too + * long. + * @throws ChannelNotRegisteredException Thrown if the channel is not + * registered for this plugin. + */ + public static void validatePluginMessage(@NotNull Messenger messenger, @NotNull Plugin source, @NotNull String channel, @NotNull byte[] message) { + if (messenger == null) { + throw new IllegalArgumentException("Messenger cannot be null"); + } + if (source == null) { + throw new IllegalArgumentException("Plugin source cannot be null"); + } + if (!source.isEnabled()) { + throw new IllegalArgumentException("Plugin must be enabled to send messages"); + } + if (message == null) { + throw new IllegalArgumentException("Message cannot be null"); + } + if (!messenger.isOutgoingChannelRegistered(source, channel)) { + throw new ChannelNotRegisteredException(channel); + } + if (message.length > Messenger.MAX_MESSAGE_SIZE) { + throw new MessageTooLargeException(message); + } + validateChannel(channel); + } +} diff --git a/api/src/main/java/org/bukkit/potion/Potion.java b/api/src/main/java/org/bukkit/potion/Potion.java new file mode 100644 index 000000000..a57f70ec6 --- /dev/null +++ b/api/src/main/java/org/bukkit/potion/Potion.java @@ -0,0 +1,404 @@ +package org.bukkit.potion; + +import java.util.Collection; + +import org.apache.commons.lang.Validate; +import org.bukkit.Material; +import org.bukkit.entity.LivingEntity; +import org.bukkit.inventory.ItemStack; +import org.bukkit.inventory.meta.PotionMeta; +import org.jetbrains.annotations.NotNull; + +/** + * Potion Adapter for pre-1.9 data values + * see @PotionMeta for 1.9+ + */ +@Deprecated +public class Potion { + private boolean extended = false; + private boolean splash = false; + private int level = 1; + private PotionType type; + + /** + * Construct a new potion of the given type. Unless the type is {@link + * PotionType#WATER}, it will be level one, without extended duration. + * Don't use this constructor to create a no-effect potion other than + * water bottle. + * + * @param type The potion type + * @see #Potion(int) + */ + public Potion(@NotNull PotionType type) { + Validate.notNull(type, "Null PotionType"); + this.type = type; + } + + /** + * Create a new potion of the given type and level. + * + * @param type The type of potion. + * @param level The potion's level. + */ + public Potion(@NotNull PotionType type, int level) { + this(type); + Validate.notNull(type, "Type cannot be null"); + Validate.isTrue(level > 0 && level < 3, "Level must be 1 or 2"); + this.level = level; + } + + /** + * Create a new potion of the given type and level. + * + * @param type The type of potion. + * @param level The potion's level. + * @param splash Whether it is a splash potion. + * @deprecated In favour of using {@link #Potion(PotionType)} with {@link + * #splash()}. + */ + @Deprecated + public Potion(@NotNull PotionType type, int level, boolean splash) { + this(type, level); + this.splash = splash; + } + + /** + * Create a new potion of the given type and level. + * + * @param type The type of potion. + * @param level The potion's level. + * @param splash Whether it is a splash potion. + * @param extended Whether it has an extended duration. + * @deprecated In favour of using {@link #Potion(PotionType)} with {@link + * #extend()} and possibly {@link #splash()}. + */ + @Deprecated + public Potion(@NotNull PotionType type, int level, boolean splash, boolean extended) { + this(type, level, splash); + this.extended = extended; + } + + /** + * @param name Unused, always uses {@link PotionType#WATER} + * @deprecated + */ + @Deprecated + public Potion(int name) { + this(PotionType.WATER); + } + + /** + * Chain this to the constructor to make the potion a splash potion. + * + * @return The potion. + */ + @NotNull + public Potion splash() { + setSplash(true); + return this; + } + + /** + * Chain this to the constructor to extend the potion's duration. + * + * @return The potion. + */ + @NotNull + public Potion extend() { + setHasExtendedDuration(true); + return this; + } + + /** + * Applies the effects of this potion to the given {@link ItemStack}. The + * ItemStack must be a potion. + * + * @param to The itemstack to apply to + */ + public void apply(@NotNull ItemStack to) { + Validate.notNull(to, "itemstack cannot be null"); + Validate.isTrue(to.hasItemMeta(), "given itemstack is not a potion"); + Validate.isTrue(to.getItemMeta() instanceof PotionMeta, "given itemstack is not a potion"); + PotionMeta meta = (PotionMeta) to.getItemMeta(); + meta.setBasePotionData(new PotionData(type, extended, level == 2)); + to.setItemMeta(meta); + } + + /** + * Applies the effects that would be applied by this potion to the given + * {@link LivingEntity}. + * + * @see LivingEntity#addPotionEffects(Collection) + * @param to The entity to apply the effects to + */ + public void apply(@NotNull LivingEntity to) { + Validate.notNull(to, "entity cannot be null"); + to.addPotionEffects(getEffects()); + } + + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } + if (obj == null || getClass() != obj.getClass()) { + return false; + } + Potion other = (Potion) obj; + return extended == other.extended && splash == other.splash && level == other.level && type == other.type; + } + + /** + * Returns a collection of {@link PotionEffect}s that this {@link Potion} + * would confer upon a {@link LivingEntity}. + * + * @see PotionBrewer#getEffectsFromDamage(int) + * @see Potion#toDamageValue() + * @return The effects that this potion applies + */ + @NotNull + public Collection getEffects() { + return getBrewer().getEffects(type, level == 2, extended); + } + + /** + * Returns the level of this potion. + * + * @return The level of this potion + */ + public int getLevel() { + return level; + } + + /** + * Returns the {@link PotionType} of this potion. + * + * @return The type of this potion + */ + @NotNull + public PotionType getType() { + return type; + } + + /** + * Returns whether this potion has an extended duration. + * + * @return Whether this potion has extended duration + */ + public boolean hasExtendedDuration() { + return extended; + } + + @Override + public int hashCode() { + final int prime = 31; + int result = prime + level; + result = prime * result + (extended ? 1231 : 1237); + result = prime * result + (splash ? 1231 : 1237); + result = prime * result + ((type == null) ? 0 : type.hashCode()); + return result; + } + + /** + * Returns whether this potion is a splash potion. + * + * @return Whether this is a splash potion + */ + public boolean isSplash() { + return splash; + } + + /** + * Set whether this potion has extended duration. This will cause the + * potion to have roughly 8/3 more duration than a regular potion. + * + * @param isExtended Whether the potion should have extended duration + */ + public void setHasExtendedDuration(boolean isExtended) { + Validate.isTrue(type == null || !type.isInstant(), "Instant potions cannot be extended"); + extended = isExtended; + } + + /** + * Sets whether this potion is a splash potion. Splash potions can be + * thrown for a radius effect. + * + * @param isSplash Whether this is a splash potion + */ + public void setSplash(boolean isSplash) { + splash = isSplash; + } + + /** + * Sets the {@link PotionType} of this potion. + * + * @param type The new type of this potion + */ + public void setType(@NotNull PotionType type) { + this.type = type; + } + + /** + * Sets the level of this potion. + * + * @param level The new level of this potion + */ + public void setLevel(int level) { + Validate.notNull(this.type, "No-effect potions don't have a level."); + Validate.isTrue(level > 0 && level <= 2, "Level must be between 1 and 2 for this potion"); + this.level = level; + } + + /** + * Converts this potion to a valid potion damage short, usable for potion + * item stacks. + * + * @return The damage value of this potion + * @deprecated Non-functional + */ + @Deprecated + public short toDamageValue() { + return 0; + } + + /** + * Converts this potion to an {@link ItemStack} with the specified amount + * and a correct damage value. + * + * @param amount The amount of the ItemStack + * @return The created ItemStack + */ + @NotNull + public ItemStack toItemStack(int amount) { + Material material; + if (isSplash()) { + material = Material.SPLASH_POTION; + } else { + material = Material.POTION; + } + ItemStack itemStack = new ItemStack(material, amount); + PotionMeta meta = (PotionMeta) itemStack.getItemMeta(); + meta.setBasePotionData(new PotionData(type, level == 2, extended)); + itemStack.setItemMeta(meta); + return itemStack; + } + + private static PotionBrewer brewer; + + private static final int EXTENDED_BIT = 0x40; + private static final int POTION_BIT = 0xF; + private static final int SPLASH_BIT = 0x4000; + private static final int TIER_BIT = 0x20; + private static final int TIER_SHIFT = 5; + + /** + * + * @param damage the damage value + * @return the produced potion + */ + @NotNull + public static Potion fromDamage(int damage) { + PotionType type; + switch (damage & POTION_BIT) { + case 0: + type = PotionType.WATER; + break; + case 1: + type = PotionType.REGEN; + break; + case 2: + type = PotionType.SPEED; + break; + case 3: + type = PotionType.FIRE_RESISTANCE; + break; + case 4: + type = PotionType.POISON; + break; + case 5: + type = PotionType.INSTANT_HEAL; + break; + case 6: + type = PotionType.NIGHT_VISION; + break; + case 8: + type = PotionType.WEAKNESS; + break; + case 9: + type = PotionType.STRENGTH; + break; + case 10: + type = PotionType.SLOWNESS; + break; + case 11: + type = PotionType.JUMP; + break; + case 12: + type = PotionType.INSTANT_DAMAGE; + break; + case 13: + type = PotionType.WATER_BREATHING; + break; + case 14: + type = PotionType.INVISIBILITY; + break; + default: + type = PotionType.WATER; + } + Potion potion; + if (type == null || type == PotionType.WATER) { + potion = new Potion(PotionType.WATER); + } else { + int level = (damage & TIER_BIT) >> TIER_SHIFT; + level++; + potion = new Potion(type, level); + } + if ((damage & SPLASH_BIT) != 0) { + potion = potion.splash(); + } + if ((damage & EXTENDED_BIT) != 0) { + potion = potion.extend(); + } + return potion; + } + + @NotNull + public static Potion fromItemStack(@NotNull ItemStack item) { + Validate.notNull(item, "item cannot be null"); + if (item.getType() != Material.POTION) + throw new IllegalArgumentException("item is not a potion"); + return fromDamage(item.getDurability()); + } + + /** + * Returns an instance of {@link PotionBrewer}. + * + * @return An instance of PotionBrewer + */ + @NotNull + public static PotionBrewer getBrewer() { + return brewer; + } + + /** + * Sets the current instance of {@link PotionBrewer}. Generally not to be + * used from within a plugin. + * + * @param other The new PotionBrewer + */ + public static void setPotionBrewer(@NotNull PotionBrewer other) { + if (brewer != null) + throw new IllegalArgumentException("brewer can only be set internally"); + brewer = other; + } + + /** + * + * @return the name id + * @deprecated Non-functional + */ + @Deprecated + public int getNameId() { + return 0; + } +} diff --git a/api/src/main/java/org/bukkit/potion/PotionBrewer.java b/api/src/main/java/org/bukkit/potion/PotionBrewer.java new file mode 100644 index 000000000..254bd6f3e --- /dev/null +++ b/api/src/main/java/org/bukkit/potion/PotionBrewer.java @@ -0,0 +1,47 @@ +package org.bukkit.potion; + +import org.jetbrains.annotations.NotNull; + +import java.util.Collection; + +/** + * Represents a brewer that can create {@link PotionEffect}s. + */ +public interface PotionBrewer { + + /** + * Creates a {@link PotionEffect} from the given {@link PotionEffectType}, + * applying duration modifiers and checks. + * + * @param potion The type of potion + * @param duration The duration in ticks + * @param amplifier The amplifier of the effect + * @return The resulting potion effect + */ + @NotNull + public PotionEffect createEffect(@NotNull PotionEffectType potion, int duration, int amplifier); + + /** + * Returns a collection of {@link PotionEffect} that would be applied from + * a potion with the given data value. + * + * @param damage The data value of the potion + * @return The list of effects + * @deprecated Non-Functional + */ + @Deprecated + @NotNull + public Collection getEffectsFromDamage(int damage); + + /** + * Returns a collection of {@link PotionEffect} that would be applied from + * a potion with the given type. + * + * @param type The type of the potion + * @param upgraded Whether the potion is upgraded + * @param extended Whether the potion is extended + * @return The list of effects + */ + @NotNull + public Collection getEffects(@NotNull PotionType type, boolean upgraded, boolean extended); +} diff --git a/api/src/main/java/org/bukkit/potion/PotionData.java b/api/src/main/java/org/bukkit/potion/PotionData.java new file mode 100644 index 000000000..ba0d67116 --- /dev/null +++ b/api/src/main/java/org/bukkit/potion/PotionData.java @@ -0,0 +1,87 @@ +package org.bukkit.potion; + +import org.apache.commons.lang.Validate; +import org.jetbrains.annotations.NotNull; + +public final class PotionData { + + private final PotionType type; + private final boolean extended; + private final boolean upgraded; + + /** + * Instantiates a final PotionData object to contain information about a + * Potion + * + * @param type the type of the Potion + * @param extended whether the potion is extended PotionType#isExtendable() + * must be true + * @param upgraded whether the potion is upgraded PotionType#isUpgradable() + * must be true + */ + public PotionData(@NotNull PotionType type, boolean extended, boolean upgraded) { + Validate.notNull(type, "Potion Type must not be null"); + Validate.isTrue(!upgraded || type.isUpgradeable(), "Potion Type is not upgradable"); + Validate.isTrue(!extended || type.isExtendable(), "Potion Type is not extendable"); + Validate.isTrue(!upgraded || !extended, "Potion cannot be both extended and upgraded"); + this.type = type; + this.extended = extended; + this.upgraded = upgraded; + } + + public PotionData(@NotNull PotionType type) { + this(type, false, false); + } + + /** + * Gets the type of the potion, Type matches up with each kind of craftable + * potion + * + * @return the potion type + */ + @NotNull + public PotionType getType() { + return type; + } + + /** + * Checks if the potion is in an upgraded state. This refers to whether or + * not the potion is Tier 2, such as Potion of Fire Resistance II. + * + * @return true if the potion is upgraded; + */ + public boolean isUpgraded() { + return upgraded; + } + + /** + * Checks if the potion is in an extended state. This refers to the extended + * duration potions + * + * @return true if the potion is extended + */ + public boolean isExtended() { + return extended; + } + + @Override + public int hashCode() { + int hash = 7; + hash = 23 * hash + (this.type != null ? this.type.hashCode() : 0); + hash = 23 * hash + (this.extended ? 1 : 0); + hash = 23 * hash + (this.upgraded ? 1 : 0); + return hash; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } + if (obj == null || getClass() != obj.getClass()) { + return false; + } + PotionData other = (PotionData) obj; + return (this.upgraded == other.upgraded) && (this.extended == other.extended) && (this.type == other.type); + } +} diff --git a/api/src/main/java/org/bukkit/potion/PotionEffect.java b/api/src/main/java/org/bukkit/potion/PotionEffect.java new file mode 100644 index 000000000..4125a024e --- /dev/null +++ b/api/src/main/java/org/bukkit/potion/PotionEffect.java @@ -0,0 +1,276 @@ +package org.bukkit.potion; + +import java.util.Map; +import java.util.NoSuchElementException; + +import org.apache.commons.lang.Validate; +import org.bukkit.Color; +import org.bukkit.configuration.serialization.ConfigurationSerializable; +import org.bukkit.configuration.serialization.SerializableAs; +import org.bukkit.entity.LivingEntity; + +import com.google.common.collect.ImmutableMap; +import org.jetbrains.annotations.Contract; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +/** + * Represents a potion effect, that can be added to a {@link LivingEntity}. A + * potion effect has a duration that it will last for, an amplifier that will + * enhance its effects, and a {@link PotionEffectType}, that represents its + * effect on an entity. + */ +@SerializableAs("PotionEffect") +public class PotionEffect implements ConfigurationSerializable { + private static final String AMPLIFIER = "amplifier"; + private static final String DURATION = "duration"; + private static final String TYPE = "effect"; + private static final String AMBIENT = "ambient"; + private static final String PARTICLES = "has-particles"; + private static final String ICON = "has-icon"; + private final int amplifier; + private final int duration; + private final PotionEffectType type; + private final boolean ambient; + private final boolean particles; + private final boolean icon; + + /** + * Creates a potion effect. + * @param type effect type + * @param duration measured in ticks, see {@link + * PotionEffect#getDuration()} + * @param amplifier the amplifier, see {@link PotionEffect#getAmplifier()} + * @param ambient the ambient status, see {@link PotionEffect#isAmbient()} + * @param particles the particle status, see {@link PotionEffect#hasParticles()} + * @param icon the icon status, see {@link PotionEffect#hasIcon()} + */ + public PotionEffect(@NotNull PotionEffectType type, int duration, int amplifier, boolean ambient, boolean particles, boolean icon) { + Validate.notNull(type, "effect type cannot be null"); + this.type = type; + this.duration = duration; + this.amplifier = amplifier; + this.ambient = ambient; + this.particles = particles; + this.icon = icon; + } + + /** + * Creates a potion effect with no defined color. + * + * @param type effect type + * @param duration measured in ticks, see {@link + * PotionEffect#getDuration()} + * @param amplifier the amplifier, see {@link PotionEffect#getAmplifier()} + * @param ambient the ambient status, see {@link PotionEffect#isAmbient()} + * @param particles the particle status, see {@link PotionEffect#hasParticles()} + */ + public PotionEffect(@NotNull PotionEffectType type, int duration, int amplifier, boolean ambient, boolean particles) { + this(type, duration, amplifier, ambient, particles, particles); + } + + /** + * Creates a potion effect. Assumes that particles are visible + * + * @param type effect type + * @param duration measured in ticks, see {@link + * PotionEffect#getDuration()} + * @param amplifier the amplifier, see {@link PotionEffect#getAmplifier()} + * @param ambient the ambient status, see {@link PotionEffect#isAmbient()} + */ + public PotionEffect(@NotNull PotionEffectType type, int duration, int amplifier, boolean ambient) { + this(type, duration, amplifier, ambient, true); + } + + /** + * Creates a potion effect. Assumes ambient is true. + * + * @param type Effect type + * @param duration measured in ticks + * @param amplifier the amplifier for the effect + * @see PotionEffect#PotionEffect(PotionEffectType, int, int, boolean) + */ + public PotionEffect(@NotNull PotionEffectType type, int duration, int amplifier) { + this(type, duration, amplifier, true); + } + + /** + * Constructor for deserialization. + * + * @param map the map to deserialize from + */ + public PotionEffect(@NotNull Map map) { + this(getEffectType(map), getInt(map, DURATION), getInt(map, AMPLIFIER), getBool(map, AMBIENT, false), getBool(map, PARTICLES, true), getBool(map, ICON, getBool(map, PARTICLES, true))); + } + + // Paper start + @NotNull + public PotionEffect withType(@NotNull PotionEffectType type) { + return new PotionEffect(type, duration, amplifier, ambient, particles, icon); + } + @NotNull + public PotionEffect withDuration(int duration) { + return new PotionEffect(this.type, duration, amplifier, ambient, particles, icon); + } + @NotNull + public PotionEffect withAmplifier(int amplifier) { + return new PotionEffect(this.type, duration, amplifier, ambient, particles, icon); + } + @NotNull + public PotionEffect withAmbient(boolean ambient) { + return new PotionEffect(this.type, duration, amplifier, ambient, particles, icon); + } + @NotNull + public PotionEffect withParticles(boolean particles) { + return new PotionEffect(this.type, duration, amplifier, ambient, particles, icon); + } + @NotNull + public PotionEffect withIcon(boolean icon) { + return new PotionEffect(this.type, duration, amplifier, ambient, particles, icon); + } + // Paper end + + @NotNull + private static PotionEffectType getEffectType(@NotNull Map map) { + int type = getInt(map, TYPE); + PotionEffectType effect = PotionEffectType.getById(type); + if (effect != null) { + return effect; + } + throw new NoSuchElementException(map + " does not contain " + TYPE); + } + + private static int getInt(@NotNull Map map, @NotNull Object key) { + Object num = map.get(key); + if (num instanceof Integer) { + return (Integer) num; + } + throw new NoSuchElementException(map + " does not contain " + key); + } + + private static boolean getBool(@NotNull Map map, @NotNull Object key, boolean def) { + Object bool = map.get(key); + if (bool instanceof Boolean) { + return (Boolean) bool; + } + return def; + } + + @NotNull + public Map serialize() { + return ImmutableMap.builder() + .put(TYPE, type.getId()) + .put(DURATION, duration) + .put(AMPLIFIER, amplifier) + .put(AMBIENT, ambient) + .put(PARTICLES, particles) + .put(ICON, icon) + .build(); + } + + /** + * Attempts to add the effect represented by this object to the given + * {@link LivingEntity}. + * + * @see LivingEntity#addPotionEffect(PotionEffect) + * @param entity The entity to add this effect to + * @return Whether the effect could be added + */ + public boolean apply(@NotNull LivingEntity entity) { + return entity.addPotionEffect(this); + } + + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } + if (!(obj instanceof PotionEffect)) { + return false; + } + PotionEffect that = (PotionEffect) obj; + return this.type.equals(that.type) && this.ambient == that.ambient && this.amplifier == that.amplifier && this.duration == that.duration && this.particles == that.particles && this.icon == that.icon; + } + + /** + * Returns the amplifier of this effect. A higher amplifier means the + * potion effect happens more often over its duration and in some cases + * has more effect on its target. + * + * @return The effect amplifier + */ + public int getAmplifier() { + return amplifier; + } + + /** + * Returns the duration (in ticks) that this effect will run for when + * applied to a {@link LivingEntity}. + * + * @return The duration of the effect + */ + public int getDuration() { + return duration; + } + + /** + * Returns the {@link PotionEffectType} of this effect. + * + * @return The potion type of this effect + */ + @NotNull + public PotionEffectType getType() { + return type; + } + + /** + * Makes potion effect produce more, translucent, particles. + * + * @return if this effect is ambient + */ + public boolean isAmbient() { + return ambient; + } + + /** + * @return whether this effect has particles or not + */ + public boolean hasParticles() { + return particles; + } + + /** + * @return color of this potion's particles. May be null if the potion has no particles or defined color. + * @deprecated color is not part of potion effects + */ + @Deprecated + @Nullable + @Contract("-> null") + public Color getColor() { + return null; + } + + /** + * @return whether this effect has an icon or not + */ + public boolean hasIcon() { + return icon; + } + + @Override + public int hashCode() { + int hash = 1; + hash = hash * 31 + type.hashCode(); + hash = hash * 31 + amplifier; + hash = hash * 31 + duration; + hash ^= 0x22222222 >> (ambient ? 1 : -1); + hash ^= 0x22222222 >> (particles ? 1 : -1); + hash ^= 0x22222222 >> (icon ? 1 : -1); + return hash; + } + + @Override + public String toString() { + return type.getName() + (ambient ? ":(" : ":") + duration + "t-x" + amplifier + (ambient ? ")" : ""); + } +} diff --git a/api/src/main/java/org/bukkit/potion/PotionEffectType.java b/api/src/main/java/org/bukkit/potion/PotionEffectType.java new file mode 100644 index 000000000..059c77d3c --- /dev/null +++ b/api/src/main/java/org/bukkit/potion/PotionEffectType.java @@ -0,0 +1,322 @@ +package org.bukkit.potion; + +import java.util.Arrays; +import java.util.HashMap; +import java.util.Map; + +import org.apache.commons.lang.Validate; +import org.bukkit.Color; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +/** + * Represents a type of potion and its effect on an entity. + */ +public abstract class PotionEffectType { + /** + * Increases movement speed. + */ + public static final PotionEffectType SPEED = new PotionEffectTypeWrapper(1); + + /** + * Decreases movement speed. + */ + public static final PotionEffectType SLOW = new PotionEffectTypeWrapper(2); + + /** + * Increases dig speed. + */ + public static final PotionEffectType FAST_DIGGING = new PotionEffectTypeWrapper(3); + + /** + * Decreases dig speed. + */ + public static final PotionEffectType SLOW_DIGGING = new PotionEffectTypeWrapper(4); + + /** + * Increases damage dealt. + */ + public static final PotionEffectType INCREASE_DAMAGE = new PotionEffectTypeWrapper(5); + + /** + * Heals an entity. + */ + public static final PotionEffectType HEAL = new PotionEffectTypeWrapper(6); + + /** + * Hurts an entity. + */ + public static final PotionEffectType HARM = new PotionEffectTypeWrapper(7); + + /** + * Increases jump height. + */ + public static final PotionEffectType JUMP = new PotionEffectTypeWrapper(8); + + /** + * Warps vision on the client. + */ + public static final PotionEffectType CONFUSION = new PotionEffectTypeWrapper(9); + + /** + * Regenerates health. + */ + public static final PotionEffectType REGENERATION = new PotionEffectTypeWrapper(10); + + /** + * Decreases damage dealt to an entity. + */ + public static final PotionEffectType DAMAGE_RESISTANCE = new PotionEffectTypeWrapper(11); + + /** + * Stops fire damage. + */ + public static final PotionEffectType FIRE_RESISTANCE = new PotionEffectTypeWrapper(12); + + /** + * Allows breathing underwater. + */ + public static final PotionEffectType WATER_BREATHING = new PotionEffectTypeWrapper(13); + + /** + * Grants invisibility. + */ + public static final PotionEffectType INVISIBILITY = new PotionEffectTypeWrapper(14); + + /** + * Blinds an entity. + */ + public static final PotionEffectType BLINDNESS = new PotionEffectTypeWrapper(15); + + /** + * Allows an entity to see in the dark. + */ + public static final PotionEffectType NIGHT_VISION = new PotionEffectTypeWrapper(16); + + /** + * Increases hunger. + */ + public static final PotionEffectType HUNGER = new PotionEffectTypeWrapper(17); + + /** + * Decreases damage dealt by an entity. + */ + public static final PotionEffectType WEAKNESS = new PotionEffectTypeWrapper(18); + + /** + * Deals damage to an entity over time. + */ + public static final PotionEffectType POISON = new PotionEffectTypeWrapper(19); + + /** + * Deals damage to an entity over time and gives the health to the + * shooter. + */ + public static final PotionEffectType WITHER = new PotionEffectTypeWrapper(20); + + /** + * Increases the maximum health of an entity. + */ + public static final PotionEffectType HEALTH_BOOST = new PotionEffectTypeWrapper(21); + + /** + * Increases the maximum health of an entity with health that cannot be + * regenerated, but is refilled every 30 seconds. + */ + public static final PotionEffectType ABSORPTION = new PotionEffectTypeWrapper(22); + + /** + * Increases the food level of an entity each tick. + */ + public static final PotionEffectType SATURATION = new PotionEffectTypeWrapper(23); + + /** + * Outlines the entity so that it can be seen from afar. + */ + public static final PotionEffectType GLOWING = new PotionEffectTypeWrapper(24); + + /** + * Causes the entity to float into the air. + */ + public static final PotionEffectType LEVITATION = new PotionEffectTypeWrapper(25); + + /** + * Loot table luck. + */ + public static final PotionEffectType LUCK = new PotionEffectTypeWrapper(26); + + /** + * Loot table unluck. + */ + public static final PotionEffectType UNLUCK = new PotionEffectTypeWrapper(27); + + /** + * Slows entity fall rate. + */ + public static final PotionEffectType SLOW_FALLING = new PotionEffectTypeWrapper(28); + + /** + * Effects granted by a nearby conduit. Includes enhanced underwater abilities. + */ + public static final PotionEffectType CONDUIT_POWER = new PotionEffectTypeWrapper(29); + + /** + * Squee'ek uh'k kk'kkkk squeek eee'eek. + */ + public static final PotionEffectType DOLPHINS_GRACE = new PotionEffectTypeWrapper(30); + + private final int id; + + protected PotionEffectType(int id) { + this.id = id; + } + + /** + * Creates a PotionEffect from this PotionEffectType, applying duration + * modifiers and checks. + * + * @see PotionBrewer#createEffect(PotionEffectType, int, int) + * @param duration time in ticks + * @param amplifier the effect's amplifier + * @return a resulting potion effect + */ + @NotNull + public PotionEffect createEffect(int duration, int amplifier) { + return new PotionEffect(this, isInstant() ? 1 : (int) (duration * getDurationModifier()), amplifier); + } + + /** + * Returns the duration modifier applied to effects of this type. + * + * @return duration modifier + */ + public abstract double getDurationModifier(); + + /** + * Returns the unique ID of this type. + * + * @return Unique ID + * @deprecated Magic value + */ + @Deprecated + public int getId() { + return id; + } + + /** + * Returns the name of this effect type. + * + * @return The name of this effect type + */ + @NotNull + public abstract String getName(); + + /** + * Returns whether the effect of this type happens once, immediately. + * + * @return whether this type is normally instant + */ + public abstract boolean isInstant(); + + /** + * Returns the color of this effect type. + * + * @return the color + */ + @NotNull + public abstract Color getColor(); + + @Override + public boolean equals(Object obj) { + if (obj == null) { + return false; + } + if (!(obj instanceof PotionEffectType)) { + return false; + } + final PotionEffectType other = (PotionEffectType) obj; + if (this.id != other.id) { + return false; + } + return true; + } + + @Override + public int hashCode() { + return id; + } + + @Override + public String toString() { + return "PotionEffectType[" + id + ", " + getName() + "]"; + } + + private static final PotionEffectType[] byId = new PotionEffectType[31]; + private static final Map byName = new HashMap(); + // will break on updates. + private static boolean acceptingNew = true; + + /** + * Gets the effect type specified by the unique id. + * + * @param id Unique ID to fetch + * @return Resulting type, or null if not found. + * @deprecated Magic value + */ + @Deprecated + @Nullable + public static PotionEffectType getById(int id) { + if (id >= byId.length || id < 0) + return null; + return byId[id]; + } + + /** + * Gets the effect type specified by the given name. + * + * @param name Name of PotionEffectType to fetch + * @return Resulting PotionEffectType, or null if not found. + */ + @Nullable + public static PotionEffectType getByName(@NotNull String name) { + Validate.notNull(name, "name cannot be null"); + return byName.get(name.toLowerCase(java.util.Locale.ENGLISH)); + } + + /** + * Registers an effect type with the given object. + *

+ * Generally not to be used from within a plugin. + * + * @param type PotionType to register + */ + public static void registerPotionEffectType(@NotNull PotionEffectType type) { + if (byId[type.id] != null || byName.containsKey(type.getName().toLowerCase(java.util.Locale.ENGLISH))) { + throw new IllegalArgumentException("Cannot set already-set type"); + } else if (!acceptingNew) { + throw new IllegalStateException( + "No longer accepting new potion effect types (can only be done by the server implementation)"); + } + + byId[type.id] = type; + byName.put(type.getName().toLowerCase(java.util.Locale.ENGLISH), type); + } + + /** + * Stops accepting any effect type registrations. + */ + public static void stopAcceptingRegistrations() { + acceptingNew = false; + } + + /** + * Returns an array of all the registered {@link PotionEffectType}s. + * This array is not necessarily in any particular order. + * + * @return Array of types. + */ + @NotNull + public static PotionEffectType[] values() { + return Arrays.copyOfRange(byId, 1, byId.length); + } +} diff --git a/api/src/main/java/org/bukkit/potion/PotionEffectTypeWrapper.java b/api/src/main/java/org/bukkit/potion/PotionEffectTypeWrapper.java new file mode 100644 index 000000000..47d46edc4 --- /dev/null +++ b/api/src/main/java/org/bukkit/potion/PotionEffectTypeWrapper.java @@ -0,0 +1,42 @@ +package org.bukkit.potion; + +import org.bukkit.Color; +import org.jetbrains.annotations.NotNull; + +public class PotionEffectTypeWrapper extends PotionEffectType { + protected PotionEffectTypeWrapper(int id) { + super(id); + } + + @Override + public double getDurationModifier() { + return getType().getDurationModifier(); + } + + @NotNull + @Override + public String getName() { + return getType().getName(); + } + + /** + * Get the potion type bound to this wrapper. + * + * @return The potion effect type + */ + @NotNull + public PotionEffectType getType() { + return PotionEffectType.getById(getId()); + } + + @Override + public boolean isInstant() { + return getType().isInstant(); + } + + @NotNull + @Override + public Color getColor() { + return getType().getColor(); + } +} diff --git a/api/src/main/java/org/bukkit/potion/PotionType.java b/api/src/main/java/org/bukkit/potion/PotionType.java new file mode 100644 index 000000000..e83e4b4fd --- /dev/null +++ b/api/src/main/java/org/bukkit/potion/PotionType.java @@ -0,0 +1,115 @@ +package org.bukkit.potion; + +import org.jetbrains.annotations.Contract; +import org.jetbrains.annotations.Nullable; + +/** + * This enum reflects and matches each potion state that can be obtained from + * the Creative mode inventory + */ +public enum PotionType { + UNCRAFTABLE(null, false, false), + WATER(null, false, false), + MUNDANE(null, false, false), + THICK(null, false, false), + AWKWARD(null, false, false), + NIGHT_VISION(PotionEffectType.NIGHT_VISION, false, true), + INVISIBILITY(PotionEffectType.INVISIBILITY, false, true), + JUMP(PotionEffectType.JUMP, true, true), + FIRE_RESISTANCE(PotionEffectType.FIRE_RESISTANCE, false, true), + SPEED(PotionEffectType.SPEED, true, true), + SLOWNESS(PotionEffectType.SLOW, true, true), + WATER_BREATHING(PotionEffectType.WATER_BREATHING, false, true), + INSTANT_HEAL(PotionEffectType.HEAL, true, false), + INSTANT_DAMAGE(PotionEffectType.HARM, true, false), + POISON(PotionEffectType.POISON, true, true), + REGEN(PotionEffectType.REGENERATION, true, true), + STRENGTH(PotionEffectType.INCREASE_DAMAGE, true, true), + WEAKNESS(PotionEffectType.WEAKNESS, false, true), + LUCK(PotionEffectType.LUCK, false, false), + TURTLE_MASTER(PotionEffectType.SLOW, true, true), // TODO: multiple effects + SLOW_FALLING(PotionEffectType.SLOW_FALLING, false, true), + ; + + private final PotionEffectType effect; + private final boolean upgradeable; + private final boolean extendable; + + PotionType(/*@Nullable*/ PotionEffectType effect, boolean upgradeable, boolean extendable) { + this.effect = effect; + this.upgradeable = upgradeable; + this.extendable = extendable; + } + + @Nullable + public PotionEffectType getEffectType() { + return effect; + } + + public boolean isInstant() { + return effect != null && effect.isInstant(); + } + + /** + * Checks if the potion type has an upgraded state. + * This refers to whether or not the potion type can be Tier 2, + * such as Potion of Fire Resistance II. + * + * @return true if the potion type can be upgraded; + */ + public boolean isUpgradeable() { + return upgradeable; + } + + /** + * Checks if the potion type has an extended state. + * This refers to the extended duration potions + * + * @return true if the potion type can be extended + */ + public boolean isExtendable() { + return extendable; + } + + /** + * @return Damage value associated with this PotionType, broken + * @deprecated Non-functional + */ + @Deprecated + public int getDamageValue() { + return this.ordinal(); + } + + public int getMaxLevel() { + return upgradeable ? 2 : 1; + } + + /** + * @param damage Damage value associated with a PotionType + * @return PotionType for given damage value, broken + * @deprecated Non-functional + */ + @Deprecated + @Nullable + @Contract("_ -> null") + public static PotionType getByDamageValue(int damage) { + return null; + } + + /** + * @param effectType EffectType + * @return Associated PotionType + * @deprecated Misleading + */ + @Deprecated + @Nullable + public static PotionType getByEffect(@Nullable PotionEffectType effectType) { + if (effectType == null) + return WATER; + for (PotionType type : PotionType.values()) { + if (effectType.equals(type.effect)) + return type; + } + return null; + } +} diff --git a/api/src/main/java/org/bukkit/projectiles/BlockProjectileSource.java b/api/src/main/java/org/bukkit/projectiles/BlockProjectileSource.java new file mode 100644 index 000000000..21a3d767b --- /dev/null +++ b/api/src/main/java/org/bukkit/projectiles/BlockProjectileSource.java @@ -0,0 +1,15 @@ +package org.bukkit.projectiles; + +import org.bukkit.block.Block; +import org.jetbrains.annotations.NotNull; + +public interface BlockProjectileSource extends ProjectileSource { + + /** + * Gets the block this projectile source belongs to. + * + * @return Block for the projectile source + */ + @NotNull + public Block getBlock(); +} diff --git a/api/src/main/java/org/bukkit/projectiles/ProjectileSource.java b/api/src/main/java/org/bukkit/projectiles/ProjectileSource.java new file mode 100644 index 000000000..eabd8b926 --- /dev/null +++ b/api/src/main/java/org/bukkit/projectiles/ProjectileSource.java @@ -0,0 +1,34 @@ +package org.bukkit.projectiles; + +import org.bukkit.entity.Projectile; +import org.bukkit.util.Vector; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +/** + * Represents a valid source of a projectile. + */ +public interface ProjectileSource { + + /** + * Launches a {@link Projectile} from the ProjectileSource. + * + * @param a projectile subclass + * @param projectile class of the projectile to launch + * @return the launched projectile + */ + @NotNull + public T launchProjectile(@NotNull Class projectile); + + /** + * Launches a {@link Projectile} from the ProjectileSource with an + * initial velocity. + * + * @param a projectile subclass + * @param projectile class of the projectile to launch + * @param velocity the velocity with which to launch + * @return the launched projectile + */ + @NotNull + public T launchProjectile(@NotNull Class projectile, @Nullable Vector velocity); +} diff --git a/api/src/main/java/org/bukkit/scheduler/BukkitRunnable.java b/api/src/main/java/org/bukkit/scheduler/BukkitRunnable.java new file mode 100644 index 000000000..35a1e92fc --- /dev/null +++ b/api/src/main/java/org/bukkit/scheduler/BukkitRunnable.java @@ -0,0 +1,171 @@ +package org.bukkit.scheduler; + +import org.bukkit.Bukkit; +import org.bukkit.plugin.Plugin; +import org.jetbrains.annotations.NotNull; + +/** + * This class is provided as an easy way to handle scheduling tasks. + */ +public abstract class BukkitRunnable implements Runnable { + private BukkitTask task; + + /** + * Returns true if this task has been cancelled. + * + * @return true if the task has been cancelled + * @throws IllegalStateException if task was not scheduled yet + */ + public synchronized boolean isCancelled() throws IllegalStateException { + checkScheduled(); + return task.isCancelled(); + } + + /** + * Attempts to cancel this task. + * + * @throws IllegalStateException if task was not scheduled yet + */ + public synchronized void cancel() throws IllegalStateException { + Bukkit.getScheduler().cancelTask(getTaskId()); + } + + /** + * Schedules this in the Bukkit scheduler to run on next tick. + * + * @param plugin the reference to the plugin scheduling task + * @return a BukkitTask that contains the id number + * @throws IllegalArgumentException if plugin is null + * @throws IllegalStateException if this was already scheduled + * @see BukkitScheduler#runTask(Plugin, Runnable) + */ + @NotNull + public synchronized BukkitTask runTask(@NotNull Plugin plugin) throws IllegalArgumentException, IllegalStateException { + checkNotYetScheduled(); + return setupTask(Bukkit.getScheduler().runTask(plugin, (Runnable) this)); + } + + /** + * Asynchronous tasks should never access any API in Bukkit. Great care + * should be taken to assure the thread-safety of asynchronous tasks. + *

+ * Schedules this in the Bukkit scheduler to run asynchronously. + * + * @param plugin the reference to the plugin scheduling task + * @return a BukkitTask that contains the id number + * @throws IllegalArgumentException if plugin is null + * @throws IllegalStateException if this was already scheduled + * @see BukkitScheduler#runTaskAsynchronously(Plugin, Runnable) + */ + @NotNull + public synchronized BukkitTask runTaskAsynchronously(@NotNull Plugin plugin) throws IllegalArgumentException, IllegalStateException { + checkNotYetScheduled(); + return setupTask(Bukkit.getScheduler().runTaskAsynchronously(plugin, (Runnable) this)); + } + + /** + * Schedules this to run after the specified number of server ticks. + * + * @param plugin the reference to the plugin scheduling task + * @param delay the ticks to wait before running the task + * @return a BukkitTask that contains the id number + * @throws IllegalArgumentException if plugin is null + * @throws IllegalStateException if this was already scheduled + * @see BukkitScheduler#runTaskLater(Plugin, Runnable, long) + */ + @NotNull + public synchronized BukkitTask runTaskLater(@NotNull Plugin plugin, long delay) throws IllegalArgumentException, IllegalStateException { + checkNotYetScheduled(); + return setupTask(Bukkit.getScheduler().runTaskLater(plugin, (Runnable) this, delay)); + } + + /** + * Asynchronous tasks should never access any API in Bukkit. Great care + * should be taken to assure the thread-safety of asynchronous tasks. + *

+ * Schedules this to run asynchronously after the specified number of + * server ticks. + * + * @param plugin the reference to the plugin scheduling task + * @param delay the ticks to wait before running the task + * @return a BukkitTask that contains the id number + * @throws IllegalArgumentException if plugin is null + * @throws IllegalStateException if this was already scheduled + * @see BukkitScheduler#runTaskLaterAsynchronously(Plugin, Runnable, long) + */ + @NotNull + public synchronized BukkitTask runTaskLaterAsynchronously(@NotNull Plugin plugin, long delay) throws IllegalArgumentException, IllegalStateException { + checkNotYetScheduled(); + return setupTask(Bukkit.getScheduler().runTaskLaterAsynchronously(plugin, (Runnable) this, delay)); + } + + /** + * Schedules this to repeatedly run until cancelled, starting after the + * specified number of server ticks. + * + * @param plugin the reference to the plugin scheduling task + * @param delay the ticks to wait before running the task + * @param period the ticks to wait between runs + * @return a BukkitTask that contains the id number + * @throws IllegalArgumentException if plugin is null + * @throws IllegalStateException if this was already scheduled + * @see BukkitScheduler#runTaskTimer(Plugin, Runnable, long, long) + */ + @NotNull + public synchronized BukkitTask runTaskTimer(@NotNull Plugin plugin, long delay, long period) throws IllegalArgumentException, IllegalStateException { + checkNotYetScheduled(); + return setupTask(Bukkit.getScheduler().runTaskTimer(plugin, (Runnable) this, delay, period)); + } + + /** + * Asynchronous tasks should never access any API in Bukkit. Great care + * should be taken to assure the thread-safety of asynchronous tasks. + *

+ * Schedules this to repeatedly run asynchronously until cancelled, + * starting after the specified number of server ticks. + * + * @param plugin the reference to the plugin scheduling task + * @param delay the ticks to wait before running the task for the first + * time + * @param period the ticks to wait between runs + * @return a BukkitTask that contains the id number + * @throws IllegalArgumentException if plugin is null + * @throws IllegalStateException if this was already scheduled + * @see BukkitScheduler#runTaskTimerAsynchronously(Plugin, Runnable, long, + * long) + */ + @NotNull + public synchronized BukkitTask runTaskTimerAsynchronously(@NotNull Plugin plugin, long delay, long period) throws IllegalArgumentException, IllegalStateException { + checkNotYetScheduled(); + return setupTask(Bukkit.getScheduler().runTaskTimerAsynchronously(plugin, (Runnable) this, delay, period)); + } + + /** + * Gets the task id for this runnable. + * + * @return the task id that this runnable was scheduled as + * @throws IllegalStateException if task was not scheduled yet + */ + public synchronized int getTaskId() throws IllegalStateException { + checkScheduled(); + return task.getTaskId(); + } + + private void checkScheduled() { + if (task == null) { + throw new IllegalStateException("Not scheduled yet"); + } + } + + private void checkNotYetScheduled() { + if (task != null) { + throw new IllegalStateException("Already scheduled as " + task.getTaskId()); + } + } + + @NotNull + private BukkitTask setupTask(@NotNull final BukkitTask task) { + this.task = task; + return task; + } +} diff --git a/api/src/main/java/org/bukkit/scheduler/BukkitScheduler.java b/api/src/main/java/org/bukkit/scheduler/BukkitScheduler.java new file mode 100644 index 000000000..acbd23687 --- /dev/null +++ b/api/src/main/java/org/bukkit/scheduler/BukkitScheduler.java @@ -0,0 +1,462 @@ +package org.bukkit.scheduler; + +import org.bukkit.plugin.Plugin; +import org.jetbrains.annotations.NotNull; + +import java.util.concurrent.Callable; +import java.util.concurrent.Future; +import java.util.List; +import java.util.function.Consumer; + +public interface BukkitScheduler { + + /** + * Schedules a once off task to occur after a delay. + *

+ * This task will be executed by the main server thread. + * + * @param plugin Plugin that owns the task + * @param task Task to be executed + * @param delay Delay in server ticks before executing task + * @return Task id number (-1 if scheduling failed) + */ + public int scheduleSyncDelayedTask(@NotNull Plugin plugin, @NotNull Runnable task, long delay); + + /** + * @deprecated Use {@link BukkitRunnable#runTaskLater(Plugin, long)} + * @param plugin Plugin that owns the task + * @param task Task to be executed + * @param delay Delay in server ticks before executing task + * @return Task id number (-1 if scheduling failed) + */ + @Deprecated + public int scheduleSyncDelayedTask(@NotNull Plugin plugin, @NotNull BukkitRunnable task, long delay); + + /** + * Schedules a once off task to occur as soon as possible. + *

+ * This task will be executed by the main server thread. + * + * @param plugin Plugin that owns the task + * @param task Task to be executed + * @return Task id number (-1 if scheduling failed) + */ + public int scheduleSyncDelayedTask(@NotNull Plugin plugin, @NotNull Runnable task); + + /** + * @deprecated Use {@link BukkitRunnable#runTask(Plugin)} + * @param plugin Plugin that owns the task + * @param task Task to be executed + * @return Task id number (-1 if scheduling failed) + */ + @Deprecated + public int scheduleSyncDelayedTask(@NotNull Plugin plugin, @NotNull BukkitRunnable task); + + /** + * Schedules a repeating task. + *

+ * This task will be executed by the main server thread. + * + * @param plugin Plugin that owns the task + * @param task Task to be executed + * @param delay Delay in server ticks before executing first repeat + * @param period Period in server ticks of the task + * @return Task id number (-1 if scheduling failed) + */ + public int scheduleSyncRepeatingTask(@NotNull Plugin plugin, @NotNull Runnable task, long delay, long period); + + /** + * @deprecated Use {@link BukkitRunnable#runTaskTimer(Plugin, long, long)} + * @param plugin Plugin that owns the task + * @param task Task to be executed + * @param delay Delay in server ticks before executing first repeat + * @param period Period in server ticks of the task + * @return Task id number (-1 if scheduling failed) + */ + @Deprecated + public int scheduleSyncRepeatingTask(@NotNull Plugin plugin, @NotNull BukkitRunnable task, long delay, long period); + + /** + * Asynchronous tasks should never access any API in Bukkit. Great care + * should be taken to assure the thread-safety of asynchronous tasks. + *

+ * Schedules a once off task to occur after a delay. This task will be + * executed by a thread managed by the scheduler. + * + * @param plugin Plugin that owns the task + * @param task Task to be executed + * @param delay Delay in server ticks before executing task + * @return Task id number (-1 if scheduling failed) + * @deprecated This name is misleading, as it does not schedule "a sync" + * task, but rather, "an async" task + */ + @Deprecated + public int scheduleAsyncDelayedTask(@NotNull Plugin plugin, @NotNull Runnable task, long delay); + + /** + * Asynchronous tasks should never access any API in Bukkit. Great care + * should be taken to assure the thread-safety of asynchronous tasks. + *

+ * Schedules a once off task to occur as soon as possible. This task will + * be executed by a thread managed by the scheduler. + * + * @param plugin Plugin that owns the task + * @param task Task to be executed + * @return Task id number (-1 if scheduling failed) + * @deprecated This name is misleading, as it does not schedule "a sync" + * task, but rather, "an async" task + */ + @Deprecated + public int scheduleAsyncDelayedTask(@NotNull Plugin plugin, @NotNull Runnable task); + + /** + * Asynchronous tasks should never access any API in Bukkit. Great care + * should be taken to assure the thread-safety of asynchronous tasks. + *

+ * Schedules a repeating task. This task will be executed by a thread + * managed by the scheduler. + * + * @param plugin Plugin that owns the task + * @param task Task to be executed + * @param delay Delay in server ticks before executing first repeat + * @param period Period in server ticks of the task + * @return Task id number (-1 if scheduling failed) + * @deprecated This name is misleading, as it does not schedule "a sync" + * task, but rather, "an async" task + */ + @Deprecated + public int scheduleAsyncRepeatingTask(@NotNull Plugin plugin, @NotNull Runnable task, long delay, long period); + + /** + * Calls a method on the main thread and returns a Future object. This + * task will be executed by the main server thread. + *

    + *
  • Note: The Future.get() methods must NOT be called from the main + * thread. + *
  • Note2: There is at least an average of 10ms latency until the + * isDone() method returns true. + *
+ * @param The callable's return type + * @param plugin Plugin that owns the task + * @param task Task to be executed + * @return Future Future object related to the task + */ + @NotNull + public Future callSyncMethod(@NotNull Plugin plugin, @NotNull Callable task); + + /** + * Removes task from scheduler. + * + * @param taskId Id number of task to be removed + */ + public void cancelTask(int taskId); + + /** + * Removes all tasks associated with a particular plugin from the + * scheduler. + * + * @param plugin Owner of tasks to be removed + */ + public void cancelTasks(@NotNull Plugin plugin); + + /** + * Check if the task currently running. + *

+ * A repeating task might not be running currently, but will be running in + * the future. A task that has finished, and does not repeat, will not be + * running ever again. + *

+ * Explicitly, a task is running if there exists a thread for it, and that + * thread is alive. + * + * @param taskId The task to check. + *

+ * @return If the task is currently running. + */ + public boolean isCurrentlyRunning(int taskId); + + /** + * Check if the task queued to be run later. + *

+ * If a repeating task is currently running, it might not be queued now + * but could be in the future. A task that is not queued, and not running, + * will not be queued again. + * + * @param taskId The task to check. + *

+ * @return If the task is queued to be run. + */ + public boolean isQueued(int taskId); + + /** + * Returns a list of all active workers. + *

+ * This list contains asynch tasks that are being executed by separate + * threads. + * + * @return Active workers + */ + @NotNull + public List getActiveWorkers(); + + /** + * Returns a list of all pending tasks. The ordering of the tasks is not + * related to their order of execution. + * + * @return Active workers + */ + @NotNull + public List getPendingTasks(); + + /** + * Returns a task that will run on the next server tick. + * + * @param plugin the reference to the plugin scheduling task + * @param task the task to be run + * @return a BukkitTask that contains the id number + * @throws IllegalArgumentException if plugin is null + * @throws IllegalArgumentException if task is null + */ + @NotNull + public BukkitTask runTask(@NotNull Plugin plugin, @NotNull Runnable task) throws IllegalArgumentException; + + /** + * Returns a task that will run on the next server tick. + * + * @param plugin the reference to the plugin scheduling task + * @param task the task to be run + * @throws IllegalArgumentException if plugin is null + * @throws IllegalArgumentException if task is null + */ + public void runTask(@NotNull Plugin plugin, @NotNull Consumer task) throws IllegalArgumentException; + + /** + * @deprecated Use {@link BukkitRunnable#runTask(Plugin)} + * + * @param plugin the reference to the plugin scheduling task + * @param task the task to be run + * @return a BukkitTask that contains the id number + * @throws IllegalArgumentException if plugin is null + * @throws IllegalArgumentException if task is null + */ + @Deprecated + @NotNull + public BukkitTask runTask(@NotNull Plugin plugin, @NotNull BukkitRunnable task) throws IllegalArgumentException; + + /** + * Asynchronous tasks should never access any API in Bukkit. Great care + * should be taken to assure the thread-safety of asynchronous tasks. + *

+ * Returns a task that will run asynchronously. + * + * @param plugin the reference to the plugin scheduling task + * @param task the task to be run + * @return a BukkitTask that contains the id number + * @throws IllegalArgumentException if plugin is null + * @throws IllegalArgumentException if task is null + */ + @NotNull + public BukkitTask runTaskAsynchronously(@NotNull Plugin plugin, @NotNull Runnable task) throws IllegalArgumentException; + + /** + * Asynchronous tasks should never access any API in Bukkit. Great care + * should be taken to assure the thread-safety of asynchronous tasks. + *

+ * Returns a task that will run asynchronously. + * + * @param plugin the reference to the plugin scheduling task + * @param task the task to be run + * @throws IllegalArgumentException if plugin is null + * @throws IllegalArgumentException if task is null + */ + public void runTaskAsynchronously(@NotNull Plugin plugin, @NotNull Consumer task) throws IllegalArgumentException; + + /** + * @deprecated Use {@link BukkitRunnable#runTaskAsynchronously(Plugin)} + * @param plugin the reference to the plugin scheduling task + * @param task the task to be run + * @return a BukkitTask that contains the id number + * @throws IllegalArgumentException if plugin is null + * @throws IllegalArgumentException if task is null + */ + @Deprecated + @NotNull + public BukkitTask runTaskAsynchronously(@NotNull Plugin plugin, @NotNull BukkitRunnable task) throws IllegalArgumentException; + + /** + * Returns a task that will run after the specified number of server + * ticks. + * + * @param plugin the reference to the plugin scheduling task + * @param task the task to be run + * @param delay the ticks to wait before running the task + * @return a BukkitTask that contains the id number + * @throws IllegalArgumentException if plugin is null + * @throws IllegalArgumentException if task is null + */ + @NotNull + public BukkitTask runTaskLater(@NotNull Plugin plugin, @NotNull Runnable task, long delay) throws IllegalArgumentException; + + /** + * Returns a task that will run after the specified number of server + * ticks. + * + * @param plugin the reference to the plugin scheduling task + * @param task the task to be run + * @param delay the ticks to wait before running the task + * @throws IllegalArgumentException if plugin is null + * @throws IllegalArgumentException if task is null + */ + public void runTaskLater(@NotNull Plugin plugin, @NotNull Consumer task, long delay) throws IllegalArgumentException; + + /** + * @deprecated Use {@link BukkitRunnable#runTaskLater(Plugin, long)} + * @param plugin the reference to the plugin scheduling task + * @param task the task to be run + * @param delay the ticks to wait before running the task + * @return a BukkitTask that contains the id number + * @throws IllegalArgumentException if plugin is null + * @throws IllegalArgumentException if task is null + */ + @Deprecated + @NotNull + public BukkitTask runTaskLater(@NotNull Plugin plugin, @NotNull BukkitRunnable task, long delay) throws IllegalArgumentException; + + /** + * Asynchronous tasks should never access any API in Bukkit. Great care + * should be taken to assure the thread-safety of asynchronous tasks. + *

+ * Returns a task that will run asynchronously after the specified number + * of server ticks. + * + * @param plugin the reference to the plugin scheduling task + * @param task the task to be run + * @param delay the ticks to wait before running the task + * @return a BukkitTask that contains the id number + * @throws IllegalArgumentException if plugin is null + * @throws IllegalArgumentException if task is null + */ + @NotNull + public BukkitTask runTaskLaterAsynchronously(@NotNull Plugin plugin, @NotNull Runnable task, long delay) throws IllegalArgumentException; + + /** + * Asynchronous tasks should never access any API in Bukkit. Great care + * should be taken to assure the thread-safety of asynchronous tasks. + *

+ * Returns a task that will run asynchronously after the specified number + * of server ticks. + * + * @param plugin the reference to the plugin scheduling task + * @param task the task to be run + * @param delay the ticks to wait before running the task + * @throws IllegalArgumentException if plugin is null + * @throws IllegalArgumentException if task is null + */ + public void runTaskLaterAsynchronously(@NotNull Plugin plugin, @NotNull Consumer task, long delay) throws IllegalArgumentException; + + /** + * @deprecated Use {@link BukkitRunnable#runTaskLaterAsynchronously(Plugin, long)} + * @param plugin the reference to the plugin scheduling task + * @param task the task to be run + * @param delay the ticks to wait before running the task + * @return a BukkitTask that contains the id number + * @throws IllegalArgumentException if plugin is null + * @throws IllegalArgumentException if task is null + */ + @Deprecated + @NotNull + public BukkitTask runTaskLaterAsynchronously(@NotNull Plugin plugin, @NotNull BukkitRunnable task, long delay) throws IllegalArgumentException; + + /** + * Returns a task that will repeatedly run until cancelled, starting after + * the specified number of server ticks. + * + * @param plugin the reference to the plugin scheduling task + * @param task the task to be run + * @param delay the ticks to wait before running the task + * @param period the ticks to wait between runs + * @return a BukkitTask that contains the id number + * @throws IllegalArgumentException if plugin is null + * @throws IllegalArgumentException if task is null + */ + @NotNull + public BukkitTask runTaskTimer(@NotNull Plugin plugin, @NotNull Runnable task, long delay, long period) throws IllegalArgumentException; + + /** + * Returns a task that will repeatedly run until cancelled, starting after + * the specified number of server ticks. + * + * @param plugin the reference to the plugin scheduling task + * @param task the task to be run + * @param delay the ticks to wait before running the task + * @param period the ticks to wait between runs + * @throws IllegalArgumentException if plugin is null + * @throws IllegalArgumentException if task is null + */ + public void runTaskTimer(@NotNull Plugin plugin, @NotNull Consumer task, long delay, long period) throws IllegalArgumentException; + + /** + * @deprecated Use {@link BukkitRunnable#runTaskTimer(Plugin, long, long)} + * @param plugin the reference to the plugin scheduling task + * @param task the task to be run + * @param delay the ticks to wait before running the task + * @param period the ticks to wait between runs + * @return a BukkitTask that contains the id number + * @throws IllegalArgumentException if plugin is null + * @throws IllegalArgumentException if task is null + */ + @Deprecated + @NotNull + public BukkitTask runTaskTimer(@NotNull Plugin plugin, @NotNull BukkitRunnable task, long delay, long period) throws IllegalArgumentException; + + /** + * Asynchronous tasks should never access any API in Bukkit. Great care + * should be taken to assure the thread-safety of asynchronous tasks. + *

+ * Returns a task that will repeatedly run asynchronously until cancelled, + * starting after the specified number of server ticks. + * + * @param plugin the reference to the plugin scheduling task + * @param task the task to be run + * @param delay the ticks to wait before running the task for the first + * time + * @param period the ticks to wait between runs + * @return a BukkitTask that contains the id number + * @throws IllegalArgumentException if plugin is null + * @throws IllegalArgumentException if task is null + */ + @NotNull + public BukkitTask runTaskTimerAsynchronously(@NotNull Plugin plugin, @NotNull Runnable task, long delay, long period) throws IllegalArgumentException; + + /** + * Asynchronous tasks should never access any API in Bukkit. Great care + * should be taken to assure the thread-safety of asynchronous tasks. + *

+ * Returns a task that will repeatedly run asynchronously until cancelled, + * starting after the specified number of server ticks. + * + * @param plugin the reference to the plugin scheduling task + * @param task the task to be run + * @param delay the ticks to wait before running the task for the first + * time + * @param period the ticks to wait between runs + * @throws IllegalArgumentException if plugin is null + * @throws IllegalArgumentException if task is null + */ + public void runTaskTimerAsynchronously(@NotNull Plugin plugin, @NotNull Consumer task, long delay, long period) throws IllegalArgumentException; + + /** + * @deprecated Use {@link BukkitRunnable#runTaskTimerAsynchronously(Plugin, long, long)} + * @param plugin the reference to the plugin scheduling task + * @param task the task to be run + * @param delay the ticks to wait before running the task for the first + * time + * @param period the ticks to wait between runs + * @return a BukkitTask that contains the id number + * @throws IllegalArgumentException if plugin is null + * @throws IllegalArgumentException if task is null + */ + @Deprecated + @NotNull + public BukkitTask runTaskTimerAsynchronously(@NotNull Plugin plugin, @NotNull BukkitRunnable task, long delay, long period) throws IllegalArgumentException; +} diff --git a/api/src/main/java/org/bukkit/scheduler/BukkitTask.java b/api/src/main/java/org/bukkit/scheduler/BukkitTask.java new file mode 100644 index 000000000..512515849 --- /dev/null +++ b/api/src/main/java/org/bukkit/scheduler/BukkitTask.java @@ -0,0 +1,44 @@ +package org.bukkit.scheduler; + +import org.bukkit.plugin.Plugin; +import org.jetbrains.annotations.NotNull; + +/** + * Represents a task being executed by the scheduler + */ +public interface BukkitTask { + + /** + * Returns the taskId for the task. + * + * @return Task id number + */ + public int getTaskId(); + + /** + * Returns the Plugin that owns this task. + * + * @return The Plugin that owns the task + */ + @NotNull + public Plugin getOwner(); + + /** + * Returns true if the Task is a sync task. + * + * @return true if the task is run by main thread + */ + public boolean isSync(); + + /** + * Returns true if this task has been cancelled. + * + * @return true if the task has been cancelled + */ + public boolean isCancelled(); + + /** + * Will attempt to cancel this task. + */ + public void cancel(); +} diff --git a/api/src/main/java/org/bukkit/scheduler/BukkitWorker.java b/api/src/main/java/org/bukkit/scheduler/BukkitWorker.java new file mode 100644 index 000000000..81bdc9cea --- /dev/null +++ b/api/src/main/java/org/bukkit/scheduler/BukkitWorker.java @@ -0,0 +1,37 @@ +package org.bukkit.scheduler; + +import org.bukkit.plugin.Plugin; +import org.jetbrains.annotations.NotNull; + +/** + * Represents a worker thread for the scheduler. This gives information about + * the Thread object for the task, owner of the task and the taskId. + *

+ * Workers are used to execute async tasks. + */ +public interface BukkitWorker { + + /** + * Returns the taskId for the task being executed by this worker. + * + * @return Task id number + */ + public int getTaskId(); + + /** + * Returns the Plugin that owns this task. + * + * @return The Plugin that owns the task + */ + @NotNull + public Plugin getOwner(); + + /** + * Returns the thread for the worker. + * + * @return The Thread object for the worker + */ + @NotNull + public Thread getThread(); + +} diff --git a/api/src/main/java/org/bukkit/scoreboard/Criterias.java b/api/src/main/java/org/bukkit/scoreboard/Criterias.java new file mode 100644 index 000000000..cd81c87f8 --- /dev/null +++ b/api/src/main/java/org/bukkit/scoreboard/Criterias.java @@ -0,0 +1,20 @@ +package org.bukkit.scoreboard; + +/** + * Criteria names which trigger an objective to be modified by actions in-game + */ +public class Criterias { + public static final String HEALTH; + public static final String PLAYER_KILLS; + public static final String TOTAL_KILLS; + public static final String DEATHS; + + static { + HEALTH="health"; + PLAYER_KILLS="playerKillCount"; + TOTAL_KILLS="totalKillCount"; + DEATHS="deathCount"; + } + + private Criterias() {} +} diff --git a/api/src/main/java/org/bukkit/scoreboard/DisplaySlot.java b/api/src/main/java/org/bukkit/scoreboard/DisplaySlot.java new file mode 100644 index 000000000..5d58a18b3 --- /dev/null +++ b/api/src/main/java/org/bukkit/scoreboard/DisplaySlot.java @@ -0,0 +1,10 @@ +package org.bukkit.scoreboard; + +/** + * Locations for displaying objectives to the player + */ +public enum DisplaySlot { + BELOW_NAME, + PLAYER_LIST, + SIDEBAR; +} diff --git a/api/src/main/java/org/bukkit/scoreboard/NameTagVisibility.java b/api/src/main/java/org/bukkit/scoreboard/NameTagVisibility.java new file mode 100644 index 000000000..d9e9ae1a9 --- /dev/null +++ b/api/src/main/java/org/bukkit/scoreboard/NameTagVisibility.java @@ -0,0 +1,25 @@ +package org.bukkit.scoreboard; + +/** + * @deprecated replaced by {@link Team.OptionStatus} + */ +@Deprecated +public enum NameTagVisibility { + + /** + * Always show the player's nametag. + */ + ALWAYS, + /** + * Never show the player's nametag. + */ + NEVER, + /** + * Show the player's nametag only to his own team members. + */ + HIDE_FOR_OTHER_TEAMS, + /** + * Show the player's nametag only to members of other teams. + */ + HIDE_FOR_OWN_TEAM; +} diff --git a/api/src/main/java/org/bukkit/scoreboard/Objective.java b/api/src/main/java/org/bukkit/scoreboard/Objective.java new file mode 100644 index 000000000..f5cbf6df3 --- /dev/null +++ b/api/src/main/java/org/bukkit/scoreboard/Objective.java @@ -0,0 +1,137 @@ +package org.bukkit.scoreboard; + +import org.bukkit.OfflinePlayer; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +/** + * An objective on a scoreboard that can show scores specific to entries. This + * objective is only relevant to the display of the associated {@link + * #getScoreboard() scoreboard}. + */ +public interface Objective { + + /** + * Gets the name of this Objective + * + * @return this objective's name + * @throws IllegalStateException if this objective has been unregistered + */ + @NotNull + String getName() throws IllegalStateException; + + /** + * Gets the name displayed to players for this objective + * + * @return this objective's display name + * @throws IllegalStateException if this objective has been unregistered + */ + @NotNull + String getDisplayName() throws IllegalStateException; + + /** + * Sets the name displayed to players for this objective. + * + * @param displayName Display name to set + * @throws IllegalStateException if this objective has been unregistered + * @throws IllegalArgumentException if displayName is null + * @throws IllegalArgumentException if displayName is longer than 128 + * characters. + */ + void setDisplayName(@NotNull String displayName) throws IllegalStateException, IllegalArgumentException; + + /** + * Gets the criteria this objective tracks. + * + * @return this objective's criteria + * @throws IllegalStateException if this objective has been unregistered + */ + @NotNull + String getCriteria() throws IllegalStateException; + + /** + * Gets if the objective's scores can be modified directly by a plugin. + * + * @return true if scores are modifiable + * @throws IllegalStateException if this objective has been unregistered + * @see Criterias#HEALTH + */ + boolean isModifiable() throws IllegalStateException; + + /** + * Gets the scoreboard to which this objective is attached. + * + * @return Owning scoreboard, or null if it has been {@link #unregister() + * unregistered} + */ + @Nullable + Scoreboard getScoreboard(); + + /** + * Unregisters this objective from the {@link Scoreboard scoreboard.} + * + * @throws IllegalStateException if this objective has been unregistered + */ + void unregister() throws IllegalStateException; + + /** + * Sets this objective to display on the specified slot for the + * scoreboard, removing it from any other display slot. + * + * @param slot display slot to change, or null to not display + * @throws IllegalStateException if this objective has been unregistered + */ + void setDisplaySlot(@Nullable DisplaySlot slot) throws IllegalStateException; + + /** + * Gets the display slot this objective is displayed at. + * + * @return the display slot for this objective, or null if not displayed + * @throws IllegalStateException if this objective has been unregistered + */ + @Nullable + DisplaySlot getDisplaySlot() throws IllegalStateException; + + /** + * Sets manner in which this objective will be rendered. + * + * @param renderType new render type + * @throws IllegalStateException if this objective has been unregistered + */ + void setRenderType(@NotNull RenderType renderType) throws IllegalStateException; + + /** + * Sets manner in which this objective will be rendered. + * + * @return the render type + * @throws IllegalStateException if this objective has been unregistered + */ + @NotNull + RenderType getRenderType() throws IllegalStateException; + + /** + * Gets a player's Score for an Objective on this Scoreboard + * + * @param player Player for the Score + * @return Score tracking the Objective and player specified + * @throws IllegalArgumentException if player is null + * @throws IllegalStateException if this objective has been unregistered + * @deprecated Scoreboards can contain entries that aren't players + * @see #getScore(String) + */ + @Deprecated + @NotNull + Score getScore(@NotNull OfflinePlayer player) throws IllegalArgumentException, IllegalStateException; + + /** + * Gets an entry's Score for an Objective on this Scoreboard. + * + * @param entry Entry for the Score + * @return Score tracking the Objective and entry specified + * @throws IllegalArgumentException if entry is null + * @throws IllegalStateException if this objective has been unregistered + * @throws IllegalArgumentException if entry is longer than 40 characters. + */ + @NotNull + Score getScore(@NotNull String entry) throws IllegalArgumentException, IllegalStateException; +} diff --git a/api/src/main/java/org/bukkit/scoreboard/RenderType.java b/api/src/main/java/org/bukkit/scoreboard/RenderType.java new file mode 100644 index 000000000..f169f8726 --- /dev/null +++ b/api/src/main/java/org/bukkit/scoreboard/RenderType.java @@ -0,0 +1,16 @@ +package org.bukkit.scoreboard; + +/** + * Controls the way in which an {@link Objective} is rendered client side. + */ +public enum RenderType { + + /** + * Display integer value. + */ + INTEGER, + /** + * Display number of hearts corresponding to value. + */ + HEARTS; +} diff --git a/api/src/main/java/org/bukkit/scoreboard/Score.java b/api/src/main/java/org/bukkit/scoreboard/Score.java new file mode 100644 index 000000000..f4e626f92 --- /dev/null +++ b/api/src/main/java/org/bukkit/scoreboard/Score.java @@ -0,0 +1,76 @@ +package org.bukkit.scoreboard; + +import org.bukkit.OfflinePlayer; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +/** + * A score entry for an {@link #getEntry() entry} on an {@link + * #getObjective() objective}. Changing this will not affect any other + * objective or scoreboard. + */ +public interface Score { + + /** + * Gets the OfflinePlayer being tracked by this Score + * + * @return this Score's tracked player + * @deprecated Scoreboards can contain entries that aren't players + * @see #getEntry() + */ + @Deprecated + @NotNull + OfflinePlayer getPlayer(); + + /** + * Gets the entry being tracked by this Score + * + * @return this Score's tracked entry + */ + @NotNull + String getEntry(); + + /** + * Gets the Objective being tracked by this Score + * + * @return this Score's tracked objective + */ + @NotNull + Objective getObjective(); + + /** + * Gets the current score + * + * @return the current score + * @throws IllegalStateException if the associated objective has been + * unregistered + */ + int getScore() throws IllegalStateException; + + /** + * Sets the current score. + * + * @param score New score + * @throws IllegalStateException if the associated objective has been + * unregistered + */ + void setScore(int score) throws IllegalStateException; + + /** + * Shows if this score has been set at any point in time. + * + * @return if this score has been set before + * @throws IllegalStateException if the associated objective has been + * unregistered + */ + boolean isScoreSet() throws IllegalStateException; + + /** + * Gets the scoreboard for the associated objective. + * + * @return the owning objective's scoreboard, or null if it has been + * {@link Objective#unregister() unregistered} + */ + @Nullable + Scoreboard getScoreboard(); +} diff --git a/api/src/main/java/org/bukkit/scoreboard/Scoreboard.java b/api/src/main/java/org/bukkit/scoreboard/Scoreboard.java new file mode 100644 index 000000000..fa35d7d59 --- /dev/null +++ b/api/src/main/java/org/bukkit/scoreboard/Scoreboard.java @@ -0,0 +1,233 @@ +package org.bukkit.scoreboard; + +import java.util.Set; + +import org.bukkit.OfflinePlayer; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +/** + * A scoreboard + */ +public interface Scoreboard { + + /** + * Registers an Objective on this Scoreboard + * + * @param name Name of the Objective + * @param criteria Criteria for the Objective + * @return The registered Objective + * @throws IllegalArgumentException if name is null + * @throws IllegalArgumentException if name is longer than 16 + * characters. + * @throws IllegalArgumentException if criteria is null + * @throws IllegalArgumentException if an objective by that name already + * exists + * @deprecated a displayName should be explicitly specified + */ + @Deprecated + @NotNull + Objective registerNewObjective(@NotNull String name, @NotNull String criteria) throws IllegalArgumentException; + + /** + * Registers an Objective on this Scoreboard + * + * @param name Name of the Objective + * @param criteria Criteria for the Objective + * @param displayName Name displayed to players for the Objective. + * @return The registered Objective + * @throws IllegalArgumentException if name is null + * @throws IllegalArgumentException if name is longer than 16 + * characters. + * @throws IllegalArgumentException if criteria is null + * @throws IllegalArgumentException if displayName is null + * @throws IllegalArgumentException if displayName is longer than 128 + * characters. + * @throws IllegalArgumentException if an objective by that name already + * exists + */ + @NotNull + Objective registerNewObjective(@NotNull String name, @NotNull String criteria, @NotNull String displayName) throws IllegalArgumentException; + + /** + * Registers an Objective on this Scoreboard + * + * @param name Name of the Objective + * @param criteria Criteria for the Objective + * @param displayName Name displayed to players for the Objective. + * @param renderType Manner of rendering the Objective + * @return The registered Objective + * @throws IllegalArgumentException if name is null + * @throws IllegalArgumentException if name is longer than 16 + * characters. + * @throws IllegalArgumentException if criteria is null + * @throws IllegalArgumentException if displayName is null + * @throws IllegalArgumentException if displayName is longer than 128 + * characters. + * @throws IllegalArgumentException if renderType is null + * @throws IllegalArgumentException if an objective by that name already + * exists + */ + @NotNull + Objective registerNewObjective(@NotNull String name, @NotNull String criteria, @NotNull String displayName, @NotNull RenderType renderType) throws IllegalArgumentException; + + /** + * Gets an Objective on this Scoreboard by name + * + * @param name Name of the Objective + * @return the Objective or null if it does not exist + * @throws IllegalArgumentException if name is null + */ + @Nullable + Objective getObjective(@NotNull String name) throws IllegalArgumentException; + + /** + * Gets all Objectives of a Criteria on the Scoreboard + * + * @param criteria Criteria to search by + * @return an immutable set of Objectives using the specified Criteria + */ + @NotNull + Set getObjectivesByCriteria(@NotNull String criteria) throws IllegalArgumentException; + + /** + * Gets all Objectives on this Scoreboard + * + * @return An immutable set of all Objectives on this Scoreboard + */ + @NotNull + Set getObjectives(); + + /** + * Gets the Objective currently displayed in a DisplaySlot on this + * Scoreboard + * + * @param slot The DisplaySlot + * @return the Objective currently displayed or null if nothing is + * displayed in that DisplaySlot + * @throws IllegalArgumentException if slot is null + */ + @Nullable + Objective getObjective(@NotNull DisplaySlot slot) throws IllegalArgumentException; + + /** + * Gets all scores for a player on this Scoreboard + * + * @param player the player whose scores are being retrieved + * @return immutable set of all scores tracked for the player + * @throws IllegalArgumentException if player is null + * @deprecated Scoreboards can contain entries that aren't players + * @see #getScores(String) + */ + @Deprecated + @NotNull + Set getScores(@NotNull OfflinePlayer player) throws IllegalArgumentException; + + /** + * Gets all scores for an entry on this Scoreboard + * + * @param entry the entry whose scores are being retrieved + * @return immutable set of all scores tracked for the entry + * @throws IllegalArgumentException if entry is null + */ + @NotNull + Set getScores(@NotNull String entry) throws IllegalArgumentException; + + /** + * Removes all scores for a player on this Scoreboard + * + * @param player the player to drop all current scores for + * @throws IllegalArgumentException if player is null + * @deprecated Scoreboards can contain entries that aren't players + * @see #resetScores(String) + */ + @Deprecated + void resetScores(@NotNull OfflinePlayer player) throws IllegalArgumentException; + + /** + * Removes all scores for an entry on this Scoreboard + * + * @param entry the entry to drop all current scores for + * @throws IllegalArgumentException if entry is null + */ + void resetScores(@NotNull String entry) throws IllegalArgumentException; + + /** + * Gets a player's Team on this Scoreboard + * + * @param player the player to search for + * @return the player's Team or null if the player is not on a team + * @throws IllegalArgumentException if player is null + * @deprecated Scoreboards can contain entries that aren't players + * @see #getEntryTeam(String) + */ + @Deprecated + @Nullable + Team getPlayerTeam(@NotNull OfflinePlayer player) throws IllegalArgumentException; + + /** + * Gets a entries Team on this Scoreboard + * + * @param entry the entry to search for + * @return the entries Team or null if the entry is not on a team + * @throws IllegalArgumentException if entry is null + */ + @Nullable + Team getEntryTeam(@NotNull String entry) throws IllegalArgumentException; + + /** + * Gets a Team by name on this Scoreboard + * + * @param teamName Team name + * @return the matching Team or null if no matches + * @throws IllegalArgumentException if teamName is null + */ + @Nullable + Team getTeam(@NotNull String teamName) throws IllegalArgumentException; + + /** + * Gets all teams on this Scoreboard + * + * @return an immutable set of Teams + */ + @NotNull + Set getTeams(); + + /** + * Registers a Team on this Scoreboard + * + * @param name Team name + * @return registered Team + * @throws IllegalArgumentException if name is null + * @throws IllegalArgumentException if team by that name already exists + */ + @NotNull + Team registerNewTeam(@NotNull String name) throws IllegalArgumentException; + + /** + * Gets all players tracked by this Scoreboard + * + * @return immutable set of all tracked players + * @deprecated Scoreboards can contain entries that aren't players + * @see #getEntries() + */ + @Deprecated + @NotNull + Set getPlayers(); + + /** + * Gets all entries tracked by this Scoreboard + * + * @return immutable set of all tracked entries + */ + @NotNull + Set getEntries(); + + /** + * Clears any objective in the specified slot. + * + * @param slot the slot to remove objectives + * @throws IllegalArgumentException if slot is null + */ + void clearSlot(@NotNull DisplaySlot slot) throws IllegalArgumentException; +} diff --git a/api/src/main/java/org/bukkit/scoreboard/ScoreboardManager.java b/api/src/main/java/org/bukkit/scoreboard/ScoreboardManager.java new file mode 100644 index 000000000..255977c2f --- /dev/null +++ b/api/src/main/java/org/bukkit/scoreboard/ScoreboardManager.java @@ -0,0 +1,33 @@ +package org.bukkit.scoreboard; + +import org.jetbrains.annotations.NotNull; + +import java.lang.ref.WeakReference; + +/** + * Manager of Scoreboards + */ +public interface ScoreboardManager { + + /** + * Gets the primary Scoreboard controlled by the server. + *

+ * This Scoreboard is saved by the server, is affected by the /scoreboard + * command, and is the scoreboard shown by default to players. + * + * @return the default sever scoreboard + */ + @NotNull + Scoreboard getMainScoreboard(); + + /** + * Gets a new Scoreboard to be tracked by the server. This scoreboard will + * be tracked as long as a reference is kept, either by a player or by a + * plugin. + * + * @return the registered Scoreboard + * @see WeakReference + */ + @NotNull + Scoreboard getNewScoreboard(); +} diff --git a/api/src/main/java/org/bukkit/scoreboard/Team.java b/api/src/main/java/org/bukkit/scoreboard/Team.java new file mode 100644 index 000000000..f75dbe846 --- /dev/null +++ b/api/src/main/java/org/bukkit/scoreboard/Team.java @@ -0,0 +1,340 @@ +package org.bukkit.scoreboard; + +import java.util.Set; + +import org.bukkit.ChatColor; +import org.bukkit.OfflinePlayer; +import org.bukkit.potion.PotionEffectType; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +/** + * A team on a scoreboard that has a common display theme and other + * properties. This team is only relevant to the display of the associated + * {@link #getScoreboard() scoreboard}. + */ +public interface Team { + + /** + * Gets the name of this Team + * + * @return Objective name + * @throws IllegalStateException if this team has been unregistered + */ + @NotNull + String getName() throws IllegalStateException; + + /** + * Gets the name displayed to entries for this team + * + * @return Team display name + * @throws IllegalStateException if this team has been unregistered + */ + @NotNull + String getDisplayName() throws IllegalStateException; + + /** + * Sets the name displayed to entries for this team + * + * @param displayName New display name + * @throws IllegalArgumentException if displayName is longer than 128 + * characters. + * @throws IllegalStateException if this team has been unregistered + */ + void setDisplayName(@NotNull String displayName) throws IllegalStateException, IllegalArgumentException; + + /** + * Gets the prefix prepended to the display of entries on this team. + * + * @return Team prefix + * @throws IllegalStateException if this team has been unregistered + */ + @NotNull + String getPrefix() throws IllegalStateException; + + /** + * Sets the prefix prepended to the display of entries on this team. + * + * @param prefix New prefix + * @throws IllegalArgumentException if prefix is null + * @throws IllegalArgumentException if prefix is longer than 64 + * characters + * @throws IllegalStateException if this team has been unregistered + */ + void setPrefix(@NotNull String prefix) throws IllegalStateException, IllegalArgumentException; + + /** + * Gets the suffix appended to the display of entries on this team. + * + * @return the team's current suffix + * @throws IllegalStateException if this team has been unregistered + */ + @NotNull + String getSuffix() throws IllegalStateException; + + /** + * Sets the suffix appended to the display of entries on this team. + * + * @param suffix the new suffix for this team. + * @throws IllegalArgumentException if suffix is null + * @throws IllegalArgumentException if suffix is longer than 64 + * characters + * @throws IllegalStateException if this team has been unregistered + */ + void setSuffix(@NotNull String suffix) throws IllegalStateException, IllegalArgumentException; + + /** + * Gets the color of the team. + *
+ * This only sets the team outline, other occurrences of colors such as in + * names are handled by prefixes / suffixes. + * + * @return team color, defaults to {@link ChatColor#RESET} + * @throws IllegalStateException if this team has been unregistered + */ + @NotNull + ChatColor getColor() throws IllegalStateException; + + /** + * Sets the color of the team. + *
+ * This only sets the team outline, other occurrences of colors such as in + * names are handled by prefixes / suffixes. + * + * @param color new color, must be non-null. Use {@link ChatColor#RESET} for + * no color + */ + void setColor(@NotNull ChatColor color); + + /** + * Gets the team friendly fire state + * + * @return true if friendly fire is enabled + * @throws IllegalStateException if this team has been unregistered + */ + boolean allowFriendlyFire() throws IllegalStateException; + + /** + * Sets the team friendly fire state + * + * @param enabled true if friendly fire is to be allowed + * @throws IllegalStateException if this team has been unregistered + */ + void setAllowFriendlyFire(boolean enabled) throws IllegalStateException; + + /** + * Gets the team's ability to see {@link PotionEffectType#INVISIBILITY + * invisible} teammates. + * + * @return true if team members can see invisible members + * @throws IllegalStateException if this team has been unregistered + */ + boolean canSeeFriendlyInvisibles() throws IllegalStateException; + + /** + * Sets the team's ability to see {@link PotionEffectType#INVISIBILITY + * invisible} teammates. + * + * @param enabled true if invisible teammates are to be visible + * @throws IllegalStateException if this team has been unregistered + */ + void setCanSeeFriendlyInvisibles(boolean enabled) throws IllegalStateException; + + /** + * Gets the team's ability to see name tags + * + * @return the current name tag visibility for the team + * @throws IllegalArgumentException if this team has been unregistered + * @deprecated see {@link #getOption(org.bukkit.scoreboard.Team.Option)} + */ + @Deprecated + @NotNull + NameTagVisibility getNameTagVisibility() throws IllegalArgumentException; + + /** + * Set's the team's ability to see name tags + * + * @param visibility The nameTagVisibility to set + * @throws IllegalArgumentException if this team has been unregistered + * @deprecated see + * {@link #setOption(org.bukkit.scoreboard.Team.Option, org.bukkit.scoreboard.Team.OptionStatus)} + */ + @Deprecated + void setNameTagVisibility(@NotNull NameTagVisibility visibility) throws IllegalArgumentException; + + /** + * Gets the Set of players on the team + * + * @return players on the team + * @throws IllegalStateException if this team has been unregistered\ + * @deprecated Teams can contain entries that aren't players + * @see #getEntries() + */ + @Deprecated + @NotNull + Set getPlayers() throws IllegalStateException; + + /** + * Gets the Set of entries on the team + * + * @return entries on the team + * @throws IllegalStateException if this entries has been unregistered\ + */ + @NotNull + Set getEntries() throws IllegalStateException; + + /** + * Gets the size of the team + * + * @return number of entries on the team + * @throws IllegalStateException if this team has been unregistered + */ + int getSize() throws IllegalStateException; + + /** + * Gets the Scoreboard to which this team is attached + * + * @return Owning scoreboard, or null if this team has been {@link + * #unregister() unregistered} + */ + @Nullable + Scoreboard getScoreboard(); + + /** + * This puts the specified player onto this team for the scoreboard. + *

+ * This will remove the player from any other team on the scoreboard. + * + * @param player the player to add + * @throws IllegalArgumentException if player is null + * @throws IllegalStateException if this team has been unregistered + * @deprecated Teams can contain entries that aren't players + * @see #addEntry(String) + */ + @Deprecated + void addPlayer(@NotNull OfflinePlayer player) throws IllegalStateException, IllegalArgumentException; + + /** + * This puts the specified entry onto this team for the scoreboard. + *

+ * This will remove the entry from any other team on the scoreboard. + * + * @param entry the entry to add + * @throws IllegalArgumentException if entry is null + * @throws IllegalStateException if this team has been unregistered + */ + void addEntry(@NotNull String entry) throws IllegalStateException, IllegalArgumentException; + + /** + * Removes the player from this team. + * + * @param player the player to remove + * @return if the player was on this team + * @throws IllegalArgumentException if player is null + * @throws IllegalStateException if this team has been unregistered + * @deprecated Teams can contain entries that aren't players + * @see #removeEntry(String) + */ + @Deprecated + boolean removePlayer(@NotNull OfflinePlayer player) throws IllegalStateException, IllegalArgumentException; + + /** + * Removes the entry from this team. + * + * @param entry the entry to remove + * @throws IllegalArgumentException if entry is null + * @throws IllegalStateException if this team has been unregistered + * @return if the entry was a part of this team + */ + boolean removeEntry(@NotNull String entry) throws IllegalStateException, IllegalArgumentException; + + /** + * Unregisters this team from the Scoreboard + * + * @throws IllegalStateException if this team has been unregistered + */ + void unregister() throws IllegalStateException; + + /** + * Checks to see if the specified player is a member of this team. + * + * @param player the player to search for + * @return true if the player is a member of this team + * @throws IllegalArgumentException if player is null + * @throws IllegalStateException if this team has been unregistered + * @deprecated Teams can contain entries that aren't players + * @see #hasEntry(String) + */ + @Deprecated + boolean hasPlayer(@NotNull OfflinePlayer player) throws IllegalArgumentException, IllegalStateException; + /** + * Checks to see if the specified entry is a member of this team. + * + * @param entry the entry to search for + * @return true if the entry is a member of this team + * @throws IllegalArgumentException if entry is null + * @throws IllegalStateException if this team has been unregistered + */ + boolean hasEntry(@NotNull String entry) throws IllegalArgumentException, IllegalStateException; + + /** + * Get an option for this team + * + * @param option the option to get + * @return the option status + * @throws IllegalStateException if this team has been unregistered + */ + @NotNull + OptionStatus getOption(@NotNull Option option) throws IllegalStateException; + + /** + * Set an option for this team + * + * @param option the option to set + * @param status the new option status + * @throws IllegalStateException if this team has been unregistered + */ + void setOption(@NotNull Option option, @NotNull OptionStatus status) throws IllegalStateException; + + /** + * Represents an option which may be applied to this team. + */ + public enum Option { + + /** + * How to display the name tags of players on this team. + */ + NAME_TAG_VISIBILITY, + /** + * How to display the death messages for players on this team. + */ + DEATH_MESSAGE_VISIBILITY, + /** + * How players of this team collide with others. + */ + COLLISION_RULE; + } + + /** + * How an option may be applied to members of this team. + */ + public enum OptionStatus { + + /** + * Apply this option to everyone. + */ + ALWAYS, + /** + * Never apply this option. + */ + NEVER, + /** + * Apply this option only for opposing teams. + */ + FOR_OTHER_TEAMS, + /** + * Apply this option for only team members. + */ + FOR_OWN_TEAM; + } +} diff --git a/api/src/main/java/org/bukkit/util/BlockIterator.java b/api/src/main/java/org/bukkit/util/BlockIterator.java new file mode 100644 index 000000000..8e6887ae7 --- /dev/null +++ b/api/src/main/java/org/bukkit/util/BlockIterator.java @@ -0,0 +1,370 @@ +package org.bukkit.util; + +import static org.bukkit.util.NumberConversions.*; + +import org.bukkit.World; +import org.bukkit.Location; +import org.bukkit.block.Block; +import org.bukkit.block.BlockFace; +import org.bukkit.entity.LivingEntity; +import org.jetbrains.annotations.NotNull; + +import java.util.Iterator; +import java.util.NoSuchElementException; + +/** + * This class performs ray tracing and iterates along blocks on a line + */ +public class BlockIterator implements Iterator { + + private final World world; + private final int maxDistance; + + private static final int gridSize = 1 << 24; + + private boolean end = false; + + private Block[] blockQueue = new Block[3]; + private int currentBlock = 0; + private int currentDistance = 0; + private int maxDistanceInt; + + private int secondError; + private int thirdError; + + private int secondStep; + private int thirdStep; + + private BlockFace mainFace; + private BlockFace secondFace; + private BlockFace thirdFace; + + /** + * Constructs the BlockIterator. + *

+ * This considers all blocks as 1x1x1 in size. + * + * @param world The world to use for tracing + * @param start A Vector giving the initial location for the trace + * @param direction A Vector pointing in the direction for the trace + * @param yOffset The trace begins vertically offset from the start vector + * by this value + * @param maxDistance This is the maximum distance in blocks for the + * trace. Setting this value above 140 may lead to problems with + * unloaded chunks. A value of 0 indicates no limit + * + */ + public BlockIterator(@NotNull World world, @NotNull Vector start, @NotNull Vector direction, double yOffset, int maxDistance) { + this.world = world; + this.maxDistance = maxDistance; + + Vector startClone = start.clone(); + + startClone.setY(startClone.getY() + yOffset); + + currentDistance = 0; + + double mainDirection = 0; + double secondDirection = 0; + double thirdDirection = 0; + + double mainPosition = 0; + double secondPosition = 0; + double thirdPosition = 0; + + Block startBlock = this.world.getBlockAt(floor(startClone.getX()), floor(startClone.getY()), floor(startClone.getZ())); + + if (getXLength(direction) > mainDirection) { + mainFace = getXFace(direction); + mainDirection = getXLength(direction); + mainPosition = getXPosition(direction, startClone, startBlock); + + secondFace = getYFace(direction); + secondDirection = getYLength(direction); + secondPosition = getYPosition(direction, startClone, startBlock); + + thirdFace = getZFace(direction); + thirdDirection = getZLength(direction); + thirdPosition = getZPosition(direction, startClone, startBlock); + } + if (getYLength(direction) > mainDirection) { + mainFace = getYFace(direction); + mainDirection = getYLength(direction); + mainPosition = getYPosition(direction, startClone, startBlock); + + secondFace = getZFace(direction); + secondDirection = getZLength(direction); + secondPosition = getZPosition(direction, startClone, startBlock); + + thirdFace = getXFace(direction); + thirdDirection = getXLength(direction); + thirdPosition = getXPosition(direction, startClone, startBlock); + } + if (getZLength(direction) > mainDirection) { + mainFace = getZFace(direction); + mainDirection = getZLength(direction); + mainPosition = getZPosition(direction, startClone, startBlock); + + secondFace = getXFace(direction); + secondDirection = getXLength(direction); + secondPosition = getXPosition(direction, startClone, startBlock); + + thirdFace = getYFace(direction); + thirdDirection = getYLength(direction); + thirdPosition = getYPosition(direction, startClone, startBlock); + } + + // trace line backwards to find intercept with plane perpendicular to the main axis + + double d = mainPosition / mainDirection; // how far to hit face behind + double secondd = secondPosition - secondDirection * d; + double thirdd = thirdPosition - thirdDirection * d; + + // Guarantee that the ray will pass though the start block. + // It is possible that it would miss due to rounding + // This should only move the ray by 1 grid position + secondError = floor(secondd * gridSize); + secondStep = round(secondDirection / mainDirection * gridSize); + thirdError = floor(thirdd * gridSize); + thirdStep = round(thirdDirection / mainDirection * gridSize); + + if (secondError + secondStep <= 0) { + secondError = -secondStep + 1; + } + + if (thirdError + thirdStep <= 0) { + thirdError = -thirdStep + 1; + } + + Block lastBlock; + + lastBlock = startBlock.getRelative(mainFace.getOppositeFace()); + + if (secondError < 0) { + secondError += gridSize; + lastBlock = lastBlock.getRelative(secondFace.getOppositeFace()); + } + + if (thirdError < 0) { + thirdError += gridSize; + lastBlock = lastBlock.getRelative(thirdFace.getOppositeFace()); + } + + // This means that when the variables are positive, it means that the coord=1 boundary has been crossed + secondError -= gridSize; + thirdError -= gridSize; + + blockQueue[0] = lastBlock; + currentBlock = -1; + + scan(); + + boolean startBlockFound = false; + + for (int cnt = currentBlock; cnt >= 0; cnt--) { + if (blockEquals(blockQueue[cnt], startBlock)) { + currentBlock = cnt; + startBlockFound = true; + break; + } + } + + if (!startBlockFound) { + throw new IllegalStateException("Start block missed in BlockIterator"); + } + + // Calculate the number of planes passed to give max distance + maxDistanceInt = round(maxDistance / (Math.sqrt(mainDirection * mainDirection + secondDirection * secondDirection + thirdDirection * thirdDirection) / mainDirection)); + + } + + private boolean blockEquals(@NotNull Block a, @NotNull Block b) { + return a.getX() == b.getX() && a.getY() == b.getY() && a.getZ() == b.getZ(); + } + + private BlockFace getXFace(@NotNull Vector direction) { + return ((direction.getX() > 0) ? BlockFace.EAST : BlockFace.WEST); + } + + private BlockFace getYFace(@NotNull Vector direction) { + return ((direction.getY() > 0) ? BlockFace.UP : BlockFace.DOWN); + } + + private BlockFace getZFace(@NotNull Vector direction) { + return ((direction.getZ() > 0) ? BlockFace.SOUTH : BlockFace.NORTH); + } + + private double getXLength(@NotNull Vector direction) { + return Math.abs(direction.getX()); + } + + private double getYLength(@NotNull Vector direction) { + return Math.abs(direction.getY()); + } + + private double getZLength(@NotNull Vector direction) { + return Math.abs(direction.getZ()); + } + + private double getPosition(double direction, double position, int blockPosition) { + return direction > 0 ? (position - blockPosition) : (blockPosition + 1 - position); + } + + private double getXPosition(@NotNull Vector direction, @NotNull Vector position, @NotNull Block block) { + return getPosition(direction.getX(), position.getX(), block.getX()); + } + + private double getYPosition(@NotNull Vector direction, @NotNull Vector position, @NotNull Block block) { + return getPosition(direction.getY(), position.getY(), block.getY()); + } + + private double getZPosition(@NotNull Vector direction, @NotNull Vector position, @NotNull Block block) { + return getPosition(direction.getZ(), position.getZ(), block.getZ()); + } + + /** + * Constructs the BlockIterator. + *

+ * This considers all blocks as 1x1x1 in size. + * + * @param loc The location for the start of the ray trace + * @param yOffset The trace begins vertically offset from the start vector + * by this value + * @param maxDistance This is the maximum distance in blocks for the + * trace. Setting this value above 140 may lead to problems with + * unloaded chunks. A value of 0 indicates no limit + */ + public BlockIterator(@NotNull Location loc, double yOffset, int maxDistance) { + this(loc.getWorld(), loc.toVector(), loc.getDirection(), yOffset, maxDistance); + } + + /** + * Constructs the BlockIterator. + *

+ * This considers all blocks as 1x1x1 in size. + * + * @param loc The location for the start of the ray trace + * @param yOffset The trace begins vertically offset from the start vector + * by this value + */ + + public BlockIterator(@NotNull Location loc, double yOffset) { + this(loc.getWorld(), loc.toVector(), loc.getDirection(), yOffset, 0); + } + + /** + * Constructs the BlockIterator. + *

+ * This considers all blocks as 1x1x1 in size. + * + * @param loc The location for the start of the ray trace + */ + + public BlockIterator(@NotNull Location loc) { + this(loc, 0D); + } + + /** + * Constructs the BlockIterator. + *

+ * This considers all blocks as 1x1x1 in size. + * + * @param entity Information from the entity is used to set up the trace + * @param maxDistance This is the maximum distance in blocks for the + * trace. Setting this value above 140 may lead to problems with + * unloaded chunks. A value of 0 indicates no limit + */ + + public BlockIterator(@NotNull LivingEntity entity, int maxDistance) { + this(entity.getLocation(), entity.getEyeHeight(), maxDistance); + } + + /** + * Constructs the BlockIterator. + *

+ * This considers all blocks as 1x1x1 in size. + * + * @param entity Information from the entity is used to set up the trace + */ + + public BlockIterator(@NotNull LivingEntity entity) { + this(entity, 0); + } + + /** + * Returns true if the iteration has more elements + */ + + public boolean hasNext() { + scan(); + return currentBlock != -1; + } + + /** + * Returns the next Block in the trace + * + * @return the next Block in the trace + */ + @NotNull + public Block next() throws NoSuchElementException { + scan(); + if (currentBlock <= -1) { + throw new NoSuchElementException(); + } else { + return blockQueue[currentBlock--]; + } + } + + public void remove() { + throw new UnsupportedOperationException("[BlockIterator] doesn't support block removal"); + } + + private void scan() { + if (currentBlock >= 0) { + return; + } + if (maxDistance != 0 && currentDistance > maxDistanceInt) { + end = true; + return; + } + if (end) { + return; + } + + currentDistance++; + + secondError += secondStep; + thirdError += thirdStep; + + if (secondError > 0 && thirdError > 0) { + blockQueue[2] = blockQueue[0].getRelative(mainFace); + if (((long) secondStep) * ((long) thirdError) < ((long) thirdStep) * ((long) secondError)) { + blockQueue[1] = blockQueue[2].getRelative(secondFace); + blockQueue[0] = blockQueue[1].getRelative(thirdFace); + } else { + blockQueue[1] = blockQueue[2].getRelative(thirdFace); + blockQueue[0] = blockQueue[1].getRelative(secondFace); + } + thirdError -= gridSize; + secondError -= gridSize; + currentBlock = 2; + return; + } else if (secondError > 0) { + blockQueue[1] = blockQueue[0].getRelative(mainFace); + blockQueue[0] = blockQueue[1].getRelative(secondFace); + secondError -= gridSize; + currentBlock = 1; + return; + } else if (thirdError > 0) { + blockQueue[1] = blockQueue[0].getRelative(mainFace); + blockQueue[0] = blockQueue[1].getRelative(thirdFace); + thirdError -= gridSize; + currentBlock = 1; + return; + } else { + blockQueue[0] = blockQueue[0].getRelative(mainFace); + currentBlock = 0; + return; + } + } +} diff --git a/api/src/main/java/org/bukkit/util/BlockVector.java b/api/src/main/java/org/bukkit/util/BlockVector.java new file mode 100644 index 000000000..eddc72489 --- /dev/null +++ b/api/src/main/java/org/bukkit/util/BlockVector.java @@ -0,0 +1,130 @@ +package org.bukkit.util; + +import java.util.Map; +import org.bukkit.configuration.serialization.SerializableAs; +import org.jetbrains.annotations.NotNull; + +/** + * A vector with a hash function that floors the X, Y, Z components, a la + * BlockVector in WorldEdit. BlockVectors can be used in hash sets and + * hash maps. Be aware that BlockVectors are mutable, but it is important + * that BlockVectors are never changed once put into a hash set or hash map. + */ +@SerializableAs("BlockVector") +public class BlockVector extends Vector { + + /** + * Construct the vector with all components as 0. + */ + public BlockVector() { + this.x = 0; + this.y = 0; + this.z = 0; + } + + /** + * Construct the vector with another vector. + * + * @param vec The other vector. + */ + public BlockVector(@NotNull Vector vec) { + this.x = vec.getX(); + this.y = vec.getY(); + this.z = vec.getZ(); + } + + /** + * Construct the vector with provided integer components. + * + * @param x X component + * @param y Y component + * @param z Z component + */ + public BlockVector(int x, int y, int z) { + this.x = x; + this.y = y; + this.z = z; + } + + /** + * Construct the vector with provided double components. + * + * @param x X component + * @param y Y component + * @param z Z component + */ + public BlockVector(double x, double y, double z) { + this.x = x; + this.y = y; + this.z = z; + } + + /** + * Construct the vector with provided float components. + * + * @param x X component + * @param y Y component + * @param z Z component + */ + public BlockVector(float x, float y, float z) { + this.x = x; + this.y = y; + this.z = z; + } + + /** + * Checks if another object is equivalent. + * + * @param obj The other object + * @return whether the other object is equivalent + */ + @Override + public boolean equals(Object obj) { + if (!(obj instanceof BlockVector)) { + return false; + } + BlockVector other = (BlockVector) obj; + + return (int) other.getX() == (int) this.x && (int) other.getY() == (int) this.y && (int) other.getZ() == (int) this.z; + + } + + /** + * Returns a hash code for this vector. + * + * @return hash code + */ + @Override + public int hashCode() { + return (Integer.valueOf((int) x).hashCode() >> 13) ^ (Integer.valueOf((int) y).hashCode() >> 7) ^ Integer.valueOf((int) z).hashCode(); + } + + /** + * Get a new block vector. + * + * @return vector + */ + @Override + public BlockVector clone() { + return (BlockVector) super.clone(); + } + + @NotNull + public static BlockVector deserialize(@NotNull Map args) { + double x = 0; + double y = 0; + double z = 0; + + if (args.containsKey("x")) { + x = (Double) args.get("x"); + } + if (args.containsKey("y")) { + y = (Double) args.get("y"); + } + if (args.containsKey("z")) { + z = (Double) args.get("z"); + } + + return new BlockVector(x, y, z); + } +} diff --git a/api/src/main/java/org/bukkit/util/BoundingBox.java b/api/src/main/java/org/bukkit/util/BoundingBox.java new file mode 100644 index 000000000..e5c7826af --- /dev/null +++ b/api/src/main/java/org/bukkit/util/BoundingBox.java @@ -0,0 +1,1064 @@ +package org.bukkit.util; + +import java.util.LinkedHashMap; +import java.util.Map; +import java.util.Objects; +import org.apache.commons.lang.Validate; +import org.bukkit.Location; +import org.bukkit.block.Block; +import org.bukkit.block.BlockFace; +import org.bukkit.configuration.serialization.ConfigurationSerializable; +import org.bukkit.configuration.serialization.SerializableAs; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +/** + * A mutable axis aligned bounding box (AABB). + *

+ * This basically represents a rectangular box (specified by minimum and maximum + * corners) that can for example be used to describe the position and extents of + * an object (such as an entity, block, or rectangular region) in 3D space. Its + * edges and faces are parallel to the axes of the cartesian coordinate system. + *

+ * The bounding box may be degenerate (one or more sides having the length 0). + *

+ * Because bounding boxes are mutable, storing them long term may be dangerous + * if they get modified later. If you want to keep around a bounding box, it may + * be wise to call {@link #clone()} in order to get a copy. + */ +@SerializableAs("BoundingBox") +public class BoundingBox implements Cloneable, ConfigurationSerializable { + + /** + * Creates a new bounding box using the coordinates of the given vectors as + * corners. + * + * @param corner1 the first corner + * @param corner2 the second corner + * @return the bounding box + */ + @NotNull + public static BoundingBox of(@NotNull Vector corner1, @NotNull Vector corner2) { + Validate.notNull(corner1, "Corner1 is null!"); + Validate.notNull(corner2, "Corner2 is null!"); + return new BoundingBox(corner1.getX(), corner1.getY(), corner1.getZ(), corner2.getX(), corner2.getY(), corner2.getZ()); + } + + /** + * Creates a new bounding box using the coordinates of the given locations + * as corners. + * + * @param corner1 the first corner + * @param corner2 the second corner + * @return the bounding box + */ + @NotNull + public static BoundingBox of(@NotNull Location corner1, @NotNull Location corner2) { + Validate.notNull(corner1, "Corner1 is null!"); + Validate.notNull(corner2, "Corner2 is null!"); + Validate.isTrue(Objects.equals(corner1.getWorld(), corner2.getWorld()), "Locations from different worlds!"); + return new BoundingBox(corner1.getX(), corner1.getY(), corner1.getZ(), corner2.getX(), corner2.getY(), corner2.getZ()); + } + + /** + * Creates a new bounding box using the coordinates of the given blocks as + * corners. + *

+ * The bounding box will be sized to fully contain both blocks. + * + * @param corner1 the first corner block + * @param corner2 the second corner block + * @return the bounding box + */ + @NotNull + public static BoundingBox of(@NotNull Block corner1, @NotNull Block corner2) { + Validate.notNull(corner1, "Corner1 is null!"); + Validate.notNull(corner2, "Corner2 is null!"); + Validate.isTrue(Objects.equals(corner1.getWorld(), corner2.getWorld()), "Blocks from different worlds!"); + + int x1 = corner1.getX(); + int y1 = corner1.getY(); + int z1 = corner1.getZ(); + int x2 = corner2.getX(); + int y2 = corner2.getY(); + int z2 = corner2.getZ(); + + int minX = Math.min(x1, x2); + int minY = Math.min(y1, y2); + int minZ = Math.min(z1, z2); + int maxX = Math.max(x1, x2) + 1; + int maxY = Math.max(y1, y2) + 1; + int maxZ = Math.max(z1, z2) + 1; + + return new BoundingBox(minX, minY, minZ, maxX, maxY, maxZ); + } + + /** + * Creates a new 1x1x1 sized bounding box containing the given block. + * + * @param block the block + * @return the bounding box + */ + @NotNull + public static BoundingBox of(@NotNull Block block) { + Validate.notNull(block, "Block is null!"); + return new BoundingBox(block.getX(), block.getY(), block.getZ(), block.getX() + 1, block.getY() + 1, block.getZ() + 1); + } + + /** + * Creates a new bounding box using the given center and extents. + * + * @param center the center + * @param x 1/2 the size of the bounding box along the x axis + * @param y 1/2 the size of the bounding box along the y axis + * @param z 1/2 the size of the bounding box along the z axis + * @return the bounding box + */ + @NotNull + public static BoundingBox of(@NotNull Vector center, double x, double y, double z) { + Validate.notNull(center, "Center is null!"); + return new BoundingBox(center.getX() - x, center.getY() - y, center.getZ() - z, center.getX() + x, center.getY() + y, center.getZ() + z); + } + + /** + * Creates a new bounding box using the given center and extents. + * + * @param center the center + * @param x 1/2 the size of the bounding box along the x axis + * @param y 1/2 the size of the bounding box along the y axis + * @param z 1/2 the size of the bounding box along the z axis + * @return the bounding box + */ + @NotNull + public static BoundingBox of(@NotNull Location center, double x, double y, double z) { + Validate.notNull(center, "Center is null!"); + return new BoundingBox(center.getX() - x, center.getY() - y, center.getZ() - z, center.getX() + x, center.getY() + y, center.getZ() + z); + } + + private double minX; + private double minY; + private double minZ; + private double maxX; + private double maxY; + private double maxZ; + + /** + * Creates a new (degenerate) bounding box with all corner coordinates at + * 0. + */ + public BoundingBox() { + this.resize(0.0D, 0.0D, 0.0D, 0.0D, 0.0D, 0.0D); + } + + /** + * Creates a new bounding box from the given corner coordinates. + * + * @param x1 the first corner's x value + * @param y1 the first corner's y value + * @param z1 the first corner's z value + * @param x2 the second corner's x value + * @param y2 the second corner's y value + * @param z2 the second corner's z value + */ + public BoundingBox(double x1, double y1, double z1, double x2, double y2, double z2) { + this.resize(x1, y1, z1, x2, y2, z2); + } + + /** + * Resizes this bounding box. + * + * @param x1 the first corner's x value + * @param y1 the first corner's y value + * @param z1 the first corner's z value + * @param x2 the second corner's x value + * @param y2 the second corner's y value + * @param z2 the second corner's z value + * @return this bounding box (resized) + */ + @NotNull + public BoundingBox resize(double x1, double y1, double z1, double x2, double y2, double z2) { + NumberConversions.checkFinite(x1, "x1 not finite"); + NumberConversions.checkFinite(y1, "y1 not finite"); + NumberConversions.checkFinite(z1, "z1 not finite"); + NumberConversions.checkFinite(x2, "x2 not finite"); + NumberConversions.checkFinite(y2, "y2 not finite"); + NumberConversions.checkFinite(z2, "z2 not finite"); + + this.minX = Math.min(x1, x2); + this.minY = Math.min(y1, y2); + this.minZ = Math.min(z1, z2); + this.maxX = Math.max(x1, x2); + this.maxY = Math.max(y1, y2); + this.maxZ = Math.max(z1, z2); + return this; + } + + /** + * Gets the minimum x value. + * + * @return the minimum x value + */ + public double getMinX() { + return minX; + } + + /** + * Gets the minimum y value. + * + * @return the minimum y value + */ + public double getMinY() { + return minY; + } + + /** + * Gets the minimum z value. + * + * @return the minimum z value + */ + public double getMinZ() { + return minZ; + } + + /** + * Gets the minimum corner as vector. + * + * @return the minimum corner as vector + */ + @NotNull + public Vector getMin() { + return new Vector(minX, minY, minZ); + } + + /** + * Gets the maximum x value. + * + * @return the maximum x value + */ + public double getMaxX() { + return maxX; + } + + /** + * Gets the maximum y value. + * + * @return the maximum y value + */ + public double getMaxY() { + return maxY; + } + + /** + * Gets the maximum z value. + * + * @return the maximum z value + */ + public double getMaxZ() { + return maxZ; + } + + /** + * Gets the maximum corner as vector. + * + * @return the maximum corner vector + */ + @NotNull + public Vector getMax() { + return new Vector(maxX, maxY, maxZ); + } + + /** + * Gets the width of the bounding box in the x direction. + * + * @return the width in the x direction + */ + public double getWidthX() { + return (this.maxX - this.minX); + } + + /** + * Gets the width of the bounding box in the z direction. + * + * @return the width in the z direction + */ + public double getWidthZ() { + return (this.maxZ - this.minZ); + } + + /** + * Gets the height of the bounding box. + * + * @return the height + */ + public double getHeight() { + return (this.maxY - this.minY); + } + + /** + * Gets the volume of the bounding box. + * + * @return the volume + */ + public double getVolume() { + return (this.getHeight() * this.getWidthX() * this.getWidthZ()); + } + + /** + * Gets the x coordinate of the center of the bounding box. + * + * @return the center's x coordinate + */ + public double getCenterX() { + return (this.minX + this.getWidthX() * 0.5D); + } + + /** + * Gets the y coordinate of the center of the bounding box. + * + * @return the center's y coordinate + */ + public double getCenterY() { + return (this.minY + this.getHeight() * 0.5D); + } + + /** + * Gets the z coordinate of the center of the bounding box. + * + * @return the center's z coordinate + */ + public double getCenterZ() { + return (this.minZ + this.getWidthZ() * 0.5D); + } + + /** + * Gets the center of the bounding box. + * + * @return the center + */ + @NotNull + public Vector getCenter() { + return new Vector(this.getCenterX(), this.getCenterY(), this.getCenterZ()); + } + + /** + * Copies another bounding box. + * + * @param other the other bounding box + * @return this bounding box + */ + @NotNull + public BoundingBox copy(@NotNull BoundingBox other) { + Validate.notNull(other, "Other bounding box is null!"); + return this.resize(other.getMinX(), other.getMinY(), other.getMinZ(), other.getMaxX(), other.getMaxY(), other.getMaxZ()); + } + + /** + * Expands this bounding box by the given values in the corresponding + * directions. + *

+ * Negative values will shrink the bounding box in the corresponding + * direction. Shrinking will be limited to the point where the affected + * opposite faces would meet if the they shrank at uniform speeds. + * + * @param negativeX the amount of expansion in the negative x direction + * @param negativeY the amount of expansion in the negative y direction + * @param negativeZ the amount of expansion in the negative z direction + * @param positiveX the amount of expansion in the positive x direction + * @param positiveY the amount of expansion in the positive y direction + * @param positiveZ the amount of expansion in the positive z direction + * @return this bounding box (now expanded) + */ + @NotNull + public BoundingBox expand(double negativeX, double negativeY, double negativeZ, double positiveX, double positiveY, double positiveZ) { + if (negativeX == 0.0D && negativeY == 0.0D && negativeZ == 0.0D && positiveX == 0.0D && positiveY == 0.0D && positiveZ == 0.0D) { + return this; + } + double newMinX = this.minX - negativeX; + double newMinY = this.minY - negativeY; + double newMinZ = this.minZ - negativeZ; + double newMaxX = this.maxX + positiveX; + double newMaxY = this.maxY + positiveY; + double newMaxZ = this.maxZ + positiveZ; + + // limit shrinking: + if (newMinX > newMaxX) { + double centerX = this.getCenterX(); + if (newMaxX >= centerX) { + newMinX = newMaxX; + } else if (newMinX <= centerX) { + newMaxX = newMinX; + } else { + newMinX = centerX; + newMaxX = centerX; + } + } + if (newMinY > newMaxY) { + double centerY = this.getCenterY(); + if (newMaxY >= centerY) { + newMinY = newMaxY; + } else if (newMinY <= centerY) { + newMaxY = newMinY; + } else { + newMinY = centerY; + newMaxY = centerY; + } + } + if (newMinZ > newMaxZ) { + double centerZ = this.getCenterZ(); + if (newMaxZ >= centerZ) { + newMinZ = newMaxZ; + } else if (newMinZ <= centerZ) { + newMaxZ = newMinZ; + } else { + newMinZ = centerZ; + newMaxZ = centerZ; + } + } + return this.resize(newMinX, newMinY, newMinZ, newMaxX, newMaxY, newMaxZ); + } + + /** + * Expands this bounding box uniformly by the given values in both positive + * and negative directions. + *

+ * Negative values will shrink the bounding box. Shrinking will be limited + * to the bounding box's current size. + * + * @param x the amount of expansion in both positive and negative x + * direction + * @param y the amount of expansion in both positive and negative y + * direction + * @param z the amount of expansion in both positive and negative z + * direction + * @return this bounding box (now expanded) + */ + @NotNull + public BoundingBox expand(double x, double y, double z) { + return this.expand(x, y, z, x, y, z); + } + + /** + * Expands this bounding box uniformly by the given values in both positive + * and negative directions. + *

+ * Negative values will shrink the bounding box. Shrinking will be limited + * to the bounding box's current size. + * + * @param expansion the expansion values + * @return this bounding box (now expanded) + */ + @NotNull + public BoundingBox expand(@NotNull Vector expansion) { + Validate.notNull(expansion, "Expansion is null!"); + double x = expansion.getX(); + double y = expansion.getY(); + double z = expansion.getZ(); + return this.expand(x, y, z, x, y, z); + } + + /** + * Expands this bounding box uniformly by the given value in all directions. + *

+ * A negative value will shrink the bounding box. Shrinking will be limited + * to the bounding box's current size. + * + * @param expansion the amount of expansion + * @return this bounding box (now expanded) + */ + @NotNull + public BoundingBox expand(double expansion) { + return this.expand(expansion, expansion, expansion, expansion, expansion, expansion); + } + + /** + * Expands this bounding box in the specified direction. + *

+ * The magnitude of the direction will scale the expansion. A negative + * expansion value will shrink the bounding box in this direction. Shrinking + * will be limited to the bounding box's current size. + * + * @param dirX the x direction component + * @param dirY the y direction component + * @param dirZ the z direction component + * @param expansion the amount of expansion + * @return this bounding box (now expanded) + */ + @NotNull + public BoundingBox expand(double dirX, double dirY, double dirZ, double expansion) { + if (expansion == 0.0D) return this; + if (dirX == 0.0D && dirY == 0.0D && dirZ == 0.0D) return this; + + double negativeX = (dirX < 0.0D ? (-dirX * expansion) : 0.0D); + double negativeY = (dirY < 0.0D ? (-dirY * expansion) : 0.0D); + double negativeZ = (dirZ < 0.0D ? (-dirZ * expansion) : 0.0D); + double positiveX = (dirX > 0.0D ? (dirX * expansion) : 0.0D); + double positiveY = (dirY > 0.0D ? (dirY * expansion) : 0.0D); + double positiveZ = (dirZ > 0.0D ? (dirZ * expansion) : 0.0D); + return this.expand(negativeX, negativeY, negativeZ, positiveX, positiveY, positiveZ); + } + + /** + * Expands this bounding box in the specified direction. + *

+ * The magnitude of the direction will scale the expansion. A negative + * expansion value will shrink the bounding box in this direction. Shrinking + * will be limited to the bounding box's current size. + * + * @param direction the direction + * @param expansion the amount of expansion + * @return this bounding box (now expanded) + */ + @NotNull + public BoundingBox expand(@NotNull Vector direction, double expansion) { + Validate.notNull(direction, "Direction is null!"); + return this.expand(direction.getX(), direction.getY(), direction.getZ(), expansion); + } + + /** + * Expands this bounding box in the direction specified by the given block + * face. + *

+ * A negative expansion value will shrink the bounding box in this + * direction. Shrinking will be limited to the bounding box's current size. + * + * @param blockFace the block face + * @param expansion the amount of expansion + * @return this bounding box (now expanded) + */ + @NotNull + public BoundingBox expand(@NotNull BlockFace blockFace, double expansion) { + Validate.notNull(blockFace, "Block face is null!"); + if (blockFace == BlockFace.SELF) return this; + + return this.expand(blockFace.getDirection(), expansion); + } + + /** + * Expands this bounding box in the specified direction. + *

+ * Negative values will expand the bounding box in the negative direction, + * positive values will expand it in the positive direction. The magnitudes + * of the direction components determine the corresponding amounts of + * expansion. + * + * @param dirX the x direction component + * @param dirY the y direction component + * @param dirZ the z direction component + * @return this bounding box (now expanded) + */ + @NotNull + public BoundingBox expandDirectional(double dirX, double dirY, double dirZ) { + return this.expand(dirX, dirY, dirZ, 1.0D); + } + + /** + * Expands this bounding box in the specified direction. + *

+ * Negative values will expand the bounding box in the negative direction, + * positive values will expand it in the positive direction. The magnitude + * of the direction vector determines the amount of expansion. + * + * @param direction the direction and magnitude of the expansion + * @return this bounding box (now expanded) + */ + @NotNull + public BoundingBox expandDirectional(@NotNull Vector direction) { + Validate.notNull(direction, "Expansion is null!"); + return this.expand(direction.getX(), direction.getY(), direction.getZ(), 1.0D); + } + + /** + * Expands this bounding box to contain (or border) the specified position. + * + * @param posX the x position value + * @param posY the y position value + * @param posZ the z position value + * @return this bounding box (now expanded) + * @see #contains(double, double, double) + */ + @NotNull + public BoundingBox union(double posX, double posY, double posZ) { + double newMinX = Math.min(this.minX, posX); + double newMinY = Math.min(this.minY, posY); + double newMinZ = Math.min(this.minZ, posZ); + double newMaxX = Math.max(this.maxX, posX); + double newMaxY = Math.max(this.maxY, posY); + double newMaxZ = Math.max(this.maxZ, posZ); + if (newMinX == this.minX && newMinY == this.minY && newMinZ == this.minZ && newMaxX == this.maxX && newMaxY == this.maxY && newMaxZ == this.maxZ) { + return this; + } + return this.resize(newMinX, newMinY, newMinZ, newMaxX, newMaxY, newMaxZ); + } + + /** + * Expands this bounding box to contain (or border) the specified position. + * + * @param position the position + * @return this bounding box (now expanded) + * @see #contains(double, double, double) + */ + @NotNull + public BoundingBox union(@NotNull Vector position) { + Validate.notNull(position, "Position is null!"); + return this.union(position.getX(), position.getY(), position.getZ()); + } + + /** + * Expands this bounding box to contain (or border) the specified position. + * + * @param position the position + * @return this bounding box (now expanded) + * @see #contains(double, double, double) + */ + @NotNull + public BoundingBox union(@NotNull Location position) { + Validate.notNull(position, "Position is null!"); + return this.union(position.getX(), position.getY(), position.getZ()); + } + + /** + * Expands this bounding box to contain both this and the given bounding + * box. + * + * @param other the other bounding box + * @return this bounding box (now expanded) + */ + @NotNull + public BoundingBox union(@NotNull BoundingBox other) { + Validate.notNull(other, "Other bounding box is null!"); + if (this.contains(other)) return this; + double newMinX = Math.min(this.minX, other.minX); + double newMinY = Math.min(this.minY, other.minY); + double newMinZ = Math.min(this.minZ, other.minZ); + double newMaxX = Math.max(this.maxX, other.maxX); + double newMaxY = Math.max(this.maxY, other.maxY); + double newMaxZ = Math.max(this.maxZ, other.maxZ); + return this.resize(newMinX, newMinY, newMinZ, newMaxX, newMaxY, newMaxZ); + } + + /** + * Resizes this bounding box to represent the intersection of this and the + * given bounding box. + * + * @param other the other bounding box + * @return this bounding box (now representing the intersection) + * @throws IllegalArgumentException if the bounding boxes don't overlap + */ + @NotNull + public BoundingBox intersection(@NotNull BoundingBox other) { + Validate.notNull(other, "Other bounding box is null!"); + Validate.isTrue(this.overlaps(other), "The bounding boxes do not overlap!"); + double newMinX = Math.max(this.minX, other.minX); + double newMinY = Math.max(this.minY, other.minY); + double newMinZ = Math.max(this.minZ, other.minZ); + double newMaxX = Math.min(this.maxX, other.maxX); + double newMaxY = Math.min(this.maxY, other.maxY); + double newMaxZ = Math.min(this.maxZ, other.maxZ); + return this.resize(newMinX, newMinY, newMinZ, newMaxX, newMaxY, newMaxZ); + } + + /** + * Shifts this bounding box by the given amounts. + * + * @param shiftX the shift in x direction + * @param shiftY the shift in y direction + * @param shiftZ the shift in z direction + * @return this bounding box (now shifted) + */ + @NotNull + public BoundingBox shift(double shiftX, double shiftY, double shiftZ) { + if (shiftX == 0.0D && shiftY == 0.0D && shiftZ == 0.0D) return this; + return this.resize(this.minX + shiftX, this.minY + shiftY, this.minZ + shiftZ, + this.maxX + shiftX, this.maxY + shiftY, this.maxZ + shiftZ); + } + + /** + * Shifts this bounding box by the given amounts. + * + * @param shift the shift + * @return this bounding box (now shifted) + */ + @NotNull + public BoundingBox shift(@NotNull Vector shift) { + Validate.notNull(shift, "Shift is null!"); + return this.shift(shift.getX(), shift.getY(), shift.getZ()); + } + + /** + * Shifts this bounding box by the given amounts. + * + * @param shift the shift + * @return this bounding box (now shifted) + */ + @NotNull + public BoundingBox shift(@NotNull Location shift) { + Validate.notNull(shift, "Shift is null!"); + return this.shift(shift.getX(), shift.getY(), shift.getZ()); + } + + private boolean overlaps(double minX, double minY, double minZ, double maxX, double maxY, double maxZ) { + return this.minX < maxX && this.maxX > minX + && this.minY < maxY && this.maxY > minY + && this.minZ < maxZ && this.maxZ > minZ; + } + + /** + * Checks if this bounding box overlaps with the given bounding box. + *

+ * Bounding boxes that are only intersecting at the borders are not + * considered overlapping. + * + * @param other the other bounding box + * @return true if overlapping + */ + public boolean overlaps(@NotNull BoundingBox other) { + Validate.notNull(other, "Other bounding box is null!"); + return this.overlaps(other.minX, other.minY, other.minZ, other.maxX, other.maxY, other.maxZ); + } + + /** + * Checks if this bounding box overlaps with the bounding box that is + * defined by the given corners. + *

+ * Bounding boxes that are only intersecting at the borders are not + * considered overlapping. + * + * @param min the first corner + * @param max the second corner + * @return true if overlapping + */ + public boolean overlaps(@NotNull Vector min, @NotNull Vector max) { + Validate.notNull(min, "Min is null!"); + Validate.notNull(max, "Max is null!"); + double x1 = min.getX(); + double y1 = min.getY(); + double z1 = min.getZ(); + double x2 = max.getX(); + double y2 = max.getY(); + double z2 = max.getZ(); + return this.overlaps(Math.min(x1, x2), Math.min(y1, y2), Math.min(z1, z2), + Math.max(x1, x2), Math.max(y1, y2), Math.max(z1, z2)); + } + + /** + * Checks if this bounding box contains the specified position. + *

+ * Positions exactly on the minimum borders of the bounding box are + * considered to be inside the bounding box, while positions exactly on the + * maximum borders are considered to be outside. This allows bounding boxes + * to reside directly next to each other with positions always only residing + * in exactly one of them. + * + * @param x the position's x coordinates + * @param y the position's y coordinates + * @param z the position's z coordinates + * @return true if the bounding box contains the position + */ + public boolean contains(double x, double y, double z) { + return x >= this.minX && x < this.maxX + && y >= this.minY && y < this.maxY + && z >= this.minZ && z < this.maxZ; + } + + /** + * Checks if this bounding box contains the specified position. + *

+ * Positions exactly on the minimum borders of the bounding box are + * considered to be inside the bounding box, while positions exactly on the + * maximum borders are considered to be outside. This allows bounding boxes + * to reside directly next to each other with positions always only residing + * in exactly one of them. + * + * @param position the position + * @return true if the bounding box contains the position + */ + public boolean contains(@NotNull Vector position) { + Validate.notNull(position, "Position is null!"); + return this.contains(position.getX(), position.getY(), position.getZ()); + } + + private boolean contains(double minX, double minY, double minZ, double maxX, double maxY, double maxZ) { + return this.minX <= minX && this.maxX >= maxX + && this.minY <= minY && this.maxY >= maxY + && this.minZ <= minZ && this.maxZ >= maxZ; + } + + /** + * Checks if this bounding box fully contains the given bounding box. + * + * @param other the other bounding box + * @return true if the bounding box contains the given bounding + * box + */ + public boolean contains(@NotNull BoundingBox other) { + Validate.notNull(other, "Other bounding box is null!"); + return this.contains(other.minX, other.minY, other.minZ, other.maxX, other.maxY, other.maxZ); + } + + /** + * Checks if this bounding box fully contains the bounding box that is + * defined by the given corners. + * + * @param min the first corner + * @param max the second corner + * @return true if the bounding box contains the specified + * bounding box + */ + public boolean contains(@NotNull Vector min, @NotNull Vector max) { + Validate.notNull(min, "Min is null!"); + Validate.notNull(max, "Max is null!"); + double x1 = min.getX(); + double y1 = min.getY(); + double z1 = min.getZ(); + double x2 = max.getX(); + double y2 = max.getY(); + double z2 = max.getZ(); + return this.contains(Math.min(x1, x2), Math.min(y1, y2), Math.min(z1, z2), + Math.max(x1, x2), Math.max(y1, y2), Math.max(z1, z2)); + } + + /** + * Calculates the intersection of this bounding box with the specified line + * segment. + *

+ * Intersections at edges and corners yield one of the affected block faces + * as hit result, but it is not defined which of them. + * + * @param start the start position + * @param direction the ray direction + * @param maxDistance the maximum distance + * @return the ray trace hit result, or null if there is no hit + */ + @Nullable + public RayTraceResult rayTrace(@NotNull Vector start, @NotNull Vector direction, double maxDistance) { + Validate.notNull(start, "Start is null!"); + start.checkFinite(); + Validate.notNull(direction, "Direction is null!"); + direction.checkFinite(); + Validate.isTrue(direction.lengthSquared() > 0, "Direction's magnitude is 0!"); + if (maxDistance < 0.0D) return null; + + // ray start: + double startX = start.getX(); + double startY = start.getY(); + double startZ = start.getZ(); + + // ray direction: + Vector dir = direction.clone().normalize(); + double dirX = dir.getX(); + double dirY = dir.getY(); + double dirZ = dir.getZ(); + + // saving a few divisions below: + double divX = 1.0D / dirX; + double divY = 1.0D / dirY; + double divZ = 1.0D / dirZ; + + double tMin; + double tMax; + BlockFace hitBlockFaceMin; + BlockFace hitBlockFaceMax; + + // intersections with x planes: + if (dirX >= 0.0D) { + tMin = (this.minX - startX) * divX; + tMax = (this.maxX - startX) * divX; + hitBlockFaceMin = BlockFace.WEST; + hitBlockFaceMax = BlockFace.EAST; + } else { + tMin = (this.maxX - startX) * divX; + tMax = (this.minX - startX) * divX; + hitBlockFaceMin = BlockFace.EAST; + hitBlockFaceMax = BlockFace.WEST; + } + + // intersections with y planes: + double tyMin; + double tyMax; + BlockFace hitBlockFaceYMin; + BlockFace hitBlockFaceYMax; + if (dirY >= 0.0D) { + tyMin = (this.minY - startY) * divY; + tyMax = (this.maxY - startY) * divY; + hitBlockFaceYMin = BlockFace.DOWN; + hitBlockFaceYMax = BlockFace.UP; + } else { + tyMin = (this.maxY - startY) * divY; + tyMax = (this.minY - startY) * divY; + hitBlockFaceYMin = BlockFace.UP; + hitBlockFaceYMax = BlockFace.DOWN; + } + if ((tMin > tyMax) || (tMax < tyMin)) { + return null; + } + if (tyMin > tMin) { + tMin = tyMin; + hitBlockFaceMin = hitBlockFaceYMin; + } + if (tyMax < tMax) { + tMax = tyMax; + hitBlockFaceMax = hitBlockFaceYMax; + } + + // intersections with z planes: + double tzMin; + double tzMax; + BlockFace hitBlockFaceZMin; + BlockFace hitBlockFaceZMax; + if (dirZ >= 0.0D) { + tzMin = (this.minZ - startZ) * divZ; + tzMax = (this.maxZ - startZ) * divZ; + hitBlockFaceZMin = BlockFace.NORTH; + hitBlockFaceZMax = BlockFace.SOUTH; + } else { + tzMin = (this.maxZ - startZ) * divZ; + tzMax = (this.minZ - startZ) * divZ; + hitBlockFaceZMin = BlockFace.SOUTH; + hitBlockFaceZMax = BlockFace.NORTH; + } + if ((tMin > tzMax) || (tMax < tzMin)) { + return null; + } + if (tzMin > tMin) { + tMin = tzMin; + hitBlockFaceMin = hitBlockFaceZMin; + } + if (tzMax < tMax) { + tMax = tzMax; + hitBlockFaceMax = hitBlockFaceZMax; + } + + // intersections are behind the start: + if (tMax < 0.0D) return null; + // intersections are to far away: + if (tMin > maxDistance) { + return null; + } + + // find the closest intersection: + double t; + BlockFace hitBlockFace; + if (tMin < 0.0D) { + t = tMax; + hitBlockFace = hitBlockFaceMax; + } else { + t = tMin; + hitBlockFace = hitBlockFaceMin; + } + // reusing the newly created direction vector for the hit position: + Vector hitPosition = dir.multiply(t).add(start); + return new RayTraceResult(hitPosition, hitBlockFace); + } + + @Override + public int hashCode() { + final int prime = 31; + int result = 1; + long temp; + temp = Double.doubleToLongBits(maxX); + result = prime * result + (int) (temp ^ (temp >>> 32)); + temp = Double.doubleToLongBits(maxY); + result = prime * result + (int) (temp ^ (temp >>> 32)); + temp = Double.doubleToLongBits(maxZ); + result = prime * result + (int) (temp ^ (temp >>> 32)); + temp = Double.doubleToLongBits(minX); + result = prime * result + (int) (temp ^ (temp >>> 32)); + temp = Double.doubleToLongBits(minY); + result = prime * result + (int) (temp ^ (temp >>> 32)); + temp = Double.doubleToLongBits(minZ); + result = prime * result + (int) (temp ^ (temp >>> 32)); + return result; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) return true; + if (!(obj instanceof BoundingBox)) return false; + BoundingBox other = (BoundingBox) obj; + if (Double.doubleToLongBits(maxX) != Double.doubleToLongBits(other.maxX)) return false; + if (Double.doubleToLongBits(maxY) != Double.doubleToLongBits(other.maxY)) return false; + if (Double.doubleToLongBits(maxZ) != Double.doubleToLongBits(other.maxZ)) return false; + if (Double.doubleToLongBits(minX) != Double.doubleToLongBits(other.minX)) return false; + if (Double.doubleToLongBits(minY) != Double.doubleToLongBits(other.minY)) return false; + if (Double.doubleToLongBits(minZ) != Double.doubleToLongBits(other.minZ)) return false; + return true; + } + + @Override + public String toString() { + StringBuilder builder = new StringBuilder(); + builder.append("BoundingBox [minX="); + builder.append(minX); + builder.append(", minY="); + builder.append(minY); + builder.append(", minZ="); + builder.append(minZ); + builder.append(", maxX="); + builder.append(maxX); + builder.append(", maxY="); + builder.append(maxY); + builder.append(", maxZ="); + builder.append(maxZ); + builder.append("]"); + return builder.toString(); + } + + /** + * Creates a copy of this bounding box. + * + * @return the cloned bounding box + */ + @NotNull + @Override + public BoundingBox clone() { + try { + return (BoundingBox) super.clone(); + } catch (CloneNotSupportedException e) { + throw new Error(e); + } + } + + @NotNull + @Override + public Map serialize() { + Map result = new LinkedHashMap(); + result.put("minX", minX); + result.put("minY", minY); + result.put("minZ", minZ); + result.put("maxX", maxX); + result.put("maxY", maxY); + result.put("maxZ", maxZ); + return result; + } + + @NotNull + public static BoundingBox deserialize(@NotNull Map args) { + double minX = 0.0D; + double minY = 0.0D; + double minZ = 0.0D; + double maxX = 0.0D; + double maxY = 0.0D; + double maxZ = 0.0D; + + if (args.containsKey("minX")) { + minX = ((Number) args.get("minX")).doubleValue(); + } + if (args.containsKey("minY")) { + minY = ((Number) args.get("minY")).doubleValue(); + } + if (args.containsKey("minZ")) { + minZ = ((Number) args.get("minZ")).doubleValue(); + } + if (args.containsKey("maxX")) { + maxX = ((Number) args.get("maxX")).doubleValue(); + } + if (args.containsKey("maxY")) { + maxY = ((Number) args.get("maxY")).doubleValue(); + } + if (args.containsKey("maxZ")) { + maxZ = ((Number) args.get("maxZ")).doubleValue(); + } + + return new BoundingBox(minX, minY, minZ, maxX, maxY, maxZ); + } +} diff --git a/api/src/main/java/org/bukkit/util/CachedServerIcon.java b/api/src/main/java/org/bukkit/util/CachedServerIcon.java new file mode 100644 index 000000000..bb4f7702c --- /dev/null +++ b/api/src/main/java/org/bukkit/util/CachedServerIcon.java @@ -0,0 +1,26 @@ +package org.bukkit.util; + +import org.bukkit.Server; +import org.bukkit.event.server.ServerListPingEvent; +import org.jetbrains.annotations.Nullable; + +/** + * This is a cached version of a server-icon. It's internal representation + * and implementation is undefined. + * + * @see Server#getServerIcon() + * @see Server#loadServerIcon(java.awt.image.BufferedImage) + * @see Server#loadServerIcon(java.io.File) + * @see ServerListPingEvent#setServerIcon(CachedServerIcon) + */ +public interface CachedServerIcon { + + @Nullable + public String getData(); // Paper + + // Paper start + default boolean isEmpty() { + return getData() == null; + } + // Paper end +} diff --git a/api/src/main/java/org/bukkit/util/ChatPaginator.java b/api/src/main/java/org/bukkit/util/ChatPaginator.java new file mode 100644 index 000000000..04358f17e --- /dev/null +++ b/api/src/main/java/org/bukkit/util/ChatPaginator.java @@ -0,0 +1,178 @@ +package org.bukkit.util; + +import org.bukkit.ChatColor; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +import java.util.Arrays; +import java.util.LinkedList; +import java.util.List; + +/** + * The ChatPaginator takes a raw string of arbitrary length and breaks it down + * into an array of strings appropriate for displaying on the Minecraft player + * console. + */ +public class ChatPaginator { + public static final int GUARANTEED_NO_WRAP_CHAT_PAGE_WIDTH = 55; // Will never wrap, even with the largest characters + public static final int AVERAGE_CHAT_PAGE_WIDTH = 65; // Will typically not wrap using an average character distribution + public static final int UNBOUNDED_PAGE_WIDTH = Integer.MAX_VALUE; + public static final int OPEN_CHAT_PAGE_HEIGHT = 20; // The height of an expanded chat window + public static final int CLOSED_CHAT_PAGE_HEIGHT = 10; // The height of the default chat window + public static final int UNBOUNDED_PAGE_HEIGHT = Integer.MAX_VALUE; + + /** + * Breaks a raw string up into pages using the default width and height. + * + * @param unpaginatedString The raw string to break. + * @param pageNumber The page number to fetch. + * @return A single chat page. + */ + @NotNull + public static ChatPage paginate(@Nullable String unpaginatedString, int pageNumber) { + return paginate(unpaginatedString, pageNumber, GUARANTEED_NO_WRAP_CHAT_PAGE_WIDTH, CLOSED_CHAT_PAGE_HEIGHT); + } + + /** + * Breaks a raw string up into pages using a provided width and height. + * + * @param unpaginatedString The raw string to break. + * @param pageNumber The page number to fetch. + * @param lineLength The desired width of a chat line. + * @param pageHeight The desired number of lines in a page. + * @return A single chat page. + */ + @NotNull + public static ChatPage paginate(@Nullable String unpaginatedString, int pageNumber, int lineLength, int pageHeight) { + String[] lines = wordWrap(unpaginatedString, lineLength); + + int totalPages = lines.length / pageHeight + (lines.length % pageHeight == 0 ? 0 : 1); + int actualPageNumber = pageNumber <= totalPages ? pageNumber : totalPages; + + int from = (actualPageNumber - 1) * pageHeight; + int to = from + pageHeight <= lines.length ? from + pageHeight : lines.length; + String[] selectedLines = Arrays.copyOfRange(lines, from, to); + + return new ChatPage(selectedLines, actualPageNumber, totalPages); + } + + /** + * Breaks a raw string up into a series of lines. Words are wrapped using + * spaces as decimeters and the newline character is respected. + * + * @param rawString The raw string to break. + * @param lineLength The length of a line of text. + * @return An array of word-wrapped lines. + */ + @NotNull + public static String[] wordWrap(@Nullable String rawString, int lineLength) { + // A null string is a single line + if (rawString == null) { + return new String[] {""}; + } + + // A string shorter than the lineWidth is a single line + if (rawString.length() <= lineLength && !rawString.contains("\n")) { + return new String[] {rawString}; + } + + char[] rawChars = (rawString + ' ').toCharArray(); // add a trailing space to trigger pagination + StringBuilder word = new StringBuilder(); + StringBuilder line = new StringBuilder(); + List lines = new LinkedList(); + int lineColorChars = 0; + + for (int i = 0; i < rawChars.length; i++) { + char c = rawChars[i]; + + // skip chat color modifiers + if (c == ChatColor.COLOR_CHAR) { + word.append(ChatColor.getByChar(rawChars[i + 1])); + lineColorChars += 2; + i++; // Eat the next character as we have already processed it + continue; + } + + if (c == ' ' || c == '\n') { + if (line.length() == 0 && word.length() > lineLength) { // special case: extremely long word begins a line + for (String partialWord : word.toString().split("(?<=\\G.{" + lineLength + "})")) { + lines.add(partialWord); + } + } else if (line.length() + 1 + word.length() - lineColorChars == lineLength) { // Line exactly the correct length...newline + if (line.length() > 0) { + line.append(' '); + } + line.append(word); + lines.add(line.toString()); + line = new StringBuilder(); + lineColorChars = 0; + } else if (line.length() + 1 + word.length() - lineColorChars > lineLength) { // Line too long...break the line + for (String partialWord : word.toString().split("(?<=\\G.{" + lineLength + "})")) { + lines.add(line.toString()); + line = new StringBuilder(partialWord); + } + lineColorChars = 0; + } else { + if (line.length() > 0) { + line.append(' '); + } + line.append(word); + } + word = new StringBuilder(); + + if (c == '\n') { // Newline forces the line to flush + lines.add(line.toString()); + line = new StringBuilder(); + } + } else { + word.append(c); + } + } + + if (line.length() > 0) { // Only add the last line if there is anything to add + lines.add(line.toString()); + } + + // Iterate over the wrapped lines, applying the last color from one line to the beginning of the next + if (lines.get(0).length() == 0 || lines.get(0).charAt(0) != ChatColor.COLOR_CHAR) { + lines.set(0, ChatColor.WHITE + lines.get(0)); + } + for (int i = 1; i < lines.size(); i++) { + final String pLine = lines.get(i - 1); + final String subLine = lines.get(i); + + char color = pLine.charAt(pLine.lastIndexOf(ChatColor.COLOR_CHAR) + 1); + if (subLine.length() == 0 || subLine.charAt(0) != ChatColor.COLOR_CHAR) { + lines.set(i, ChatColor.getByChar(color) + subLine); + } + } + + return lines.toArray(new String[lines.size()]); + } + + public static class ChatPage { + + private String[] lines; + private int pageNumber; + private int totalPages; + + public ChatPage(@NotNull String[] lines, int pageNumber, int totalPages) { + this.lines = lines; + this.pageNumber = pageNumber; + this.totalPages = totalPages; + } + + public int getPageNumber() { + return pageNumber; + } + + public int getTotalPages() { + return totalPages; + } + + @NotNull + public String[] getLines() { + return lines; + } + } +} diff --git a/api/src/main/java/org/bukkit/util/Consumer.java b/api/src/main/java/org/bukkit/util/Consumer.java new file mode 100644 index 000000000..fb9e6b90f --- /dev/null +++ b/api/src/main/java/org/bukkit/util/Consumer.java @@ -0,0 +1,17 @@ +package org.bukkit.util; + +/** + * Represents an operation that accepts a single input argument and returns no + * result. + * + * @param the type of the input to the operation + */ +public interface Consumer { + + /** + * Performs this operation on the given argument. + * + * @param t the input argument + */ + void accept(T t); +} diff --git a/api/src/main/java/org/bukkit/util/EulerAngle.java b/api/src/main/java/org/bukkit/util/EulerAngle.java new file mode 100644 index 000000000..201cb4a8c --- /dev/null +++ b/api/src/main/java/org/bukkit/util/EulerAngle.java @@ -0,0 +1,154 @@ +package org.bukkit.util; + +import org.jetbrains.annotations.NotNull; + +/** + * EulerAngle is used to represent 3 angles, one for each + * axis (x, y, z). The angles are in radians + */ +public class EulerAngle { + + /** + * A EulerAngle with every axis set to 0 + */ + public static final EulerAngle ZERO = new EulerAngle(0, 0, 0); + + private final double x; + private final double y; + private final double z; + + /** + * Creates a EularAngle with each axis set to the + * passed angle in radians + * + * @param x the angle for the x axis in radians + * @param y the angle for the y axis in radians + * @param z the angle for the z axis in radians + */ + public EulerAngle(double x, double y, double z) { + this.x = x; + this.y = y; + this.z = z; + } + + /** + * Returns the angle on the x axis in radians + * + * @return the angle in radians + */ + public double getX() { + return x; + } + + /** + * Returns the angle on the y axis in radians + * + * @return the angle in radians + */ + public double getY() { + return y; + } + + /** + * Returns the angle on the z axis in radians + * + * @return the angle in radians + */ + public double getZ() { + return z; + } + + /** + * Return a EulerAngle which is the result of changing + * the x axis to the passed angle + * + * @param x the angle in radians + * @return the resultant EulerAngle + */ + @NotNull + public EulerAngle setX(double x) { + return new EulerAngle(x, y, z); + } + + /** + * Return a EulerAngle which is the result of changing + * the y axis to the passed angle + * + * @param y the angle in radians + * @return the resultant EulerAngle + */ + @NotNull + public EulerAngle setY(double y) { + return new EulerAngle(x, y, z); + } + + /** + * Return a EulerAngle which is the result of changing + * the z axis to the passed angle + * + * @param z the angle in radians + * @return the resultant EulerAngle + */ + @NotNull + public EulerAngle setZ(double z) { + return new EulerAngle(x, y, z); + } + + /** + * Creates a new EulerAngle which is the result of adding + * the x, y, z components to this EulerAngle + * + * @param x the angle to add to the x axis in radians + * @param y the angle to add to the y axis in radians + * @param z the angle to add to the z axis in radians + * @return the resultant EulerAngle + */ + @NotNull + public EulerAngle add(double x, double y, double z) { + return new EulerAngle( + this.x + x, + this.y + y, + this.z + z + ); + } + + /** + * Creates a new EulerAngle which is the result of subtracting + * the x, y, z components to this EulerAngle + * + * @param x the angle to subtract to the x axis in radians + * @param y the angle to subtract to the y axis in radians + * @param z the angle to subtract to the z axis in radians + * @return the resultant EulerAngle + */ + @NotNull + public EulerAngle subtract(double x, double y, double z) { + return add(-x, -y, -z); + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + + EulerAngle that = (EulerAngle) o; + + return Double.compare(that.x, x) == 0 + && Double.compare(that.y, y) == 0 + && Double.compare(that.z, z) == 0; + + } + + @Override + public int hashCode() { + int result; + long temp; + temp = Double.doubleToLongBits(x); + result = (int) (temp ^ (temp >>> 32)); + temp = Double.doubleToLongBits(y); + result = 31 * result + (int) (temp ^ (temp >>> 32)); + temp = Double.doubleToLongBits(z); + result = 31 * result + (int) (temp ^ (temp >>> 32)); + return result; + } +} diff --git a/api/src/main/java/org/bukkit/util/FileUtil.java b/api/src/main/java/org/bukkit/util/FileUtil.java new file mode 100644 index 000000000..af75ce2e7 --- /dev/null +++ b/api/src/main/java/org/bukkit/util/FileUtil.java @@ -0,0 +1,59 @@ +package org.bukkit.util; + +import org.jetbrains.annotations.NotNull; + +import java.nio.channels.FileChannel; +import java.io.File; +import java.io.FileInputStream; +import java.io.FileOutputStream; +import java.io.IOException; + +/** + * Class containing file utilities + */ +public class FileUtil { + + /** + * This method copies one file to another location + * + * @param inFile the source filename + * @param outFile the target filename + * @return true on success + */ + public static boolean copy(@NotNull File inFile, @NotNull File outFile) { + if (!inFile.exists()) { + return false; + } + + FileChannel in = null; + FileChannel out = null; + + try { + in = new FileInputStream(inFile).getChannel(); + out = new FileOutputStream(outFile).getChannel(); + + long pos = 0; + long size = in.size(); + + while (pos < size) { + pos += in.transferTo(pos, 10 * 1024 * 1024, out); + } + } catch (IOException ioe) { + return false; + } finally { + try { + if (in != null) { + in.close(); + } + if (out != null) { + out.close(); + } + } catch (IOException ioe) { + return false; + } + } + + return true; + + } +} diff --git a/api/src/main/java/org/bukkit/util/NumberConversions.java b/api/src/main/java/org/bukkit/util/NumberConversions.java new file mode 100644 index 000000000..e10b9a4e5 --- /dev/null +++ b/api/src/main/java/org/bukkit/util/NumberConversions.java @@ -0,0 +1,127 @@ +package org.bukkit.util; + +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +/** + * Utils for casting number types to other number types + */ +public final class NumberConversions { + private NumberConversions() {} + + public static int floor(double num) { + final int floor = (int) num; + return floor == num ? floor : floor - (int) (Double.doubleToRawLongBits(num) >>> 63); + } + + public static int ceil(final double num) { + final int floor = (int) num; + return floor == num ? floor : floor + (int) (~Double.doubleToRawLongBits(num) >>> 63); + } + + public static int round(double num) { + return floor(num + 0.5d); + } + + public static double square(double num) { + return num * num; + } + + public static int toInt(@Nullable Object object) { + if (object instanceof Number) { + return ((Number) object).intValue(); + } + + try { + return Integer.parseInt(object.toString()); + } catch (NumberFormatException e) { + } catch (NullPointerException e) { + } + return 0; + } + + public static float toFloat(@Nullable Object object) { + if (object instanceof Number) { + return ((Number) object).floatValue(); + } + + try { + return Float.parseFloat(object.toString()); + } catch (NumberFormatException e) { + } catch (NullPointerException e) { + } + return 0; + } + + public static double toDouble(@Nullable Object object) { + if (object instanceof Number) { + return ((Number) object).doubleValue(); + } + + try { + return Double.parseDouble(object.toString()); + } catch (NumberFormatException e) { + } catch (NullPointerException e) { + } + return 0; + } + + public static long toLong(@Nullable Object object) { + if (object instanceof Number) { + return ((Number) object).longValue(); + } + + try { + return Long.parseLong(object.toString()); + } catch (NumberFormatException e) { + } catch (NullPointerException e) { + } + return 0; + } + + public static short toShort(@Nullable Object object) { + if (object instanceof Number) { + return ((Number) object).shortValue(); + } + + try { + return Short.parseShort(object.toString()); + } catch (NumberFormatException e) { + } catch (NullPointerException e) { + } + return 0; + } + + public static byte toByte(@Nullable Object object) { + if (object instanceof Number) { + return ((Number) object).byteValue(); + } + + try { + return Byte.parseByte(object.toString()); + } catch (NumberFormatException e) { + } catch (NullPointerException e) { + } + return 0; + } + + public static boolean isFinite(double d) { + return Math.abs(d) <= Double.MAX_VALUE; + } + + public static boolean isFinite(float f) { + return Math.abs(f) <= Float.MAX_VALUE; + } + + public static void checkFinite(double d, @NotNull String message) { + if (!isFinite(d)) { + throw new IllegalArgumentException(message); + } + } + + public static void checkFinite(float d, @NotNull String message) { + if (!isFinite(d)) { + throw new IllegalArgumentException(message); + } + } +} diff --git a/api/src/main/java/org/bukkit/util/RayTraceResult.java b/api/src/main/java/org/bukkit/util/RayTraceResult.java new file mode 100644 index 000000000..054630191 --- /dev/null +++ b/api/src/main/java/org/bukkit/util/RayTraceResult.java @@ -0,0 +1,163 @@ +package org.bukkit.util; + +import java.util.Objects; + +import org.apache.commons.lang.Validate; +import org.bukkit.block.Block; +import org.bukkit.block.BlockFace; +import org.bukkit.entity.Entity; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +/** + * The hit result of a ray trace. + *

+ * Only the hit position is guaranteed to always be available. The availability + * of the other attributes depends on what got hit and on the context in which + * the ray trace was performed. + */ +public class RayTraceResult { + + private final Vector hitPosition; + + private final Block hitBlock; + private final BlockFace hitBlockFace; + private final Entity hitEntity; + + private RayTraceResult(@NotNull Vector hitPosition, @Nullable Block hitBlock, @Nullable BlockFace hitBlockFace, @Nullable Entity hitEntity) { + Validate.notNull(hitPosition, "Hit position is null!"); + this.hitPosition = hitPosition.clone(); + this.hitBlock = hitBlock; + this.hitBlockFace = hitBlockFace; + this.hitEntity = hitEntity; + } + + /** + * Creates a RayTraceResult. + * + * @param hitPosition the hit position + */ + public RayTraceResult(@NotNull Vector hitPosition) { + this(hitPosition, null, null, null); + } + + /** + * Creates a RayTraceResult. + * + * @param hitPosition the hit position + * @param hitBlockFace the hit block face + */ + public RayTraceResult(@NotNull Vector hitPosition, @Nullable BlockFace hitBlockFace) { + this(hitPosition, null, hitBlockFace, null); + } + + /** + * Creates a RayTraceResult. + * + * @param hitPosition the hit position + * @param hitBlock the hit block + * @param hitBlockFace the hit block face + */ + public RayTraceResult(@NotNull Vector hitPosition, @Nullable Block hitBlock, @Nullable BlockFace hitBlockFace) { + this(hitPosition, hitBlock, hitBlockFace, null); + } + + /** + * Creates a RayTraceResult. + * + * @param hitPosition the hit position + * @param hitEntity the hit entity + */ + public RayTraceResult(@NotNull Vector hitPosition, @Nullable Entity hitEntity) { + this(hitPosition, null, null, hitEntity); + } + + /** + * Creates a RayTraceResult. + * + * @param hitPosition the hit position + * @param hitEntity the hit entity + * @param hitBlockFace the hit block face + */ + public RayTraceResult(@NotNull Vector hitPosition, @Nullable Entity hitEntity, @Nullable BlockFace hitBlockFace) { + this(hitPosition, null, hitBlockFace, hitEntity); + } + + /** + * Gets the exact position of the hit. + * + * @return a copy of the exact hit position + */ + @NotNull + public Vector getHitPosition() { + return hitPosition.clone(); + } + + /** + * Gets the hit block. + * + * @return the hit block, or null if not available + */ + @Nullable + public Block getHitBlock() { + return hitBlock; + } + + /** + * Gets the hit block face. + * + * @return the hit block face, or null if not available + */ + @Nullable + public BlockFace getHitBlockFace() { + return hitBlockFace; + } + + /** + * Gets the hit entity. + * + * @return the hit entity, or null if not available + */ + @Nullable + public Entity getHitEntity() { + return hitEntity; + } + + @Override + public int hashCode() { + final int prime = 31; + int result = 1; + result = prime * result + hitPosition.hashCode(); + result = prime * result + ((hitBlock == null) ? 0 : hitBlock.hashCode()); + result = prime * result + ((hitBlockFace == null) ? 0 : hitBlockFace.hashCode()); + result = prime * result + ((hitEntity == null) ? 0 : hitEntity.hashCode()); + return result; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) return true; + if (!(obj instanceof RayTraceResult)) return false; + RayTraceResult other = (RayTraceResult) obj; + if (!hitPosition.equals(other.hitPosition)) return false; + if (!Objects.equals(hitBlock, other.hitBlock)) return false; + if (!Objects.equals(hitBlockFace, other.hitBlockFace)) return false; + if (!Objects.equals(hitEntity, other.hitEntity)) return false; + return true; + } + + @Override + public String toString() { + StringBuilder builder = new StringBuilder(); + builder.append("RayTraceResult [hitPosition="); + builder.append(hitPosition); + builder.append(", hitBlock="); + builder.append(hitBlock); + builder.append(", hitBlockFace="); + builder.append(hitBlockFace); + builder.append(", hitEntity="); + builder.append(hitEntity); + builder.append("]"); + return builder.toString(); + } +} diff --git a/api/src/main/java/org/bukkit/util/StringUtil.java b/api/src/main/java/org/bukkit/util/StringUtil.java new file mode 100644 index 000000000..458d5ebdf --- /dev/null +++ b/api/src/main/java/org/bukkit/util/StringUtil.java @@ -0,0 +1,60 @@ +package org.bukkit.util; + +import java.util.Collection; +import org.apache.commons.lang.Validate; +import org.jetbrains.annotations.NotNull; + +public class StringUtil { + + /** + * Copies all elements from the iterable collection of originals to the + * collection provided. + * + * @param the collection of strings + * @param token String to search for + * @param originals An iterable collection of strings to filter. + * @param collection The collection to add matches to + * @return the collection provided that would have the elements copied + * into + * @throws UnsupportedOperationException if the collection is immutable + * and originals contains a string which starts with the specified + * search string. + * @throws IllegalArgumentException if any parameter is is null + * @throws IllegalArgumentException if originals contains a null element. + * Note: the collection may be modified before this is thrown + */ + @NotNull + public static > T copyPartialMatches(@NotNull final String token, @NotNull final Iterable originals, @NotNull final T collection) throws UnsupportedOperationException, IllegalArgumentException { + Validate.notNull(token, "Search token cannot be null"); + Validate.notNull(collection, "Collection cannot be null"); + Validate.notNull(originals, "Originals cannot be null"); + + for (String string : originals) { + if (startsWithIgnoreCase(string, token)) { + collection.add(string); + } + } + + return collection; + } + + /** + * This method uses a region to check case-insensitive equality. This + * means the internal array does not need to be copied like a + * toLowerCase() call would. + * + * @param string String to check + * @param prefix Prefix of string to compare + * @return true if provided string starts with, ignoring case, the prefix + * provided + * @throws NullPointerException if prefix is null + * @throws IllegalArgumentException if string is null + */ + public static boolean startsWithIgnoreCase(@NotNull final String string, @NotNull final String prefix) throws IllegalArgumentException, NullPointerException { + Validate.notNull(string, "Cannot check a null string for a match"); + if (string.length() < prefix.length()) { + return false; + } + return string.regionMatches(true, 0, prefix, 0, prefix.length()); + } +} diff --git a/api/src/main/java/org/bukkit/util/Vector.java b/api/src/main/java/org/bukkit/util/Vector.java new file mode 100644 index 000000000..b218a1dc8 --- /dev/null +++ b/api/src/main/java/org/bukkit/util/Vector.java @@ -0,0 +1,878 @@ +package org.bukkit.util; + +import com.google.common.base.Preconditions; +import java.util.LinkedHashMap; +import java.util.Map; +import java.util.Random; +import org.bukkit.Location; +import org.bukkit.World; +import org.bukkit.configuration.serialization.ConfigurationSerializable; +import org.bukkit.configuration.serialization.SerializableAs; +import org.jetbrains.annotations.NotNull; + +/** + * Represents a mutable vector. Because the components of Vectors are mutable, + * storing Vectors long term may be dangerous if passing code modifies the + * Vector later. If you want to keep around a Vector, it may be wise to call + * clone() in order to get a copy. + */ +@SerializableAs("Vector") +public class Vector implements Cloneable, ConfigurationSerializable { + private static final long serialVersionUID = -2657651106777219169L; + + private static Random random = new Random(); + + /** + * Threshold for fuzzy equals(). + */ + private static final double epsilon = 0.000001; + + protected double x; + protected double y; + protected double z; + + /** + * Construct the vector with all components as 0. + */ + public Vector() { + this.x = 0; + this.y = 0; + this.z = 0; + } + + /** + * Construct the vector with provided integer components. + * + * @param x X component + * @param y Y component + * @param z Z component + */ + public Vector(int x, int y, int z) { + this.x = x; + this.y = y; + this.z = z; + } + + /** + * Construct the vector with provided double components. + * + * @param x X component + * @param y Y component + * @param z Z component + */ + public Vector(double x, double y, double z) { + this.x = x; + this.y = y; + this.z = z; + } + + /** + * Construct the vector with provided float components. + * + * @param x X component + * @param y Y component + * @param z Z component + */ + public Vector(float x, float y, float z) { + this.x = x; + this.y = y; + this.z = z; + } + + /** + * Adds a vector to this one + * + * @param vec The other vector + * @return the same vector + */ + @NotNull + public Vector add(@NotNull Vector vec) { + x += vec.x; + y += vec.y; + z += vec.z; + return this; + } + + /** + * Subtracts a vector from this one. + * + * @param vec The other vector + * @return the same vector + */ + @NotNull + public Vector subtract(@NotNull Vector vec) { + x -= vec.x; + y -= vec.y; + z -= vec.z; + return this; + } + + /** + * Multiplies the vector by another. + * + * @param vec The other vector + * @return the same vector + */ + @NotNull + public Vector multiply(@NotNull Vector vec) { + x *= vec.x; + y *= vec.y; + z *= vec.z; + return this; + } + + /** + * Divides the vector by another. + * + * @param vec The other vector + * @return the same vector + */ + @NotNull + public Vector divide(@NotNull Vector vec) { + x /= vec.x; + y /= vec.y; + z /= vec.z; + return this; + } + + /** + * Copies another vector + * + * @param vec The other vector + * @return the same vector + */ + @NotNull + public Vector copy(@NotNull Vector vec) { + x = vec.x; + y = vec.y; + z = vec.z; + return this; + } + + /** + * Gets the magnitude of the vector, defined as sqrt(x^2+y^2+z^2). The + * value of this method is not cached and uses a costly square-root + * function, so do not repeatedly call this method to get the vector's + * magnitude. NaN will be returned if the inner result of the sqrt() + * function overflows, which will be caused if the length is too long. + * + * @return the magnitude + */ + public double length() { + return Math.sqrt(NumberConversions.square(x) + NumberConversions.square(y) + NumberConversions.square(z)); + } + + /** + * Gets the magnitude of the vector squared. + * + * @return the magnitude + */ + public double lengthSquared() { + return NumberConversions.square(x) + NumberConversions.square(y) + NumberConversions.square(z); + } + + /** + * Get the distance between this vector and another. The value of this + * method is not cached and uses a costly square-root function, so do not + * repeatedly call this method to get the vector's magnitude. NaN will be + * returned if the inner result of the sqrt() function overflows, which + * will be caused if the distance is too long. + * + * @param o The other vector + * @return the distance + */ + public double distance(@NotNull Vector o) { + return Math.sqrt(NumberConversions.square(x - o.x) + NumberConversions.square(y - o.y) + NumberConversions.square(z - o.z)); + } + + /** + * Get the squared distance between this vector and another. + * + * @param o The other vector + * @return the distance + */ + public double distanceSquared(@NotNull Vector o) { + return NumberConversions.square(x - o.x) + NumberConversions.square(y - o.y) + NumberConversions.square(z - o.z); + } + + /** + * Gets the angle between this vector and another in radians. + * + * @param other The other vector + * @return angle in radians + */ + public float angle(@NotNull Vector other) { + double dot = dot(other) / (length() * other.length()); + + return (float) Math.acos(dot); + } + + /** + * Sets this vector to the midpoint between this vector and another. + * + * @param other The other vector + * @return this same vector (now a midpoint) + */ + @NotNull + public Vector midpoint(@NotNull Vector other) { + x = (x + other.x) / 2; + y = (y + other.y) / 2; + z = (z + other.z) / 2; + return this; + } + + /** + * Gets a new midpoint vector between this vector and another. + * + * @param other The other vector + * @return a new midpoint vector + */ + @NotNull + public Vector getMidpoint(@NotNull Vector other) { + double x = (this.x + other.x) / 2; + double y = (this.y + other.y) / 2; + double z = (this.z + other.z) / 2; + return new Vector(x, y, z); + } + + /** + * Performs scalar multiplication, multiplying all components with a + * scalar. + * + * @param m The factor + * @return the same vector + */ + @NotNull + public Vector multiply(int m) { + x *= m; + y *= m; + z *= m; + return this; + } + + /** + * Performs scalar multiplication, multiplying all components with a + * scalar. + * + * @param m The factor + * @return the same vector + */ + @NotNull + public Vector multiply(double m) { + x *= m; + y *= m; + z *= m; + return this; + } + + /** + * Performs scalar multiplication, multiplying all components with a + * scalar. + * + * @param m The factor + * @return the same vector + */ + @NotNull + public Vector multiply(float m) { + x *= m; + y *= m; + z *= m; + return this; + } + + /** + * Calculates the dot product of this vector with another. The dot product + * is defined as x1*x2+y1*y2+z1*z2. The returned value is a scalar. + * + * @param other The other vector + * @return dot product + */ + public double dot(@NotNull Vector other) { + return x * other.x + y * other.y + z * other.z; + } + + /** + * Calculates the cross product of this vector with another. The cross + * product is defined as: + *

    + *
  • x = y1 * z2 - y2 * z1 + *
  • y = z1 * x2 - z2 * x1 + *
  • z = x1 * y2 - x2 * y1 + *
+ * + * @param o The other vector + * @return the same vector + */ + @NotNull + public Vector crossProduct(@NotNull Vector o) { + double newX = y * o.z - o.y * z; + double newY = z * o.x - o.z * x; + double newZ = x * o.y - o.x * y; + + x = newX; + y = newY; + z = newZ; + return this; + } + + /** + * Calculates the cross product of this vector with another without mutating + * the original. The cross product is defined as: + *
    + *
  • x = y1 * z2 - y2 * z1 + *
  • y = z1 * x2 - z2 * x1 + *
  • z = x1 * y2 - x2 * y1 + *
+ * + * @param o The other vector + * @return a new vector + */ + @NotNull + public Vector getCrossProduct(@NotNull Vector o) { + double x = this.y * o.z - o.y * this.z; + double y = this.z * o.x - o.z * this.x; + double z = this.x * o.y - o.x * this.y; + return new Vector(x, y, z); + } + + /** + * Converts this vector to a unit vector (a vector with length of 1). + * + * @return the same vector + */ + @NotNull + public Vector normalize() { + double length = length(); + + x /= length; + y /= length; + z /= length; + + return this; + } + + /** + * Zero this vector's components. + * + * @return the same vector + */ + @NotNull + public Vector zero() { + x = 0; + y = 0; + z = 0; + return this; + } + + /** + * Returns whether this vector is in an axis-aligned bounding box. + *

+ * The minimum and maximum vectors given must be truly the minimum and + * maximum X, Y and Z components. + * + * @param min Minimum vector + * @param max Maximum vector + * @return whether this vector is in the AABB + */ + public boolean isInAABB(@NotNull Vector min, @NotNull Vector max) { + return x >= min.x && x <= max.x && y >= min.y && y <= max.y && z >= min.z && z <= max.z; + } + + /** + * Returns whether this vector is within a sphere. + * + * @param origin Sphere origin. + * @param radius Sphere radius + * @return whether this vector is in the sphere + */ + public boolean isInSphere(@NotNull Vector origin, double radius) { + return (NumberConversions.square(origin.x - x) + NumberConversions.square(origin.y - y) + NumberConversions.square(origin.z - z)) <= NumberConversions.square(radius); + } + + /** + * Returns if a vector is normalized + * + * @return whether the vector is normalised + */ + public boolean isNormalized() { + return Math.abs(this.lengthSquared() - 1) < getEpsilon(); + } + + /** + * Rotates the vector around the x axis. + *

+ * This piece of math is based on the standard rotation matrix for vectors + * in three dimensional space. This matrix can be found here: + * Rotation + * Matrix. + * + * @param angle the angle to rotate the vector about. This angle is passed + * in radians + * @return the same vector + */ + @NotNull + public Vector rotateAroundX(double angle) { + double angleCos = Math.cos(angle); + double angleSin = Math.sin(angle); + + double y = angleCos * getY() - angleSin * getZ(); + double z = angleSin * getY() + angleCos * getZ(); + return setY(y).setZ(z); + } + + /** + * Rotates the vector around the y axis. + *

+ * This piece of math is based on the standard rotation matrix for vectors + * in three dimensional space. This matrix can be found here: + * Rotation + * Matrix. + * + * @param angle the angle to rotate the vector about. This angle is passed + * in radians + * @return the same vector + */ + @NotNull + public Vector rotateAroundY(double angle) { + double angleCos = Math.cos(angle); + double angleSin = Math.sin(angle); + + double x = angleCos * getX() + angleSin * getZ(); + double z = -angleSin * getX() + angleCos * getZ(); + return setX(x).setZ(z); + } + + /** + * Rotates the vector around the z axis + *

+ * This piece of math is based on the standard rotation matrix for vectors + * in three dimensional space. This matrix can be found here: + * Rotation + * Matrix. + * + * @param angle the angle to rotate the vector about. This angle is passed + * in radians + * @return the same vector + */ + @NotNull + public Vector rotateAroundZ(double angle) { + double angleCos = Math.cos(angle); + double angleSin = Math.sin(angle); + + double x = angleCos * getX() - angleSin * getY(); + double y = angleSin * getX() + angleCos * getY(); + return setX(x).setY(y); + } + + /** + * Rotates the vector around a given arbitrary axis in 3 dimensional space. + * + *

+ * Rotation will follow the general Right-Hand-Rule, which means rotation + * will be counterclockwise when the axis is pointing towards the observer. + *

+ * This method will always make sure the provided axis is a unit vector, to + * not modify the length of the vector when rotating. If you are experienced + * with the scaling of a non-unit axis vector, you can use + * {@link Vector#rotateAroundNonUnitAxis(Vector, double)}. + * + * @param axis the axis to rotate the vector around. If the passed vector is + * not of length 1, it gets copied and normalized before using it for the + * rotation. Please use {@link Vector#normalize()} on the instance before + * passing it to this method + * @param angle the angle to rotate the vector around the axis + * @return the same vector + * @throws IllegalArgumentException if the provided axis vector instance is + * null + */ + @NotNull + public Vector rotateAroundAxis(@NotNull Vector axis, double angle) throws IllegalArgumentException { + Preconditions.checkArgument(axis != null, "The provided axis vector was null"); + + return rotateAroundNonUnitAxis(axis.isNormalized() ? axis : axis.clone().normalize(), angle); + } + + /** + * Rotates the vector around a given arbitrary axis in 3 dimensional space. + * + *

+ * Rotation will follow the general Right-Hand-Rule, which means rotation + * will be counterclockwise when the axis is pointing towards the observer. + *

+ * Note that the vector length will change accordingly to the axis vector + * length. If the provided axis is not a unit vector, the rotated vector + * will not have its previous length. The scaled length of the resulting + * vector will be related to the axis vector. If you are not perfectly sure + * about the scaling of the vector, use + * {@link Vector#rotateAroundAxis(Vector, double)} + * + * @param axis the axis to rotate the vector around. + * @param angle the angle to rotate the vector around the axis + * @return the same vector + * @throws IllegalArgumentException if the provided axis vector instance is + * null + */ + @NotNull + public Vector rotateAroundNonUnitAxis(@NotNull Vector axis, double angle) throws IllegalArgumentException { + Preconditions.checkArgument(axis != null, "The provided axis vector was null"); + + double x = getX(), y = getY(), z = getZ(); + double x2 = axis.getX(), y2 = axis.getY(), z2 = axis.getZ(); + + double cosTheta = Math.cos(angle); + double sinTheta = Math.sin(angle); + double dotProduct = this.dot(axis); + + double xPrime = x2 * dotProduct * (1d - cosTheta) + + x * cosTheta + + (-z2 * y + y2 * z) * sinTheta; + double yPrime = y2 * dotProduct * (1d - cosTheta) + + y * cosTheta + + (z2 * x - x2 * z) * sinTheta; + double zPrime = z2 * dotProduct * (1d - cosTheta) + + z * cosTheta + + (-y2 * x + x2 * y) * sinTheta; + + return setX(xPrime).setY(yPrime).setZ(zPrime); + } + + /** + * Gets the X component. + * + * @return The X component. + */ + public double getX() { + return x; + } + + /** + * Gets the floored value of the X component, indicating the block that + * this vector is contained with. + * + * @return block X + */ + public int getBlockX() { + return NumberConversions.floor(x); + } + + /** + * Gets the Y component. + * + * @return The Y component. + */ + public double getY() { + return y; + } + + /** + * Gets the floored value of the Y component, indicating the block that + * this vector is contained with. + * + * @return block y + */ + public int getBlockY() { + return NumberConversions.floor(y); + } + + /** + * Gets the Z component. + * + * @return The Z component. + */ + public double getZ() { + return z; + } + + /** + * Gets the floored value of the Z component, indicating the block that + * this vector is contained with. + * + * @return block z + */ + public int getBlockZ() { + return NumberConversions.floor(z); + } + + /** + * Set the X component. + * + * @param x The new X component. + * @return This vector. + */ + @NotNull + public Vector setX(int x) { + this.x = x; + return this; + } + + /** + * Set the X component. + * + * @param x The new X component. + * @return This vector. + */ + @NotNull + public Vector setX(double x) { + this.x = x; + return this; + } + + /** + * Set the X component. + * + * @param x The new X component. + * @return This vector. + */ + @NotNull + public Vector setX(float x) { + this.x = x; + return this; + } + + /** + * Set the Y component. + * + * @param y The new Y component. + * @return This vector. + */ + @NotNull + public Vector setY(int y) { + this.y = y; + return this; + } + + /** + * Set the Y component. + * + * @param y The new Y component. + * @return This vector. + */ + @NotNull + public Vector setY(double y) { + this.y = y; + return this; + } + + /** + * Set the Y component. + * + * @param y The new Y component. + * @return This vector. + */ + @NotNull + public Vector setY(float y) { + this.y = y; + return this; + } + + /** + * Set the Z component. + * + * @param z The new Z component. + * @return This vector. + */ + @NotNull + public Vector setZ(int z) { + this.z = z; + return this; + } + + /** + * Set the Z component. + * + * @param z The new Z component. + * @return This vector. + */ + @NotNull + public Vector setZ(double z) { + this.z = z; + return this; + } + + /** + * Set the Z component. + * + * @param z The new Z component. + * @return This vector. + */ + @NotNull + public Vector setZ(float z) { + this.z = z; + return this; + } + + /** + * Checks to see if two objects are equal. + *

+ * Only two Vectors can ever return true. This method uses a fuzzy match + * to account for floating point errors. The epsilon can be retrieved + * with epsilon. + */ + @Override + public boolean equals(Object obj) { + if (!(obj instanceof Vector)) { + return false; + } + + Vector other = (Vector) obj; + + return Math.abs(x - other.x) < epsilon && Math.abs(y - other.y) < epsilon && Math.abs(z - other.z) < epsilon && (this.getClass().equals(obj.getClass())); + } + + /** + * Returns a hash code for this vector + * + * @return hash code + */ + @Override + public int hashCode() { + int hash = 7; + + hash = 79 * hash + (int) (Double.doubleToLongBits(this.x) ^ (Double.doubleToLongBits(this.x) >>> 32)); + hash = 79 * hash + (int) (Double.doubleToLongBits(this.y) ^ (Double.doubleToLongBits(this.y) >>> 32)); + hash = 79 * hash + (int) (Double.doubleToLongBits(this.z) ^ (Double.doubleToLongBits(this.z) >>> 32)); + return hash; + } + + /** + * Get a new vector. + * + * @return vector + */ + @NotNull + @Override + public Vector clone() { + try { + return (Vector) super.clone(); + } catch (CloneNotSupportedException e) { + throw new Error(e); + } + } + + /** + * Returns this vector's components as x,y,z. + */ + @Override + public String toString() { + return x + "," + y + "," + z; + } + + /** + * Gets a Location version of this vector with yaw and pitch being 0. + * + * @param world The world to link the location to. + * @return the location + */ + @NotNull + public Location toLocation(@NotNull World world) { + return new Location(world, x, y, z); + } + + /** + * Gets a Location version of this vector. + * + * @param world The world to link the location to. + * @param yaw The desired yaw. + * @param pitch The desired pitch. + * @return the location + */ + @NotNull + public Location toLocation(@NotNull World world, float yaw, float pitch) { + return new Location(world, x, y, z, yaw, pitch); + } + + /** + * Get the block vector of this vector. + * + * @return A block vector. + */ + @NotNull + public BlockVector toBlockVector() { + return new BlockVector(x, y, z); + } + + /** + * Check if each component of this Vector is finite. + * + * @throws IllegalArgumentException if any component is not finite + */ + public void checkFinite() throws IllegalArgumentException { + NumberConversions.checkFinite(x, "x not finite"); + NumberConversions.checkFinite(y, "y not finite"); + NumberConversions.checkFinite(z, "z not finite"); + } + + /** + * Get the threshold used for equals(). + * + * @return The epsilon. + */ + public static double getEpsilon() { + return epsilon; + } + + /** + * Gets the minimum components of two vectors. + * + * @param v1 The first vector. + * @param v2 The second vector. + * @return minimum + */ + @NotNull + public static Vector getMinimum(@NotNull Vector v1, @NotNull Vector v2) { + return new Vector(Math.min(v1.x, v2.x), Math.min(v1.y, v2.y), Math.min(v1.z, v2.z)); + } + + /** + * Gets the maximum components of two vectors. + * + * @param v1 The first vector. + * @param v2 The second vector. + * @return maximum + */ + @NotNull + public static Vector getMaximum(@NotNull Vector v1, @NotNull Vector v2) { + return new Vector(Math.max(v1.x, v2.x), Math.max(v1.y, v2.y), Math.max(v1.z, v2.z)); + } + + /** + * Gets a random vector with components having a random value between 0 + * and 1. + * + * @return A random vector. + */ + @NotNull + public static Vector getRandom() { + return new Vector(random.nextDouble(), random.nextDouble(), random.nextDouble()); + } + + @NotNull + public Map serialize() { + Map result = new LinkedHashMap(); + + result.put("x", getX()); + result.put("y", getY()); + result.put("z", getZ()); + + return result; + } + + @NotNull + public static Vector deserialize(@NotNull Map args) { + double x = 0; + double y = 0; + double z = 0; + + if (args.containsKey("x")) { + x = (Double) args.get("x"); + } + if (args.containsKey("y")) { + y = (Double) args.get("y"); + } + if (args.containsKey("z")) { + z = (Double) args.get("z"); + } + + return new Vector(x, y, z); + } +} diff --git a/api/src/main/java/org/bukkit/util/io/BukkitObjectInputStream.java b/api/src/main/java/org/bukkit/util/io/BukkitObjectInputStream.java new file mode 100644 index 000000000..13725531f --- /dev/null +++ b/api/src/main/java/org/bukkit/util/io/BukkitObjectInputStream.java @@ -0,0 +1,62 @@ +package org.bukkit.util.io; + +import java.io.IOException; +import java.io.InputStream; +import java.io.ObjectInputStream; + +import org.bukkit.configuration.serialization.ConfigurationSerializable; +import org.bukkit.configuration.serialization.ConfigurationSerialization; + +/** + * This class is designed to be used in conjunction with the {@link + * ConfigurationSerializable} API. It translates objects back to their + * original implementation after being serialized by {@link + * BukkitObjectInputStream}. + *

+ * Behavior of implementations extending this class is not guaranteed across + * future versions. + */ +public class BukkitObjectInputStream extends ObjectInputStream { + + /** + * Constructor provided to mirror super functionality. + * + * @throws IOException if an I/O error occurs while reading stream heade + * @see ObjectInputStream#ObjectInputStream() + */ + protected BukkitObjectInputStream() throws IOException, SecurityException { + super(); + super.enableResolveObject(true); + } + + /** + * Object input stream decoration constructor. + * + * @param in the input stream to wrap + * @throws IOException if an I/O error occurs while reading stream header + * @see ObjectInputStream#ObjectInputStream(InputStream) + */ + public BukkitObjectInputStream(InputStream in) throws IOException { + super(in); + super.enableResolveObject(true); + } + + @Override + protected Object resolveObject(Object obj) throws IOException { + if (obj instanceof Wrapper) { + try { + (obj = ConfigurationSerialization.deserializeObject(((Wrapper) obj).map)).getClass(); // NPE + } catch (Throwable ex) { + throw newIOException("Failed to deserialize object", ex); + } + } + + return super.resolveObject(obj); + } + + private static IOException newIOException(String string, Throwable cause) { + IOException exception = new IOException(string); + exception.initCause(cause); + return exception; + } +} diff --git a/api/src/main/java/org/bukkit/util/io/BukkitObjectOutputStream.java b/api/src/main/java/org/bukkit/util/io/BukkitObjectOutputStream.java new file mode 100644 index 000000000..845285025 --- /dev/null +++ b/api/src/main/java/org/bukkit/util/io/BukkitObjectOutputStream.java @@ -0,0 +1,52 @@ +package org.bukkit.util.io; + +import java.io.IOException; +import java.io.ObjectOutputStream; +import java.io.OutputStream; +import java.io.Serializable; + +import org.bukkit.configuration.serialization.ConfigurationSerializable; + +/** + * This class is designed to be used in conjunction with the {@link + * ConfigurationSerializable} API. It translates objects to an internal + * implementation for later deserialization using {@link + * BukkitObjectInputStream}. + *

+ * Behavior of implementations extending this class is not guaranteed across + * future versions. + */ +public class BukkitObjectOutputStream extends ObjectOutputStream { + + /** + * Constructor provided to mirror super functionality. + * + * @throws IOException if an I/O error occurs while writing stream header + * @see ObjectOutputStream#ObjectOutputStream() + */ + protected BukkitObjectOutputStream() throws IOException, SecurityException { + super(); + super.enableReplaceObject(true); + } + + /** + * Object output stream decoration constructor. + * + * @param out the stream to wrap + * @throws IOException if an I/O error occurs while writing stream header + * @see ObjectOutputStream#ObjectOutputStream(OutputStream) + */ + public BukkitObjectOutputStream(OutputStream out) throws IOException { + super(out); + super.enableReplaceObject(true); + } + + @Override + protected Object replaceObject(Object obj) throws IOException { + if (!(obj instanceof Serializable) && (obj instanceof ConfigurationSerializable)) { + obj = Wrapper.newWrapper((ConfigurationSerializable) obj); + } + + return super.replaceObject(obj); + } +} diff --git a/api/src/main/java/org/bukkit/util/io/Wrapper.java b/api/src/main/java/org/bukkit/util/io/Wrapper.java new file mode 100644 index 000000000..51dd951b0 --- /dev/null +++ b/api/src/main/java/org/bukkit/util/io/Wrapper.java @@ -0,0 +1,24 @@ +package org.bukkit.util.io; + +import java.io.Serializable; +import java.util.Map; + +import org.bukkit.configuration.serialization.ConfigurationSerializable; +import org.bukkit.configuration.serialization.ConfigurationSerialization; + +import com.google.common.collect.ImmutableMap; +import org.jetbrains.annotations.NotNull; + +class Wrapper & Serializable> implements Serializable { + private static final long serialVersionUID = -986209235411767547L; + + final T map; + + static Wrapper> newWrapper(@NotNull ConfigurationSerializable obj) { + return new Wrapper>(ImmutableMap.builder().put(ConfigurationSerialization.SERIALIZED_TYPE_KEY, ConfigurationSerialization.getAlias(obj.getClass())).putAll(obj.serialize()).build()); + } + + private Wrapper(@NotNull T map) { + this.map = map; + } +} diff --git a/api/src/main/java/org/bukkit/util/noise/NoiseGenerator.java b/api/src/main/java/org/bukkit/util/noise/NoiseGenerator.java new file mode 100644 index 000000000..72c92f33c --- /dev/null +++ b/api/src/main/java/org/bukkit/util/noise/NoiseGenerator.java @@ -0,0 +1,176 @@ +package org.bukkit.util.noise; + +/** + * Base class for all noise generators + */ +public abstract class NoiseGenerator { + protected final int perm[] = new int[512]; + protected double offsetX; + protected double offsetY; + protected double offsetZ; + + /** + * Speedy floor, faster than (int)Math.floor(x) + * + * @param x Value to floor + * @return Floored value + */ + public static int floor(double x) { + return x >= 0 ? (int) x : (int) x - 1; + } + + protected static double fade(double x) { + return x * x * x * (x * (x * 6 - 15) + 10); + } + + protected static double lerp(double x, double y, double z) { + return y + x * (z - y); + } + + protected static double grad(int hash, double x, double y, double z) { + hash &= 15; + double u = hash < 8 ? x : y; + double v = hash < 4 ? y : hash == 12 || hash == 14 ? x : z; + return ((hash & 1) == 0 ? u : -u) + ((hash & 2) == 0 ? v : -v); + } + + /** + * Computes and returns the 1D noise for the given coordinate in 1D space + * + * @param x X coordinate + * @return Noise at given location, from range -1 to 1 + */ + public double noise(double x) { + return noise(x, 0, 0); + } + + /** + * Computes and returns the 2D noise for the given coordinates in 2D space + * + * @param x X coordinate + * @param y Y coordinate + * @return Noise at given location, from range -1 to 1 + */ + public double noise(double x, double y) { + return noise(x, y, 0); + } + + /** + * Computes and returns the 3D noise for the given coordinates in 3D space + * + * @param x X coordinate + * @param y Y coordinate + * @param z Z coordinate + * @return Noise at given location, from range -1 to 1 + */ + public abstract double noise(double x, double y, double z); + + /** + * Generates noise for the 1D coordinates using the specified number of + * octaves and parameters + * + * @param x X-coordinate + * @param octaves Number of octaves to use + * @param frequency How much to alter the frequency by each octave + * @param amplitude How much to alter the amplitude by each octave + * @return Resulting noise + */ + public double noise(double x, int octaves, double frequency, double amplitude) { + return noise(x, 0, 0, octaves, frequency, amplitude); + } + + /** + * Generates noise for the 1D coordinates using the specified number of + * octaves and parameters + * + * @param x X-coordinate + * @param octaves Number of octaves to use + * @param frequency How much to alter the frequency by each octave + * @param amplitude How much to alter the amplitude by each octave + * @param normalized If true, normalize the value to [-1, 1] + * @return Resulting noise + */ + public double noise(double x, int octaves, double frequency, double amplitude, boolean normalized) { + return noise(x, 0, 0, octaves, frequency, amplitude, normalized); + } + + /** + * Generates noise for the 2D coordinates using the specified number of + * octaves and parameters + * + * @param x X-coordinate + * @param y Y-coordinate + * @param octaves Number of octaves to use + * @param frequency How much to alter the frequency by each octave + * @param amplitude How much to alter the amplitude by each octave + * @return Resulting noise + */ + public double noise(double x, double y, int octaves, double frequency, double amplitude) { + return noise(x, y, 0, octaves, frequency, amplitude); + } + + /** + * Generates noise for the 2D coordinates using the specified number of + * octaves and parameters + * + * @param x X-coordinate + * @param y Y-coordinate + * @param octaves Number of octaves to use + * @param frequency How much to alter the frequency by each octave + * @param amplitude How much to alter the amplitude by each octave + * @param normalized If true, normalize the value to [-1, 1] + * @return Resulting noise + */ + public double noise(double x, double y, int octaves, double frequency, double amplitude, boolean normalized) { + return noise(x, y, 0, octaves, frequency, amplitude, normalized); + } + + /** + * Generates noise for the 3D coordinates using the specified number of + * octaves and parameters + * + * @param x X-coordinate + * @param y Y-coordinate + * @param z Z-coordinate + * @param octaves Number of octaves to use + * @param frequency How much to alter the frequency by each octave + * @param amplitude How much to alter the amplitude by each octave + * @return Resulting noise + */ + public double noise(double x, double y, double z, int octaves, double frequency, double amplitude) { + return noise(x, y, z, octaves, frequency, amplitude, false); + } + + /** + * Generates noise for the 3D coordinates using the specified number of + * octaves and parameters + * + * @param x X-coordinate + * @param y Y-coordinate + * @param z Z-coordinate + * @param octaves Number of octaves to use + * @param frequency How much to alter the frequency by each octave + * @param amplitude How much to alter the amplitude by each octave + * @param normalized If true, normalize the value to [-1, 1] + * @return Resulting noise + */ + public double noise(double x, double y, double z, int octaves, double frequency, double amplitude, boolean normalized) { + double result = 0; + double amp = 1; + double freq = 1; + double max = 0; + + for (int i = 0; i < octaves; i++) { + result += noise(x * freq, y * freq, z * freq) * amp; + max += amp; + freq *= frequency; + amp *= amplitude; + } + + if (normalized) { + result /= max; + } + + return result; + } +} diff --git a/api/src/main/java/org/bukkit/util/noise/OctaveGenerator.java b/api/src/main/java/org/bukkit/util/noise/OctaveGenerator.java new file mode 100644 index 000000000..618ed706e --- /dev/null +++ b/api/src/main/java/org/bukkit/util/noise/OctaveGenerator.java @@ -0,0 +1,203 @@ +package org.bukkit.util.noise; + +import org.jetbrains.annotations.NotNull; + +/** + * Creates noise using unbiased octaves + */ +public abstract class OctaveGenerator { + @NotNull + protected final NoiseGenerator[] octaves; + protected double xScale = 1; + protected double yScale = 1; + protected double zScale = 1; + + protected OctaveGenerator(@NotNull NoiseGenerator[] octaves) { + this.octaves = octaves; + } + + /** + * Sets the scale used for all coordinates passed to this generator. + *

+ * This is the equivalent to setting each coordinate to the specified + * value. + * + * @param scale New value to scale each coordinate by + */ + public void setScale(double scale) { + setXScale(scale); + setYScale(scale); + setZScale(scale); + } + + /** + * Gets the scale used for each X-coordinates passed + * + * @return X scale + */ + public double getXScale() { + return xScale; + } + + /** + * Sets the scale used for each X-coordinates passed + * + * @param scale New X scale + */ + public void setXScale(double scale) { + xScale = scale; + } + + /** + * Gets the scale used for each Y-coordinates passed + * + * @return Y scale + */ + public double getYScale() { + return yScale; + } + + /** + * Sets the scale used for each Y-coordinates passed + * + * @param scale New Y scale + */ + public void setYScale(double scale) { + yScale = scale; + } + + /** + * Gets the scale used for each Z-coordinates passed + * + * @return Z scale + */ + public double getZScale() { + return zScale; + } + + /** + * Sets the scale used for each Z-coordinates passed + * + * @param scale New Z scale + */ + public void setZScale(double scale) { + zScale = scale; + } + + /** + * Gets a clone of the individual octaves used within this generator + * + * @return Clone of the individual octaves + */ + @NotNull + public NoiseGenerator[] getOctaves() { + return octaves.clone(); + } + + /** + * Generates noise for the 1D coordinates using the specified number of + * octaves and parameters + * + * @param x X-coordinate + * @param frequency How much to alter the frequency by each octave + * @param amplitude How much to alter the amplitude by each octave + * @return Resulting noise + */ + public double noise(double x, double frequency, double amplitude) { + return noise(x, 0, 0, frequency, amplitude); + } + + /** + * Generates noise for the 1D coordinates using the specified number of + * octaves and parameters + * + * @param x X-coordinate + * @param frequency How much to alter the frequency by each octave + * @param amplitude How much to alter the amplitude by each octave + * @param normalized If true, normalize the value to [-1, 1] + * @return Resulting noise + */ + public double noise(double x, double frequency, double amplitude, boolean normalized) { + return noise(x, 0, 0, frequency, amplitude, normalized); + } + + /** + * Generates noise for the 2D coordinates using the specified number of + * octaves and parameters + * + * @param x X-coordinate + * @param y Y-coordinate + * @param frequency How much to alter the frequency by each octave + * @param amplitude How much to alter the amplitude by each octave + * @return Resulting noise + */ + public double noise(double x, double y, double frequency, double amplitude) { + return noise(x, y, 0, frequency, amplitude); + } + + /** + * Generates noise for the 2D coordinates using the specified number of + * octaves and parameters + * + * @param x X-coordinate + * @param y Y-coordinate + * @param frequency How much to alter the frequency by each octave + * @param amplitude How much to alter the amplitude by each octave + * @param normalized If true, normalize the value to [-1, 1] + * @return Resulting noise + */ + public double noise(double x, double y, double frequency, double amplitude, boolean normalized) { + return noise(x, y, 0, frequency, amplitude, normalized); + } + + /** + * Generates noise for the 3D coordinates using the specified number of + * octaves and parameters + * + * @param x X-coordinate + * @param y Y-coordinate + * @param z Z-coordinate + * @param frequency How much to alter the frequency by each octave + * @param amplitude How much to alter the amplitude by each octave + * @return Resulting noise + */ + public double noise(double x, double y, double z, double frequency, double amplitude) { + return noise(x, y, z, frequency, amplitude, false); + } + + /** + * Generates noise for the 3D coordinates using the specified number of + * octaves and parameters + * + * @param x X-coordinate + * @param y Y-coordinate + * @param z Z-coordinate + * @param frequency How much to alter the frequency by each octave + * @param amplitude How much to alter the amplitude by each octave + * @param normalized If true, normalize the value to [-1, 1] + * @return Resulting noise + */ + public double noise(double x, double y, double z, double frequency, double amplitude, boolean normalized) { + double result = 0; + double amp = 1; + double freq = 1; + double max = 0; + + x *= xScale; + y *= yScale; + z *= zScale; + + for (NoiseGenerator octave : octaves) { + result += octave.noise(x * freq, y * freq, z * freq) * amp; + max += amp; + freq *= frequency; + amp *= amplitude; + } + + if (normalized) { + result /= max; + } + + return result; + } +} diff --git a/api/src/main/java/org/bukkit/util/noise/PerlinNoiseGenerator.java b/api/src/main/java/org/bukkit/util/noise/PerlinNoiseGenerator.java new file mode 100644 index 000000000..f481286b8 --- /dev/null +++ b/api/src/main/java/org/bukkit/util/noise/PerlinNoiseGenerator.java @@ -0,0 +1,219 @@ +package org.bukkit.util.noise; + +import java.util.Random; +import org.bukkit.World; +import org.jetbrains.annotations.NotNull; + +/** + * Generates noise using the "classic" perlin generator + * + * @see SimplexNoiseGenerator "Improved" and faster version with slightly + * different results + */ +public class PerlinNoiseGenerator extends NoiseGenerator { + protected static final int grad3[][] = {{1, 1, 0}, {-1, 1, 0}, {1, -1, 0}, {-1, -1, 0}, + {1, 0, 1}, {-1, 0, 1}, {1, 0, -1}, {-1, 0, -1}, + {0, 1, 1}, {0, -1, 1}, {0, 1, -1}, {0, -1, -1}}; + private static final PerlinNoiseGenerator instance = new PerlinNoiseGenerator(); + + protected PerlinNoiseGenerator() { + int p[] = {151, 160, 137, 91, 90, 15, 131, 13, 201, + 95, 96, 53, 194, 233, 7, 225, 140, 36, 103, 30, 69, 142, 8, 99, 37, + 240, 21, 10, 23, 190, 6, 148, 247, 120, 234, 75, 0, 26, 197, 62, + 94, 252, 219, 203, 117, 35, 11, 32, 57, 177, 33, 88, 237, 149, 56, + 87, 174, 20, 125, 136, 171, 168, 68, 175, 74, 165, 71, 134, 139, + 48, 27, 166, 77, 146, 158, 231, 83, 111, 229, 122, 60, 211, 133, + 230, 220, 105, 92, 41, 55, 46, 245, 40, 244, 102, 143, 54, 65, 25, + 63, 161, 1, 216, 80, 73, 209, 76, 132, 187, 208, 89, 18, 169, 200, + 196, 135, 130, 116, 188, 159, 86, 164, 100, 109, 198, 173, 186, 3, + 64, 52, 217, 226, 250, 124, 123, 5, 202, 38, 147, 118, 126, 255, + 82, 85, 212, 207, 206, 59, 227, 47, 16, 58, 17, 182, 189, 28, 42, + 223, 183, 170, 213, 119, 248, 152, 2, 44, 154, 163, 70, 221, 153, + 101, 155, 167, 43, 172, 9, 129, 22, 39, 253, 19, 98, 108, 110, 79, + 113, 224, 232, 178, 185, 112, 104, 218, 246, 97, 228, 251, 34, 242, + 193, 238, 210, 144, 12, 191, 179, 162, 241, 81, 51, 145, 235, 249, + 14, 239, 107, 49, 192, 214, 31, 181, 199, 106, 157, 184, 84, 204, + 176, 115, 121, 50, 45, 127, 4, 150, 254, 138, 236, 205, 93, 222, + 114, 67, 29, 24, 72, 243, 141, 128, 195, 78, 66, 215, 61, 156, 180}; + + for (int i = 0; i < 512; i++) { + perm[i] = p[i & 255]; + } + } + + /** + * Creates a seeded perlin noise generator for the given world + * + * @param world World to construct this generator for + */ + public PerlinNoiseGenerator(@NotNull World world) { + this(new Random(world.getSeed())); + } + + /** + * Creates a seeded perlin noise generator for the given seed + * + * @param seed Seed to construct this generator for + */ + public PerlinNoiseGenerator(long seed) { + this(new Random(seed)); + } + + /** + * Creates a seeded perlin noise generator with the given Random + * + * @param rand Random to construct with + */ + public PerlinNoiseGenerator(@NotNull Random rand) { + offsetX = rand.nextDouble() * 256; + offsetY = rand.nextDouble() * 256; + offsetZ = rand.nextDouble() * 256; + + for (int i = 0; i < 256; i++) { + perm[i] = rand.nextInt(256); + } + + for (int i = 0; i < 256; i++) { + int pos = rand.nextInt(256 - i) + i; + int old = perm[i]; + + perm[i] = perm[pos]; + perm[pos] = old; + perm[i + 256] = perm[i]; + } + } + + /** + * Computes and returns the 1D unseeded perlin noise for the given + * coordinates in 1D space + * + * @param x X coordinate + * @return Noise at given location, from range -1 to 1 + */ + public static double getNoise(double x) { + return instance.noise(x); + } + + /** + * Computes and returns the 2D unseeded perlin noise for the given + * coordinates in 2D space + * + * @param x X coordinate + * @param y Y coordinate + * @return Noise at given location, from range -1 to 1 + */ + public static double getNoise(double x, double y) { + return instance.noise(x, y); + } + + /** + * Computes and returns the 3D unseeded perlin noise for the given + * coordinates in 3D space + * + * @param x X coordinate + * @param y Y coordinate + * @param z Z coordinate + * @return Noise at given location, from range -1 to 1 + */ + public static double getNoise(double x, double y, double z) { + return instance.noise(x, y, z); + } + + /** + * Gets the singleton unseeded instance of this generator + * + * @return Singleton + */ + @NotNull + public static PerlinNoiseGenerator getInstance() { + return instance; + } + + @Override + public double noise(double x, double y, double z) { + x += offsetX; + y += offsetY; + z += offsetZ; + + int floorX = floor(x); + int floorY = floor(y); + int floorZ = floor(z); + + // Find unit cube containing the point + int X = floorX & 255; + int Y = floorY & 255; + int Z = floorZ & 255; + + // Get relative xyz coordinates of the point within the cube + x -= floorX; + y -= floorY; + z -= floorZ; + + // Compute fade curves for xyz + double fX = fade(x); + double fY = fade(y); + double fZ = fade(z); + + // Hash coordinates of the cube corners + int A = perm[X] + Y; + int AA = perm[A] + Z; + int AB = perm[A + 1] + Z; + int B = perm[X + 1] + Y; + int BA = perm[B] + Z; + int BB = perm[B + 1] + Z; + + return lerp(fZ, lerp(fY, lerp(fX, grad(perm[AA], x, y, z), + grad(perm[BA], x - 1, y, z)), + lerp(fX, grad(perm[AB], x, y - 1, z), + grad(perm[BB], x - 1, y - 1, z))), + lerp(fY, lerp(fX, grad(perm[AA + 1], x, y, z - 1), + grad(perm[BA + 1], x - 1, y, z - 1)), + lerp(fX, grad(perm[AB + 1], x, y - 1, z - 1), + grad(perm[BB + 1], x - 1, y - 1, z - 1)))); + } + + /** + * Generates noise for the 1D coordinates using the specified number of + * octaves and parameters + * + * @param x X-coordinate + * @param octaves Number of octaves to use + * @param frequency How much to alter the frequency by each octave + * @param amplitude How much to alter the amplitude by each octave + * @return Resulting noise + */ + public static double getNoise(double x, int octaves, double frequency, double amplitude) { + return instance.noise(x, octaves, frequency, amplitude); + } + + /** + * Generates noise for the 2D coordinates using the specified number of + * octaves and parameters + * + * @param x X-coordinate + * @param y Y-coordinate + * @param octaves Number of octaves to use + * @param frequency How much to alter the frequency by each octave + * @param amplitude How much to alter the amplitude by each octave + * @return Resulting noise + */ + public static double getNoise(double x, double y, int octaves, double frequency, double amplitude) { + return instance.noise(x, y, octaves, frequency, amplitude); + } + + /** + * Generates noise for the 3D coordinates using the specified number of + * octaves and parameters + * + * @param x X-coordinate + * @param y Y-coordinate + * @param z Z-coordinate + * @param octaves Number of octaves to use + * @param frequency How much to alter the frequency by each octave + * @param amplitude How much to alter the amplitude by each octave + * @return Resulting noise + */ + public static double getNoise(double x, double y, double z, int octaves, double frequency, double amplitude) { + return instance.noise(x, y, z, octaves, frequency, amplitude); + } +} diff --git a/api/src/main/java/org/bukkit/util/noise/PerlinOctaveGenerator.java b/api/src/main/java/org/bukkit/util/noise/PerlinOctaveGenerator.java new file mode 100644 index 000000000..af7e01188 --- /dev/null +++ b/api/src/main/java/org/bukkit/util/noise/PerlinOctaveGenerator.java @@ -0,0 +1,52 @@ +package org.bukkit.util.noise; + +import java.util.Random; +import org.bukkit.World; +import org.jetbrains.annotations.NotNull; + +/** + * Creates perlin noise through unbiased octaves + */ +public class PerlinOctaveGenerator extends OctaveGenerator { + + /** + * Creates a perlin octave generator for the given world + * + * @param world World to construct this generator for + * @param octaves Amount of octaves to create + */ + public PerlinOctaveGenerator(@NotNull World world, int octaves) { + this(new Random(world.getSeed()), octaves); + } + + /** + * Creates a perlin octave generator for the given world + * + * @param seed Seed to construct this generator for + * @param octaves Amount of octaves to create + */ + public PerlinOctaveGenerator(long seed, int octaves) { + this(new Random(seed), octaves); + } + + /** + * Creates a perlin octave generator for the given {@link Random} + * + * @param rand Random object to construct this generator for + * @param octaves Amount of octaves to create + */ + public PerlinOctaveGenerator(@NotNull Random rand, int octaves) { + super(createOctaves(rand, octaves)); + } + + @NotNull + private static NoiseGenerator[] createOctaves(@NotNull Random rand, int octaves) { + NoiseGenerator[] result = new NoiseGenerator[octaves]; + + for (int i = 0; i < octaves; i++) { + result[i] = new PerlinNoiseGenerator(rand); + } + + return result; + } +} diff --git a/api/src/main/java/org/bukkit/util/noise/SimplexNoiseGenerator.java b/api/src/main/java/org/bukkit/util/noise/SimplexNoiseGenerator.java new file mode 100644 index 000000000..850604a4d --- /dev/null +++ b/api/src/main/java/org/bukkit/util/noise/SimplexNoiseGenerator.java @@ -0,0 +1,522 @@ +package org.bukkit.util.noise; + +import java.util.Random; +import org.bukkit.World; +import org.jetbrains.annotations.NotNull; + +/** + * Generates simplex-based noise. + *

+ * This is a modified version of the freely published version in the paper by + * Stefan Gustavson at + * + * http://staffwww.itn.liu.se/~stegu/simplexnoise/simplexnoise.pdf + */ +public class SimplexNoiseGenerator extends PerlinNoiseGenerator { + protected static final double SQRT_3 = Math.sqrt(3); + protected static final double SQRT_5 = Math.sqrt(5); + protected static final double F2 = 0.5 * (SQRT_3 - 1); + protected static final double G2 = (3 - SQRT_3) / 6; + protected static final double G22 = G2 * 2.0 - 1; + protected static final double F3 = 1.0 / 3.0; + protected static final double G3 = 1.0 / 6.0; + protected static final double F4 = (SQRT_5 - 1.0) / 4.0; + protected static final double G4 = (5.0 - SQRT_5) / 20.0; + protected static final double G42 = G4 * 2.0; + protected static final double G43 = G4 * 3.0; + protected static final double G44 = G4 * 4.0 - 1.0; + protected static final int grad4[][] = {{0, 1, 1, 1}, {0, 1, 1, -1}, {0, 1, -1, 1}, {0, 1, -1, -1}, + {0, -1, 1, 1}, {0, -1, 1, -1}, {0, -1, -1, 1}, {0, -1, -1, -1}, + {1, 0, 1, 1}, {1, 0, 1, -1}, {1, 0, -1, 1}, {1, 0, -1, -1}, + {-1, 0, 1, 1}, {-1, 0, 1, -1}, {-1, 0, -1, 1}, {-1, 0, -1, -1}, + {1, 1, 0, 1}, {1, 1, 0, -1}, {1, -1, 0, 1}, {1, -1, 0, -1}, + {-1, 1, 0, 1}, {-1, 1, 0, -1}, {-1, -1, 0, 1}, {-1, -1, 0, -1}, + {1, 1, 1, 0}, {1, 1, -1, 0}, {1, -1, 1, 0}, {1, -1, -1, 0}, + {-1, 1, 1, 0}, {-1, 1, -1, 0}, {-1, -1, 1, 0}, {-1, -1, -1, 0}}; + protected static final int simplex[][] = { + {0, 1, 2, 3}, {0, 1, 3, 2}, {0, 0, 0, 0}, {0, 2, 3, 1}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {1, 2, 3, 0}, + {0, 2, 1, 3}, {0, 0, 0, 0}, {0, 3, 1, 2}, {0, 3, 2, 1}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {1, 3, 2, 0}, + {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, + {1, 2, 0, 3}, {0, 0, 0, 0}, {1, 3, 0, 2}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {2, 3, 0, 1}, {2, 3, 1, 0}, + {1, 0, 2, 3}, {1, 0, 3, 2}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {2, 0, 3, 1}, {0, 0, 0, 0}, {2, 1, 3, 0}, + {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, + {2, 0, 1, 3}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {3, 0, 1, 2}, {3, 0, 2, 1}, {0, 0, 0, 0}, {3, 1, 2, 0}, + {2, 1, 0, 3}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {3, 1, 0, 2}, {0, 0, 0, 0}, {3, 2, 0, 1}, {3, 2, 1, 0}}; + protected double offsetW; + private static final SimplexNoiseGenerator instance = new SimplexNoiseGenerator(); + + protected SimplexNoiseGenerator() { + super(); + } + + /** + * Creates a seeded simplex noise generator for the given world + * + * @param world World to construct this generator for + */ + public SimplexNoiseGenerator(@NotNull World world) { + this(new Random(world.getSeed())); + } + + /** + * Creates a seeded simplex noise generator for the given seed + * + * @param seed Seed to construct this generator for + */ + public SimplexNoiseGenerator(long seed) { + this(new Random(seed)); + } + + /** + * Creates a seeded simplex noise generator with the given Random + * + * @param rand Random to construct with + */ + public SimplexNoiseGenerator(@NotNull Random rand) { + super(rand); + offsetW = rand.nextDouble() * 256; + } + + protected static double dot(@NotNull int[] g, double x, double y) { + return g[0] * x + g[1] * y; + } + + protected static double dot(@NotNull int[] g, double x, double y, double z) { + return g[0] * x + g[1] * y + g[2] * z; + } + + protected static double dot(@NotNull int[] g, double x, double y, double z, double w) { + return g[0] * x + g[1] * y + g[2] * z + g[3] * w; + } + + /** + * Computes and returns the 1D unseeded simplex noise for the given + * coordinates in 1D space + * + * @param xin X coordinate + * @return Noise at given location, from range -1 to 1 + */ + public static double getNoise(double xin) { + return instance.noise(xin); + } + + /** + * Computes and returns the 2D unseeded simplex noise for the given + * coordinates in 2D space + * + * @param xin X coordinate + * @param yin Y coordinate + * @return Noise at given location, from range -1 to 1 + */ + public static double getNoise(double xin, double yin) { + return instance.noise(xin, yin); + } + + /** + * Computes and returns the 3D unseeded simplex noise for the given + * coordinates in 3D space + * + * @param xin X coordinate + * @param yin Y coordinate + * @param zin Z coordinate + * @return Noise at given location, from range -1 to 1 + */ + public static double getNoise(double xin, double yin, double zin) { + return instance.noise(xin, yin, zin); + } + + /** + * Computes and returns the 4D simplex noise for the given coordinates in + * 4D space + * + * @param x X coordinate + * @param y Y coordinate + * @param z Z coordinate + * @param w W coordinate + * @return Noise at given location, from range -1 to 1 + */ + public static double getNoise(double x, double y, double z, double w) { + return instance.noise(x, y, z, w); + } + + @Override + public double noise(double xin, double yin, double zin) { + xin += offsetX; + yin += offsetY; + zin += offsetZ; + + double n0, n1, n2, n3; // Noise contributions from the four corners + + // Skew the input space to determine which simplex cell we're in + double s = (xin + yin + zin) * F3; // Very nice and simple skew factor for 3D + int i = floor(xin + s); + int j = floor(yin + s); + int k = floor(zin + s); + double t = (i + j + k) * G3; + double X0 = i - t; // Unskew the cell origin back to (x,y,z) space + double Y0 = j - t; + double Z0 = k - t; + double x0 = xin - X0; // The x,y,z distances from the cell origin + double y0 = yin - Y0; + double z0 = zin - Z0; + + // For the 3D case, the simplex shape is a slightly irregular tetrahedron. + + // Determine which simplex we are in. + int i1, j1, k1; // Offsets for second corner of simplex in (i,j,k) coords + int i2, j2, k2; // Offsets for third corner of simplex in (i,j,k) coords + if (x0 >= y0) { + if (y0 >= z0) { + i1 = 1; + j1 = 0; + k1 = 0; + i2 = 1; + j2 = 1; + k2 = 0; + } // X Y Z order + else if (x0 >= z0) { + i1 = 1; + j1 = 0; + k1 = 0; + i2 = 1; + j2 = 0; + k2 = 1; + } // X Z Y order + else { + i1 = 0; + j1 = 0; + k1 = 1; + i2 = 1; + j2 = 0; + k2 = 1; + } // Z X Y order + } else { // x0 y0) { + i1 = 1; + j1 = 0; + } // lower triangle, XY order: (0,0)->(1,0)->(1,1) + else { + i1 = 0; + j1 = 1; + } // upper triangle, YX order: (0,0)->(0,1)->(1,1) + + // A step of (1,0) in (i,j) means a step of (1-c,-c) in (x,y), and + // a step of (0,1) in (i,j) means a step of (-c,1-c) in (x,y), where + // c = (3-sqrt(3))/6 + + double x1 = x0 - i1 + G2; // Offsets for middle corner in (x,y) unskewed coords + double y1 = y0 - j1 + G2; + double x2 = x0 + G22; // Offsets for last corner in (x,y) unskewed coords + double y2 = y0 + G22; + + // Work out the hashed gradient indices of the three simplex corners + int ii = i & 255; + int jj = j & 255; + int gi0 = perm[ii + perm[jj]] % 12; + int gi1 = perm[ii + i1 + perm[jj + j1]] % 12; + int gi2 = perm[ii + 1 + perm[jj + 1]] % 12; + + // Calculate the contribution from the three corners + double t0 = 0.5 - x0 * x0 - y0 * y0; + if (t0 < 0) { + n0 = 0.0; + } else { + t0 *= t0; + n0 = t0 * t0 * dot(grad3[gi0], x0, y0); // (x,y) of grad3 used for 2D gradient + } + + double t1 = 0.5 - x1 * x1 - y1 * y1; + if (t1 < 0) { + n1 = 0.0; + } else { + t1 *= t1; + n1 = t1 * t1 * dot(grad3[gi1], x1, y1); + } + + double t2 = 0.5 - x2 * x2 - y2 * y2; + if (t2 < 0) { + n2 = 0.0; + } else { + t2 *= t2; + n2 = t2 * t2 * dot(grad3[gi2], x2, y2); + } + + // Add contributions from each corner to get the final noise value. + // The result is scaled to return values in the interval [-1,1]. + return 70.0 * (n0 + n1 + n2); + } + + /** + * Computes and returns the 4D simplex noise for the given coordinates in + * 4D space + * + * @param x X coordinate + * @param y Y coordinate + * @param z Z coordinate + * @param w W coordinate + * @return Noise at given location, from range -1 to 1 + */ + public double noise(double x, double y, double z, double w) { + x += offsetX; + y += offsetY; + z += offsetZ; + w += offsetW; + + double n0, n1, n2, n3, n4; // Noise contributions from the five corners + + // Skew the (x,y,z,w) space to determine which cell of 24 simplices we're in + double s = (x + y + z + w) * F4; // Factor for 4D skewing + int i = floor(x + s); + int j = floor(y + s); + int k = floor(z + s); + int l = floor(w + s); + + double t = (i + j + k + l) * G4; // Factor for 4D unskewing + double X0 = i - t; // Unskew the cell origin back to (x,y,z,w) space + double Y0 = j - t; + double Z0 = k - t; + double W0 = l - t; + double x0 = x - X0; // The x,y,z,w distances from the cell origin + double y0 = y - Y0; + double z0 = z - Z0; + double w0 = w - W0; + + // For the 4D case, the simplex is a 4D shape I won't even try to describe. + // To find out which of the 24 possible simplices we're in, we need to + // determine the magnitude ordering of x0, y0, z0 and w0. + // The method below is a good way of finding the ordering of x,y,z,w and + // then find the correct traversal order for the simplex we’re in. + // First, six pair-wise comparisons are performed between each possible pair + // of the four coordinates, and the results are used to add up binary bits + // for an integer index. + int c1 = (x0 > y0) ? 32 : 0; + int c2 = (x0 > z0) ? 16 : 0; + int c3 = (y0 > z0) ? 8 : 0; + int c4 = (x0 > w0) ? 4 : 0; + int c5 = (y0 > w0) ? 2 : 0; + int c6 = (z0 > w0) ? 1 : 0; + int c = c1 + c2 + c3 + c4 + c5 + c6; + int i1, j1, k1, l1; // The integer offsets for the second simplex corner + int i2, j2, k2, l2; // The integer offsets for the third simplex corner + int i3, j3, k3, l3; // The integer offsets for the fourth simplex corner + + // simplex[c] is a 4-vector with the numbers 0, 1, 2 and 3 in some order. + // Many values of c will never occur, since e.g. x>y>z>w makes x= 3 ? 1 : 0; + j1 = simplex[c][1] >= 3 ? 1 : 0; + k1 = simplex[c][2] >= 3 ? 1 : 0; + l1 = simplex[c][3] >= 3 ? 1 : 0; + + // The number 2 in the "simplex" array is at the second largest coordinate. + i2 = simplex[c][0] >= 2 ? 1 : 0; + j2 = simplex[c][1] >= 2 ? 1 : 0; + k2 = simplex[c][2] >= 2 ? 1 : 0; + l2 = simplex[c][3] >= 2 ? 1 : 0; + + // The number 1 in the "simplex" array is at the second smallest coordinate. + i3 = simplex[c][0] >= 1 ? 1 : 0; + j3 = simplex[c][1] >= 1 ? 1 : 0; + k3 = simplex[c][2] >= 1 ? 1 : 0; + l3 = simplex[c][3] >= 1 ? 1 : 0; + + // The fifth corner has all coordinate offsets = 1, so no need to look that up. + + double x1 = x0 - i1 + G4; // Offsets for second corner in (x,y,z,w) coords + double y1 = y0 - j1 + G4; + double z1 = z0 - k1 + G4; + double w1 = w0 - l1 + G4; + + double x2 = x0 - i2 + G42; // Offsets for third corner in (x,y,z,w) coords + double y2 = y0 - j2 + G42; + double z2 = z0 - k2 + G42; + double w2 = w0 - l2 + G42; + + double x3 = x0 - i3 + G43; // Offsets for fourth corner in (x,y,z,w) coords + double y3 = y0 - j3 + G43; + double z3 = z0 - k3 + G43; + double w3 = w0 - l3 + G43; + + double x4 = x0 + G44; // Offsets for last corner in (x,y,z,w) coords + double y4 = y0 + G44; + double z4 = z0 + G44; + double w4 = w0 + G44; + + // Work out the hashed gradient indices of the five simplex corners + int ii = i & 255; + int jj = j & 255; + int kk = k & 255; + int ll = l & 255; + + int gi0 = perm[ii + perm[jj + perm[kk + perm[ll]]]] % 32; + int gi1 = perm[ii + i1 + perm[jj + j1 + perm[kk + k1 + perm[ll + l1]]]] % 32; + int gi2 = perm[ii + i2 + perm[jj + j2 + perm[kk + k2 + perm[ll + l2]]]] % 32; + int gi3 = perm[ii + i3 + perm[jj + j3 + perm[kk + k3 + perm[ll + l3]]]] % 32; + int gi4 = perm[ii + 1 + perm[jj + 1 + perm[kk + 1 + perm[ll + 1]]]] % 32; + + // Calculate the contribution from the five corners + double t0 = 0.6 - x0 * x0 - y0 * y0 - z0 * z0 - w0 * w0; + if (t0 < 0) { + n0 = 0.0; + } else { + t0 *= t0; + n0 = t0 * t0 * dot(grad4[gi0], x0, y0, z0, w0); + } + + double t1 = 0.6 - x1 * x1 - y1 * y1 - z1 * z1 - w1 * w1; + if (t1 < 0) { + n1 = 0.0; + } else { + t1 *= t1; + n1 = t1 * t1 * dot(grad4[gi1], x1, y1, z1, w1); + } + + double t2 = 0.6 - x2 * x2 - y2 * y2 - z2 * z2 - w2 * w2; + if (t2 < 0) { + n2 = 0.0; + } else { + t2 *= t2; + n2 = t2 * t2 * dot(grad4[gi2], x2, y2, z2, w2); + } + + double t3 = 0.6 - x3 * x3 - y3 * y3 - z3 * z3 - w3 * w3; + if (t3 < 0) { + n3 = 0.0; + } else { + t3 *= t3; + n3 = t3 * t3 * dot(grad4[gi3], x3, y3, z3, w3); + } + + double t4 = 0.6 - x4 * x4 - y4 * y4 - z4 * z4 - w4 * w4; + if (t4 < 0) { + n4 = 0.0; + } else { + t4 *= t4; + n4 = t4 * t4 * dot(grad4[gi4], x4, y4, z4, w4); + } + + // Sum up and scale the result to cover the range [-1,1] + return 27.0 * (n0 + n1 + n2 + n3 + n4); + } + + /** + * Gets the singleton unseeded instance of this generator + * + * @return Singleton + */ + @NotNull + public static SimplexNoiseGenerator getInstance() { + return instance; + } +} diff --git a/api/src/main/java/org/bukkit/util/noise/SimplexOctaveGenerator.java b/api/src/main/java/org/bukkit/util/noise/SimplexOctaveGenerator.java new file mode 100644 index 000000000..6cc15431e --- /dev/null +++ b/api/src/main/java/org/bukkit/util/noise/SimplexOctaveGenerator.java @@ -0,0 +1,131 @@ +package org.bukkit.util.noise; + +import java.util.Random; +import org.bukkit.World; +import org.jetbrains.annotations.NotNull; + +/** + * Creates simplex noise through unbiased octaves + */ +public class SimplexOctaveGenerator extends OctaveGenerator { + private double wScale = 1; + + /** + * Creates a simplex octave generator for the given world + * + * @param world World to construct this generator for + * @param octaves Amount of octaves to create + */ + public SimplexOctaveGenerator(@NotNull World world, int octaves) { + this(new Random(world.getSeed()), octaves); + } + + /** + * Creates a simplex octave generator for the given world + * + * @param seed Seed to construct this generator for + * @param octaves Amount of octaves to create + */ + public SimplexOctaveGenerator(long seed, int octaves) { + this(new Random(seed), octaves); + } + + /** + * Creates a simplex octave generator for the given {@link Random} + * + * @param rand Random object to construct this generator for + * @param octaves Amount of octaves to create + */ + public SimplexOctaveGenerator(@NotNull Random rand, int octaves) { + super(createOctaves(rand, octaves)); + } + + @Override + public void setScale(double scale) { + super.setScale(scale); + setWScale(scale); + } + + /** + * Gets the scale used for each W-coordinates passed + * + * @return W scale + */ + public double getWScale() { + return wScale; + } + + /** + * Sets the scale used for each W-coordinates passed + * + * @param scale New W scale + */ + public void setWScale(double scale) { + wScale = scale; + } + + /** + * Generates noise for the 3D coordinates using the specified number of + * octaves and parameters + * + * @param x X-coordinate + * @param y Y-coordinate + * @param z Z-coordinate + * @param w W-coordinate + * @param frequency How much to alter the frequency by each octave + * @param amplitude How much to alter the amplitude by each octave + * @return Resulting noise + */ + public double noise(double x, double y, double z, double w, double frequency, double amplitude) { + return noise(x, y, z, w, frequency, amplitude, false); + } + + /** + * Generates noise for the 3D coordinates using the specified number of + * octaves and parameters + * + * @param x X-coordinate + * @param y Y-coordinate + * @param z Z-coordinate + * @param w W-coordinate + * @param frequency How much to alter the frequency by each octave + * @param amplitude How much to alter the amplitude by each octave + * @param normalized If true, normalize the value to [-1, 1] + * @return Resulting noise + */ + public double noise(double x, double y, double z, double w, double frequency, double amplitude, boolean normalized) { + double result = 0; + double amp = 1; + double freq = 1; + double max = 0; + + x *= xScale; + y *= yScale; + z *= zScale; + w *= wScale; + + for (NoiseGenerator octave : octaves) { + result += ((SimplexNoiseGenerator) octave).noise(x * freq, y * freq, z * freq, w * freq) * amp; + max += amp; + freq *= frequency; + amp *= amplitude; + } + + if (normalized) { + result /= max; + } + + return result; + } + + @NotNull + private static NoiseGenerator[] createOctaves(@NotNull Random rand, int octaves) { + NoiseGenerator[] result = new NoiseGenerator[octaves]; + + for (int i = 0; i < octaves; i++) { + result[i] = new SimplexNoiseGenerator(rand); + } + + return result; + } +} diff --git a/api/src/main/java/org/bukkit/util/permissions/BroadcastPermissions.java b/api/src/main/java/org/bukkit/util/permissions/BroadcastPermissions.java new file mode 100644 index 000000000..54bc41370 --- /dev/null +++ b/api/src/main/java/org/bukkit/util/permissions/BroadcastPermissions.java @@ -0,0 +1,24 @@ +package org.bukkit.util.permissions; + +import org.bukkit.permissions.Permission; +import org.bukkit.permissions.PermissionDefault; +import org.jetbrains.annotations.NotNull; + +public final class BroadcastPermissions { + private static final String ROOT = "bukkit.broadcast"; + private static final String PREFIX = ROOT + "."; + + private BroadcastPermissions() {} + + @NotNull + public static Permission registerPermissions(@NotNull Permission parent) { + Permission broadcasts = DefaultPermissions.registerPermission(ROOT, "Allows the user to receive all broadcast messages", parent); + + DefaultPermissions.registerPermission(PREFIX + "admin", "Allows the user to receive administrative broadcasts", PermissionDefault.OP, broadcasts); + DefaultPermissions.registerPermission(PREFIX + "user", "Allows the user to receive user broadcasts", PermissionDefault.TRUE, broadcasts); + + broadcasts.recalculatePermissibles(); + + return broadcasts; + } +} diff --git a/api/src/main/java/org/bukkit/util/permissions/CommandPermissions.java b/api/src/main/java/org/bukkit/util/permissions/CommandPermissions.java new file mode 100644 index 000000000..7763d6101 --- /dev/null +++ b/api/src/main/java/org/bukkit/util/permissions/CommandPermissions.java @@ -0,0 +1,25 @@ +package org.bukkit.util.permissions; + +import org.bukkit.permissions.Permission; +import org.bukkit.permissions.PermissionDefault; +import org.jetbrains.annotations.NotNull; + +public final class CommandPermissions { + private static final String ROOT = "bukkit.command"; + private static final String PREFIX = ROOT + "."; + + private CommandPermissions() {} + + @NotNull + public static Permission registerPermissions(@NotNull Permission parent) { + Permission commands = DefaultPermissions.registerPermission(ROOT, "Gives the user the ability to use all CraftBukkit commands", parent); + + DefaultPermissions.registerPermission(PREFIX + "help", "Allows the user to view the vanilla help menu", PermissionDefault.TRUE, commands); + DefaultPermissions.registerPermission(PREFIX + "plugins", "Allows the user to view the list of plugins running on this server", PermissionDefault.TRUE, commands); + DefaultPermissions.registerPermission(PREFIX + "reload", "Allows the user to reload the server settings", PermissionDefault.OP, commands); + DefaultPermissions.registerPermission(PREFIX + "version", "Allows the user to view the version of the server", PermissionDefault.TRUE, commands); + + commands.recalculatePermissibles(); + return commands; + } +} diff --git a/api/src/main/java/org/bukkit/util/permissions/DefaultPermissions.java b/api/src/main/java/org/bukkit/util/permissions/DefaultPermissions.java new file mode 100644 index 000000000..e1a4ddf2c --- /dev/null +++ b/api/src/main/java/org/bukkit/util/permissions/DefaultPermissions.java @@ -0,0 +1,94 @@ +package org.bukkit.util.permissions; + +import java.util.Map; +import org.bukkit.Bukkit; +import org.bukkit.permissions.Permission; +import org.bukkit.permissions.PermissionDefault; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +public final class DefaultPermissions { + private static final String ROOT = "craftbukkit"; + private static final String LEGACY_PREFIX = "craft"; + + private DefaultPermissions() {} + + @NotNull + public static Permission registerPermission(@NotNull Permission perm) { + return registerPermission(perm, true); + } + + @NotNull + public static Permission registerPermission(@NotNull Permission perm, boolean withLegacy) { + Permission result = perm; + + try { + Bukkit.getPluginManager().addPermission(perm); + } catch (IllegalArgumentException ex) { + result = Bukkit.getPluginManager().getPermission(perm.getName()); + assert result != null; + } + + if (withLegacy) { + Permission legacy = new Permission(LEGACY_PREFIX + result.getName(), result.getDescription(), PermissionDefault.FALSE); + legacy.getChildren().put(result.getName(), true); + registerPermission(perm, false); + } + + return result; + } + + @NotNull + public static Permission registerPermission(@NotNull Permission perm, @NotNull Permission parent) { + parent.getChildren().put(perm.getName(), true); + return registerPermission(perm); + } + + @NotNull + public static Permission registerPermission(@NotNull String name, @Nullable String desc) { + Permission perm = registerPermission(new Permission(name, desc)); + return perm; + } + + @NotNull + public static Permission registerPermission(@NotNull String name, @Nullable String desc, @NotNull Permission parent) { + Permission perm = registerPermission(name, desc); + parent.getChildren().put(perm.getName(), true); + return perm; + } + + @NotNull + public static Permission registerPermission(@NotNull String name, @Nullable String desc, @Nullable PermissionDefault def) { + Permission perm = registerPermission(new Permission(name, desc, def)); + return perm; + } + + @NotNull + public static Permission registerPermission(@NotNull String name, @Nullable String desc, @Nullable PermissionDefault def, @NotNull Permission parent) { + Permission perm = registerPermission(name, desc, def); + parent.getChildren().put(perm.getName(), true); + return perm; + } + + @NotNull + public static Permission registerPermission(@NotNull String name, @Nullable String desc, @Nullable PermissionDefault def, @Nullable Map children) { + Permission perm = registerPermission(new Permission(name, desc, def, children)); + return perm; + } + + @NotNull + public static Permission registerPermission(@NotNull String name, @Nullable String desc, @Nullable PermissionDefault def, @Nullable Map children, @NotNull Permission parent) { + Permission perm = registerPermission(name, desc, def, children); + parent.getChildren().put(perm.getName(), true); + return perm; + } + + public static void registerCorePermissions() { + Permission parent = registerPermission(ROOT, "Gives the user the ability to use all CraftBukkit utilities and commands"); + + CommandPermissions.registerPermissions(parent); + BroadcastPermissions.registerPermissions(parent); + + parent.recalculatePermissibles(); + } +} diff --git a/api/src/main/java/org/spigotmc/CustomTimingsHandler.java b/api/src/main/java/org/spigotmc/CustomTimingsHandler.java new file mode 100644 index 000000000..3cbe5c2bb --- /dev/null +++ b/api/src/main/java/org/spigotmc/CustomTimingsHandler.java @@ -0,0 +1,111 @@ +/* + * This file is licensed under the MIT License (MIT). + * + * Copyright (c) 2014 Daniel Ennis + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +package org.spigotmc; + +import java.io.PrintStream; +import java.util.Queue; +import java.util.concurrent.ConcurrentLinkedQueue; +import org.bukkit.Bukkit; +import org.bukkit.World; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; +import org.bukkit.plugin.AuthorNagException; +import org.bukkit.plugin.Plugin; +import co.aikar.timings.Timing; +import co.aikar.timings.Timings; +import co.aikar.timings.TimingsManager; + +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; +import java.util.logging.Level; + +/** + * This is here for legacy purposes incase any plugin used it. + * + * If you use this, migrate ASAP as this will be removed in the future! + * + * @deprecated + * @see co.aikar.timings.Timings#of + */ +@Deprecated +public final class CustomTimingsHandler { + private final Timing handler; + private static Boolean sunReflectAvailable; + private static Method getCallerClass; + + public CustomTimingsHandler(@NotNull String name) { + if (sunReflectAvailable == null) { + String javaVer = System.getProperty("java.version"); + String[] elements = javaVer.split("\\."); + + int major = Integer.parseInt(elements.length >= 2 ? elements[1] : javaVer); + if (major <= 8) { + sunReflectAvailable = true; + + try { + Class reflection = Class.forName("sun.reflect.Reflection"); + getCallerClass = reflection.getMethod("getCallerClass", int.class); + } catch (ClassNotFoundException | NoSuchMethodException ignored) { + } + } else { + sunReflectAvailable = false; + } + } + + Class calling = null; + if (sunReflectAvailable) { + try { + calling = (Class) getCallerClass.invoke(null, 2); + } catch (IllegalAccessException | InvocationTargetException ignored) { + } + } + + Timing timing; + + Plugin plugin = null; + try { + plugin = TimingsManager.getPluginByClassloader(calling); + } catch (Exception ignored) {} + + new AuthorNagException("Deprecated use of CustomTimingsHandler. Please Switch to Timings.of ASAP").printStackTrace(); + if (plugin != null) { + timing = Timings.of(plugin, "(Deprecated API) " + name); + } else { + try { + final Method ofSafe = TimingsManager.class.getDeclaredMethod("getHandler", String.class, String.class, Timing.class); + ofSafe.setAccessible(true); + timing = (Timing) ofSafe.invoke(null,"Minecraft", "(Deprecated API) " + name, null); + } catch (Exception e) { + e.printStackTrace(); + Bukkit.getLogger().log(Level.SEVERE, "This handler could not be registered"); + timing = Timings.NULL_HANDLER; + } + } + handler = timing; + } + + public void startTiming() { handler.startTiming(); } + public void stopTiming() { handler.stopTiming(); } + +} diff --git a/api/src/main/java/org/spigotmc/event/entity/EntityDismountEvent.java b/api/src/main/java/org/spigotmc/event/entity/EntityDismountEvent.java new file mode 100644 index 000000000..a5b4aed52 --- /dev/null +++ b/api/src/main/java/org/spigotmc/event/entity/EntityDismountEvent.java @@ -0,0 +1,74 @@ +package org.spigotmc.event.entity; + +import org.bukkit.entity.Entity; +import org.bukkit.event.Cancellable; +import org.bukkit.event.HandlerList; +import org.bukkit.event.entity.EntityEvent; +import org.jetbrains.annotations.NotNull; + +/** + * Called when an entity stops riding another entity. + * + */ +public class EntityDismountEvent extends EntityEvent implements Cancellable +{ + + private static final HandlerList handlers = new HandlerList(); + private boolean cancelled; + private final Entity dismounted; + private final boolean isCancellable; // Paper + + public EntityDismountEvent(@NotNull Entity what, @NotNull Entity dismounted) + { + // Paper start + this(what, dismounted, true); + } + + public EntityDismountEvent(@NotNull Entity what, @NotNull Entity dismounted, boolean isCancellable) + { + // Paper end + super( what ); + this.dismounted = dismounted; + this.isCancellable = isCancellable; // Paper + } + + @NotNull + public Entity getDismounted() + { + return dismounted; + } + + @Override + public boolean isCancelled() + { + return cancelled; + } + + @Override + public void setCancelled(boolean cancel) + { + // Paper start + if (cancel && !isCancellable) { + return; + } + this.cancelled = cancel; + } + + public boolean isCancellable() { + return isCancellable; + // Paper end + } + + @NotNull + @Override + public HandlerList getHandlers() + { + return handlers; + } + + @NotNull + public static HandlerList getHandlerList() + { + return handlers; + } +} diff --git a/api/src/main/java/org/spigotmc/event/entity/EntityMountEvent.java b/api/src/main/java/org/spigotmc/event/entity/EntityMountEvent.java new file mode 100644 index 000000000..c608e65bd --- /dev/null +++ b/api/src/main/java/org/spigotmc/event/entity/EntityMountEvent.java @@ -0,0 +1,56 @@ +package org.spigotmc.event.entity; + +import org.bukkit.entity.Entity; +import org.bukkit.event.Cancellable; +import org.bukkit.event.HandlerList; +import org.bukkit.event.entity.EntityEvent; +import org.jetbrains.annotations.NotNull; + +/** + * Called when an entity attempts to ride another entity. + * + */ +public class EntityMountEvent extends EntityEvent implements Cancellable +{ + + private static final HandlerList handlers = new HandlerList(); + private boolean cancelled; + private final Entity mount; + + public EntityMountEvent(@NotNull Entity what, @NotNull Entity mount) + { + super( what ); + this.mount = mount; + } + + @NotNull + public Entity getMount() + { + return mount; + } + + @Override + public boolean isCancelled() + { + return cancelled; + } + + @Override + public void setCancelled(boolean cancel) + { + this.cancelled = cancel; + } + + @NotNull + @Override + public HandlerList getHandlers() + { + return handlers; + } + + @NotNull + public static HandlerList getHandlerList() + { + return handlers; + } +} diff --git a/api/src/main/java/org/spigotmc/event/player/PlayerSpawnLocationEvent.java b/api/src/main/java/org/spigotmc/event/player/PlayerSpawnLocationEvent.java new file mode 100644 index 000000000..2515887c2 --- /dev/null +++ b/api/src/main/java/org/spigotmc/event/player/PlayerSpawnLocationEvent.java @@ -0,0 +1,53 @@ +package org.spigotmc.event.player; + +import org.bukkit.Location; +import org.bukkit.entity.Player; +import org.bukkit.event.HandlerList; +import org.bukkit.event.player.PlayerEvent; +import org.jetbrains.annotations.NotNull; + +/** + * Called when player is about to spawn in a world after joining the server. + */ +public class PlayerSpawnLocationEvent extends PlayerEvent { + private static final HandlerList handlers = new HandlerList(); + private Location spawnLocation; + + public PlayerSpawnLocationEvent(@NotNull final Player who, @NotNull Location spawnLocation) { + super(who); + this.spawnLocation = spawnLocation; + } + + + /** + * Gets player's spawn location. + * If the player {@link Player#hasPlayedBefore()}, it's going to default to the location inside player.dat file. + * For new players, the default spawn location is spawn of the main Bukkit world. + * + * @return the spawn location + */ + @NotNull + public Location getSpawnLocation() { + return spawnLocation; + } + + /** + * Sets player's spawn location. + * + * @param location the spawn location + */ + public void setSpawnLocation(@NotNull Location location) { + this.spawnLocation = location; + } + + @NotNull + @Override + public HandlerList getHandlers() { + return handlers; + } + + @NotNull + public static HandlerList getHandlerList() { + return handlers; + } +} diff --git a/api/src/main/javadoc/org/bukkit/block/package-info.java b/api/src/main/javadoc/org/bukkit/block/package-info.java new file mode 100644 index 000000000..dc0f36a80 --- /dev/null +++ b/api/src/main/javadoc/org/bukkit/block/package-info.java @@ -0,0 +1,6 @@ +/** + * Classes used to manipulate the voxels in a {@link org.bukkit.World world}, + * including special states. + */ +package org.bukkit.block; + diff --git a/api/src/main/javadoc/org/bukkit/command/defaults/package-info.java b/api/src/main/javadoc/org/bukkit/command/defaults/package-info.java new file mode 100644 index 000000000..b67dfba0f --- /dev/null +++ b/api/src/main/javadoc/org/bukkit/command/defaults/package-info.java @@ -0,0 +1,6 @@ +/** + * Commands for emulating the Minecraft commands and other necessary ones for + * use by a Bukkit implementation. + */ +package org.bukkit.command.defaults; + diff --git a/api/src/main/javadoc/org/bukkit/command/package-info.java b/api/src/main/javadoc/org/bukkit/command/package-info.java new file mode 100644 index 000000000..6428f474a --- /dev/null +++ b/api/src/main/javadoc/org/bukkit/command/package-info.java @@ -0,0 +1,5 @@ +/** + * Classes relating to handling specialized non-chat player input. + */ +package org.bukkit.command; + diff --git a/api/src/main/javadoc/org/bukkit/configuration/file/package-info.java b/api/src/main/javadoc/org/bukkit/configuration/file/package-info.java new file mode 100644 index 000000000..4973ffc18 --- /dev/null +++ b/api/src/main/javadoc/org/bukkit/configuration/file/package-info.java @@ -0,0 +1,7 @@ +/** + * Classes dedicated facilitating {@link + * org.bukkit.configuration.Configuration configurations} to be read and + * stored on the filesystem. + */ +package org.bukkit.configuration.file; + diff --git a/api/src/main/javadoc/org/bukkit/configuration/package-info.java b/api/src/main/javadoc/org/bukkit/configuration/package-info.java new file mode 100644 index 000000000..63a39650b --- /dev/null +++ b/api/src/main/javadoc/org/bukkit/configuration/package-info.java @@ -0,0 +1,5 @@ +/** + * Classes dedicated to handling a plugin's runtime configuration. + */ +package org.bukkit.configuration; + diff --git a/api/src/main/javadoc/org/bukkit/configuration/serialization/package-info.java b/api/src/main/javadoc/org/bukkit/configuration/serialization/package-info.java new file mode 100644 index 000000000..529de2e8c --- /dev/null +++ b/api/src/main/javadoc/org/bukkit/configuration/serialization/package-info.java @@ -0,0 +1,7 @@ +/** + * Classes dedicated to being able to perform serialization specialized for + * the Bukkit {@link org.bukkit.configuration.Configuration configuration} + * implementation. + */ +package org.bukkit.configuration.serialization; + diff --git a/api/src/main/javadoc/org/bukkit/conversations/package-info.java b/api/src/main/javadoc/org/bukkit/conversations/package-info.java new file mode 100644 index 000000000..d49dafaf0 --- /dev/null +++ b/api/src/main/javadoc/org/bukkit/conversations/package-info.java @@ -0,0 +1,5 @@ +/** + * Classes dedicated to facilitate direct player-to-plugin communication. + */ +package org.bukkit.conversations; + diff --git a/api/src/main/javadoc/org/bukkit/enchantments/package-info.java b/api/src/main/javadoc/org/bukkit/enchantments/package-info.java new file mode 100644 index 000000000..fb67cc7e6 --- /dev/null +++ b/api/src/main/javadoc/org/bukkit/enchantments/package-info.java @@ -0,0 +1,7 @@ +/** + * Classes relating to the specialized enhancements to {@link + * org.bukkit.inventory.ItemStack item stacks}, as part of the {@link + * org.bukkit.inventory.meta.ItemMeta meta data}. + */ +package org.bukkit.enchantments; + diff --git a/api/src/main/javadoc/org/bukkit/entity/minecart/package-info.java b/api/src/main/javadoc/org/bukkit/entity/minecart/package-info.java new file mode 100644 index 000000000..342f10bac --- /dev/null +++ b/api/src/main/javadoc/org/bukkit/entity/minecart/package-info.java @@ -0,0 +1,5 @@ +/** + * Interfaces for various {@link org.bukkit.entity.Minecart} types. + */ +package org.bukkit.entity.minecart; + diff --git a/api/src/main/javadoc/org/bukkit/entity/package-info.java b/api/src/main/javadoc/org/bukkit/entity/package-info.java new file mode 100644 index 000000000..167a830d3 --- /dev/null +++ b/api/src/main/javadoc/org/bukkit/entity/package-info.java @@ -0,0 +1,6 @@ +/** + * Interfaces for non-voxel objects that can exist in a {@link + * org.bukkit.World world}, including all players, monsters, projectiles, etc. + */ +package org.bukkit.entity; + diff --git a/api/src/main/javadoc/org/bukkit/event/block/package-info.java b/api/src/main/javadoc/org/bukkit/event/block/package-info.java new file mode 100644 index 000000000..30aec2185 --- /dev/null +++ b/api/src/main/javadoc/org/bukkit/event/block/package-info.java @@ -0,0 +1,7 @@ +/** + * {@link org.bukkit.event.Event Events} relating to when a {@link + * org.bukkit.block.Block block} is changed or interacts with the {@link + * org.bukkit.World world}. + */ +package org.bukkit.event.block; + diff --git a/api/src/main/javadoc/org/bukkit/event/enchantment/package-info.java b/api/src/main/javadoc/org/bukkit/event/enchantment/package-info.java new file mode 100644 index 000000000..e609c6348 --- /dev/null +++ b/api/src/main/javadoc/org/bukkit/event/enchantment/package-info.java @@ -0,0 +1,6 @@ +/** + * {@link org.bukkit.event.Event Events} triggered from an {@link + * org.bukkit.inventory.EnchantingInventory enchantment table}. + */ +package org.bukkit.event.enchantment; + diff --git a/api/src/main/javadoc/org/bukkit/event/entity/package-info.java b/api/src/main/javadoc/org/bukkit/event/entity/package-info.java new file mode 100644 index 000000000..cddcd4e46 --- /dev/null +++ b/api/src/main/javadoc/org/bukkit/event/entity/package-info.java @@ -0,0 +1,7 @@ +/** + * {@link org.bukkit.event.Event Events} relating to {@link + * org.bukkit.entity.Entity entities}, excluding some directly referencing + * some more specific entity types. + */ +package org.bukkit.event.entity; + diff --git a/api/src/main/javadoc/org/bukkit/event/hanging/package-info.java b/api/src/main/javadoc/org/bukkit/event/hanging/package-info.java new file mode 100644 index 000000000..bf16382bc --- /dev/null +++ b/api/src/main/javadoc/org/bukkit/event/hanging/package-info.java @@ -0,0 +1,6 @@ +/** + * {@link org.bukkit.event.Event Events} relating to {@link + * org.bukkit.entity.Hanging entities that hang}. + */ +package org.bukkit.event.hanging; + diff --git a/api/src/main/javadoc/org/bukkit/event/inventory/package-info.java b/api/src/main/javadoc/org/bukkit/event/inventory/package-info.java new file mode 100644 index 000000000..7dd5b699e --- /dev/null +++ b/api/src/main/javadoc/org/bukkit/event/inventory/package-info.java @@ -0,0 +1,6 @@ +/** + * {@link org.bukkit.event.Event Events} relating to {@link + * org.bukkit.inventory.Inventory inventory} manipulation. + */ +package org.bukkit.event.inventory; + diff --git a/api/src/main/javadoc/org/bukkit/event/package-info.java b/api/src/main/javadoc/org/bukkit/event/package-info.java new file mode 100644 index 000000000..dd8baa1dc --- /dev/null +++ b/api/src/main/javadoc/org/bukkit/event/package-info.java @@ -0,0 +1,5 @@ +/** + * Classes dedicated to handling triggered code executions. + */ +package org.bukkit.event; + diff --git a/api/src/main/javadoc/org/bukkit/event/player/package-info.java b/api/src/main/javadoc/org/bukkit/event/player/package-info.java new file mode 100644 index 000000000..fb645f32a --- /dev/null +++ b/api/src/main/javadoc/org/bukkit/event/player/package-info.java @@ -0,0 +1,6 @@ +/** + * {@link org.bukkit.event.Event Events} relating to {@link + * org.bukkit.entity.Player players}. + */ +package org.bukkit.event.player; + diff --git a/api/src/main/javadoc/org/bukkit/event/server/package-info.java b/api/src/main/javadoc/org/bukkit/event/server/package-info.java new file mode 100644 index 000000000..b548270ca --- /dev/null +++ b/api/src/main/javadoc/org/bukkit/event/server/package-info.java @@ -0,0 +1,6 @@ +/** + * {@link org.bukkit.event.Event Events} relating to programmatic state + * changes on the server. + */ +package org.bukkit.event.server; + diff --git a/api/src/main/javadoc/org/bukkit/event/vehicle/package-info.java b/api/src/main/javadoc/org/bukkit/event/vehicle/package-info.java new file mode 100644 index 000000000..b88cbcd6b --- /dev/null +++ b/api/src/main/javadoc/org/bukkit/event/vehicle/package-info.java @@ -0,0 +1,6 @@ +/** + * {@link org.bukkit.event.Event Events} relating to {@link + * org.bukkit.entity.Vehicle vehicular entities}. + */ +package org.bukkit.event.vehicle; + diff --git a/api/src/main/javadoc/org/bukkit/event/weather/package-info.java b/api/src/main/javadoc/org/bukkit/event/weather/package-info.java new file mode 100644 index 000000000..edaaf9f19 --- /dev/null +++ b/api/src/main/javadoc/org/bukkit/event/weather/package-info.java @@ -0,0 +1,5 @@ +/** + * {@link org.bukkit.event.Event Events} relating to weather. + */ +package org.bukkit.event.weather; + diff --git a/api/src/main/javadoc/org/bukkit/event/world/package-info.java b/api/src/main/javadoc/org/bukkit/event/world/package-info.java new file mode 100644 index 000000000..4cbb818a3 --- /dev/null +++ b/api/src/main/javadoc/org/bukkit/event/world/package-info.java @@ -0,0 +1,6 @@ +/** + * {@link org.bukkit.event.Event Events} triggered by various {@link + * org.bukkit.World world} states or changes. + */ +package org.bukkit.event.world; + diff --git a/api/src/main/javadoc/org/bukkit/generator/package-info.java b/api/src/main/javadoc/org/bukkit/generator/package-info.java new file mode 100644 index 000000000..2532addf5 --- /dev/null +++ b/api/src/main/javadoc/org/bukkit/generator/package-info.java @@ -0,0 +1,6 @@ +/** + * Classes to facilitate {@link org.bukkit.World world} generation + * implementation. + */ +package org.bukkit.generator; + diff --git a/api/src/main/javadoc/org/bukkit/help/package-info.java b/api/src/main/javadoc/org/bukkit/help/package-info.java new file mode 100644 index 000000000..66a4aacbe --- /dev/null +++ b/api/src/main/javadoc/org/bukkit/help/package-info.java @@ -0,0 +1,5 @@ +/** + * Classes used to manipulate the default command and topic assistance system. + */ +package org.bukkit.help; + diff --git a/api/src/main/javadoc/org/bukkit/inventory/meta/package-info.java b/api/src/main/javadoc/org/bukkit/inventory/meta/package-info.java new file mode 100644 index 000000000..b80eb88b6 --- /dev/null +++ b/api/src/main/javadoc/org/bukkit/inventory/meta/package-info.java @@ -0,0 +1,6 @@ +/** + * The interfaces used when manipulating extra data can can be stored inside + * {@link org.bukkit.inventory.ItemStack item stacks}. + */ +package org.bukkit.inventory.meta; + diff --git a/api/src/main/javadoc/org/bukkit/inventory/package-info.java b/api/src/main/javadoc/org/bukkit/inventory/package-info.java new file mode 100644 index 000000000..8881abcc0 --- /dev/null +++ b/api/src/main/javadoc/org/bukkit/inventory/package-info.java @@ -0,0 +1,5 @@ +/** + * Classes involved in manipulating player inventories and item interactions. + */ +package org.bukkit.inventory; + diff --git a/api/src/main/javadoc/org/bukkit/map/package-info.java b/api/src/main/javadoc/org/bukkit/map/package-info.java new file mode 100644 index 000000000..30de8b24c --- /dev/null +++ b/api/src/main/javadoc/org/bukkit/map/package-info.java @@ -0,0 +1,6 @@ +/** + * Classes to facilitate plugin handling of {@link org.bukkit.Material#MAP + * map} displays. + */ +package org.bukkit.map; + diff --git a/api/src/main/javadoc/org/bukkit/material/package-info.java b/api/src/main/javadoc/org/bukkit/material/package-info.java new file mode 100644 index 000000000..f3be435e5 --- /dev/null +++ b/api/src/main/javadoc/org/bukkit/material/package-info.java @@ -0,0 +1,5 @@ +/** + * Classes that represents various voxel types and states. + */ +package org.bukkit.material; + diff --git a/api/src/main/javadoc/org/bukkit/metadata/package-info.java b/api/src/main/javadoc/org/bukkit/metadata/package-info.java new file mode 100644 index 000000000..f8e00397a --- /dev/null +++ b/api/src/main/javadoc/org/bukkit/metadata/package-info.java @@ -0,0 +1,6 @@ +/** + * Classes dedicated to providing a layer of plugin specified data on various + * Minecraft concepts. + */ +package org.bukkit.metadata; + diff --git a/api/src/main/javadoc/org/bukkit/package-info.java b/api/src/main/javadoc/org/bukkit/package-info.java new file mode 100644 index 000000000..da7d9f12c --- /dev/null +++ b/api/src/main/javadoc/org/bukkit/package-info.java @@ -0,0 +1,5 @@ +/** + * More generalized classes in the API. + */ +package org.bukkit; + diff --git a/api/src/main/javadoc/org/bukkit/permissions/package-info.java b/api/src/main/javadoc/org/bukkit/permissions/package-info.java new file mode 100644 index 000000000..860abc3f3 --- /dev/null +++ b/api/src/main/javadoc/org/bukkit/permissions/package-info.java @@ -0,0 +1,5 @@ +/** + * Classes dedicated to providing binary state properties to players. + */ +package org.bukkit.permissions; + diff --git a/api/src/main/javadoc/org/bukkit/plugin/doc-files/permissions-example_plugin.yml b/api/src/main/javadoc/org/bukkit/plugin/doc-files/permissions-example_plugin.yml new file mode 100644 index 000000000..34d0381a6 --- /dev/null +++ b/api/src/main/javadoc/org/bukkit/plugin/doc-files/permissions-example_plugin.yml @@ -0,0 +1,64 @@ +name: ScrapBukkit +main: com.dinnerbone.bukkit.scrap.ScrapBukkit +version: 1.0.0 +website: http://www.bukkit.org +author: The Bukkit Team +description: > + Miscellaneous administrative commands for Bukkit. + This plugin is one of the default plugins shipped with Bukkit. +# commands: snipped + +permissions: + scrapbukkit.*: + description: Gives all permissions for Scrapbukkit + default: op + children: + scrapbukkit.remove: + description: Allows the player to remove items from anyones inventory + children: + scrapbukkit.remove.self: + description: Allows the player to remove items from their own inventory + scrapbukkit.remove.other: + description: Allows the player to remove items from other peoples inventory + + scrapbukkit.time: + description: Allows the player to view and change the time + children: + scrapbukkit.time.view: + description: Allows the player to view the time + default: true + scrapbukkit.time.change: + description: Allows the player to change the time + + scrapbukkit.tp: + description: Allows the player to teleport anyone to anyone else + children: + scrapbukkit.tp.here: + description: Allows the player to teleport other players to themselves + scrapbukkit.tp.self: + description: Allows the player to teleport themselves to another player + scrapbukkit.tp.other: + description: Allows the player to teleport anyone to another player + + scrapbukkit.give: + description: Allows the player to give items + children: + scrapbukkit.give.self: + description: Allows the player to give themselves items + scrapbukkit.give.other: + description: Allows the player to give other players items + + scrapbukkit.clear: + description: Allows the player to clear inventories + children: + scrapbukkit.clear.self: + description: Allows the player to clear their own inventory + scrapbukkit.clear.other: + description: Allows the player to clear other players inventory + + scrapbukkit.some.standard.perm: {} + scrapbukkit.some.other.perm: true + scrapbukkit.some.bad.perm: false + # This is defined here individually, as simply giving it an association will not cause it to exist at runtime + scrapbukkit.some.bad.perm: {} + scrapbukkit.some.other.perm: {} diff --git a/api/src/main/javadoc/org/bukkit/plugin/java/package-info.java b/api/src/main/javadoc/org/bukkit/plugin/java/package-info.java new file mode 100644 index 000000000..c74e70935 --- /dev/null +++ b/api/src/main/javadoc/org/bukkit/plugin/java/package-info.java @@ -0,0 +1,6 @@ +/** + * Classes for handling {@link org.bukkit.plugin.Plugin plugins} written in + * java. + */ +package org.bukkit.plugin.java; + diff --git a/api/src/main/javadoc/org/bukkit/plugin/messaging/package-info.java b/api/src/main/javadoc/org/bukkit/plugin/messaging/package-info.java new file mode 100644 index 000000000..04cb2b93c --- /dev/null +++ b/api/src/main/javadoc/org/bukkit/plugin/messaging/package-info.java @@ -0,0 +1,5 @@ +/** + * Classes dedicated to specialized plugin to client protocols. + */ +package org.bukkit.plugin.messaging; + diff --git a/api/src/main/javadoc/org/bukkit/plugin/package-info.java b/api/src/main/javadoc/org/bukkit/plugin/package-info.java new file mode 100644 index 000000000..76fbe6ba1 --- /dev/null +++ b/api/src/main/javadoc/org/bukkit/plugin/package-info.java @@ -0,0 +1,5 @@ +/** + * Classes specifically relating to loading software modules at runtime. + */ +package org.bukkit.plugin; + diff --git a/api/src/main/javadoc/org/bukkit/potion/package-info.java b/api/src/main/javadoc/org/bukkit/potion/package-info.java new file mode 100644 index 000000000..6dd345449 --- /dev/null +++ b/api/src/main/javadoc/org/bukkit/potion/package-info.java @@ -0,0 +1,6 @@ +/** + * Classes to represent various {@link org.bukkit.Material#POTION potion} + * properties and manipulation. + */ +package org.bukkit.potion; + diff --git a/api/src/main/javadoc/org/bukkit/projectiles/package-info.java b/api/src/main/javadoc/org/bukkit/projectiles/package-info.java new file mode 100644 index 000000000..5efe7364a --- /dev/null +++ b/api/src/main/javadoc/org/bukkit/projectiles/package-info.java @@ -0,0 +1,5 @@ +/** + * Classes to represent the source of a projectile + */ +package org.bukkit.projectiles; + diff --git a/api/src/main/javadoc/org/bukkit/scheduler/package-info.java b/api/src/main/javadoc/org/bukkit/scheduler/package-info.java new file mode 100644 index 000000000..c441df679 --- /dev/null +++ b/api/src/main/javadoc/org/bukkit/scheduler/package-info.java @@ -0,0 +1,6 @@ +/** + * Classes dedicated to letting {@link org.bukkit.plugin.Plugin plugins} run + * code at specific time intervals, including thread safety. + */ +package org.bukkit.scheduler; + diff --git a/api/src/main/javadoc/org/bukkit/scoreboard/package-info.java b/api/src/main/javadoc/org/bukkit/scoreboard/package-info.java new file mode 100644 index 000000000..4d0b7e206 --- /dev/null +++ b/api/src/main/javadoc/org/bukkit/scoreboard/package-info.java @@ -0,0 +1,5 @@ +/** + * Interfaces used to manage the client side score display system. + */ +package org.bukkit.scoreboard; + diff --git a/api/src/main/javadoc/org/bukkit/util/io/package-info.java b/api/src/main/javadoc/org/bukkit/util/io/package-info.java new file mode 100644 index 000000000..c4efffbd0 --- /dev/null +++ b/api/src/main/javadoc/org/bukkit/util/io/package-info.java @@ -0,0 +1,5 @@ +/** + * Classes used to facilitate stream processing for specific Bukkit concepts. + */ +package org.bukkit.util.io; + diff --git a/api/src/main/javadoc/org/bukkit/util/noise/package-info.java b/api/src/main/javadoc/org/bukkit/util/noise/package-info.java new file mode 100644 index 000000000..8dd1501c5 --- /dev/null +++ b/api/src/main/javadoc/org/bukkit/util/noise/package-info.java @@ -0,0 +1,5 @@ +/** + * Classes dedicated to facilitating deterministic noise. + */ +package org.bukkit.util.noise; + diff --git a/api/src/main/javadoc/org/bukkit/util/package-info.java b/api/src/main/javadoc/org/bukkit/util/package-info.java new file mode 100644 index 000000000..58176d08b --- /dev/null +++ b/api/src/main/javadoc/org/bukkit/util/package-info.java @@ -0,0 +1,6 @@ +/** + * Multi and single purpose classes to facilitate various programmatic + * concepts. + */ +package org.bukkit.util; + diff --git a/api/src/main/javadoc/org/bukkit/util/permissions/package-info.java b/api/src/main/javadoc/org/bukkit/util/permissions/package-info.java new file mode 100644 index 000000000..07498118d --- /dev/null +++ b/api/src/main/javadoc/org/bukkit/util/permissions/package-info.java @@ -0,0 +1,6 @@ +/** + * Static methods for miscellaneous {@link org.bukkit.permissions.Permission + * permission} functionality. + */ +package org.bukkit.util.permissions; + diff --git a/api/src/main/javadoc/overview.html b/api/src/main/javadoc/overview.html new file mode 100644 index 000000000..e96bf35eb --- /dev/null +++ b/api/src/main/javadoc/overview.html @@ -0,0 +1,17 @@ + +

Bukkit, the plugin development framework.

+ +

+ The documentation is for developing plugins and is split into the + respective packages for each subject matter. This documentation does not + cover running a server, contributing code back to the project, or setting + up a workspace. Working knowledge of the Java language is a prerequisite + for developing plugins. +

+ For basic plugin development, see the {@link org.bukkit.plugin plugin + package}. It covers the basic requirements of a plugin jar. +

+ For handling events and triggered code, see the {@link org.bukkit.event + event package}. +

+ diff --git a/src/api/pom.xml b/api/src/pom.xml similarity index 98% rename from src/api/pom.xml rename to api/src/pom.xml index fc0033a93..db802b650 100644 --- a/src/api/pom.xml +++ b/api/src/pom.xml @@ -3,18 +3,18 @@ xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> 4.0.0 + akarin-api + 1.13.2-R0.1-SNAPSHOT + jar + com.destroystokyo.paper paper-parent dev-SNAPSHOT - paper-api - 1.13.2-R0.1-SNAPSHOT - jar - - Paper-API - https://github.com/PaperMC/Paper + Akarin-API + https://github.com/Akarin-project/Akarin An enhanced plugin API for Minecraft servers. diff --git a/api/src/test/java/com/destroystokyo/paper/MaterialTagsTest.java b/api/src/test/java/com/destroystokyo/paper/MaterialTagsTest.java new file mode 100644 index 000000000..328c51471 --- /dev/null +++ b/api/src/test/java/com/destroystokyo/paper/MaterialTagsTest.java @@ -0,0 +1,25 @@ +/* + * Copyright (c) 2018 Daniel Ennis (Aikar) MIT License + */ + +package com.destroystokyo.paper; + +import org.bukkit.Bukkit; +import org.bukkit.TestServer; +import org.junit.Test; + +import java.util.logging.Level; + +public class MaterialTagsTest { + @Test + public void testInitialize() { + try { + TestServer.getInstance(); + MaterialTags.SHULKER_BOXES.getValues(); + assert true; + } catch (Throwable e) { + Bukkit.getLogger().log(Level.SEVERE, e.getMessage(), e); + assert false; + } + } +} diff --git a/api/src/test/java/org/bukkit/AnnotationTest.java b/api/src/test/java/org/bukkit/AnnotationTest.java new file mode 100644 index 000000000..cdc0afc0c --- /dev/null +++ b/api/src/test/java/org/bukkit/AnnotationTest.java @@ -0,0 +1,257 @@ +package org.bukkit; + +import java.io.File; +import java.io.FileInputStream; +import java.io.IOException; +import java.net.URISyntaxException; +import java.net.URL; +import java.util.ArrayList; +import java.util.Collection; +import java.util.Collections; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; +import org.junit.Assert; +import org.junit.Test; +import org.objectweb.asm.ClassReader; +import org.objectweb.asm.Opcodes; +import org.objectweb.asm.Type; +import org.objectweb.asm.tree.AnnotationNode; +import org.objectweb.asm.tree.ClassNode; +import org.objectweb.asm.tree.MethodNode; +import org.objectweb.asm.tree.ParameterNode; + +public class AnnotationTest { + + private static final String[] ACCEPTED_ANNOTATIONS = { + "Lorg/jetbrains/annotations/Nullable;", + "Lorg/jetbrains/annotations/NotNull;", + "Lorg/jetbrains/annotations/Contract;", + "Lorg/bukkit/UndefinedNullability;" + }; + + private static final String[] EXCLUDED_CLASSES = { + // Internal technical classes + "org/bukkit/plugin/java/JavaPluginLoader", + "org/bukkit/util/io/BukkitObjectInputStream", + "org/bukkit/util/io/BukkitObjectOutputStream", + "org/bukkit/util/io/Wrapper", + "org/bukkit/plugin/java/PluginClassLoader", + // Generic functional interface + "org/bukkit/util/Consumer", + // Paper start + // Timings history is broken in terms of nullability due to guavas Function defining that the param is NonNull + "co/aikar/timings/TimingHistory$2", + "co/aikar/timings/TimingHistory$2$1", + "co/aikar/timings/TimingHistory$2$1$1", + "co/aikar/timings/TimingHistory$2$1$2", + "co/aikar/timings/TimingHistory$3", + "co/aikar/timings/TimingHistory$4", + "co/aikar/timings/TimingHistoryEntry$1" + // Paper end + }; + + @Test + public void testAll() throws IOException, URISyntaxException { + URL loc = Bukkit.class.getProtectionDomain().getCodeSource().getLocation(); + File file = new File(loc.toURI()); + + // Running from jar is not supported yet + Assert.assertTrue("code must be in a directory", file.isDirectory()); + + final HashMap foundClasses = new HashMap<>(); + collectClasses(file, foundClasses); + + final ArrayList errors = new ArrayList<>(); + + for (ClassNode clazz : foundClasses.values()) { + if (!isClassIncluded(clazz, foundClasses)) { + continue; + } + + for (MethodNode method : clazz.methods) { + if (!isMethodIncluded(clazz, method, foundClasses)) { + continue; + } + + if (mustBeAnnotated(Type.getReturnType(method.desc)) && !isWellAnnotated(method.invisibleAnnotations)) { + warn(errors, clazz, method, "return value"); + } + + Type[] paramTypes = Type.getArgumentTypes(method.desc); + List parameters = method.parameters; + + for (int i = 0; i < paramTypes.length; i++) { + if (mustBeAnnotated(paramTypes[i]) && !isWellAnnotated(method.invisibleParameterAnnotations == null ? null : method.invisibleParameterAnnotations[i])) { + ParameterNode paramNode = parameters == null ? null : parameters.get(i); + String paramName = paramNode == null ? null : paramNode.name; + + warn(errors, clazz, method, "parameter " + i + (paramName == null ? "" : ": " + paramName)); + } + } + } + } + + if (errors.isEmpty()) { + // Success + return; + } + + Collections.sort(errors); + + System.out.println(errors.size() + " missing annotation(s):"); + for (String message : errors) { + System.out.print("\t"); + System.out.println(message); + } + + Assert.fail("There " + errors.size() + " are missing annotation(s)"); + } + + private static void collectClasses(@NotNull File from, @NotNull Map to) throws IOException { + if (from.isDirectory()) { + final File[] files = from.listFiles(); + assert files != null; + + for (File file : files) { + collectClasses(file, to); + } + return; + } + + if (!from.getName().endsWith(".class")) { + return; + } + + try (FileInputStream in = new FileInputStream(from)) { + final ClassReader cr = new ClassReader(in); + + final ClassNode node = new ClassNode(); + cr.accept(node, ClassReader.SKIP_CODE | ClassReader.SKIP_DEBUG | ClassReader.SKIP_FRAMES); + + to.put(node.name, node); + } + } + + private static boolean isClassIncluded(@NotNull ClassNode clazz, @NotNull Map allClasses) { + // Exclude private, synthetic or deprecated classes and annotations, since their members can't be null + if ((clazz.access & (Opcodes.ACC_PRIVATE | Opcodes.ACC_SYNTHETIC | Opcodes.ACC_DEPRECATED | Opcodes.ACC_ANNOTATION)) != 0) { + return false; + } + + if (isSubclassOf(clazz, "org/bukkit/material/MaterialData", allClasses)) { + // MaterialData is deprecated and all of its subclasses are excluded + return false; + } + + if (isSubclassOf(clazz, "java/lang/Exception", allClasses) + || isSubclassOf(clazz, "java/lang/RuntimeException", allClasses)) { + // Exceptions are excluded + return false; + } + + for (String excludedClass : EXCLUDED_CLASSES) { + if (excludedClass.equals(clazz.name)) { + return false; + } + } + + return true; + } + + private static boolean isMethodIncluded(@NotNull ClassNode clazz, @NotNull MethodNode method, @NotNull Map allClasses) { + // Exclude private, synthetic and deprecated methods + if ((method.access & (Opcodes.ACC_PRIVATE | Opcodes.ACC_SYNTHETIC | Opcodes.ACC_DEPRECATED)) != 0 || (method.access & (Opcodes.ACC_PRIVATE | Opcodes.ACC_PROTECTED | Opcodes.ACC_PUBLIC)) == 0) { // Paper - ignore package-private + return false; + } + + // Exclude Java methods + if (is(method, "toString", 0) || is(method, "clone", 0) || is(method, "equals", 1)) { + return false; + } + + // Exclude generated Enum methods + if (isSubclassOf(clazz, "java/lang/Enum", allClasses) && (is(method, "values", 0) || is(method, "valueOf", 1))) { + return false; + } + + // Anonymous classes have generated constructors, which can't be annotated nor invoked + if ("".equals(method.name) && isAnonymous(clazz)) { + return false; + } + + return true; + } + + private static boolean isWellAnnotated(@Nullable List annotations) { + if (annotations == null) { + return false; + } + + for (AnnotationNode node : annotations) { + for (String acceptedAnnotation : ACCEPTED_ANNOTATIONS) { + if (acceptedAnnotation.equals(node.desc)) { + return true; + } + } + } + + return false; + } + + private static boolean mustBeAnnotated(@NotNull Type type) { + return type.getSort() == Type.ARRAY || type.getSort() == Type.OBJECT; + } + + private static boolean is(@NotNull MethodNode method, @NotNull String name, int parameters) { + final List params = method.parameters; + return method.name.equals(name) && (params == null || params.size() == parameters); + } + + /** + * @return true if given class is anonymous + */ + private static boolean isAnonymous(@NotNull ClassNode clazz) { + final String name = clazz.name; + if (name == null) { + return false; + } + final int nestedSeparator = name.lastIndexOf('$'); + if (nestedSeparator == -1 || nestedSeparator + 1 == name.length()) { + return false; + } + + // Nested classes have purely numeric names. Java classes can't begin with a number, + // so if first character is a number, the class must be anonymous + final char c = name.charAt(nestedSeparator + 1); + return c >= '0' && c <= '9'; + } + + private static boolean isSubclassOf(@NotNull ClassNode what, @NotNull String ofWhat, @NotNull Map allClasses) { + if (ofWhat.equals(what.name) + // Not only optimization: Super class may not be present in allClasses, so it is checked here + || ofWhat.equals(what.superName)) { + return true; + } + + final ClassNode parent = allClasses.get(what.superName); + if (parent != null && isSubclassOf(parent, ofWhat, allClasses)) { + return true; + } + + for (String superInterface : what.interfaces) { + final ClassNode interfaceParent = allClasses.get(superInterface); + if (interfaceParent != null && isSubclassOf(interfaceParent, ofWhat, allClasses)) { + return true; + } + } + + return false; + } + + private static void warn(@NotNull Collection out, @NotNull ClassNode clazz, @NotNull MethodNode method, @NotNull String description) { + out.add(clazz.name + " \t" + method.name + " \t" + description); + } +} diff --git a/api/src/test/java/org/bukkit/ArtTest.java b/api/src/test/java/org/bukkit/ArtTest.java new file mode 100644 index 000000000..29fe34ad4 --- /dev/null +++ b/api/src/test/java/org/bukkit/ArtTest.java @@ -0,0 +1,45 @@ +package org.bukkit; + +import static org.hamcrest.CoreMatchers.is; +import static org.hamcrest.Matchers.greaterThan; +import static org.junit.Assert.assertThat; + +import org.junit.Test; + +public class ArtTest { + + @Test(expected = IllegalArgumentException.class) + public void getByNullName() { + Art.getByName(null); + } + + @Test + public void getById() { + for (Art art : Art.values()) { + assertThat(Art.getById(art.getId()), is(art)); + } + } + + @Test + public void getByName() { + for (Art art : Art.values()) { + assertThat(Art.getByName(art.toString()), is(art)); + } + } + + @Test + public void dimensionSanityCheck() { + for (Art art : Art.values()) { + assertThat(art.getBlockHeight(), is(greaterThan(0))); + assertThat(art.getBlockWidth(), is(greaterThan(0))); + } + } + + @Test + public void getByNameWithMixedCase() { + Art subject = Art.values()[0]; + String name = subject.toString().replace('E', 'e'); + + assertThat(Art.getByName(name), is(subject)); + } +} diff --git a/api/src/test/java/org/bukkit/BukkitMirrorTest.java b/api/src/test/java/org/bukkit/BukkitMirrorTest.java new file mode 100644 index 000000000..9d7b9eb65 --- /dev/null +++ b/api/src/test/java/org/bukkit/BukkitMirrorTest.java @@ -0,0 +1,75 @@ +package org.bukkit; + +import static org.hamcrest.Matchers.*; +import static org.junit.Assert.*; + +import java.lang.reflect.Method; +import java.lang.reflect.Modifier; +import java.util.Arrays; +import java.util.List; + +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.Parameterized; +import org.junit.runners.Parameterized.Parameter; +import org.junit.runners.Parameterized.Parameters; + +import com.google.common.base.Function; +import com.google.common.collect.Lists; + +@RunWith(Parameterized.class) +public class BukkitMirrorTest { + + @Parameters(name="{index}: {1}") + public static List data() { + return Lists.transform(Arrays.asList(Server.class.getDeclaredMethods()), new Function() { + @Override + public Object[] apply(Method input) { + return new Object[] { + input, + input.toGenericString().substring("public abstract ".length()).replace("(", "{").replace(")", "}") + }; + } + }); + } + + @Parameter(0) + public Method server; + + @Parameter(1) + public String name; + + private Method bukkit; + + @Before + public void makeBukkit() throws Throwable { + bukkit = Bukkit.class.getDeclaredMethod(server.getName(), server.getParameterTypes()); + } + + @Test + public void isStatic() throws Throwable { + assertThat(Modifier.isStatic(bukkit.getModifiers()), is(true)); + } + + @Test + public void isDeprecated() throws Throwable { + assertThat(bukkit.isAnnotationPresent(Deprecated.class), is(server.isAnnotationPresent(Deprecated.class))); + } + + @Test + public void returnType() throws Throwable { + assertThat(bukkit.getReturnType(), is((Object) server.getReturnType())); + // assertThat(bukkit.getGenericReturnType(), is(server.getGenericReturnType())); // too strict on type generics + } + + @Test + public void parameterTypes() throws Throwable { + // assertThat(bukkit.getGenericParameterTypes(), is(server.getGenericParameterTypes())); // too strict on type generics + } + + @Test + public void declaredException() throws Throwable { + assertThat(bukkit.getGenericExceptionTypes(), is(server.getGenericExceptionTypes())); + } +} diff --git a/api/src/test/java/org/bukkit/ChatColorTest.java b/api/src/test/java/org/bukkit/ChatColorTest.java new file mode 100644 index 000000000..80108a5d3 --- /dev/null +++ b/api/src/test/java/org/bukkit/ChatColorTest.java @@ -0,0 +1,83 @@ +package org.bukkit; + +import static org.hamcrest.CoreMatchers.is; +import static org.hamcrest.CoreMatchers.nullValue; +import static org.junit.Assert.assertThat; + +import org.junit.Test; + +public class ChatColorTest { + + @Test + public void getByChar() { + for (ChatColor color : ChatColor.values()) { + assertThat(ChatColor.getByChar(color.getChar()), is(color)); + } + } + + @Test(expected = IllegalArgumentException.class) + public void getByStringWithNull() { + ChatColor.getByChar((String) null); + } + + @Test(expected = IllegalArgumentException.class) + public void getByStringWithEmpty() { + ChatColor.getByChar(""); + } + + @Test + public void getByNull() { + assertThat(ChatColor.stripColor(null), is(nullValue())); + } + + @Test + public void getByString() { + for (ChatColor color : ChatColor.values()) { + assertThat(ChatColor.getByChar(String.valueOf(color.getChar())), is(color)); + } + } + + @Test + public void stripColorOnNullString() { + assertThat(ChatColor.stripColor(null), is(nullValue())); + } + + @Test + public void stripColor() { + StringBuilder subject = new StringBuilder(); + StringBuilder expected = new StringBuilder(); + + final String filler = "test"; + for (ChatColor color : ChatColor.values()) { + subject.append(color).append(filler); + expected.append(filler); + } + + assertThat(ChatColor.stripColor(subject.toString()), is(expected.toString())); + } + + @Test + public void toStringWorks() { + for (ChatColor color : ChatColor.values()) { + assertThat(String.format("%c%c", ChatColor.COLOR_CHAR, color.getChar()), is(color.toString())); + } + } + + @Test + public void translateAlternateColorCodes() { + String s = "&0&1&2&3&4&5&6&7&8&9&A&a&B&b&C&c&D&d&E&e&F&f&K&k & more"; + String t = ChatColor.translateAlternateColorCodes('&', s); + String u = ChatColor.BLACK.toString() + ChatColor.DARK_BLUE + ChatColor.DARK_GREEN + ChatColor.DARK_AQUA + ChatColor.DARK_RED + ChatColor.DARK_PURPLE + ChatColor.GOLD + ChatColor.GRAY + ChatColor.DARK_GRAY + ChatColor.BLUE + ChatColor.GREEN + ChatColor.GREEN + ChatColor.AQUA + ChatColor.AQUA + ChatColor.RED + ChatColor.RED + ChatColor.LIGHT_PURPLE + ChatColor.LIGHT_PURPLE + ChatColor.YELLOW + ChatColor.YELLOW + ChatColor.WHITE + ChatColor.WHITE + ChatColor.MAGIC + ChatColor.MAGIC + " & more"; + assertThat(t, is(u)); + } + + @Test + public void getChatColors() { + String s = String.format("%c%ctest%c%ctest%c", ChatColor.COLOR_CHAR, ChatColor.RED.getChar(), ChatColor.COLOR_CHAR, ChatColor.ITALIC.getChar(), ChatColor.COLOR_CHAR); + String expected = ChatColor.RED.toString() + ChatColor.ITALIC; + assertThat(ChatColor.getLastColors(s), is(expected)); + + s = String.format("%c%ctest%c%ctest", ChatColor.COLOR_CHAR, ChatColor.RED.getChar(), ChatColor.COLOR_CHAR, ChatColor.BLUE.getChar()); + assertThat(ChatColor.getLastColors(s), is(ChatColor.BLUE.toString())); + } +} diff --git a/api/src/test/java/org/bukkit/ChatPaginatorTest.java b/api/src/test/java/org/bukkit/ChatPaginatorTest.java new file mode 100644 index 000000000..a644b150a --- /dev/null +++ b/api/src/test/java/org/bukkit/ChatPaginatorTest.java @@ -0,0 +1,174 @@ +package org.bukkit; + +import org.bukkit.util.ChatPaginator; +import org.junit.Test; + +import static org.hamcrest.CoreMatchers.is; +import static org.hamcrest.CoreMatchers.nullValue; +import static org.junit.Assert.assertThat; + +public class ChatPaginatorTest { + @Test + public void testWordWrap1() { + String rawString = ChatColor.RED + "123456789 123456789 123456789"; + String[] lines = ChatPaginator.wordWrap(rawString, 19); + + assertThat(lines.length, is(2)); + assertThat(lines[0], is(ChatColor.RED + "123456789 123456789")); + assertThat(lines[1], is(ChatColor.RED.toString() + "123456789")); + } + + @Test + public void testWordWrap2() { + String rawString = "123456789 123456789 123456789"; + String[] lines = ChatPaginator.wordWrap(rawString, 22); + + assertThat(lines.length, is(2)); + assertThat(lines[0], is(ChatColor.WHITE.toString() + "123456789 123456789")); + assertThat(lines[1], is(ChatColor.WHITE.toString() + "123456789")); + } + + @Test + public void testWordWrap3() { + String rawString = ChatColor.RED + "123456789 " + ChatColor.RED + ChatColor.RED + "123456789 " + ChatColor.RED + "123456789"; + String[] lines = ChatPaginator.wordWrap(rawString, 16); + + assertThat(lines.length, is(3)); + assertThat(lines[0], is(ChatColor.RED + "123456789")); + assertThat(lines[1], is(ChatColor.RED.toString() + ChatColor.RED + "123456789")); + assertThat(lines[2], is(ChatColor.RED + "123456789")); + } + + @Test + public void testWordWrap4() { + String rawString = "123456789 123456789 123456789 12345"; + String[] lines = ChatPaginator.wordWrap(rawString, 19); + + assertThat(lines.length, is(2)); + assertThat(lines[0], is(ChatColor.WHITE.toString() + "123456789 123456789")); + assertThat(lines[1], is(ChatColor.WHITE.toString() + "123456789 12345")); + } + + @Test + public void testWordWrap5() { + String rawString = "123456789\n123456789 123456789"; + String[] lines = ChatPaginator.wordWrap(rawString, 19); + + assertThat(lines.length, is(2)); + assertThat(lines[0], is(ChatColor.WHITE.toString() + "123456789")); + assertThat(lines[1], is(ChatColor.WHITE.toString() + "123456789 123456789")); + } + + @Test + public void testWordWrap6() { + String rawString = "12345678 23456789 123456789"; + String[] lines = ChatPaginator.wordWrap(rawString, 19); + + assertThat(lines.length, is(2)); + assertThat(lines[0], is(ChatColor.WHITE.toString() + "12345678 23456789")); + assertThat(lines[1], is(ChatColor.WHITE.toString() + "123456789")); + } + + @Test + public void testWordWrap7() { + String rawString = "12345678 23456789 123456789"; + String[] lines = ChatPaginator.wordWrap(rawString, 19); + + assertThat(lines.length, is(2)); + assertThat(lines[0], is(ChatColor.WHITE.toString() + "12345678 23456789")); + assertThat(lines[1], is(ChatColor.WHITE.toString() + "123456789")); + } + + @Test + public void testWordWrap8() { + String rawString = "123456789 123456789 123456789"; + String[] lines = ChatPaginator.wordWrap(rawString, 6); + + assertThat(lines.length, is(6)); + assertThat(lines[0], is(ChatColor.WHITE.toString() + "123456")); + assertThat(lines[1], is(ChatColor.WHITE.toString() + "789")); + assertThat(lines[2], is(ChatColor.WHITE.toString() + "123456")); + assertThat(lines[3], is(ChatColor.WHITE.toString() + "789")); + assertThat(lines[4], is(ChatColor.WHITE.toString() + "123456")); + assertThat(lines[5], is(ChatColor.WHITE.toString() + "789")); + } + + @Test + public void testWordWrap9() { + String rawString = "1234 123456789 123456789 123456789"; + String[] lines = ChatPaginator.wordWrap(rawString, 6); + + assertThat(lines.length, is(7)); + assertThat(lines[0], is(ChatColor.WHITE.toString() + "1234")); + assertThat(lines[1], is(ChatColor.WHITE.toString() + "123456")); + assertThat(lines[2], is(ChatColor.WHITE.toString() + "789")); + assertThat(lines[3], is(ChatColor.WHITE.toString() + "123456")); + assertThat(lines[4], is(ChatColor.WHITE.toString() + "789")); + assertThat(lines[5], is(ChatColor.WHITE.toString() + "123456")); + assertThat(lines[6], is(ChatColor.WHITE.toString() + "789")); + } + + @Test + public void testWordWrap10() { + String rawString = "123456789\n123456789"; + String[] lines = ChatPaginator.wordWrap(rawString, 19); + + assertThat(lines.length, is(2)); + assertThat(lines[0], is(ChatColor.WHITE.toString() + "123456789")); + assertThat(lines[1], is(ChatColor.WHITE.toString() + "123456789")); + } + + @Test + public void testWordWrap11() { + String rawString = ChatColor.RED + "a a a " + ChatColor.BLUE + "a a"; + String[] lines = ChatPaginator.wordWrap(rawString, 9); + + assertThat(lines.length, is(1)); + assertThat(lines[0], is(ChatColor.RED + "a a a " + ChatColor.BLUE + "a a")); + } + + @Test + public void testWordWrap12() { + String rawString = "123 1 123"; + String[] lines = ChatPaginator.wordWrap(rawString, 5); + + assertThat(lines.length, is(2)); + assertThat(lines[0], is(ChatColor.WHITE.toString() + "123 1")); + assertThat(lines[1], is(ChatColor.WHITE.toString() + "123")); + } + + @Test + public void testPaginate1() { + String rawString = "1234 123456789 123456789 123456789"; + ChatPaginator.ChatPage page = ChatPaginator.paginate(rawString, 1, 6, 2); + + assertThat(page.getPageNumber(), is(1)); + assertThat(page.getTotalPages(), is(4)); + assertThat(page.getLines().length, is(2)); + assertThat(page.getLines()[0], is(ChatColor.WHITE.toString() + "1234")); + assertThat(page.getLines()[1], is(ChatColor.WHITE.toString() + "123456")); + } + + @Test + public void testPaginate2() { + String rawString = "1234 123456789 123456789 123456789"; + ChatPaginator.ChatPage page = ChatPaginator.paginate(rawString, 2, 6, 2); + + assertThat(page.getPageNumber(), is(2)); + assertThat(page.getTotalPages(), is(4)); + assertThat(page.getLines().length, is(2)); + assertThat(page.getLines()[0], is(ChatColor.WHITE.toString() + "789")); + assertThat(page.getLines()[1], is(ChatColor.WHITE.toString() + "123456")); + } + + @Test + public void testPaginate3() { + String rawString = "1234 123456789 123456789 123456789"; + ChatPaginator.ChatPage page = ChatPaginator.paginate(rawString, 4, 6, 2); + + assertThat(page.getPageNumber(), is(4)); + assertThat(page.getTotalPages(), is(4)); + assertThat(page.getLines().length, is(1)); + assertThat(page.getLines()[0], is(ChatColor.WHITE.toString() + "789")); + } +} diff --git a/api/src/test/java/org/bukkit/CoalTypeTest.java b/api/src/test/java/org/bukkit/CoalTypeTest.java new file mode 100644 index 000000000..7eb11ceaf --- /dev/null +++ b/api/src/test/java/org/bukkit/CoalTypeTest.java @@ -0,0 +1,15 @@ +package org.bukkit; + +import static org.hamcrest.CoreMatchers.is; +import static org.junit.Assert.assertThat; + +import org.junit.Test; + +public class CoalTypeTest { + @Test + public void getByData() { + for (CoalType coalType : CoalType.values()) { + assertThat(CoalType.getByData(coalType.getData()), is(coalType)); + } + } +} diff --git a/api/src/test/java/org/bukkit/ColorTest.java b/api/src/test/java/org/bukkit/ColorTest.java new file mode 100644 index 000000000..8d9557af1 --- /dev/null +++ b/api/src/test/java/org/bukkit/ColorTest.java @@ -0,0 +1,365 @@ +package org.bukkit; + +import static org.hamcrest.Matchers.*; +import static org.junit.Assert.*; + +import org.bukkit.configuration.file.YamlConfiguration; +import org.junit.Test; + +@SuppressWarnings("javadoc") +public class ColorTest { + static class TestColor { + static int id = 0; + final String name; + final int rgb; + final int bgr; + final int r; + final int g; + final int b; + + TestColor(int rgb, int bgr, int r, int g, int b) { + this.rgb = rgb; + this.bgr = bgr; + this.r = r; + this.g = g; + this.b = b; + this.name = id + ":" + Integer.toHexString(rgb).toUpperCase() + "_" + Integer.toHexString(bgr).toUpperCase() + "-r" + Integer.toHexString(r).toUpperCase() + "-g" + Integer.toHexString(g).toUpperCase() + "-b" + Integer.toHexString(b).toUpperCase(); + } + } + + static TestColor[] examples = new TestColor[] { + /* 0xRRGGBB, 0xBBGGRR, 0xRR, 0xGG, 0xBB */ + new TestColor(0xFFFFFF, 0xFFFFFF, 0xFF, 0xFF, 0xFF), + new TestColor(0xFFFFAA, 0xAAFFFF, 0xFF, 0xFF, 0xAA), + new TestColor(0xFF00FF, 0xFF00FF, 0xFF, 0x00, 0xFF), + new TestColor(0x67FF22, 0x22FF67, 0x67, 0xFF, 0x22), + new TestColor(0x000000, 0x000000, 0x00, 0x00, 0x00) + }; + + @Test + public void testSerialization() throws Throwable { + for (TestColor testColor : examples) { + Color base = Color.fromRGB(testColor.rgb); + + YamlConfiguration toSerialize = new YamlConfiguration(); + toSerialize.set("color", base); + String serialized = toSerialize.saveToString(); + + YamlConfiguration deserialized = new YamlConfiguration(); + deserialized.loadFromString(serialized); + + assertThat(testColor.name + " on " + serialized, base, is(deserialized.getColor("color"))); + } + } + + // Equality tests + @Test + public void testEqualities() { + for (TestColor testColor : examples) { + Color fromRGB = Color.fromRGB(testColor.rgb); + Color fromBGR = Color.fromBGR(testColor.bgr); + Color fromRGBs = Color.fromRGB(testColor.r, testColor.g, testColor.b); + Color fromBGRs = Color.fromBGR(testColor.b, testColor.g, testColor.r); + + assertThat(testColor.name, fromRGB, is(fromRGBs)); + assertThat(testColor.name, fromRGB, is(fromBGR)); + assertThat(testColor.name, fromRGB, is(fromBGRs)); + assertThat(testColor.name, fromRGBs, is(fromBGR)); + assertThat(testColor.name, fromRGBs, is(fromBGRs)); + assertThat(testColor.name, fromBGR, is(fromBGRs)); + } + } + + @Test + public void testInequalities() { + for (int i = 1; i < examples.length; i++) { + TestColor testFrom = examples[i]; + Color from = Color.fromRGB(testFrom.rgb); + for (int j = i - 1; j >= 0; j--) { + TestColor testTo = examples[j]; + Color to = Color.fromRGB(testTo.rgb); + String name = testFrom.name + " to " + testTo.name; + assertThat(name, from, is(not(to))); + + Color transform = from.setRed(testTo.r).setBlue(testTo.b).setGreen(testTo.g); + assertThat(name, transform, is(not(sameInstance(from)))); + assertThat(name, transform, is(to)); + } + } + } + + // RGB tests + @Test + public void testRGB() { + for (TestColor testColor : examples) { + assertThat(testColor.name, Color.fromRGB(testColor.rgb).asRGB(), is(testColor.rgb)); + assertThat(testColor.name, Color.fromBGR(testColor.bgr).asRGB(), is(testColor.rgb)); + assertThat(testColor.name, Color.fromRGB(testColor.r, testColor.g, testColor.b).asRGB(), is(testColor.rgb)); + assertThat(testColor.name, Color.fromBGR(testColor.b, testColor.g, testColor.r).asRGB(), is(testColor.rgb)); + } + } + + @Test(expected=IllegalArgumentException.class) + public void testInvalidRGB1() { + Color.fromRGB(0x01000000); + } + + @Test(expected=IllegalArgumentException.class) + public void testInvalidRGB2() { + Color.fromRGB(Integer.MIN_VALUE); + } + + @Test(expected=IllegalArgumentException.class) + public void testInvalidRGB3() { + Color.fromRGB(Integer.MAX_VALUE); + } + + @Test(expected=IllegalArgumentException.class) + public void testInvalidRGB4() { + Color.fromRGB(-1); + } + + // BGR tests + @Test + public void testBGR() { + for (TestColor testColor : examples) { + assertThat(testColor.name, Color.fromRGB(testColor.rgb).asBGR(), is(testColor.bgr)); + assertThat(testColor.name, Color.fromBGR(testColor.bgr).asBGR(), is(testColor.bgr)); + assertThat(testColor.name, Color.fromRGB(testColor.r, testColor.g, testColor.b).asBGR(), is(testColor.bgr)); + assertThat(testColor.name, Color.fromBGR(testColor.b, testColor.g, testColor.r).asBGR(), is(testColor.bgr)); + } + } + + @Test(expected=IllegalArgumentException.class) + public void testInvalidBGR1() { + Color.fromBGR(0x01000000); + } + + @Test(expected=IllegalArgumentException.class) + public void testInvalidBGR2() { + Color.fromBGR(Integer.MIN_VALUE); + } + + @Test(expected=IllegalArgumentException.class) + public void testInvalidBGR3() { + Color.fromBGR(Integer.MAX_VALUE); + } + + @Test(expected=IllegalArgumentException.class) + public void testInvalidBGR4() { + Color.fromBGR(-1); + } + + // Red tests + @Test + public void testRed() { + for (TestColor testColor : examples) { + assertThat(testColor.name, Color.fromRGB(testColor.rgb).getRed(), is(testColor.r)); + assertThat(testColor.name, Color.fromBGR(testColor.bgr).getRed(), is(testColor.r)); + assertThat(testColor.name, Color.fromRGB(testColor.r, testColor.g, testColor.b).getRed(), is(testColor.r)); + assertThat(testColor.name, Color.fromBGR(testColor.b, testColor.g, testColor.r).getRed(), is(testColor.r)); + } + } + + @Test(expected=IllegalArgumentException.class) + public void testInvalidR01() { + Color.fromRGB(-1, 0x00, 0x00); + } + + @Test(expected=IllegalArgumentException.class) + public void testInvalidR02() { + Color.fromRGB(Integer.MAX_VALUE, 0x00, 0x00); + } + + @Test(expected=IllegalArgumentException.class) + public void testInvalidR03() { + Color.fromRGB(Integer.MIN_VALUE, 0x00, 0x00); + } + + @Test(expected=IllegalArgumentException.class) + public void testInvalidR04() { + Color.fromRGB(0x100, 0x00, 0x00); + } + + @Test(expected=IllegalArgumentException.class) + public void testInvalidR05() { + Color.fromBGR(0x00, 0x00, -1); + } + + @Test(expected=IllegalArgumentException.class) + public void testInvalidR06() { + Color.fromBGR(0x00, 0x00, Integer.MAX_VALUE); + } + + @Test(expected=IllegalArgumentException.class) + public void testInvalidR07() { + Color.fromBGR(0x00, 0x00, Integer.MIN_VALUE); + } + + @Test(expected=IllegalArgumentException.class) + public void testInvalidR08() { + Color.fromBGR(0x00, 0x00, 0x100); + } + + @Test(expected=IllegalArgumentException.class) + public void testInvalidR09() { + Color.WHITE.setRed(-1); + } + + @Test(expected=IllegalArgumentException.class) + public void testInvalidR10() { + Color.WHITE.setRed(Integer.MAX_VALUE); + } + + @Test(expected=IllegalArgumentException.class) + public void testInvalidR11() { + Color.WHITE.setRed(Integer.MIN_VALUE); + } + + @Test(expected=IllegalArgumentException.class) + public void testInvalidR12() { + Color.WHITE.setRed(0x100); + } + + // Blue tests + @Test + public void testBlue() { + for (TestColor testColor : examples) { + assertThat(testColor.name, Color.fromRGB(testColor.rgb).getBlue(), is(testColor.b)); + assertThat(testColor.name, Color.fromBGR(testColor.bgr).getBlue(), is(testColor.b)); + assertThat(testColor.name, Color.fromRGB(testColor.r, testColor.g, testColor.b).getBlue(), is(testColor.b)); + assertThat(testColor.name, Color.fromBGR(testColor.b, testColor.g, testColor.r).getBlue(), is(testColor.b)); + } + } + + @Test(expected=IllegalArgumentException.class) + public void testInvalidB01() { + Color.fromRGB(0x00, 0x00, -1); + } + + @Test(expected=IllegalArgumentException.class) + public void testInvalidB02() { + Color.fromRGB(0x00, 0x00, Integer.MAX_VALUE); + } + + @Test(expected=IllegalArgumentException.class) + public void testInvalidB03() { + Color.fromRGB(0x00, 0x00, Integer.MIN_VALUE); + } + + @Test(expected=IllegalArgumentException.class) + public void testInvalidB04() { + Color.fromRGB(0x00, 0x00, 0x100); + } + + @Test(expected=IllegalArgumentException.class) + public void testInvalidB05() { + Color.fromBGR(-1, 0x00, 0x00); + } + + @Test(expected=IllegalArgumentException.class) + public void testInvalidB06() { + Color.fromBGR(Integer.MAX_VALUE, 0x00, 0x00); + } + + @Test(expected=IllegalArgumentException.class) + public void testInvalidB07() { + Color.fromBGR(Integer.MIN_VALUE, 0x00, 0x00); + } + + @Test(expected=IllegalArgumentException.class) + public void testInvalidB08() { + Color.fromBGR(0x100, 0x00, 0x00); + } + + @Test(expected=IllegalArgumentException.class) + public void testInvalidB09() { + Color.WHITE.setBlue(-1); + } + + @Test(expected=IllegalArgumentException.class) + public void testInvalidB10() { + Color.WHITE.setBlue(Integer.MAX_VALUE); + } + + @Test(expected=IllegalArgumentException.class) + public void testInvalidB11() { + Color.WHITE.setBlue(Integer.MIN_VALUE); + } + + @Test(expected=IllegalArgumentException.class) + public void testInvalidB12() { + Color.WHITE.setBlue(0x100); + } + + // Green tests + @Test + public void testGreen() { + for (TestColor testColor : examples) { + assertThat(testColor.name, Color.fromRGB(testColor.rgb).getGreen(), is(testColor.g)); + assertThat(testColor.name, Color.fromBGR(testColor.bgr).getGreen(), is(testColor.g)); + assertThat(testColor.name, Color.fromRGB(testColor.r, testColor.g, testColor.b).getGreen(), is(testColor.g)); + assertThat(testColor.name, Color.fromBGR(testColor.b, testColor.g, testColor.r).getGreen(), is(testColor.g)); + } + } + + @Test(expected=IllegalArgumentException.class) + public void testInvalidG01() { + Color.fromRGB(0x00, -1, 0x00); + } + + @Test(expected=IllegalArgumentException.class) + public void testInvalidG02() { + Color.fromRGB(0x00, Integer.MAX_VALUE, 0x00); + } + + @Test(expected=IllegalArgumentException.class) + public void testInvalidG03() { + Color.fromRGB(0x00, Integer.MIN_VALUE, 0x00); + } + + @Test(expected=IllegalArgumentException.class) + public void testInvalidG04() { + Color.fromRGB(0x00, 0x100, 0x00); + } + + @Test(expected=IllegalArgumentException.class) + public void testInvalidG05() { + Color.fromBGR(0x00, -1, 0x00); + } + + @Test(expected=IllegalArgumentException.class) + public void testInvalidG06() { + Color.fromBGR(0x00, Integer.MAX_VALUE, 0x00); + } + + @Test(expected=IllegalArgumentException.class) + public void testInvalidG07() { + Color.fromBGR(0x00, Integer.MIN_VALUE, 0x00); + } + + @Test(expected=IllegalArgumentException.class) + public void testInvalidG08() { + Color.fromBGR(0x00, 0x100, 0x00); + } + + @Test(expected=IllegalArgumentException.class) + public void testInvalidG09() { + Color.WHITE.setGreen(-1); + } + + @Test(expected=IllegalArgumentException.class) + public void testInvalidG10() { + Color.WHITE.setGreen(Integer.MAX_VALUE); + } + + @Test(expected=IllegalArgumentException.class) + public void testInvalidG11() { + Color.WHITE.setGreen(Integer.MIN_VALUE); + } + + @Test(expected=IllegalArgumentException.class) + public void testInvalidG12() { + Color.WHITE.setGreen(0x100); + } +} diff --git a/api/src/test/java/org/bukkit/CropStateTest.java b/api/src/test/java/org/bukkit/CropStateTest.java new file mode 100644 index 000000000..f792a1c4d --- /dev/null +++ b/api/src/test/java/org/bukkit/CropStateTest.java @@ -0,0 +1,15 @@ +package org.bukkit; + +import static org.hamcrest.CoreMatchers.is; +import static org.junit.Assert.assertThat; + +import org.junit.Test; + +public class CropStateTest { + @Test + public void getByData() { + for (CropState cropState : CropState.values()) { + assertThat(CropState.getByData(cropState.getData()), is(cropState)); + } + } +} diff --git a/api/src/test/java/org/bukkit/DifficultyTest.java b/api/src/test/java/org/bukkit/DifficultyTest.java new file mode 100644 index 000000000..293e283a2 --- /dev/null +++ b/api/src/test/java/org/bukkit/DifficultyTest.java @@ -0,0 +1,15 @@ +package org.bukkit; + +import static org.hamcrest.CoreMatchers.is; +import static org.junit.Assert.assertThat; + +import org.junit.Test; + +public class DifficultyTest { + @Test + public void getByValue() { + for (Difficulty difficulty : Difficulty.values()) { + assertThat(Difficulty.getByValue(difficulty.getValue()), is(difficulty)); + } + } +} diff --git a/api/src/test/java/org/bukkit/DyeColorTest.java b/api/src/test/java/org/bukkit/DyeColorTest.java new file mode 100644 index 000000000..c5aaecf57 --- /dev/null +++ b/api/src/test/java/org/bukkit/DyeColorTest.java @@ -0,0 +1,71 @@ +package org.bukkit; + +import static org.junit.Assert.*; +import static org.hamcrest.Matchers.*; + +import java.util.ArrayList; +import java.util.List; + +import org.bukkit.material.Colorable; +import org.bukkit.material.Dye; +import org.bukkit.material.Wool; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.Parameterized; +import org.junit.runners.Parameterized.Parameter; +import org.junit.runners.Parameterized.Parameters; + +@RunWith(Parameterized.class) +public class DyeColorTest { + + @Parameters(name= "{index}: {0}") + public static List data() { + List list = new ArrayList(); + for (DyeColor dye : DyeColor.values()) { + list.add(new Object[] {dye}); + } + return list; + } + + @Parameter public DyeColor dye; + + @Test + @SuppressWarnings("deprecation") + public void getByData() { + byte data = dye.getWoolData(); + + DyeColor byData = DyeColor.getByWoolData(data); + assertThat(byData, is(dye)); + } + + @Test + public void getByWoolData() { + byte data = dye.getWoolData(); + + DyeColor byData = DyeColor.getByWoolData(data); + assertThat(byData, is(dye)); + } + + @Test + public void getByDyeData() { + byte data = dye.getDyeData(); + + DyeColor byData = DyeColor.getByDyeData(data); + assertThat(byData, is(dye)); + } + + @Test + public void getDyeDyeColor() { + testColorable(new Dye(Material.LEGACY_INK_SACK, dye.getDyeData())); + testColorable(new Dye(dye)); + } + + @Test + public void getWoolDyeColor() { + testColorable(new Wool(Material.LEGACY_WOOL, dye.getWoolData())); + } + + private void testColorable(final Colorable colorable) { + assertThat(colorable.getColor(), is(this.dye)); + } +} diff --git a/api/src/test/java/org/bukkit/EffectTest.java b/api/src/test/java/org/bukkit/EffectTest.java new file mode 100644 index 000000000..08aa71d3a --- /dev/null +++ b/api/src/test/java/org/bukkit/EffectTest.java @@ -0,0 +1,15 @@ +package org.bukkit; + +import static org.hamcrest.CoreMatchers.is; +import static org.junit.Assert.assertThat; + +import org.junit.Test; + +public class EffectTest { + @Test + public void getById() { + for (Effect effect : Effect.values()) { + assertThat(Effect.getById(effect.getId()), is(effect)); + } + } +} diff --git a/api/src/test/java/org/bukkit/EntityEffectTest.java b/api/src/test/java/org/bukkit/EntityEffectTest.java new file mode 100644 index 000000000..0c92d5cdd --- /dev/null +++ b/api/src/test/java/org/bukkit/EntityEffectTest.java @@ -0,0 +1,15 @@ +package org.bukkit; + +import static org.hamcrest.CoreMatchers.is; +import static org.junit.Assert.assertThat; + +import org.junit.Test; + +public class EntityEffectTest { + @Test + public void getByData() { + for (EntityEffect entityEffect : EntityEffect.values()) { + assertThat(EntityEffect.getByData(entityEffect.getData()), is(entityEffect)); + } + } +} diff --git a/api/src/test/java/org/bukkit/GameModeTest.java b/api/src/test/java/org/bukkit/GameModeTest.java new file mode 100644 index 000000000..f671faefe --- /dev/null +++ b/api/src/test/java/org/bukkit/GameModeTest.java @@ -0,0 +1,15 @@ +package org.bukkit; + +import static org.hamcrest.CoreMatchers.is; +import static org.junit.Assert.assertThat; + +import org.junit.Test; + +public class GameModeTest { + @Test + public void getByValue() { + for (GameMode gameMode : GameMode.values()) { + assertThat(GameMode.getByValue(gameMode.getValue()), is(gameMode)); + } + } +} diff --git a/api/src/test/java/org/bukkit/GrassSpeciesTest.java b/api/src/test/java/org/bukkit/GrassSpeciesTest.java new file mode 100644 index 000000000..9e332c71d --- /dev/null +++ b/api/src/test/java/org/bukkit/GrassSpeciesTest.java @@ -0,0 +1,15 @@ +package org.bukkit; + +import static org.hamcrest.CoreMatchers.is; +import static org.junit.Assert.assertThat; + +import org.junit.Test; + +public class GrassSpeciesTest { + @Test + public void getByData() { + for (GrassSpecies grassSpecies : GrassSpecies.values()) { + assertThat(GrassSpecies.getByData(grassSpecies.getData()), is(grassSpecies)); + } + } +} diff --git a/api/src/test/java/org/bukkit/InstrumentTest.java b/api/src/test/java/org/bukkit/InstrumentTest.java new file mode 100644 index 000000000..f1a05705f --- /dev/null +++ b/api/src/test/java/org/bukkit/InstrumentTest.java @@ -0,0 +1,15 @@ +package org.bukkit; + +import static org.hamcrest.CoreMatchers.is; +import static org.junit.Assert.assertThat; + +import org.junit.Test; + +public class InstrumentTest { + @Test + public void getByType() { + for (Instrument instrument : Instrument.values()) { + assertThat(Instrument.getByType(instrument.getType()), is(instrument)); + } + } +} diff --git a/api/src/test/java/org/bukkit/LocationTest.java b/api/src/test/java/org/bukkit/LocationTest.java new file mode 100644 index 000000000..48d20761b --- /dev/null +++ b/api/src/test/java/org/bukkit/LocationTest.java @@ -0,0 +1,196 @@ +package org.bukkit; + +import static org.hamcrest.Matchers.*; +import static org.junit.Assert.*; + +import java.util.List; +import java.util.Random; + +import org.bukkit.util.Vector; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.Parameterized; +import org.junit.runners.Parameterized.Parameter; +import org.junit.runners.Parameterized.Parameters; + +import com.google.common.collect.ImmutableList; + +@RunWith(Parameterized.class) +public class LocationTest { + private static final double delta = 1.0 / 1000000; + /** + *
+     * a² + b² = c², a = b
+     * => 2∙(a²) = 2∙(b²) = c², c = 1
+     * => 2∙(a²) = 1
+     * => a² = 1/2
+     * => a = √(1/2) ∎
+     * 
+ */ + private static final double HALF_UNIT = Math.sqrt(1 / 2f); + /** + *
+     * a² + b² = c², c = √(1/2)
+     * => a² + b² = √(1/2)², a = b
+     * => 2∙(a²) = 2∙(b²) = 1/2
+     * => a² = 1/4
+     * => a = √(1/4) ∎
+     * 
+ */ + private static final double HALF_HALF_UNIT = Math.sqrt(1 / 4f); + + @Parameters(name= "{index}: {0}") + public static List data() { + Random RANDOM = new Random(1l); // Test is deterministic + int r = 0; + return ImmutableList.of( + new Object[] { "X", + 1, 0, 0, + 270, 0 + }, + new Object[] { "-X", + -1, 0, 0, + 90, 0 + }, + new Object[] { "Z", + 0, 0, 1, + 0, 0 + }, + new Object[] { "-Z", + 0, 0, -1, + 180, 0 + }, + new Object[] { "Y", + 0, 1, 0, + 0, -90 // Zero is here as a "default" value + }, + new Object[] { "-Y", + 0, -1, 0, + 0, 90 // Zero is here as a "default" value + }, + new Object[] { "X Z", + HALF_UNIT, 0, HALF_UNIT, + (270 + 360) / 2, 0 + }, + new Object[] { "X -Z", + HALF_UNIT, 0, -HALF_UNIT, + (270 + 180) / 2, 0 + }, + new Object[] { "-X -Z", + -HALF_UNIT, 0, -HALF_UNIT, + (90 + 180) / 2, 0 + }, + new Object[] { "-X Z", + -HALF_UNIT, 0, HALF_UNIT, + (90 + 0) / 2, 0 + }, + new Object[] { "X Y Z", + HALF_HALF_UNIT, HALF_UNIT, HALF_HALF_UNIT, + (270 + 360) / 2, -45 + }, + new Object[] { "-X -Y -Z", + -HALF_HALF_UNIT, -HALF_UNIT, -HALF_HALF_UNIT, + (90 + 180) / 2, 45 + }, + getRandom(RANDOM, r++), + getRandom(RANDOM, r++), + getRandom(RANDOM, r++), + getRandom(RANDOM, r++), + getRandom(RANDOM, r++), + getRandom(RANDOM, r++), + getRandom(RANDOM, r++), + getRandom(RANDOM, r++), + getRandom(RANDOM, r++), + getRandom(RANDOM, r++), + getRandom(RANDOM, r++), + getRandom(RANDOM, r++), + getRandom(RANDOM, r++), + getRandom(RANDOM, r++), + getRandom(RANDOM, r++), + getRandom(RANDOM, r++), + getRandom(RANDOM, r++), + getRandom(RANDOM, r++) + ); + } + + private static Object[] getRandom(Random random, int index) { + final double YAW_FACTOR = 360; + final double YAW_OFFSET = 0; + final double PITCH_FACTOR = 180; + final double PITCH_OFFSET = -90; + final double CARTESIAN_FACTOR = 256; + final double CARTESIAN_OFFSET = -128; + + Vector vector; + Location location; + if (random.nextBoolean()) { + float pitch = (float) (random.nextDouble() * PITCH_FACTOR + PITCH_OFFSET); + float yaw = (float) (random.nextDouble() * YAW_FACTOR + YAW_OFFSET); + + location = getEmptyLocation(); + location.setPitch(pitch); + location.setYaw(yaw); + + vector = location.getDirection(); + } else { + double x = random.nextDouble() * CARTESIAN_FACTOR + CARTESIAN_OFFSET; + double y = random.nextDouble() * CARTESIAN_FACTOR + CARTESIAN_OFFSET; + double z = random.nextDouble() * CARTESIAN_FACTOR + CARTESIAN_OFFSET; + + location = getEmptyLocation(); + vector = new Vector(x, y, z).normalize(); + + location.setDirection(vector); + } + + return new Object[] { "R" + index, + vector.getX(), vector.getY(), vector.getZ(), + location.getYaw(), location.getPitch() + }; + } + + @Parameter(0) + public String nane; + @Parameter(1) + public double x; + @Parameter(2) + public double y; + @Parameter(3) + public double z; + @Parameter(4) + public float yaw; + @Parameter(5) + public float pitch; + + @Test + public void testExpectedPitchYaw() { + Location location = getEmptyLocation().setDirection(getVector()); + + assertThat((double) location.getYaw(), is(closeTo(yaw, delta))); + assertThat((double) location.getPitch(), is(closeTo(pitch, delta))); + } + + @Test + public void testExpectedXYZ() { + Vector vector = getLocation().getDirection(); + + assertThat(vector.getX(), is(closeTo(x, delta))); + assertThat(vector.getY(), is(closeTo(y, delta))); + assertThat(vector.getZ(), is(closeTo(z, delta))); + } + + private Vector getVector() { + return new Vector(x, y, z); + } + + private static Location getEmptyLocation() { + return new Location(null, 0, 0, 0); + } + + private Location getLocation() { + Location location = getEmptyLocation(); + location.setYaw(yaw); + location.setPitch(pitch); + return location; + } +} diff --git a/api/src/test/java/org/bukkit/MaterialTest.java b/api/src/test/java/org/bukkit/MaterialTest.java new file mode 100644 index 000000000..66f4603a2 --- /dev/null +++ b/api/src/test/java/org/bukkit/MaterialTest.java @@ -0,0 +1,73 @@ +package org.bukkit; + +import static org.hamcrest.Matchers.*; +import static org.junit.Assert.*; + +import org.bukkit.material.MaterialData; +import org.junit.Test; + +public class MaterialTest { + @Test + public void getByName() { + for (Material material : Material.values()) { + assertThat(Material.getMaterial(material.toString()), is(material)); + } + } + + @Test + public void getByNameNull() { + assertThat(Material.getMaterial(null), is(nullValue())); + } + + @Test + public void getData() { + for (Material material : Material.values()) { + if (!material.isLegacy()) { + continue; + } + Class clazz = material.getData(); + + assertThat(material.getNewData((byte) 0), is(instanceOf(clazz))); + } + } + + @Test(expected = IllegalArgumentException.class) + public void matchMaterialByNull() { + Material.matchMaterial(null); + } + + @Test + public void matchMaterialByName() { + for (Material material : Material.values()) { + assertThat(Material.matchMaterial(material.toString()), is(material)); + } + } + + @Test + public void matchMaterialByKey() { + for (Material material : Material.values()) { + if (material.isLegacy()) { + continue; + } + assertThat(Material.matchMaterial(material.getKey().toString()), is(material)); + } + } + + @Test + public void matchMaterialByWrongNamespace() { + for (Material material : Material.values()) { + if (material.isLegacy()) { + continue; + } + assertNull(Material.matchMaterial("bogus:" + material.getKey().getKey())); + } + } + + @Test + public void matchMaterialByLowerCaseAndSpaces() { + for (Material material : Material.values()) { + String name = material.toString().replaceAll("_", " ").toLowerCase(java.util.Locale.ENGLISH); + assertThat(Material.matchMaterial(name), is(material)); + } + } +} diff --git a/api/src/test/java/org/bukkit/NamespacedKeyTest.java b/api/src/test/java/org/bukkit/NamespacedKeyTest.java new file mode 100644 index 000000000..8c5e5ca78 --- /dev/null +++ b/api/src/test/java/org/bukkit/NamespacedKeyTest.java @@ -0,0 +1,59 @@ +package org.bukkit; + +import org.junit.Assert; +import org.junit.Test; + +public class NamespacedKeyTest { + + @Test + public void testValid() { + Assert.assertEquals("minecraft:foo", new NamespacedKey("minecraft", "foo").toString()); + Assert.assertEquals("minecraft:foo/bar", new NamespacedKey("minecraft", "foo/bar").toString()); + Assert.assertEquals("minecraft:foo/bar_baz", new NamespacedKey("minecraft", "foo/bar_baz").toString()); + Assert.assertEquals("minecraft:foo/bar_baz-qux", new NamespacedKey("minecraft", "foo/bar_baz-qux").toString()); + Assert.assertEquals("minecraft:foo/bar_baz-qux.quux", new NamespacedKey("minecraft", "foo/bar_baz-qux.quux").toString()); + } + + @Test(expected = IllegalArgumentException.class) + public void testEmptyNamespace() { + new NamespacedKey("", "foo").toString(); + } + + @Test(expected = IllegalArgumentException.class) + public void testEmptyKey() { + new NamespacedKey("minecraft", "").toString(); + } + + @Test(expected = IllegalArgumentException.class) + public void testInvalidNamespace() { + new NamespacedKey("minecraft/test", "foo").toString(); + } + + @Test(expected = IllegalArgumentException.class) + public void testInvalidNamespaceCasing() { + new NamespacedKey("Minecraft", "foo").toString(); + } + + @Test(expected = IllegalArgumentException.class) + public void testInvalidKeyCasing() { + new NamespacedKey("minecraft", "Foo").toString(); + } + + @Test(expected = IllegalArgumentException.class) + public void testInvalidKey() { + new NamespacedKey("minecraft", "foo!").toString(); + } + + @Test + public void testBelowLength() { + new NamespacedKey("loremipsumdolorsitametconsecteturadipiscingelitduisvolutpatvelitsitametmaximusscelerisquemorbiullamcorperexacconsequategestas", + "loremipsumdolorsitametconsecteturadipiscingelitduisvolutpatvelitsitametmaximusscelerisquemorbiullamcorperexacconsequategestas").toString(); + } + + @Test(expected = IllegalArgumentException.class) + public void testAboveLength() { + new NamespacedKey("loremipsumdolorsitametconsecteturadipiscingelitduisvolutpatvelitsitametmaximusscelerisquemorbiullamcorperexacconsequategestas", + "loremipsumdolorsitametconsecteturadipiscingelitduisvolutpatvelitsitametmaximusscelerisquemorbiullamcorperexacconsequategestas/" + + "loremipsumdolorsitametconsecteturadipiscingelitduisvolutpatvelitsitametmaximusscelerisquemorbiullamcorperexacconsequategestas").toString(); + } +} diff --git a/api/src/test/java/org/bukkit/NoteTest.java b/api/src/test/java/org/bukkit/NoteTest.java new file mode 100644 index 000000000..4759041a7 --- /dev/null +++ b/api/src/test/java/org/bukkit/NoteTest.java @@ -0,0 +1,156 @@ +package org.bukkit; + +import static org.hamcrest.CoreMatchers.is; +import static org.hamcrest.CoreMatchers.nullValue; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertThat; +import static org.junit.Assert.assertTrue; +import static org.junit.Assert.fail; + +import java.util.Collection; + +import org.junit.Test; + +import com.google.common.collect.Lists; + +public class NoteTest { + @Test + public void getToneByData() { + for (Note.Tone tone : Note.Tone.values()) { + assertThat(Note.Tone.getById(tone.getId()), is(tone)); + } + } + + @Test + public void verifySharpedData() { + for (Note.Tone tone : Note.Tone.values()) { + if (!tone.isSharpable()) return; + + assertFalse(tone.isSharped(tone.getId(false))); + assertTrue(tone.isSharped(tone.getId(true))); + } + } + + @Test + public void verifyUnknownToneData() { + Collection tones = Lists.newArrayList(); + for (int i = Byte.MIN_VALUE; i <= Byte.MAX_VALUE; i++) { + tones.add((byte) i); + } + + for (Note.Tone tone : Note.Tone.values()) { + if (tone.isSharpable()) tones.remove(tone.getId(true)); + tones.remove(tone.getId()); + } + + for (Byte data : tones) { + assertThat(Note.Tone.getById(data), is(nullValue())); + + for (Note.Tone tone : Note.Tone.values()) { + try { + tone.isSharped(data); + + fail(data + " should throw IllegalArgumentException"); + } catch (IllegalArgumentException e) { + assertNotNull(e); + } + } + } + } + + @Test(expected = IllegalArgumentException.class) + public void createNoteBelowMin() { + new Note((byte) -1); + } + + @Test(expected = IllegalArgumentException.class) + public void createNoteAboveMax() { + new Note((byte) 25); + } + + @Test(expected = IllegalArgumentException.class) + public void createNoteOctaveBelowMax() { + new Note((byte) -1, Note.Tone.A, true); + } + + @Test(expected = IllegalArgumentException.class) + public void createNoteOctaveAboveMax() { + new Note((byte) 3, Note.Tone.A, true); + } + + @Test + public void createNoteOctaveNonSharpable() { + Note note = new Note((byte) 0, Note.Tone.B, true); + assertFalse(note.isSharped()); + assertThat(note.getTone(), is(Note.Tone.C)); + } + + @Test + public void createNoteFlat() { + Note note = Note.flat(0, Note.Tone.D); + assertTrue(note.isSharped()); + assertThat(note.getTone(), is(Note.Tone.C)); + } + + @Test + public void createNoteFlatNonFlattenable() { + Note note = Note.flat(0, Note.Tone.C); + assertFalse(note.isSharped()); + assertThat(note.getTone(), is(Note.Tone.B)); + } + + @Test + public void testFlatWrapping() { + Note note = Note.flat(1, Note.Tone.G); + assertTrue(note.isSharped()); + assertThat(note.getTone(), is(Note.Tone.F)); + } + + @Test + public void testFlatWrapping2() { + Note note = new Note(1, Note.Tone.G, false).flattened(); + assertTrue(note.isSharped()); + assertThat(note.getTone(), is(Note.Tone.F)); + } + + @Test + public void testSharpWrapping() { + Note note = new Note(1, Note.Tone.F, false).sharped(); + assertTrue(note.isSharped()); + assertThat(note.getTone(), is(Note.Tone.F)); + assertEquals(note.getOctave(), 2); + } + + @Test(expected=IllegalArgumentException.class) + public void testSharpWrapping2() { + new Note(2, Note.Tone.F, true).sharped(); + } + + @Test + public void testHighest() { + Note note = new Note(2, Note.Tone.F, true); + assertEquals(note.getId(), (byte)24); + } + + @Test + public void testLowest() { + Note note = new Note(0, Note.Tone.F, true); + assertEquals(note.getId(), (byte)0); + } + + @Test + public void doo() { + for (int i = 1; i <= 24; i++) { + Note note = new Note((byte) i); + int octave = note.getOctave(); + Note.Tone tone = note.getTone(); + boolean sharped = note.isSharped(); + + Note newNote = new Note(octave, tone, sharped); + assertThat(newNote, is(note)); + assertThat(newNote.getId(), is(note.getId())); + } + } +} diff --git a/api/src/test/java/org/bukkit/TestServer.java b/api/src/test/java/org/bukkit/TestServer.java new file mode 100644 index 000000000..613675c3b --- /dev/null +++ b/api/src/test/java/org/bukkit/TestServer.java @@ -0,0 +1,110 @@ +package org.bukkit; + +import java.lang.reflect.InvocationHandler; +import java.lang.reflect.Method; +import java.lang.reflect.Proxy; +import java.util.Map; +import java.util.logging.Logger; + +import org.bukkit.command.SimpleCommandMap; +import org.bukkit.plugin.PluginManager; +import org.bukkit.plugin.SimplePluginManager; + +import com.google.common.collect.ImmutableMap; + +public class TestServer implements InvocationHandler { + private static interface MethodHandler { + Object handle(TestServer server, Object[] args); + } + + private static final Map methods; + + static { + try { + ImmutableMap.Builder methodMap = ImmutableMap.builder(); + methodMap.put( + Server.class.getMethod("isPrimaryThread"), + new MethodHandler() { + public Object handle(TestServer server, Object[] args) { + return Thread.currentThread().equals(server.creatingThread); + } + } + ); + // Paper start + methodMap.put( + Server.class.getMethod("getTag", String.class, NamespacedKey.class, Class.class), + new MethodHandler() { + public Object handle(TestServer server, Object[] args) { + return new com.destroystokyo.paper.MaterialSetTag(); + } + } + ); + // Paper end + methodMap.put( + Server.class.getMethod("getPluginManager"), + new MethodHandler() { + public Object handle(TestServer server, Object[] args) { + return server.pluginManager; + } + } + ); + methodMap.put( + Server.class.getMethod("getLogger"), + new MethodHandler() { + final Logger logger = Logger.getLogger(TestServer.class.getCanonicalName()); + public Object handle(TestServer server, Object[] args) { + return logger; + } + } + ); + methodMap.put( + Server.class.getMethod("getName"), + new MethodHandler() { + public Object handle(TestServer server, Object[] args) { + return TestServer.class.getSimpleName(); + } + } + ); + methodMap.put( + Server.class.getMethod("getVersion"), + new MethodHandler() { + public Object handle(TestServer server, Object[] args) { + return "Version_" + TestServer.class.getPackage().getImplementationVersion(); + } + } + ); + methodMap.put( + Server.class.getMethod("getBukkitVersion"), + new MethodHandler() { + public Object handle(TestServer server, Object[] args) { + return "BukkitVersion_" + TestServer.class.getPackage().getImplementationVersion(); + } + } + ); + methods = methodMap.build(); + + TestServer server = new TestServer(); + Server instance = Proxy.getProxyClass(Server.class.getClassLoader(), Server.class).asSubclass(Server.class).getConstructor(InvocationHandler.class).newInstance(server); + Bukkit.setServer(instance); + server.pluginManager = new SimplePluginManager(instance, new SimpleCommandMap(instance)); + } catch (Throwable t) { + throw new Error(t); + } + } + + private Thread creatingThread = Thread.currentThread(); + private PluginManager pluginManager; + private TestServer() {}; + + public static Server getInstance() { + return Bukkit.getServer(); + } + + public Object invoke(Object proxy, Method method, Object[] args) { + MethodHandler handler = methods.get(method); + if (handler != null) { + return handler.handle(this, args); + } + throw new UnsupportedOperationException(String.valueOf(method)); + } +} diff --git a/api/src/test/java/org/bukkit/TreeSpeciesTest.java b/api/src/test/java/org/bukkit/TreeSpeciesTest.java new file mode 100644 index 000000000..4a997306f --- /dev/null +++ b/api/src/test/java/org/bukkit/TreeSpeciesTest.java @@ -0,0 +1,15 @@ +package org.bukkit; + +import static org.hamcrest.CoreMatchers.is; +import static org.junit.Assert.assertThat; + +import org.junit.Test; + +public class TreeSpeciesTest { + @Test + public void getByData() { + for (TreeSpecies treeSpecies : TreeSpecies.values()) { + assertThat(TreeSpecies.getByData(treeSpecies.getData()), is(treeSpecies)); + } + } +} diff --git a/api/src/test/java/org/bukkit/WorldTypeTest.java b/api/src/test/java/org/bukkit/WorldTypeTest.java new file mode 100644 index 000000000..d31c20522 --- /dev/null +++ b/api/src/test/java/org/bukkit/WorldTypeTest.java @@ -0,0 +1,15 @@ +package org.bukkit; + +import static org.hamcrest.CoreMatchers.is; +import static org.junit.Assert.assertThat; + +import org.junit.Test; + +public class WorldTypeTest { + @Test + public void getByName() { + for (WorldType worldType : WorldType.values()) { + assertThat(WorldType.getByName(worldType.getName()), is(worldType)); + } + } +} diff --git a/api/src/test/java/org/bukkit/configuration/ConfigurationSectionTest.java b/api/src/test/java/org/bukkit/configuration/ConfigurationSectionTest.java new file mode 100644 index 000000000..4e91b1a27 --- /dev/null +++ b/api/src/test/java/org/bukkit/configuration/ConfigurationSectionTest.java @@ -0,0 +1,600 @@ +package org.bukkit.configuration; + +import org.bukkit.Material; +import java.util.Arrays; +import java.util.HashMap; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Map; +import org.bukkit.inventory.ItemStack; +import org.bukkit.util.Vector; +import org.junit.Test; +import static org.junit.Assert.*; + +public abstract class ConfigurationSectionTest { + public abstract ConfigurationSection getConfigurationSection(); + + @Test + public void testGetKeys() { + ConfigurationSection section = getConfigurationSection(); + + section.set("key", true); + section.set("subsection.subkey", true); + section.set("subsection.subkey2", true); + section.set("subsection.subsubsection.key", true); + section.set("key2", true); + section.set("42", true); + + assertArrayEquals(new String[] { "key", "subsection", "key2", "42" }, section.getKeys(false).toArray()); + assertArrayEquals(new String[] { "key", "subsection", "subsection.subkey", "subsection.subkey2", "subsection.subsubsection", "subsection.subsubsection.key", "key2", "42" }, section.getKeys(true).toArray()); + assertArrayEquals(new String[] { "subkey", "subkey2", "subsubsection", "subsubsection.key" }, section.getConfigurationSection("subsection").getKeys(true).toArray()); + } + + @Test + public void testGetKeysWithDefaults() { + ConfigurationSection section = getConfigurationSection(); + section.getRoot().options().copyDefaults(true); + + section.set("key", true); + section.addDefault("subsection.subkey", true); + section.addDefault("subsection.subkey2", true); + section.addDefault("subsection.subsubsection.key", true); + section.addDefault("key2", true); + + assertArrayEquals(new String[] { "subsection", "key2", "key" }, section.getKeys(false).toArray()); + assertArrayEquals(new String[] { "subsection", "subsection.subkey", "subsection.subkey2", "subsection.subsubsection", "subsection.subsubsection.key", "key2", "key" }, section.getKeys(true).toArray()); + assertArrayEquals(new String[] { "subkey", "subkey2", "subsubsection", "subsubsection.key" }, section.getConfigurationSection("subsection").getKeys(true).toArray()); + } + + @Test + public void testGetValues() { + ConfigurationSection section = getConfigurationSection(); + + section.set("bool", true); + section.set("subsection.string", "test"); + section.set("subsection.long", Long.MAX_VALUE); + section.set("int", 42); + + Map shallowValues = section.getValues(false); + assertArrayEquals(new String[] { "bool", "subsection", "int" }, shallowValues.keySet().toArray()); + assertArrayEquals(new Object[] { true, section.getConfigurationSection("subsection"), 42 }, shallowValues.values().toArray()); + + Map deepValues = section.getValues(true); + assertArrayEquals(new String[] { "bool", "subsection", "subsection.string", "subsection.long", "int" }, deepValues.keySet().toArray()); + assertArrayEquals(new Object[] { true, section.getConfigurationSection("subsection"), "test", Long.MAX_VALUE, 42 }, deepValues.values().toArray()); + } + + @Test + public void testGetValuesWithDefaults() { + ConfigurationSection section = getConfigurationSection(); + section.getRoot().options().copyDefaults(true); + + // Fix for SPIGOT-4558 means that defaults will always be first + // This is a little bit unintuitive for section defaults when deep iterating keys / values as shown below + // But the API doesn't guarantee order & when serialized (using shallow getters) all is well + section.set("bool", true); + section.set("subsection.string", "test"); + section.addDefault("subsection.long", Long.MAX_VALUE); + section.addDefault("int", 42); + + Map shallowValues = section.getValues(false); + assertArrayEquals(new String[] { "int", "bool", "subsection" }, shallowValues.keySet().toArray()); + assertArrayEquals(new Object[] { 42, true, section.getConfigurationSection("subsection") }, shallowValues.values().toArray()); + + Map deepValues = section.getValues(true); + assertArrayEquals(new String[] { "subsection.long", "int", "bool", "subsection", "subsection.string" }, deepValues.keySet().toArray()); + assertArrayEquals(new Object[] { Long.MAX_VALUE, 42, true, section.getConfigurationSection("subsection"), "test" }, deepValues.values().toArray()); + } + + @Test + public void testContains() { + ConfigurationSection section = getConfigurationSection(); + + section.set("exists", true); + + assertTrue(section.contains("exists")); + assertFalse(section.contains("doesnt-exist")); + + assertTrue(section.contains("exists", true)); + assertTrue(section.contains("exists", false)); + + assertFalse(section.contains("doesnt-exist", true)); + assertFalse(section.contains("doesnt-exist", false)); + + section.addDefault("doenst-exist-two", true); + section.set("doenst-exist-two", null); + + assertFalse(section.contains("doenst-exist-two", true)); + assertTrue(section.contains("doenst-exist-two", false)); + } + + @Test + public void testIsSet() { + ConfigurationSection section = getConfigurationSection(); + + section.set("notDefault", true); + section.getRoot().addDefault("default", true); + section.getRoot().addDefault("defaultAndSet", true); + section.set("defaultAndSet", true); + + assertTrue(section.isSet("notDefault")); + assertFalse(section.isSet("default")); + assertTrue(section.isSet("defaultAndSet")); + } + + @Test + public void testGetCurrentPath() { + ConfigurationSection section = getConfigurationSection(); + + assertEquals(section.getName(), section.getCurrentPath()); + } + + @Test + public void testGetName() { + ConfigurationSection section = getConfigurationSection().createSection("subsection"); + + assertEquals("subsection", section.getName()); + assertEquals("", section.getRoot().getName()); + } + + @Test + public void testGetRoot() { + ConfigurationSection section = getConfigurationSection(); + + assertNotNull(section.getRoot()); + assertTrue(section.getRoot().contains(section.getCurrentPath())); + } + + @Test + public void testGetParent() { + ConfigurationSection section = getConfigurationSection(); + ConfigurationSection subsection = section.createSection("subsection"); + + assertEquals(section.getRoot(), section.getParent()); + assertEquals(section, subsection.getParent()); + } + + @Test + public void testGet_String() { + ConfigurationSection section = getConfigurationSection(); + + section.set("exists", "hello world"); + + assertEquals("hello world", section.getString("exists")); + assertNull(section.getString("doesntExist")); + } + + @Test + public void testGet_String_Object() { + ConfigurationSection section = getConfigurationSection(); + + section.set("exists", "Set Value"); + + assertEquals("Set Value", section.get("exists", "Default Value")); + assertEquals("Default Value", section.get("doesntExist", "Default Value")); + } + + @Test + public void testSet() { + ConfigurationSection section = getConfigurationSection(); + + section.set("exists", "hello world"); + + assertTrue(section.contains("exists")); + assertTrue(section.isSet("exists")); + assertEquals("hello world", section.get("exists")); + + section.set("exists", null); + + assertFalse(section.contains("exists")); + assertFalse(section.isSet("exists")); + } + + @Test + public void testCreateSection() { + ConfigurationSection section = getConfigurationSection(); + ConfigurationSection subsection = section.createSection("subsection"); + + assertEquals("subsection", subsection.getName()); + } + + @Test + public void testSectionMap() { + ConfigurationSection config = getConfigurationSection(); + Map testMap = new LinkedHashMap(); + + testMap.put("string", "Hello World"); + testMap.put("integer", 15); + + config.createSection("test.path", testMap); + + assertEquals(testMap, config.getConfigurationSection("test.path").getValues(false)); + } + + @Test + public void testGetString_String() { + ConfigurationSection section = getConfigurationSection(); + String key = "exists"; + String value = "Hello World"; + + section.set(key, value); + + assertEquals(value, section.getString(key)); + assertNull(section.getString("doesntExist")); + } + + @Test + public void testGetString_String_String() { + ConfigurationSection section = getConfigurationSection(); + String key = "exists"; + String value = "Hello World"; + String def = "Default Value"; + + section.set(key, value); + + assertEquals(value, section.getString(key, def)); + assertEquals(def, section.getString("doesntExist", def)); + } + + @Test + public void testIsString() { + ConfigurationSection section = getConfigurationSection(); + String key = "exists"; + String value = "Hello World"; + + section.set(key, value); + + assertTrue(section.isString(key)); + assertFalse(section.isString("doesntExist")); + } + + @Test + public void testGetInt_String() { + ConfigurationSection section = getConfigurationSection(); + String key = "exists"; + int value = Integer.MAX_VALUE; + + section.set(key, value); + + assertEquals(value, section.getInt(key)); + assertNull(section.getString("doesntExist")); + } + + @Test + public void testGetInt_String_Int() { + ConfigurationSection section = getConfigurationSection(); + String key = "exists"; + int value = Integer.MAX_VALUE; + int def = Integer.MIN_VALUE; + + section.set(key, value); + + assertEquals(value, section.getInt(key, def)); + assertEquals(def, section.getInt("doesntExist", def)); + } + + @Test + public void testIsInt() { + ConfigurationSection section = getConfigurationSection(); + String key = "exists"; + int value = Integer.MAX_VALUE; + + section.set(key, value); + + assertTrue(section.isInt(key)); + assertFalse(section.isInt("doesntExist")); + } + + @Test + public void testGetBoolean_String() { + ConfigurationSection section = getConfigurationSection(); + String key = "exists"; + boolean value = true; + + section.set(key, value); + + assertEquals(value, section.getBoolean(key)); + assertNull(section.getString("doesntExist")); + } + + @Test + public void testGetBoolean_String_Boolean() { + ConfigurationSection section = getConfigurationSection(); + String key = "exists"; + boolean value = true; + boolean def = false; + + section.set(key, value); + + assertEquals(value, section.getBoolean(key, def)); + assertEquals(def, section.getBoolean("doesntExist", def)); + } + + @Test + public void testIsBoolean() { + ConfigurationSection section = getConfigurationSection(); + String key = "exists"; + boolean value = true; + + section.set(key, value); + + assertTrue(section.isBoolean(key)); + assertFalse(section.isBoolean("doesntExist")); + } + + @Test + public void testGetDouble_String() { + ConfigurationSection section = getConfigurationSection(); + String key = "exists"; + double value = Double.MAX_VALUE; + + section.set(key, value); + + assertEquals(value, section.getDouble(key), 1); + assertNull(section.getString("doesntExist")); + } + + @Test + public void testGetDoubleFromInt() { + ConfigurationSection section = getConfigurationSection(); + String key = "exists"; + double value = 123; + + section.set(key, (int) value); + + assertEquals(value, section.getDouble(key), 1); + assertNull(section.getString("doesntExist")); + } + + @Test + public void testGetDouble_String_Double() { + ConfigurationSection section = getConfigurationSection(); + String key = "exists"; + double value = Double.MAX_VALUE; + double def = Double.MIN_VALUE; + + section.set(key, value); + + assertEquals(value, section.getDouble(key, def), 1); + assertEquals(def, section.getDouble("doesntExist", def), 1); + } + + @Test + public void testIsDouble() { + ConfigurationSection section = getConfigurationSection(); + String key = "exists"; + double value = Double.MAX_VALUE; + + section.set(key, value); + + assertTrue(section.isDouble(key)); + assertFalse(section.isDouble("doesntExist")); + } + + @Test + public void testGetLong_String() { + ConfigurationSection section = getConfigurationSection(); + String key = "exists"; + long value = Long.MAX_VALUE; + + section.set(key, value); + + assertEquals(value, section.getLong(key)); + assertNull(section.getString("doesntExist")); + } + + @Test + public void testGetLong_String_Long() { + ConfigurationSection section = getConfigurationSection(); + String key = "exists"; + long value = Long.MAX_VALUE; + long def = Long.MIN_VALUE; + + section.set(key, value); + + assertEquals(value, section.getLong(key, def)); + assertEquals(def, section.getLong("doesntExist", def)); + } + + @Test + public void testIsLong() { + ConfigurationSection section = getConfigurationSection(); + String key = "exists"; + long value = Long.MAX_VALUE; + + section.set(key, value); + + assertTrue(section.isLong(key)); + assertFalse(section.isLong("doesntExist")); + } + + @Test + public void testGetList_String() { + ConfigurationSection section = getConfigurationSection(); + String key = "exists"; + Map map = new HashMap(); + + map.put("one", 1); + map.put("two", "two"); + map.put("three", 3.14); + + List value = Arrays.asList("One", "Two", "Three", 4, "5", 6.0, true, "false", map); + + section.set(key, value); + + assertEquals(value, section.getList(key)); + assertEquals(Arrays.asList((Object) "One", "Two", "Three", "4", "5", "6.0", "true", "false"), section.getStringList(key)); + assertEquals(Arrays.asList((Object) 4, 5, 6), section.getIntegerList(key)); + assertEquals(Arrays.asList((Object) true, false), section.getBooleanList(key)); + assertEquals(Arrays.asList((Object) 4.0, 5.0, 6.0), section.getDoubleList(key)); + assertEquals(Arrays.asList((Object) 4.0f, 5.0f, 6.0f), section.getFloatList(key)); + assertEquals(Arrays.asList((Object) 4l, 5l, 6l), section.getLongList(key)); + assertEquals(Arrays.asList((Object) (byte) 4, (byte) 5, (byte) 6), section.getByteList(key)); + assertEquals(Arrays.asList((Object) (short) 4, (short) 5, (short) 6), section.getShortList(key)); + assertEquals(map, section.getMapList(key).get(0)); + assertNull(section.getString("doesntExist")); + } + + @Test + public void testGetList_String_List() { + ConfigurationSection section = getConfigurationSection(); + String key = "exists"; + List value = Arrays.asList("One", "Two", "Three"); + List def = Arrays.asList("A", "B", "C"); + + section.set(key, value); + + assertEquals(value, section.getList(key, def)); + assertEquals(def, section.getList("doesntExist", def)); + } + + @Test + public void testIsList() { + ConfigurationSection section = getConfigurationSection(); + String key = "exists"; + List value = Arrays.asList("One", "Two", "Three"); + + section.set(key, value); + + assertTrue(section.isList(key)); + assertFalse(section.isList("doesntExist")); + } + + @Test + public void testGetObject_String_Class() { + ConfigurationSection section = getConfigurationSection(); + + section.set("set", Integer.valueOf(1)); + section.addDefault("default", Integer.valueOf(2)); + section.addDefault("defaultAndSet", Boolean.TRUE); + section.set("defaultAndSet", Integer.valueOf(3)); + + assertEquals(Integer.valueOf(1), section.getObject("set", Integer.class)); + assertNull(section.getObject("set", Boolean.class)); + assertEquals(Integer.valueOf(2), section.getObject("default", Number.class)); + assertNull(section.getObject("default", Boolean.class)); + assertEquals(Integer.valueOf(3), section.getObject("defaultAndSet", Integer.class)); + assertEquals(Boolean.TRUE, section.getObject("defaultAndSet", Boolean.class)); + assertEquals(Integer.valueOf(3), section.getObject("defaultAndSet", Object.class)); + assertNull(section.getObject("defaultAndSet", String.class)); + assertNull(section.getObject("doesntExist", Boolean.class)); + assertNull(section.getString("doesntExist")); + } + + @Test + public void testGetObject_String_Class_T() { + ConfigurationSection section = getConfigurationSection(); + + section.set("set", Integer.valueOf(1)); + section.addDefault("default", Integer.valueOf(2)); + + assertEquals(Integer.valueOf(1), section.getObject("set", Integer.class, null)); + assertEquals(Integer.valueOf(1), section.getObject("set", Integer.class, Integer.valueOf(4))); + assertNull(section.getObject("set", Boolean.class, null)); + assertNull(section.getObject("default", Integer.class, null)); + assertNull(section.getObject("doesntExist", Boolean.class, null)); + assertEquals(Boolean.TRUE, section.getObject("doesntExist", Boolean.class, Boolean.TRUE)); + assertNull(section.getString("doesntExist")); + } + + @Test + public void testGetVector_String() { + ConfigurationSection section = getConfigurationSection(); + String key = "exists"; + Vector value = new Vector(Double.MIN_VALUE, Double.MAX_VALUE, 5); + + section.set(key, value); + + assertEquals(value, section.getVector(key)); + assertNull(section.getString("doesntExist")); + } + + @Test + public void testGetVector_String_Vector() { + ConfigurationSection section = getConfigurationSection(); + String key = "exists"; + Vector value = new Vector(Double.MIN_VALUE, Double.MAX_VALUE, 5); + Vector def = new Vector(100, Double.MIN_VALUE, Double.MAX_VALUE); + + section.set(key, value); + + assertEquals(value, section.getVector(key, def)); + assertEquals(def, section.getVector("doesntExist", def)); + } + + @Test + public void testIsVector() { + ConfigurationSection section = getConfigurationSection(); + String key = "exists"; + Vector value = new Vector(Double.MIN_VALUE, Double.MAX_VALUE, 5); + + section.set(key, value); + + assertTrue(section.isVector(key)); + assertFalse(section.isVector("doesntExist")); + } + + @Test + public void testGetItemStack_String() { + ConfigurationSection section = getConfigurationSection(); + String key = "exists"; + ItemStack value = new ItemStack(Material.ACACIA_WOOD, 50); + + section.set(key, value); + + assertEquals(value, section.getItemStack(key)); + assertNull(section.getString("doesntExist")); + } + + @Test + public void testGetItemStack_String_ItemStack() { + ConfigurationSection section = getConfigurationSection(); + String key = "exists"; + ItemStack value = new ItemStack(Material.ACACIA_WOOD, 50); + ItemStack def = new ItemStack(Material.STONE, 1); + + section.set(key, value); + + assertEquals(value, section.getItemStack(key, def)); + assertEquals(def, section.getItemStack("doesntExist", def)); + } + + @Test + public void testIsItemStack() { + ConfigurationSection section = getConfigurationSection(); + String key = "exists"; + ItemStack value = new ItemStack(Material.ACACIA_WOOD, 50); + + section.set(key, value); + + assertTrue(section.isItemStack(key)); + assertFalse(section.isItemStack("doesntExist")); + } + + @Test + public void testGetConfigurationSection() { + ConfigurationSection section = getConfigurationSection(); + String key = "exists"; + + ConfigurationSection subsection = section.createSection(key); + + assertEquals(subsection, section.getConfigurationSection(key)); + } + + @Test + public void testIsConfigurationSection() { + ConfigurationSection section = getConfigurationSection(); + String key = "exists"; + + section.createSection(key); + + assertTrue(section.isConfigurationSection(key)); + assertFalse(section.isConfigurationSection("doesntExist")); + } + + public enum TestEnum { + HELLO, + WORLD, + BANANAS + } +} \ No newline at end of file diff --git a/api/src/test/java/org/bukkit/configuration/ConfigurationTest.java b/api/src/test/java/org/bukkit/configuration/ConfigurationTest.java new file mode 100644 index 000000000..e187d1500 --- /dev/null +++ b/api/src/test/java/org/bukkit/configuration/ConfigurationTest.java @@ -0,0 +1,156 @@ +package org.bukkit.configuration; + +import java.util.LinkedHashMap; +import java.util.Arrays; +import java.util.HashMap; +import java.util.HashSet; +import java.util.Map; +import java.util.Set; +import org.bukkit.util.Vector; +import org.junit.Test; +import static org.junit.Assert.*; + +public abstract class ConfigurationTest { + public abstract Configuration getConfig(); + + public Map getTestValues() { + HashMap result = new LinkedHashMap(); + + result.put("integer", Integer.MIN_VALUE); + result.put("string", "String Value"); + result.put("long", Long.MAX_VALUE); + result.put("true-boolean", true); + result.put("false-boolean", false); + result.put("vector", new Vector(12345.67, 64, -12345.6789)); + result.put("list", Arrays.asList(1, 2, 3, 4, 5)); + result.put("42", "The Answer"); + + return result; + } + + /** + * Test of addDefault method, of class Configuration. + */ + @Test + public void testAddDefault() { + Configuration config = getConfig(); + Map values = getTestValues(); + + for (Map.Entry entry : values.entrySet()) { + String path = entry.getKey(); + Object object = entry.getValue(); + + config.addDefault(path, object); + + assertEquals(object, config.get(path)); + assertTrue(config.contains(path)); + assertFalse(config.isSet(path)); + assertTrue(config.getDefaults().isSet(path)); + } + } + + /** + * Test of addDefaults method, of class Configuration. + */ + @Test + public void testAddDefaults_Map() { + Configuration config = getConfig(); + Map values = getTestValues(); + + config.addDefaults(values); + + for (Map.Entry entry : values.entrySet()) { + String path = entry.getKey(); + Object object = entry.getValue(); + + assertEquals(object, config.get(path)); + assertTrue(config.contains(path)); + assertFalse(config.isSet(path)); + assertTrue(config.getDefaults().isSet(path)); + } + } + + /** + * Test of addDefaults method, of class Configuration. + */ + @Test + public void testAddDefaults_Configuration() { + Configuration config = getConfig(); + Map values = getTestValues(); + Configuration defaults = getConfig(); + + for (Map.Entry entry : values.entrySet()) { + defaults.set(entry.getKey(), entry.getValue()); + } + + config.addDefaults(defaults); + + for (Map.Entry entry : values.entrySet()) { + String path = entry.getKey(); + Object object = entry.getValue(); + + assertEquals(object, config.get(path)); + assertTrue(config.contains(path)); + assertFalse(config.isSet(path)); + assertTrue(config.getDefaults().isSet(path)); + } + } + + /** + * Test of setDefaults method, of class Configuration. + */ + @Test + public void testSetDefaults() { + Configuration config = getConfig(); + Map values = getTestValues(); + Configuration defaults = getConfig(); + + for (Map.Entry entry : values.entrySet()) { + defaults.set(entry.getKey(), entry.getValue()); + } + + config.setDefaults(defaults); + + for (Map.Entry entry : values.entrySet()) { + String path = entry.getKey(); + Object object = entry.getValue(); + + assertEquals(object, config.get(path)); + assertTrue(config.contains(path)); + assertFalse(config.isSet(path)); + assertTrue(config.getDefaults().isSet(path)); + } + } + + /** + * Test creation of ConfigurationSection + */ + @Test + public void testCreateSection() { + Configuration config = getConfig(); + + Set set = new HashSet(); + set.add("this"); + set.add("this.test.sub"); + set.add("this.test"); + set.add("this.test.other"); + + config.createSection("this.test.sub"); + config.createSection("this.test.other"); + + assertEquals(set, config.getKeys(true)); + } + + /** + * Test of getDefaults method, of class Configuration. + */ + @Test + public void testGetDefaults() { + Configuration config = getConfig(); + Configuration defaults = getConfig(); + + config.setDefaults(defaults); + + assertEquals(defaults, config.getDefaults()); + } +} \ No newline at end of file diff --git a/api/src/test/java/org/bukkit/configuration/MemoryConfigurationTest.java b/api/src/test/java/org/bukkit/configuration/MemoryConfigurationTest.java new file mode 100644 index 000000000..3de0ce926 --- /dev/null +++ b/api/src/test/java/org/bukkit/configuration/MemoryConfigurationTest.java @@ -0,0 +1,8 @@ +package org.bukkit.configuration; + +public class MemoryConfigurationTest extends ConfigurationTest { + @Override + public Configuration getConfig() { + return new MemoryConfiguration(); + } +} diff --git a/api/src/test/java/org/bukkit/configuration/MemorySectionTest.java b/api/src/test/java/org/bukkit/configuration/MemorySectionTest.java new file mode 100644 index 000000000..be7768abe --- /dev/null +++ b/api/src/test/java/org/bukkit/configuration/MemorySectionTest.java @@ -0,0 +1,8 @@ +package org.bukkit.configuration; + +public class MemorySectionTest extends ConfigurationSectionTest { + @Override + public ConfigurationSection getConfigurationSection() { + return new MemoryConfiguration().createSection("section"); + } +} diff --git a/api/src/test/java/org/bukkit/configuration/file/FileConfigurationTest.java b/api/src/test/java/org/bukkit/configuration/file/FileConfigurationTest.java new file mode 100644 index 000000000..ce0c2e5a4 --- /dev/null +++ b/api/src/test/java/org/bukkit/configuration/file/FileConfigurationTest.java @@ -0,0 +1,209 @@ +package org.bukkit.configuration.file; + +import java.io.BufferedWriter; +import java.io.File; +import java.io.FileWriter; +import java.util.Map; +import org.bukkit.configuration.MemoryConfigurationTest; +import org.junit.Rule; +import org.junit.Test; +import org.junit.rules.TemporaryFolder; +import static org.junit.Assert.*; + +public abstract class FileConfigurationTest extends MemoryConfigurationTest { + @Rule + public TemporaryFolder testFolder = new TemporaryFolder(); + + @Override + public abstract FileConfiguration getConfig(); + + public abstract String getTestValuesString(); + + public abstract String getTestHeaderInput(); + + public abstract String getTestHeaderResult(); + + @Test + public void testSave_File() throws Exception { + FileConfiguration config = getConfig(); + File file = testFolder.newFile("test.config"); + + for (Map.Entry entry : getTestValues().entrySet()) { + config.set(entry.getKey(), entry.getValue()); + } + + config.save(file); + + assertTrue(file.isFile()); + } + + @Test + public void testSave_String() throws Exception { + FileConfiguration config = getConfig(); + File file = testFolder.newFile("test.config"); + + for (Map.Entry entry : getTestValues().entrySet()) { + config.set(entry.getKey(), entry.getValue()); + } + + config.save(file.getAbsolutePath()); + + assertTrue(file.isFile()); + } + + @Test + public void testSaveToString() { + FileConfiguration config = getConfig(); + + for (Map.Entry entry : getTestValues().entrySet()) { + config.set(entry.getKey(), entry.getValue()); + } + + String result = config.saveToString(); + String expected = getTestValuesString(); + + assertEquals(expected, result); + } + + @Test + public void testLoad_File() throws Exception { + FileConfiguration config = getConfig(); + File file = testFolder.newFile("test.config"); + BufferedWriter writer = new BufferedWriter(new FileWriter(file)); + String saved = getTestValuesString(); + Map values = getTestValues(); + + try { + writer.write(saved); + } finally { + writer.close(); + } + + config.load(file); + + for (Map.Entry entry : values.entrySet()) { + assertEquals(entry.getValue(), config.get(entry.getKey())); + } + + assertEquals(values.keySet(), config.getKeys(true)); + } + + @Test + public void testLoad_String() throws Exception { + FileConfiguration config = getConfig(); + File file = testFolder.newFile("test.config"); + BufferedWriter writer = new BufferedWriter(new FileWriter(file)); + String saved = getTestValuesString(); + Map values = getTestValues(); + + try { + writer.write(saved); + } finally { + writer.close(); + } + + config.load(file.getAbsolutePath()); + + for (Map.Entry entry : values.entrySet()) { + assertEquals(entry.getValue(), config.get(entry.getKey())); + } + + assertEquals(values.keySet(), config.getKeys(true)); + } + + @Test + public void testLoadFromString() throws Exception { + FileConfiguration config = getConfig(); + Map values = getTestValues(); + String saved = getTestValuesString(); + + config.loadFromString(saved); + + for (Map.Entry entry : values.entrySet()) { + assertEquals(entry.getValue(), config.get(entry.getKey())); + } + + assertEquals(values.keySet(), config.getKeys(true)); + assertEquals(saved, config.saveToString()); + } + + @Test + public void testSaveToStringWithHeader() { + FileConfiguration config = getConfig(); + config.options().header(getTestHeaderInput()); + + for (Map.Entry entry : getTestValues().entrySet()) { + config.set(entry.getKey(), entry.getValue()); + } + + String result = config.saveToString(); + String expected = getTestHeaderResult() + "\n" + getTestValuesString(); + + assertEquals(expected, result); + } + + @Test + public void testParseHeader() throws Exception { + FileConfiguration config = getConfig(); + Map values = getTestValues(); + String saved = getTestValuesString(); + String header = getTestHeaderResult(); + String expected = getTestHeaderInput(); + + config.loadFromString(header + "\n" + saved); + + assertEquals(expected, config.options().header()); + + for (Map.Entry entry : values.entrySet()) { + assertEquals(entry.getValue(), config.get(entry.getKey())); + } + + assertEquals(values.keySet(), config.getKeys(true)); + assertEquals(header + "\n" + saved, config.saveToString()); + } + + @Test + public void testCopyHeader() throws Exception { + FileConfiguration config = getConfig(); + FileConfiguration defaults = getConfig(); + Map values = getTestValues(); + String saved = getTestValuesString(); + String header = getTestHeaderResult(); + String expected = getTestHeaderInput(); + + defaults.loadFromString(header); + config.loadFromString(saved); + config.setDefaults(defaults); + + assertNull(config.options().header()); + assertEquals(expected, defaults.options().header()); + + for (Map.Entry entry : values.entrySet()) { + assertEquals(entry.getValue(), config.get(entry.getKey())); + } + + assertEquals(values.keySet(), config.getKeys(true)); + assertEquals(header + "\n" + saved, config.saveToString()); + + config = getConfig(); + config.loadFromString(getTestHeaderResult() + saved); + assertEquals(getTestHeaderResult() + saved, config.saveToString()); + } + + @Test + public void testReloadEmptyConfig() throws Exception { + FileConfiguration config = getConfig(); + + assertEquals("", config.saveToString()); + + config = getConfig(); + config.loadFromString(""); + + assertEquals("", config.saveToString()); + + config = getConfig(); + config.loadFromString("\n\n"); // Should trim the first newlines of a header + + assertEquals("", config.saveToString()); + } +} diff --git a/api/src/test/java/org/bukkit/configuration/file/YamlConfigurationTest.java b/api/src/test/java/org/bukkit/configuration/file/YamlConfigurationTest.java new file mode 100644 index 000000000..aa83af320 --- /dev/null +++ b/api/src/test/java/org/bukkit/configuration/file/YamlConfigurationTest.java @@ -0,0 +1,56 @@ +package org.bukkit.configuration.file; + +import org.junit.Test; +import static org.junit.Assert.*; + +public class YamlConfigurationTest extends FileConfigurationTest { + + @Override + public YamlConfiguration getConfig() { + return new YamlConfiguration(); + } + + @Override + public String getTestHeaderInput() { + return "This is a sample\nheader.\n\nNewline above should be commented.\n\n"; + } + + @Override + public String getTestHeaderResult() { + return "# This is a sample\n# header.\n# \n# Newline above should be commented.\n\n"; + } + + @Override + public String getTestValuesString() { + return "integer: -2147483648\n" + + "string: String Value\n" + + "long: 9223372036854775807\n" + + "true-boolean: true\n" + + "false-boolean: false\n" + + "vector:\n" + + " ==: Vector\n" + + " x: 12345.67\n" + + " y: 64.0\n" + + " z: -12345.6789\n" + + "list:\n" + + "- 1\n" + + "- 2\n" + + "- 3\n" + + "- 4\n" + + "- 5\n" + + "'42': The Answer\n"; + } + + @Test + public void testSaveToStringWithIndent() { + YamlConfiguration config = getConfig(); + config.options().indent(9); + + config.set("section.key", 1); + + String result = config.saveToString(); + String expected = "section:\n key: 1\n"; + + assertEquals(expected, result); + } +} diff --git a/api/src/test/java/org/bukkit/conversations/ConversationContextTest.java b/api/src/test/java/org/bukkit/conversations/ConversationContextTest.java new file mode 100644 index 000000000..dfc462b51 --- /dev/null +++ b/api/src/test/java/org/bukkit/conversations/ConversationContextTest.java @@ -0,0 +1,34 @@ +package org.bukkit.conversations; + +import org.junit.Test; +import static org.junit.Assert.*; + +import java.util.HashMap; +import java.util.Map; + +/** + */ +public class ConversationContextTest { + @Test + public void TestFromWhom() { + Conversable conversable = new FakeConversable(); + ConversationContext context = new ConversationContext(null, conversable, new HashMap()); + assertEquals(conversable, context.getForWhom()); + } + + @Test + public void TestPlugin() { + Conversable conversable = new FakeConversable(); + ConversationContext context = new ConversationContext(null, conversable, new HashMap()); + assertEquals(null, context.getPlugin()); + } + + @Test + public void TestSessionData() { + Conversable conversable = new FakeConversable(); + Map session = new HashMap(); + session.put("key", "value"); + ConversationContext context = new ConversationContext(null, conversable, session); + assertEquals("value", context.getSessionData("key")); + } +} diff --git a/api/src/test/java/org/bukkit/conversations/ConversationTest.java b/api/src/test/java/org/bukkit/conversations/ConversationTest.java new file mode 100644 index 000000000..544f1f3c2 --- /dev/null +++ b/api/src/test/java/org/bukkit/conversations/ConversationTest.java @@ -0,0 +1,116 @@ +package org.bukkit.conversations; + +import org.bukkit.plugin.TestPlugin; +import org.junit.Test; +import static org.junit.Assert.*; + +/** + */ +public class ConversationTest { + + @Test + public void testBaseConversationFlow() { + FakeConversable forWhom = new FakeConversable(); + Conversation conversation = new Conversation(null, forWhom, new FirstPrompt()); + + // Conversation not yet begun + assertNull(forWhom.lastSentMessage); + assertEquals(conversation.getForWhom(), forWhom); + assertTrue(conversation.isModal()); + + // Begin the conversation + conversation.begin(); + assertEquals("FirstPrompt", forWhom.lastSentMessage); + assertEquals(conversation, forWhom.begunConversation); + + // Send the first input + conversation.acceptInput("FirstInput"); + assertEquals("SecondPrompt", forWhom.lastSentMessage); + assertEquals(conversation, forWhom.abandonedConverstion); + } + + @Test + public void testConversationFactory() { + FakeConversable forWhom = new FakeConversable(); + NullConversationPrefix prefix = new NullConversationPrefix(); + ConversationFactory factory = new ConversationFactory(new TestPlugin("Test")) + .withFirstPrompt(new FirstPrompt()) + .withModality(false) + .withPrefix(prefix); + Conversation conversation = factory.buildConversation(forWhom); + + // Conversation not yet begun + assertNull(forWhom.lastSentMessage); + assertEquals(conversation.getForWhom(), forWhom); + assertFalse(conversation.isModal()); + assertEquals(conversation.getPrefix(), prefix); + + // Begin the conversation + conversation.begin(); + assertEquals("FirstPrompt", forWhom.lastSentMessage); + assertEquals(conversation, forWhom.begunConversation); + + // Send the first input + conversation.acceptInput("FirstInput"); + assertEquals("SecondPrompt", forWhom.lastSentMessage); + assertEquals(conversation, forWhom.abandonedConverstion); + } + + @Test + public void testEscapeSequence() { + FakeConversable forWhom = new FakeConversable(); + Conversation conversation = new Conversation(null, forWhom, new FirstPrompt()); + conversation.addConversationCanceller(new ExactMatchConversationCanceller("bananas")); + + // Begin the conversation + conversation.begin(); + assertEquals("FirstPrompt", forWhom.lastSentMessage); + assertEquals(conversation, forWhom.begunConversation); + + // Send the first input + conversation.acceptInput("bananas"); + assertEquals("bananas", forWhom.lastSentMessage); + assertEquals(conversation, forWhom.abandonedConverstion); + } + + @Test + public void testNotPlayer() { + FakeConversable forWhom = new FakeConversable(); + ConversationFactory factory = new ConversationFactory(new TestPlugin("Test")) + .thatExcludesNonPlayersWithMessage("bye"); + Conversation conversation = factory.buildConversation(forWhom); + + // Begin the conversation + conversation.begin(); + assertEquals("bye", forWhom.lastSentMessage); + assertEquals(conversation, forWhom.begunConversation); + assertEquals(conversation, forWhom.abandonedConverstion); + } + + private class FirstPrompt extends StringPrompt { + + public String getPromptText(ConversationContext context) { + return "FirstPrompt"; + } + + public Prompt acceptInput(ConversationContext context, String input) { + assertEquals("FirstInput", input); + context.setSessionData("data", 10); + return new SecondPrompt(); + } + } + + private class SecondPrompt extends MessagePrompt { + + @Override + protected Prompt getNextPrompt(ConversationContext context) { + return Prompt.END_OF_CONVERSATION; + } + + public String getPromptText(ConversationContext context) { + // Assert that session data passes from one prompt to the next + assertEquals(context.getSessionData("data"), 10); + return "SecondPrompt"; + } + } +} diff --git a/api/src/test/java/org/bukkit/conversations/FakeConversable.java b/api/src/test/java/org/bukkit/conversations/FakeConversable.java new file mode 100644 index 000000000..87fb31139 --- /dev/null +++ b/api/src/test/java/org/bukkit/conversations/FakeConversable.java @@ -0,0 +1,105 @@ +package org.bukkit.conversations; + +import org.bukkit.Server; +import org.bukkit.permissions.Permission; +import org.bukkit.permissions.PermissionAttachment; +import org.bukkit.permissions.PermissionAttachmentInfo; +import org.bukkit.plugin.Plugin; + +import java.util.Set; + +/** + */ +public class FakeConversable implements Conversable { + public String lastSentMessage; + public Conversation begunConversation; + public Conversation abandonedConverstion; + public ConversationAbandonedEvent abandonedConversationEvent; + + public boolean isConversing() { + return false; + } + + public void acceptConversationInput(String input) { + + } + + public boolean beginConversation(Conversation conversation) { + begunConversation = conversation; + conversation.outputNextPrompt(); + return true; + } + + public void abandonConversation(Conversation conversation) { + abandonedConverstion = conversation; + } + + public void abandonConversation(Conversation conversation, ConversationAbandonedEvent details) { + abandonedConverstion = conversation; + abandonedConversationEvent = details; + } + + public void sendRawMessage(String message) { + lastSentMessage = message; + } + + public Server getServer() { + return null; + } + + public String getName() { + return null; + } + + public boolean isPermissionSet(String name) { + return false; + } + + public boolean isPermissionSet(Permission perm) { + return false; + } + + public boolean hasPermission(String name) { + return false; + } + + public boolean hasPermission(Permission perm) { + return false; + } + + public PermissionAttachment addAttachment(Plugin plugin, String name, boolean value) { + return null; + } + + public PermissionAttachment addAttachment(Plugin plugin) { + return null; + } + + public PermissionAttachment addAttachment(Plugin plugin, String name, boolean value, int ticks) { + return null; + } + + public PermissionAttachment addAttachment(Plugin plugin, int ticks) { + return null; + } + + public void removeAttachment(PermissionAttachment attachment) { + + } + + public void recalculatePermissions() { + + } + + public Set getEffectivePermissions() { + return null; + } + + public boolean isOp() { + return false; + } + + public void setOp(boolean value) { + + } +} diff --git a/api/src/test/java/org/bukkit/conversations/ValidatingPromptTest.java b/api/src/test/java/org/bukkit/conversations/ValidatingPromptTest.java new file mode 100644 index 000000000..d1c0f42c0 --- /dev/null +++ b/api/src/test/java/org/bukkit/conversations/ValidatingPromptTest.java @@ -0,0 +1,115 @@ +package org.bukkit.conversations; + +import org.junit.Test; + +import static org.junit.Assert.*; + +/** + */ +public class ValidatingPromptTest { + + @Test + public void TestBooleanPrompt() { + TestBooleanPrompt prompt = new TestBooleanPrompt(); + assertTrue(prompt.isInputValid(null, "true")); + assertFalse(prompt.isInputValid(null, "bananas")); + prompt.acceptInput(null, "true"); + assertTrue(prompt.result); + prompt.acceptInput(null, "no"); + assertFalse(prompt.result); + } + + @Test + public void TestFixedSetPrompt() { + TestFixedSetPrompt prompt = new TestFixedSetPrompt("foo", "bar"); + assertTrue(prompt.isInputValid(null, "foo")); + assertFalse(prompt.isInputValid(null, "cheese")); + prompt.acceptInput(null, "foo"); + assertEquals("foo", prompt.result); + } + + @Test + public void TestNumericPrompt() { + TestNumericPrompt prompt = new TestNumericPrompt(); + assertTrue(prompt.isInputValid(null, "1010220")); + assertFalse(prompt.isInputValid(null, "tomato")); + prompt.acceptInput(null, "1010220"); + assertEquals(1010220, prompt.result); + } + + @Test + public void TestRegexPrompt() { + TestRegexPrompt prompt = new TestRegexPrompt("a.c"); + assertTrue(prompt.isInputValid(null, "abc")); + assertTrue(prompt.isInputValid(null, "axc")); + assertFalse(prompt.isInputValid(null, "xyz")); + prompt.acceptInput(null, "abc"); + assertEquals("abc", prompt.result); + } + + //TODO: TestPlayerNamePrompt() + + private class TestBooleanPrompt extends BooleanPrompt { + public boolean result; + + @Override + protected Prompt acceptValidatedInput(ConversationContext context, boolean input) { + result = input; + return null; + } + + public String getPromptText(ConversationContext context) { + return null; + } + } + + private class TestFixedSetPrompt extends FixedSetPrompt { + public String result; + + public TestFixedSetPrompt(String... fixedSet) { + super(fixedSet); + } + + @Override + protected Prompt acceptValidatedInput(ConversationContext context, String input) { + result = input; + return null; + } + + public String getPromptText(ConversationContext context) { + return null; + } + } + + private class TestNumericPrompt extends NumericPrompt { + public Number result; + + @Override + protected Prompt acceptValidatedInput(ConversationContext context, Number input) { + result = input; + return null; + } + + public String getPromptText(ConversationContext context) { + return null; + } + } + + private class TestRegexPrompt extends RegexPrompt { + public String result; + + public TestRegexPrompt(String pattern) { + super(pattern); + } + + @Override + protected Prompt acceptValidatedInput(ConversationContext context, String input) { + result = input; + return null; + } + + public String getPromptText(ConversationContext context) { + return null; + } + } +} diff --git a/api/src/test/java/org/bukkit/event/PlayerChatTabCompleteEventTest.java b/api/src/test/java/org/bukkit/event/PlayerChatTabCompleteEventTest.java new file mode 100644 index 000000000..619bf30b0 --- /dev/null +++ b/api/src/test/java/org/bukkit/event/PlayerChatTabCompleteEventTest.java @@ -0,0 +1,28 @@ +package org.bukkit.event; + +import static org.hamcrest.CoreMatchers.is; +import static org.junit.Assert.assertThat; + +import org.bukkit.event.player.PlayerChatTabCompleteEvent; +import org.bukkit.plugin.messaging.TestPlayer; +import org.junit.Test; + +import com.google.common.collect.ImmutableList; + +public class PlayerChatTabCompleteEventTest { + + @Test + public void testGetLastToken() { + assertThat(getToken("Hello everyone!"), is("everyone!")); + assertThat(getToken(" welcome to the show..."), is("show...")); + assertThat(getToken("The whitespace is here "), is("")); + assertThat(getToken("Too much whitespace is here "), is("")); + assertThat(getToken("The_whitespace_is_missing"), is("The_whitespace_is_missing")); + assertThat(getToken(""), is("")); + assertThat(getToken(" "), is("")); + } + + private String getToken(String message) { + return new PlayerChatTabCompleteEvent(TestPlayer.getInstance(), message, ImmutableList.of()).getLastToken(); + } +} diff --git a/api/src/test/java/org/bukkit/event/SyntheticEventTest.java b/api/src/test/java/org/bukkit/event/SyntheticEventTest.java new file mode 100644 index 000000000..04b1b9840 --- /dev/null +++ b/api/src/test/java/org/bukkit/event/SyntheticEventTest.java @@ -0,0 +1,48 @@ +package org.bukkit.event; + +import org.bukkit.TestServer; +import org.bukkit.plugin.PluginLoader; +import org.bukkit.plugin.SimplePluginManager; +import org.bukkit.plugin.TestPlugin; +import org.bukkit.plugin.java.JavaPluginLoader; +import org.junit.Assert; +import org.junit.Test; + +public class SyntheticEventTest { + @SuppressWarnings("deprecation") + @Test + public void test() { + final JavaPluginLoader loader = new JavaPluginLoader(TestServer.getInstance()); + TestPlugin plugin = new TestPlugin(getClass().getName()) { + @Override + public PluginLoader getPluginLoader() { + return loader; + } + }; + SimplePluginManager pluginManager = new SimplePluginManager(TestServer.getInstance(), null); + + TestEvent event = new TestEvent(false); + Impl impl = new Impl(); + + pluginManager.registerEvents(impl, plugin); + pluginManager.callEvent(event); + + Assert.assertEquals(1, impl.callCount); + } + + public static abstract class Base implements Listener { + int callCount = 0; + + public void accept(E evt) { + callCount++; + } + } + + public static class Impl extends Base { + @Override + @EventHandler + public void accept(TestEvent evt) { + super.accept(evt); + } + } +} diff --git a/api/src/test/java/org/bukkit/event/TestEvent.java b/api/src/test/java/org/bukkit/event/TestEvent.java new file mode 100644 index 000000000..25904f5f8 --- /dev/null +++ b/api/src/test/java/org/bukkit/event/TestEvent.java @@ -0,0 +1,19 @@ +package org.bukkit.event; + + +public class TestEvent extends Event { + private static final HandlerList handlers = new HandlerList(); + + public TestEvent(boolean async) { + super(async); + } + + @Override + public HandlerList getHandlers() { + return handlers; + } + + public static HandlerList getHandlerList() { + return handlers; + } +} diff --git a/api/src/test/java/org/bukkit/materials/MaterialDataTest.java b/api/src/test/java/org/bukkit/materials/MaterialDataTest.java new file mode 100644 index 000000000..eb55c02e8 --- /dev/null +++ b/api/src/test/java/org/bukkit/materials/MaterialDataTest.java @@ -0,0 +1,433 @@ +package org.bukkit.materials; + +import static org.hamcrest.Matchers.equalTo; +import static org.junit.Assert.assertThat; + +import org.bukkit.CropState; +import org.bukkit.Material; +import org.bukkit.NetherWartsState; +import org.bukkit.TreeSpecies; +import org.bukkit.block.BlockFace; +import org.bukkit.material.Crops; +import org.bukkit.material.Comparator; +import org.bukkit.material.Diode; +import org.bukkit.material.Door; +import org.bukkit.material.Hopper; +import org.bukkit.material.Leaves; +import org.bukkit.material.Mushroom; +import org.bukkit.material.NetherWarts; +import org.bukkit.material.Sapling; +import org.bukkit.material.Tree; +import org.bukkit.material.Wood; +import org.bukkit.material.WoodenStep; +import org.bukkit.material.types.MushroomBlockTexture; +import org.junit.Test; + +public class MaterialDataTest { + + @Test + public void testDoor() + { + @SuppressWarnings("deprecation") + Door door = new Door(); + assertThat("Constructed with default door type",door.getItemType(),equalTo(Material.LEGACY_WOODEN_DOOR)); + assertThat("Constructed with default top or bottom",door.isTopHalf(),equalTo(false)); + assertThat("Constructed with default direction",door.getFacing(),equalTo(BlockFace.WEST)); + assertThat("Constructed with default open state",door.isOpen(),equalTo(false)); + + Material[] types = new Material[] { Material.LEGACY_WOODEN_DOOR, + Material.LEGACY_IRON_DOOR_BLOCK, Material.LEGACY_SPRUCE_DOOR, + Material.LEGACY_BIRCH_DOOR, Material.LEGACY_JUNGLE_DOOR, + Material.LEGACY_ACACIA_DOOR, Material.LEGACY_DARK_OAK_DOOR }; + BlockFace[] directions = new BlockFace[] { BlockFace.WEST, BlockFace.NORTH, BlockFace.EAST, BlockFace.SOUTH }; + boolean[] openStates = new boolean[] {false, true}; + boolean[] hingeStates = new boolean[] {false, true}; + for(Material type : types) + { + // Test bottom half + for(BlockFace facing : directions) + { + door = new Door(type,facing); + assertThat("Constructed with correct door type",door.getItemType(),equalTo(type)); + assertThat("Constructed with default top or bottom",door.isTopHalf(),equalTo(false)); + assertThat("Constructed with correct direction",door.getFacing(),equalTo(facing)); + assertThat("Constructed with default open state",door.isOpen(),equalTo(false)); + + for(boolean openState : openStates) + { + door = new Door(type,facing,openState); + assertThat("Constructed with correct door type",door.getItemType(),equalTo(type)); + assertThat("Constructed with default top or bottom",door.isTopHalf(),equalTo(false)); + assertThat("Constructed with correct direction",door.getFacing(),equalTo(facing)); + assertThat("Constructed with correct open state",door.isOpen(),equalTo(openState)); + } + } + + // Test top half + for(boolean hingeState : hingeStates) + { + door = new Door(type,hingeState); + assertThat("Constructed with correct door type",door.getItemType(),equalTo(type)); + assertThat("Constructed with default top or bottom",door.isTopHalf(),equalTo(true)); + assertThat("Constructed with correct direction",door.getHinge(),equalTo(hingeState)); + } + } + } + + @Test + public void testWood() { + Wood wood = new Wood(); + assertThat("Constructed with default wood type", wood.getItemType(), equalTo(Material.LEGACY_WOOD)); + assertThat("Constructed with default tree species", wood.getSpecies(), equalTo(TreeSpecies.GENERIC)); + + TreeSpecies[] allSpecies = TreeSpecies.values(); + for (TreeSpecies species : allSpecies) { + wood = new Wood(species); + assertThat("Constructed with default wood type", wood.getItemType(), equalTo(Material.LEGACY_WOOD)); + assertThat("Constructed with correct tree species", wood.getSpecies(), equalTo(species)); + } + + Material[] types = new Material[]{Material.LEGACY_WOOD, Material.LEGACY_WOOD_DOUBLE_STEP}; + for (Material type : types) { + wood = new Wood(type); + assertThat("Constructed with correct wood type", wood.getItemType(), equalTo(type)); + assertThat("Constructed with default tree species", wood.getSpecies(), equalTo(TreeSpecies.GENERIC)); + + for (TreeSpecies species : allSpecies) { + wood = new Wood(type, species); + assertThat("Constructed with correct wood type", wood.getItemType(), equalTo(type)); + assertThat("Constructed with correct tree species", wood.getSpecies(), equalTo(species)); + } + } + } + + @Test + public void testTree() { + Tree tree = new Tree(); + assertThat("Constructed with default tree type", tree.getItemType(), equalTo(Material.LEGACY_LOG)); + assertThat("Constructed with default tree species", tree.getSpecies(), equalTo(TreeSpecies.GENERIC)); + assertThat("Constructed with default direction", tree.getDirection(), equalTo(BlockFace.UP)); + + tree = new Tree(Material.LEGACY_LOG); + assertThat("Constructed with correct tree type", tree.getItemType(), equalTo(Material.LEGACY_LOG)); + assertThat("Constructed with default tree species", tree.getSpecies(), equalTo(TreeSpecies.GENERIC)); + assertThat("Constructed with default direction", tree.getDirection(), equalTo(BlockFace.UP)); + + Material[] types = new Material[]{Material.LEGACY_LOG, Material.LEGACY_LOG_2}; + TreeSpecies[][] allSpecies = new TreeSpecies[][]{ + {TreeSpecies.GENERIC, TreeSpecies.REDWOOD, TreeSpecies.BIRCH, TreeSpecies.JUNGLE}, + {TreeSpecies.ACACIA, TreeSpecies.DARK_OAK} + }; + BlockFace[] allDirections = new BlockFace[]{BlockFace.UP, BlockFace.WEST, BlockFace.NORTH, BlockFace.SELF}; + for (int t = 0; t < types.length; t++) { + for (TreeSpecies species : allSpecies[t]) { + tree = new Tree(types[t], species); + assertThat("Constructed with correct tree type", tree.getItemType(), equalTo(types[t])); + assertThat("Constructed with correct tree species", tree.getSpecies(), equalTo(species)); + assertThat("Constructed with default direction", tree.getDirection(), equalTo(BlockFace.UP)); + + // check item type is fixed automatically for invalid type-species combo + tree = new Tree(types[types.length - 1 - t], species); + assertThat("Constructed with fixed tree type", tree.getItemType(), equalTo(types[t])); + assertThat("Constructed with correct tree species", tree.getSpecies(), equalTo(species)); + assertThat("Constructed with default direction", tree.getDirection(), equalTo(BlockFace.UP)); + for (BlockFace dir : allDirections) { + tree = new Tree(types[t], species, dir); + assertThat("Constructed with correct tree type", tree.getItemType(), equalTo(types[t])); + assertThat("Constructed with correct tree species", tree.getSpecies(), equalTo(species)); + assertThat("Constructed with correct direction", tree.getDirection(), equalTo(dir)); + } + } + } + } + + @Test + public void testLeaves() { + Leaves leaves = new Leaves(); + assertThat("Constructed with default leaf type", leaves.getItemType(), equalTo(Material.LEGACY_LEAVES)); + assertThat("Constructed with default tree species", leaves.getSpecies(), equalTo(TreeSpecies.GENERIC)); + assertThat("Constructed with default decayable", leaves.isDecayable(), equalTo(true)); + assertThat("Constructed with default decaying", leaves.isDecaying(), equalTo(false)); + + leaves = new Leaves(Material.LEGACY_LEAVES); + assertThat("Constructed with correct leaf type", leaves.getItemType(), equalTo(Material.LEGACY_LEAVES)); + assertThat("Constructed with default tree species", leaves.getSpecies(), equalTo(TreeSpecies.GENERIC)); + assertThat("Constructed with default decayable", leaves.isDecayable(), equalTo(true)); + assertThat("Constructed with default decaying", leaves.isDecaying(), equalTo(false)); + + Material[] types = new Material[]{Material.LEGACY_LEAVES, Material.LEGACY_LEAVES_2}; + TreeSpecies[][] allSpecies = new TreeSpecies[][]{ + {TreeSpecies.GENERIC, TreeSpecies.REDWOOD, TreeSpecies.BIRCH, TreeSpecies.JUNGLE}, + {TreeSpecies.ACACIA, TreeSpecies.DARK_OAK} + }; + boolean[] decayable = new boolean[]{true, false}; + boolean[] decaying = new boolean[]{true, false}; + for (int t = 0; t < types.length; t++) { + for (TreeSpecies species : allSpecies[t]) { + leaves = new Leaves(types[t], species); + assertThat("Constructed with correct leaf type", leaves.getItemType(), equalTo(types[t])); + assertThat("Constructed with correct tree species", leaves.getSpecies(), equalTo(species)); + assertThat("Constructed with default decayable", leaves.isDecayable(), equalTo(true)); + assertThat("Constructed with default decaying", leaves.isDecaying(), equalTo(false)); + + // check item type is fixed automatically for invalid type-species combo + leaves = new Leaves(types[types.length - 1 - t], species); + assertThat("Constructed with fixed leaf type", leaves.getItemType(), equalTo(types[t])); + assertThat("Constructed with correct tree species", leaves.getSpecies(), equalTo(species)); + assertThat("Constructed with default decayable", leaves.isDecayable(), equalTo(true)); + assertThat("Constructed with default decaying", leaves.isDecaying(), equalTo(false)); + for (boolean isDecayable : decayable) { + leaves = new Leaves(types[t], species, isDecayable); + assertThat("Constructed with correct wood type", leaves.getItemType(), equalTo(types[t])); + assertThat("Constructed with correct tree species", leaves.getSpecies(), equalTo(species)); + assertThat("Constructed with correct decayable", leaves.isDecayable(), equalTo(isDecayable)); + assertThat("Constructed with default decaying", leaves.isDecaying(), equalTo(false)); + for (boolean isDecaying : decaying) { + leaves = new Leaves(types[t], species, isDecayable); + leaves.setDecaying(isDecaying); + assertThat("Constructed with correct wood type", leaves.getItemType(), equalTo(types[t])); + assertThat("Constructed with correct tree species", leaves.getSpecies(), equalTo(species)); + assertThat("Constructed with correct decayable", leaves.isDecayable(), equalTo(isDecaying || isDecayable)); + assertThat("Constructed with correct decaying", leaves.isDecaying(), equalTo(isDecaying)); + } + } + } + } + } + + @Test + public void testWoodenStep() { + WoodenStep woodenStep = new WoodenStep(); + assertThat("Constructed with default step type", woodenStep.getItemType(), equalTo(Material.LEGACY_WOOD_STEP)); + assertThat("Constructed with default tree species", woodenStep.getSpecies(), equalTo(TreeSpecies.GENERIC)); + assertThat("Constructed with default inversion", woodenStep.isInverted(), equalTo(false)); + + TreeSpecies[] allSpecies = TreeSpecies.values(); + boolean[] inversion = new boolean[]{true, false}; + for (TreeSpecies species : allSpecies) { + woodenStep = new WoodenStep(species); + assertThat("Constructed with default step type", woodenStep.getItemType(), equalTo(Material.LEGACY_WOOD_STEP)); + assertThat("Constructed with correct tree species", woodenStep.getSpecies(), equalTo(species)); + assertThat("Constructed with default inversion", woodenStep.isInverted(), equalTo(false)); + for (boolean isInverted : inversion) { + woodenStep = new WoodenStep(species, isInverted); + assertThat("Constructed with default step type", woodenStep.getItemType(), equalTo(Material.LEGACY_WOOD_STEP)); + assertThat("Constructed with correct tree species", woodenStep.getSpecies(), equalTo(species)); + assertThat("Constructed with correct inversion", woodenStep.isInverted(), equalTo(isInverted)); + } + } + } + + @Test + public void testSapling() { + Sapling sapling = new Sapling(); + assertThat("Constructed with default sapling type", sapling.getItemType(), equalTo(Material.LEGACY_SAPLING)); + assertThat("Constructed with default tree species", sapling.getSpecies(), equalTo(TreeSpecies.GENERIC)); + assertThat("Constructed with default growable", sapling.isInstantGrowable(), equalTo(false)); + + TreeSpecies[] allSpecies = TreeSpecies.values(); + boolean[] growable = new boolean[]{true, false}; + for (TreeSpecies species : allSpecies) { + sapling = new Sapling(species); + assertThat("Constructed with default sapling type", sapling.getItemType(), equalTo(Material.LEGACY_SAPLING)); + assertThat("Constructed with correct tree species", sapling.getSpecies(), equalTo(species)); + assertThat("Constructed with default growable", sapling.isInstantGrowable(), equalTo(false)); + for (boolean isInstantGrowable : growable) { + sapling = new Sapling(species, isInstantGrowable); + assertThat("Constructed with default sapling type", sapling.getItemType(), equalTo(Material.LEGACY_SAPLING)); + assertThat("Constructed with correct tree species", sapling.getSpecies(), equalTo(species)); + assertThat("Constructed with correct growable", sapling.isInstantGrowable(), equalTo(isInstantGrowable)); + } + } + } + + @Test + public void testMushroom() { + Material[] mushroomTypes = new Material[] { Material.LEGACY_HUGE_MUSHROOM_1, Material.LEGACY_HUGE_MUSHROOM_2 }; + BlockFace[] setFaces = new BlockFace[] { BlockFace.SELF, BlockFace.UP, BlockFace.NORTH, + BlockFace.SOUTH, BlockFace.EAST, BlockFace.WEST, BlockFace.NORTH_EAST, BlockFace.NORTH_WEST, + BlockFace.SOUTH_EAST, BlockFace.SOUTH_WEST }; + MushroomBlockTexture[] textures = MushroomBlockTexture.values(); + for (Material type : mushroomTypes) { + Mushroom mushroom = new Mushroom(type); + assertThat("Constructed with correct mushroom type", mushroom.getItemType(), equalTo(type)); + assertThat("Constructed with default pores face", mushroom.getBlockTexture(), equalTo(MushroomBlockTexture.ALL_PORES)); + + for (int f = 0; f < setFaces.length; f++) { + mushroom = new Mushroom(type, setFaces[f]); + assertThat("Constructed with correct mushroom type", mushroom.getItemType(), equalTo(type)); + assertThat("Constructed with correct texture", mushroom.getBlockTexture(), equalTo(MushroomBlockTexture.getCapByFace(setFaces[f]))); + } + + for (MushroomBlockTexture texture : textures) { + mushroom = new Mushroom(type, texture); + assertThat("Constructed with correct mushroom type", mushroom.getItemType(), equalTo(type)); + assertThat("Constructed with correct texture", mushroom.getBlockTexture(), equalTo(texture)); + } + } + } + + @Test + public void testCrops() { + Crops crops = new Crops(); + assertThat("Constructed with default crops type", crops.getItemType(), equalTo(Material.LEGACY_CROPS)); + assertThat("Constructed with default crop state", crops.getState(), equalTo(CropState.SEEDED)); + + CropState[] allStates = CropState.values(); + for (CropState state : allStates) { + crops = new Crops(state); + assertThat("Constructed with default crops type", crops.getItemType(), equalTo(Material.LEGACY_CROPS)); + assertThat("Constructed with correct crop state", crops.getState(), equalTo(state)); + } + + // The crops which fully implement all crop states + Material[] allCrops = new Material[] {Material.LEGACY_CROPS, Material.LEGACY_CARROT, Material.LEGACY_POTATO}; + for (Material crop : allCrops) { + crops = new Crops(crop); + assertThat("Constructed with correct crops type", crops.getItemType(), equalTo(crop)); + assertThat("Constructed with default crop state", crops.getState(), equalTo(CropState.SEEDED)); + + for (CropState state : allStates) { + crops = new Crops(crop, state); + assertThat("Constructed with correct crops type", crops.getItemType(), equalTo(crop)); + assertThat("Constructed with correct crop state", crops.getState(), equalTo(state)); + } + } + + // Beetroot are crops too, but they only have four states + // Setting different crop states for beetroot will return the following when retrieved back + CropState[] beetrootStates = new CropState[] {CropState.SEEDED, CropState.SEEDED, CropState.SMALL, CropState.SMALL, CropState.TALL, CropState.TALL, CropState.RIPE, CropState.RIPE}; + assertThat("Beetroot state translations match size", beetrootStates.length, equalTo(allStates.length)); + crops = new Crops(Material.LEGACY_BEETROOT_BLOCK); + assertThat("Constructed with correct crops type", crops.getItemType(), equalTo(Material.LEGACY_BEETROOT_BLOCK)); + assertThat("Constructed with default crop state", crops.getState(), equalTo(CropState.SEEDED)); + for (int s = 0; s < beetrootStates.length; s++) { + crops = new Crops(Material.LEGACY_BEETROOT_BLOCK, allStates[s]); + assertThat("Constructed with correct crops type", crops.getItemType(), equalTo(Material.LEGACY_BEETROOT_BLOCK)); + assertThat("Constructed with correct crop state", crops.getState(), equalTo(beetrootStates[s])); + } + + // In case you want to treat NetherWarts as Crops, although they really aren't + crops = new Crops(Material.LEGACY_NETHER_WARTS); + NetherWarts warts = new NetherWarts(); + assertThat("Constructed with correct crops type", crops.getItemType(), equalTo(warts.getItemType())); + assertThat("Constructed with default crop state", crops.getState(), equalTo(CropState.SEEDED)); + assertThat("Constructed with default wart state", warts.getState(), equalTo(NetherWartsState.SEEDED)); + allStates = new CropState[] {CropState.SEEDED, CropState.SMALL, CropState.TALL, CropState.RIPE}; + NetherWartsState[] allWartStates = NetherWartsState.values(); + assertThat("Nether Warts state translations match size", allWartStates.length, equalTo(allStates.length)); + for (int s = 0; s < allStates.length; s++) { + crops = new Crops(Material.LEGACY_NETHER_WARTS, allStates[s]); + warts = new NetherWarts(allWartStates[s]); + assertThat("Constructed with correct crops type", crops.getItemType(), equalTo(warts.getItemType())); + assertThat("Constructed with correct crop state", crops.getState(), equalTo(allStates[s])); + assertThat("Constructed with correct wart state", warts.getState(), equalTo(allWartStates[s])); + } + } + + @Test + public void testDiode() { + Diode diode = new Diode(); + assertThat("Constructed with backward compatible diode state", diode.getItemType(), equalTo(Material.LEGACY_DIODE_BLOCK_ON)); + assertThat("Constructed with backward compatible powered", diode.isPowered(), equalTo(true)); + assertThat("Constructed with default delay", diode.getDelay(), equalTo(1)); + assertThat("Constructed with default direction", diode.getFacing(), equalTo(BlockFace.NORTH)); + + BlockFace[] directions = new BlockFace[] {BlockFace.NORTH, BlockFace.EAST, BlockFace.SOUTH, BlockFace.WEST}; + int[] delays = new int[] {1, 2, 3, 4}; + boolean[] states = new boolean[] {false, true}; + for (BlockFace direction : directions) { + diode = new Diode(direction); + assertThat("Constructed with default diode state", diode.getItemType(), equalTo(Material.LEGACY_DIODE_BLOCK_OFF)); + assertThat("Constructed with default powered", diode.isPowered(), equalTo(false)); + assertThat("Constructed with default delay", diode.getDelay(), equalTo(1)); + assertThat("Constructed with correct direction", diode.getFacing(), equalTo(direction)); + for (int delay : delays) { + diode = new Diode(direction, delay); + assertThat("Constructed with default diode state", diode.getItemType(), equalTo(Material.LEGACY_DIODE_BLOCK_OFF)); + assertThat("Constructed with default powered", diode.isPowered(), equalTo(false)); + assertThat("Constructed with correct delay", diode.getDelay(), equalTo(delay)); + assertThat("Constructed with correct direction", diode.getFacing(), equalTo(direction)); + for (boolean state : states) { + diode = new Diode(direction, delay, state); + assertThat("Constructed with correct diode state", diode.getItemType(), equalTo(state ? Material.LEGACY_DIODE_BLOCK_ON : Material.LEGACY_DIODE_BLOCK_OFF)); + assertThat("Constructed with default powered", diode.isPowered(), equalTo(state)); + assertThat("Constructed with correct delay", diode.getDelay(), equalTo(delay)); + assertThat("Constructed with correct direction", diode.getFacing(), equalTo(direction)); + } + } + } + } + + @Test + public void testComparator() { + Comparator comparator = new Comparator(); + assertThat("Constructed with default comparator state", comparator.getItemType(), equalTo(Material.LEGACY_REDSTONE_COMPARATOR_OFF)); + assertThat("Constructed with default powered", comparator.isPowered(), equalTo(false)); + assertThat("Constructed with default being powered", comparator.isBeingPowered(), equalTo(false)); + assertThat("Constructed with default mode", comparator.isSubtractionMode(), equalTo(false)); + assertThat("Constructed with default direction", comparator.getFacing(), equalTo(BlockFace.NORTH)); + + BlockFace[] directions = new BlockFace[] {BlockFace.NORTH, BlockFace.EAST, BlockFace.SOUTH, BlockFace.WEST}; + boolean[] modes = new boolean[] {false, true}; + boolean[] states = new boolean[] {false, true}; + for (BlockFace direction : directions) { + comparator = new Comparator(direction); + assertThat("Constructed with default comparator state", comparator.getItemType(), equalTo(Material.LEGACY_REDSTONE_COMPARATOR_OFF)); + assertThat("Constructed with default powered", comparator.isPowered(), equalTo(false)); + assertThat("Constructed with default being powered", comparator.isBeingPowered(), equalTo(false)); + assertThat("Constructed with default mode", comparator.isSubtractionMode(), equalTo(false)); + assertThat("Constructed with correct direction", comparator.getFacing(), equalTo(direction)); + for (boolean mode : modes) { + comparator = new Comparator(direction, mode); + assertThat("Constructed with default comparator state", comparator.getItemType(), equalTo(Material.LEGACY_REDSTONE_COMPARATOR_OFF)); + assertThat("Constructed with default powered", comparator.isPowered(), equalTo(false)); + assertThat("Constructed with default being powered", comparator.isBeingPowered(), equalTo(false)); + assertThat("Constructed with correct mode", comparator.isSubtractionMode(), equalTo(mode)); + assertThat("Constructed with correct direction", comparator.getFacing(), equalTo(direction)); + for (boolean state : states) { + comparator = new Comparator(direction, mode, state); + assertThat("Constructed with correct comparator state", comparator.getItemType(), equalTo(state ? Material.LEGACY_REDSTONE_COMPARATOR_ON : Material.LEGACY_REDSTONE_COMPARATOR_OFF)); + assertThat("Constructed with correct powered", comparator.isPowered(), equalTo(state)); + assertThat("Constructed with default being powered", comparator.isBeingPowered(), equalTo(false)); + assertThat("Constructed with correct mode", comparator.isSubtractionMode(), equalTo(mode)); + assertThat("Constructed with correct direction", comparator.getFacing(), equalTo(direction)); + + // Check if the game sets the fourth bit, that block data is still interpreted correctly + comparator.setData((byte)((comparator.getData() & 0x7) | 0x8)); + assertThat("Constructed with correct comparator state", comparator.getItemType(), equalTo(state ? Material.LEGACY_REDSTONE_COMPARATOR_ON : Material.LEGACY_REDSTONE_COMPARATOR_OFF)); + assertThat("Constructed with correct powered", comparator.isPowered(), equalTo(state)); + assertThat("Constructed with correct being powered", comparator.isBeingPowered(), equalTo(true)); + assertThat("Constructed with correct mode", comparator.isSubtractionMode(), equalTo(mode)); + assertThat("Constructed with correct direction", comparator.getFacing(), equalTo(direction)); + } + } + } + } + + @Test + public void testHopper() { + Hopper hopper = new Hopper(); + assertThat("Constructed with default hopper type", hopper.getItemType(), equalTo(Material.LEGACY_HOPPER)); + assertThat("Constructed with default active state", hopper.isActive(), equalTo(true)); + assertThat("Constructed with default powered state", hopper.isPowered(), equalTo(false)); + assertThat("Constructed with default direction", hopper.getFacing(), equalTo(BlockFace.DOWN)); + + BlockFace[] directions = new BlockFace[] {BlockFace.DOWN, BlockFace.NORTH, BlockFace.SOUTH, BlockFace.WEST, BlockFace.EAST}; + boolean[] activeStates = new boolean[] {true, false}; + for (BlockFace direction : directions) { + hopper = new Hopper(direction); + assertThat("Constructed with default hopper type", hopper.getItemType(), equalTo(Material.LEGACY_HOPPER)); + assertThat("Constructed with default active state", hopper.isActive(), equalTo(true)); + assertThat("Constructed with correct powered state", hopper.isPowered(), equalTo(false)); + assertThat("Constructed with correct direction", hopper.getFacing(), equalTo(direction)); + for(boolean isActive : activeStates) { + hopper = new Hopper(direction, isActive); + assertThat("Constructed with default hopper type", hopper.getItemType(), equalTo(Material.LEGACY_HOPPER)); + assertThat("Constructed with correct active state", hopper.isActive(), equalTo(isActive)); + assertThat("Constructed with correct powered state", hopper.isPowered(), equalTo(!isActive)); + assertThat("Constructed with correct direction", hopper.getFacing(), equalTo(direction)); + } + } + } +} diff --git a/api/src/test/java/org/bukkit/metadata/FixedMetadataValueTest.java b/api/src/test/java/org/bukkit/metadata/FixedMetadataValueTest.java new file mode 100644 index 000000000..5583b274b --- /dev/null +++ b/api/src/test/java/org/bukkit/metadata/FixedMetadataValueTest.java @@ -0,0 +1,42 @@ +package org.bukkit.metadata; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertSame; + +import org.bukkit.plugin.Plugin; +import org.bukkit.plugin.TestPlugin; +import org.junit.Test; + +public class FixedMetadataValueTest { + private Plugin plugin = new TestPlugin("X"); + private FixedMetadataValue subject; + + @Test + public void testBasic() { + subject = new FixedMetadataValue(plugin, new Integer(50)); + assertSame(plugin, subject.getOwningPlugin()); + assertEquals(new Integer(50), subject.value()); + } + + @Test + public void testNumberTypes() { + subject = new FixedMetadataValue(plugin, new Integer(5)); + assertEquals(new Integer(5), subject.value()); + assertEquals(5, subject.asInt()); + assertEquals(true, subject.asBoolean()); + assertEquals(5, subject.asByte()); + assertEquals(5.0, subject.asFloat(), 0.1e-8); + assertEquals(5.0D, subject.asDouble(), 0.1e-8D); + assertEquals(5L, subject.asLong()); + assertEquals(5, subject.asShort()); + assertEquals("5", subject.asString()); + } + + @Test + public void testInvalidateDoesNothing() { + Object o = new Object(); + subject = new FixedMetadataValue(plugin, o); + subject.invalidate(); + assertSame(o, subject.value()); + } +} diff --git a/api/src/test/java/org/bukkit/metadata/LazyMetadataValueTest.java b/api/src/test/java/org/bukkit/metadata/LazyMetadataValueTest.java new file mode 100644 index 000000000..a3172db70 --- /dev/null +++ b/api/src/test/java/org/bukkit/metadata/LazyMetadataValueTest.java @@ -0,0 +1,135 @@ +package org.bukkit.metadata; + +import org.bukkit.plugin.TestPlugin; +import org.junit.Test; + +import java.util.concurrent.Callable; + +import static org.junit.Assert.*; + +public class LazyMetadataValueTest { + private LazyMetadataValue subject; + private TestPlugin plugin = new TestPlugin("x"); + + @Test + public void testLazyInt() { + int value = 10; + subject = makeSimpleCallable(value); + + assertEquals(value, subject.value()); + } + + @Test + public void testLazyDouble() { + double value = 10.5; + subject = makeSimpleCallable(value); + + assertEquals(value, (Double)subject.value(), 0.01); + } + + @Test + public void testLazyString() { + String value = "TEN"; + subject = makeSimpleCallable(value); + + assertEquals(value, subject.value()); + } + + @Test + public void testLazyBoolean() { + boolean value = false; + subject = makeSimpleCallable(value); + + assertEquals(value, subject.value()); + } + + @Test(expected=MetadataEvaluationException.class) + public void testEvalException() { + subject = new LazyMetadataValue(plugin, LazyMetadataValue.CacheStrategy.CACHE_AFTER_FIRST_EVAL, new Callable() { + public Object call() throws Exception { + throw new RuntimeException("Gotcha!"); + } + }); + subject.value(); + } + + @Test + public void testCacheStrategyCacheAfterFirstEval() { + final Counter counter = new Counter(); + final int value = 10; + subject = new LazyMetadataValue(plugin, LazyMetadataValue.CacheStrategy.CACHE_AFTER_FIRST_EVAL, new Callable() { + public Object call() throws Exception { + counter.increment(); + return value; + } + }); + + subject.value(); + subject.value(); + assertEquals(value, subject.value()); + assertEquals(1, counter.value()); + + subject.invalidate(); + subject.value(); + assertEquals(2, counter.value()); + } + + @Test + public void testCacheStrategyNeverCache() { + final Counter counter = new Counter(); + final int value = 10; + subject = new LazyMetadataValue(plugin, LazyMetadataValue.CacheStrategy.NEVER_CACHE, new Callable() { + public Object call() throws Exception { + counter.increment(); + return value; + } + }); + + subject.value(); + subject.value(); + assertEquals(value, subject.value()); + assertEquals(3, counter.value()); + } + + @Test + public void testCacheStrategyEternally() { + final Counter counter = new Counter(); + final int value = 10; + subject = new LazyMetadataValue(plugin, LazyMetadataValue.CacheStrategy.CACHE_ETERNALLY, new Callable() { + public Object call() throws Exception { + counter.increment(); + return value; + } + }); + + subject.value(); + subject.value(); + assertEquals(value, subject.value()); + assertEquals(1, counter.value()); + + subject.invalidate(); + subject.value(); + assertEquals(value, subject.value()); + assertEquals(1, counter.value()); + } + + private LazyMetadataValue makeSimpleCallable(final Object value) { + return new LazyMetadataValue(plugin, new Callable() { + public Object call() throws Exception { + return value; + } + }); + } + + private class Counter { + private int c = 0; + + public void increment() { + c++; + } + + public int value() { + return c; + } + } +} diff --git a/api/src/test/java/org/bukkit/metadata/MetadataConversionTest.java b/api/src/test/java/org/bukkit/metadata/MetadataConversionTest.java new file mode 100644 index 000000000..a595cc8d6 --- /dev/null +++ b/api/src/test/java/org/bukkit/metadata/MetadataConversionTest.java @@ -0,0 +1,103 @@ +// Copyright (C) 2011 Ryan Michela +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . + +package org.bukkit.metadata; + +import org.bukkit.plugin.Plugin; +import org.bukkit.plugin.TestPlugin; +import org.junit.Test; + +import static org.junit.Assert.assertEquals; + +/** + */ +public class MetadataConversionTest { + private Plugin plugin = new TestPlugin("x"); + private FixedMetadataValue subject; + + private void setSubject(Object value) { + subject = new FixedMetadataValue(plugin, value); + } + + @Test + public void testFromInt() { + setSubject(10); + + assertEquals(10, subject.asInt()); + assertEquals(10, subject.asFloat(), 0.000001); + assertEquals(10, subject.asDouble(), 0.000001); + assertEquals(10, subject.asLong()); + assertEquals(10, subject.asShort()); + assertEquals(10, subject.asByte()); + assertEquals(true, subject.asBoolean()); + assertEquals("10", subject.asString()); + } + + @Test + public void testFromFloat() { + setSubject(10.5); + + assertEquals(10, subject.asInt()); + assertEquals(10.5, subject.asFloat(), 0.000001); + assertEquals(10.5, subject.asDouble(), 0.000001); + assertEquals(10, subject.asLong()); + assertEquals(10, subject.asShort()); + assertEquals(10, subject.asByte()); + assertEquals(true, subject.asBoolean()); + assertEquals("10.5", subject.asString()); + } + + @Test + public void testFromNumericString() { + setSubject("10"); + + assertEquals(10, subject.asInt()); + assertEquals(10, subject.asFloat(), 0.000001); + assertEquals(10, subject.asDouble(), 0.000001); + assertEquals(10, subject.asLong()); + assertEquals(10, subject.asShort()); + assertEquals(10, subject.asByte()); + assertEquals(false, subject.asBoolean()); + assertEquals("10", subject.asString()); + } + + @Test + public void testFromNonNumericString() { + setSubject("true"); + + assertEquals(0, subject.asInt()); + assertEquals(0, subject.asFloat(), 0.000001); + assertEquals(0, subject.asDouble(), 0.000001); + assertEquals(0, subject.asLong()); + assertEquals(0, subject.asShort()); + assertEquals(0, subject.asByte()); + assertEquals(true, subject.asBoolean()); + assertEquals("true", subject.asString()); + } + + @Test + public void testFromNull() { + setSubject(null); + + assertEquals(0, subject.asInt()); + assertEquals(0, subject.asFloat(), 0.000001); + assertEquals(0, subject.asDouble(), 0.000001); + assertEquals(0, subject.asLong()); + assertEquals(0, subject.asShort()); + assertEquals(0, subject.asByte()); + assertEquals(false, subject.asBoolean()); + assertEquals("", subject.asString()); + } +} diff --git a/api/src/test/java/org/bukkit/metadata/MetadataStoreTest.java b/api/src/test/java/org/bukkit/metadata/MetadataStoreTest.java new file mode 100644 index 000000000..30f036864 --- /dev/null +++ b/api/src/test/java/org/bukkit/metadata/MetadataStoreTest.java @@ -0,0 +1,143 @@ +package org.bukkit.metadata; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertTrue; + +import java.util.List; +import java.util.concurrent.Callable; + +import org.bukkit.plugin.Plugin; +import org.bukkit.plugin.TestPlugin; +import org.junit.Test; + +public class MetadataStoreTest { + private Plugin pluginX = new TestPlugin("x"); + private Plugin pluginY = new TestPlugin("y"); + + StringMetadataStore subject = new StringMetadataStore(); + + @Test + public void testMetadataStore() { + subject.setMetadata("subject", "key", new FixedMetadataValue(pluginX, 10)); + + assertTrue(subject.hasMetadata("subject", "key")); + List values = subject.getMetadata("subject", "key"); + assertEquals(10, values.get(0).value()); + } + + @Test + public void testMetadataNotPresent() { + assertFalse(subject.hasMetadata("subject", "key")); + List values = subject.getMetadata("subject", "key"); + assertTrue(values.isEmpty()); + } + + @Test + public void testInvalidateAll() { + final Counter counter = new Counter(); + + subject.setMetadata("subject", "key", new LazyMetadataValue(pluginX, new Callable() { + public Object call() throws Exception { + counter.increment(); + return 10; + } + })); + + assertTrue(subject.hasMetadata("subject", "key")); + subject.getMetadata("subject", "key").get(0).value(); + subject.invalidateAll(pluginX); + subject.getMetadata("subject", "key").get(0).value(); + assertEquals(2, counter.value()); + } + + @Test + public void testInvalidateAllButActuallyNothing() { + final Counter counter = new Counter(); + + subject.setMetadata("subject", "key", new LazyMetadataValue(pluginX, new Callable() { + public Object call() throws Exception { + counter.increment(); + return 10; + } + })); + + assertTrue(subject.hasMetadata("subject", "key")); + subject.getMetadata("subject", "key").get(0).value(); + subject.invalidateAll(pluginY); + subject.getMetadata("subject", "key").get(0).value(); + assertEquals(1, counter.value()); + } + + @Test + public void testMetadataReplace() { + subject.setMetadata("subject", "key", new FixedMetadataValue(pluginX, 10)); + subject.setMetadata("subject", "key", new FixedMetadataValue(pluginY, 10)); + subject.setMetadata("subject", "key", new FixedMetadataValue(pluginX, 20)); + + for (MetadataValue mv : subject.getMetadata("subject", "key")) { + if (mv.getOwningPlugin().equals(pluginX)) { + assertEquals(20, mv.value()); + } + if (mv.getOwningPlugin().equals(pluginY)) { + assertEquals(10, mv.value()); + } + } + } + + @Test + public void testMetadataRemove() { + subject.setMetadata("subject", "key", new FixedMetadataValue(pluginX, 10)); + subject.setMetadata("subject", "key", new FixedMetadataValue(pluginY, 20)); + subject.removeMetadata("subject", "key", pluginX); + + assertTrue(subject.hasMetadata("subject", "key")); + assertEquals(1, subject.getMetadata("subject", "key").size()); + assertEquals(20, subject.getMetadata("subject", "key").get(0).value()); + } + + @Test + public void testMetadataRemoveLast() { + subject.setMetadata("subject", "key", new FixedMetadataValue(pluginX, 10)); + subject.removeMetadata("subject", "key", pluginX); + + assertFalse(subject.hasMetadata("subject", "key")); + assertEquals(0, subject.getMetadata("subject", "key").size()); + } + + @Test + public void testMetadataRemoveForNonExistingPlugin() { + subject.setMetadata("subject", "key", new FixedMetadataValue(pluginX, 10)); + subject.removeMetadata("subject", "key", pluginY); + + assertTrue(subject.hasMetadata("subject", "key")); + assertEquals(1, subject.getMetadata("subject", "key").size()); + assertEquals(10, subject.getMetadata("subject", "key").get(0).value()); + } + + @Test + public void testHasMetadata() { + subject.setMetadata("subject", "key", new FixedMetadataValue(pluginX, 10)); + assertTrue(subject.hasMetadata("subject", "key")); + assertFalse(subject.hasMetadata("subject", "otherKey")); + } + + private class StringMetadataStore extends MetadataStoreBase implements MetadataStore { + @Override + protected String disambiguate(String subject, String metadataKey) { + return subject + ":" + metadataKey; + } + } + + private class Counter { + int c = 0; + + public void increment() { + c++; + } + + public int value() { + return c; + } + } +} diff --git a/api/src/test/java/org/bukkit/metadata/MetadataValueAdapterTest.java b/api/src/test/java/org/bukkit/metadata/MetadataValueAdapterTest.java new file mode 100644 index 000000000..7d8a17fee --- /dev/null +++ b/api/src/test/java/org/bukkit/metadata/MetadataValueAdapterTest.java @@ -0,0 +1,97 @@ +package org.bukkit.metadata; + +import static org.junit.Assert.assertEquals; + +import org.bukkit.plugin.Plugin; +import org.bukkit.plugin.TestPlugin; +import org.junit.Test; + +public class MetadataValueAdapterTest { + private TestPlugin plugin = new TestPlugin("x"); + + @Test + public void testAdapterBasics() { + IncrementingMetaValue mv = new IncrementingMetaValue(plugin); + // check getOwningPlugin + assertEquals(mv.getOwningPlugin(), this.plugin); + + // Check value-getting and invalidation. + assertEquals(new Integer(1), mv.value()); + assertEquals(new Integer(2), mv.value()); + mv.invalidate(); + assertEquals(new Integer(1), mv.value()); + } + + @Test + public void testAdapterConversions() { + IncrementingMetaValue mv = new IncrementingMetaValue(plugin); + + assertEquals(1, mv.asInt()); + assertEquals(2L, mv.asLong()); + assertEquals(3.0, mv.asFloat(), 0.001); + assertEquals(4, mv.asByte()); + assertEquals(5.0, mv.asDouble(), 0.001); + assertEquals(6, mv.asShort()); + assertEquals("7", mv.asString()); + } + + /** Boolean conversion is non-trivial, we want to test it thoroughly. */ + @Test + public void testBooleanConversion() { + // null is False. + assertEquals(false, simpleValue(null).asBoolean()); + + // String to boolean. + assertEquals(true, simpleValue("True").asBoolean()); + assertEquals(true, simpleValue("TRUE").asBoolean()); + assertEquals(false, simpleValue("false").asBoolean()); + + // Number to boolean. + assertEquals(true, simpleValue(1).asBoolean()); + assertEquals(true, simpleValue(5.0).asBoolean()); + assertEquals(false, simpleValue(0).asBoolean()); + assertEquals(false, simpleValue(0.1).asBoolean()); + + // Boolean as boolean, of course. + assertEquals(true, simpleValue(Boolean.TRUE).asBoolean()); + assertEquals(false, simpleValue(Boolean.FALSE).asBoolean()); + + // any object that is not null and not a Boolean, String, or Number is true. + assertEquals(true, simpleValue(new Object()).asBoolean()); + } + + /** Test String conversions return an empty string when given null. */ + @Test + public void testStringConversionNull() { + assertEquals("", simpleValue(null).asString()); + } + + /** Get a fixed value MetadataValue. */ + private MetadataValue simpleValue(Object value) { + return new FixedMetadataValue(plugin, value); + } + + /** + * A sample non-trivial MetadataValueAdapter implementation. + * + * The rationale for implementing an incrementing value is to have a value + * which changes with every call to value(). This is important for testing + * because we want to make sure all the tested conversions are calling the + * value() method exactly once and no caching is going on. + */ + class IncrementingMetaValue extends MetadataValueAdapter { + private int internalValue = 0; + + protected IncrementingMetaValue(Plugin owningPlugin) { + super(owningPlugin); + } + + public Object value() { + return ++internalValue; + } + + public void invalidate() { + internalValue = 0; + } + } +} diff --git a/api/src/test/java/org/bukkit/plugin/PluginManagerTest.java b/api/src/test/java/org/bukkit/plugin/PluginManagerTest.java new file mode 100644 index 000000000..56308c0c6 --- /dev/null +++ b/api/src/test/java/org/bukkit/plugin/PluginManagerTest.java @@ -0,0 +1,176 @@ +package org.bukkit.plugin; + +import static org.hamcrest.Matchers.*; +import static org.junit.Assert.*; + +import org.bukkit.TestServer; +import org.bukkit.event.Event; +import org.bukkit.event.TestEvent; +import org.bukkit.permissions.Permission; + +import org.junit.After; +import org.junit.Test; + +public class PluginManagerTest { + private class MutableObject { + volatile Object value = null; + } + + private static final PluginManager pm = TestServer.getInstance().getPluginManager(); + + private final MutableObject store = new MutableObject(); +/* // Paper start - remove unneeded test + @Test + public void testAsyncSameThread() { + final Event event = new TestEvent(true); + try { + pm.callEvent(event); + } catch (IllegalStateException ex) { + assertThat(event.getEventName() + " cannot be triggered asynchronously from primary server thread.", is(ex.getMessage())); + return; + } + throw new IllegalStateException("No exception thrown"); + }*/ // Paper end + + @Test + public void testSyncSameThread() { + final Event event = new TestEvent(false); + pm.callEvent(event); + } +/* // Paper start - remove unneeded test + @Test + public void testAsyncLocked() throws InterruptedException { + final Event event = new TestEvent(true); + Thread secondThread = new Thread( + new Runnable() { + public void run() { + try { + synchronized (pm) { + pm.callEvent(event); + } + } catch (Throwable ex) { + store.value = ex; + } + } + } + ); + secondThread.start(); + secondThread.join(); + assertThat(store.value, is(instanceOf(IllegalStateException.class))); + assertThat(event.getEventName() + " cannot be triggered asynchronously from inside synchronized code.", is(((Throwable) store.value).getMessage())); + }*/ // Paper end + + @Test + public void testAsyncUnlocked() throws InterruptedException { + final Event event = new TestEvent(true); + Thread secondThread = new Thread( + new Runnable() { + public void run() { + try { + pm.callEvent(event); + } catch (Throwable ex) { + store.value = ex; + } + }}); + secondThread.start(); + secondThread.join(); + if (store.value != null) { + throw new RuntimeException((Throwable) store.value); + } + } + + @Test + public void testSyncUnlocked() throws InterruptedException { + final Event event = new TestEvent(false); + Thread secondThread = new Thread( + new Runnable() { + public void run() { + try { + pm.callEvent(event); + } catch (Throwable ex) { + store.value = ex; + } + } + } + ); + secondThread.start(); + secondThread.join(); + if (store.value != null) { + throw new RuntimeException((Throwable) store.value); + } + } + + @Test + public void testSyncLocked() throws InterruptedException { + final Event event = new TestEvent(false); + Thread secondThread = new Thread( + new Runnable() { + public void run() { + try { + synchronized (pm) { + pm.callEvent(event); + } + } catch (Throwable ex) { + store.value = ex; + } + } + } + ); + secondThread.start(); + secondThread.join(); + if (store.value != null) { + throw new RuntimeException((Throwable) store.value); + } + } + + @Test + public void testRemovePermissionByNameLower() { + this.testRemovePermissionByName("lower"); + } + + @Test + public void testRemovePermissionByNameUpper() { + this.testRemovePermissionByName("UPPER"); + } + + @Test + public void testRemovePermissionByNameCamel() { + this.testRemovePermissionByName("CaMeL"); + } + + public void testRemovePermissionByPermissionLower() { + this.testRemovePermissionByPermission("lower"); + } + + @Test + public void testRemovePermissionByPermissionUpper() { + this.testRemovePermissionByPermission("UPPER"); + } + + @Test + public void testRemovePermissionByPermissionCamel() { + this.testRemovePermissionByPermission("CaMeL"); + } + + private void testRemovePermissionByName(final String name) { + final Permission perm = new Permission(name); + pm.addPermission(perm); + assertThat("Permission \"" + name + "\" was not added", pm.getPermission(name), is(perm)); + pm.removePermission(name); + assertThat("Permission \"" + name + "\" was not removed", pm.getPermission(name), is(nullValue())); + } + + private void testRemovePermissionByPermission(final String name) { + final Permission perm = new Permission(name); + pm.addPermission(perm); + assertThat("Permission \"" + name + "\" was not added", pm.getPermission(name), is(perm)); + pm.removePermission(perm); + assertThat("Permission \"" + name + "\" was not removed", pm.getPermission(name), is(nullValue())); + } + + @After + public void tearDown() { + pm.clearPlugins(); + assertThat(pm.getPermissions(), is(empty())); + } +} diff --git a/api/src/test/java/org/bukkit/plugin/TestPlugin.java b/api/src/test/java/org/bukkit/plugin/TestPlugin.java new file mode 100644 index 000000000..f85e5f175 --- /dev/null +++ b/api/src/test/java/org/bukkit/plugin/TestPlugin.java @@ -0,0 +1,105 @@ +package org.bukkit.plugin; + +import java.io.File; +import java.io.InputStream; +import java.util.List; + +import org.bukkit.Server; +import org.bukkit.command.Command; +import org.bukkit.command.CommandSender; +import org.bukkit.configuration.file.FileConfiguration; +import org.bukkit.generator.ChunkGenerator; + +public class TestPlugin extends PluginBase { + private boolean enabled = true; + + final private String pluginName; + + public TestPlugin(String pluginName) { + this.pluginName = pluginName; + } + + public void setEnabled(boolean enabled) { + this.enabled = enabled; + } + + public File getDataFolder() { + throw new UnsupportedOperationException("Not supported."); + } + + public PluginDescriptionFile getDescription() { + return new PluginDescriptionFile(pluginName, "1.0", "test.test"); + } + + public FileConfiguration getConfig() { + throw new UnsupportedOperationException("Not supported."); + } + + public InputStream getResource(String filename) { + throw new UnsupportedOperationException("Not supported."); + } + + public void saveConfig() { + throw new UnsupportedOperationException("Not supported."); + } + + public void saveDefaultConfig() { + throw new UnsupportedOperationException("Not supported."); + } + + public void saveResource(String resourcePath, boolean replace) { + throw new UnsupportedOperationException("Not supported."); + } + + public void reloadConfig() { + throw new UnsupportedOperationException("Not supported."); + } + + public PluginLogger getLogger() { + throw new UnsupportedOperationException("Not supported."); + } + + public PluginLoader getPluginLoader() { + throw new UnsupportedOperationException("Not supported."); + } + + public Server getServer() { + throw new UnsupportedOperationException("Not supported."); + } + + public boolean isEnabled() { + return enabled; + } + + public void onDisable() { + throw new UnsupportedOperationException("Not supported."); + } + + public void onLoad() { + throw new UnsupportedOperationException("Not supported."); + } + + public void onEnable() { + throw new UnsupportedOperationException("Not supported."); + } + + public boolean isNaggable() { + throw new UnsupportedOperationException("Not supported."); + } + + public void setNaggable(boolean canNag) { + throw new UnsupportedOperationException("Not supported."); + } + + public ChunkGenerator getDefaultWorldGenerator(String worldName, String id) { + throw new UnsupportedOperationException("Not supported."); + } + + public boolean onCommand(CommandSender sender, Command command, String label, String[] args) { + throw new UnsupportedOperationException("Not supported."); + } + + public List onTabComplete(CommandSender sender, Command command, String alias, String[] args) { + throw new UnsupportedOperationException("Not supported."); + } +} diff --git a/api/src/test/java/org/bukkit/plugin/TimedRegisteredListenerTest.java b/api/src/test/java/org/bukkit/plugin/TimedRegisteredListenerTest.java new file mode 100644 index 000000000..b206b1f38 --- /dev/null +++ b/api/src/test/java/org/bukkit/plugin/TimedRegisteredListenerTest.java @@ -0,0 +1,56 @@ +package org.bukkit.plugin; + +import static org.hamcrest.Matchers.*; +import static org.junit.Assert.*; + +import org.bukkit.event.Event; +import org.bukkit.event.EventException; +import org.bukkit.event.EventPriority; +import org.bukkit.event.Listener; +import org.bukkit.event.block.BlockBreakEvent; +import org.bukkit.event.player.PlayerEvent; +import org.bukkit.event.player.PlayerInteractEvent; +import org.bukkit.event.player.PlayerMoveEvent; +import org.junit.Test; + +public class TimedRegisteredListenerTest { + + @Test + public void testEventClass() throws EventException { + Listener listener = new Listener() {}; + EventExecutor executor = new EventExecutor() { + public void execute(Listener listener, Event event) {} + }; + TestPlugin plugin = new TestPlugin("Test"); + + PlayerInteractEvent interactEvent = new PlayerInteractEvent(null, null, null, null, null); + PlayerMoveEvent moveEvent = new PlayerMoveEvent(null, null, null); + BlockBreakEvent breakEvent = new BlockBreakEvent(null, null); + + TimedRegisteredListener trl = new TimedRegisteredListener(listener, executor, EventPriority.NORMAL, plugin, false); + + // Ensure that the correct event type is reported for a single event + trl.callEvent(interactEvent); + assertThat(trl.getEventClass(), is((Object) PlayerInteractEvent.class)); + // Ensure that no superclass is used in lieu of the actual event, after two identical event types + trl.callEvent(interactEvent); + assertThat(trl.getEventClass(), is((Object) PlayerInteractEvent.class)); + // Ensure that the closest superclass of the two events is chosen + trl.callEvent(moveEvent); + assertThat(trl.getEventClass(), is((Object) PlayerEvent.class)); + // As above, so below + trl.callEvent(breakEvent); + assertThat(trl.getEventClass(), is((Object) Event.class)); + // In the name of being thorough, check that it never travels down the hierarchy again. + trl.callEvent(breakEvent); + assertThat(trl.getEventClass(), is((Object) Event.class)); + + trl = new TimedRegisteredListener(listener, executor, EventPriority.NORMAL, plugin, false); + + trl.callEvent(breakEvent); + assertThat(trl.getEventClass(), is((Object) BlockBreakEvent.class)); + // Test moving up the class hierarchy by more than one class at a time + trl.callEvent(moveEvent); + assertThat(trl.getEventClass(), is((Object) Event.class)); + } +} diff --git a/api/src/test/java/org/bukkit/plugin/messaging/StandardMessengerTest.java b/api/src/test/java/org/bukkit/plugin/messaging/StandardMessengerTest.java new file mode 100644 index 000000000..31ff2f61d --- /dev/null +++ b/api/src/test/java/org/bukkit/plugin/messaging/StandardMessengerTest.java @@ -0,0 +1,305 @@ +package org.bukkit.plugin.messaging; + +import org.bukkit.entity.Player; +import org.bukkit.plugin.TestPlugin; +import java.util.Collection; +import org.junit.Test; +import org.junit.Assert; +import static org.junit.Assert.*; +import static org.hamcrest.CoreMatchers.*; + +public class StandardMessengerTest { + public StandardMessenger getMessenger() { + return new StandardMessenger(); + } + + private int count = 0; + public TestPlugin getPlugin() { + return new TestPlugin("" + count++); + } + + @Test + public void testIsReservedChannel() { + Messenger messenger = getMessenger(); + + assertTrue(messenger.isReservedChannel("minecraft:register")); + assertFalse(messenger.isReservedChannel("test:register")); + assertTrue(messenger.isReservedChannel("minecraft:unregister")); + assertFalse(messenger.isReservedChannel("test:unregister")); // Paper - fix typo + assertFalse(messenger.isReservedChannel("minecraft:something")); // Paper - now less strict + assertFalse(messenger.isReservedChannel("minecraft:brand")); + } + + @Test + public void testRegisterAndUnregisterOutgoingPluginChannel() { + Messenger messenger = getMessenger(); + TestPlugin plugin = getPlugin(); + + assertFalse(messenger.isOutgoingChannelRegistered(plugin, "test:foo")); + messenger.registerOutgoingPluginChannel(plugin, "test:foo"); + assertTrue(messenger.isOutgoingChannelRegistered(plugin, "test:foo")); + assertFalse(messenger.isOutgoingChannelRegistered(plugin, "test:bar")); + + messenger.unregisterOutgoingPluginChannel(plugin, "test:foo"); + assertFalse(messenger.isOutgoingChannelRegistered(plugin, "test:foo")); + } + + @Test(expected = ReservedChannelException.class) + public void testReservedOutgoingRegistration() { + Messenger messenger = getMessenger(); + TestPlugin plugin = getPlugin(); + + messenger.registerOutgoingPluginChannel(plugin, "minecraft:register"); + } + + @Test + public void testUnregisterOutgoingPluginChannel_Plugin() { + Messenger messenger = getMessenger(); + TestPlugin plugin = getPlugin(); + + assertFalse(messenger.isOutgoingChannelRegistered(plugin, "test:foo")); + messenger.registerOutgoingPluginChannel(plugin, "test:foo"); + messenger.registerOutgoingPluginChannel(plugin, "test:bar"); + assertTrue(messenger.isOutgoingChannelRegistered(plugin, "test:foo")); + assertTrue(messenger.isOutgoingChannelRegistered(plugin, "test:bar")); + + messenger.unregisterOutgoingPluginChannel(plugin); + assertFalse(messenger.isOutgoingChannelRegistered(plugin, "test:foo")); + assertFalse(messenger.isOutgoingChannelRegistered(plugin, "test:bar")); + } + + @Test + public void testRegisterIncomingPluginChannel() { + Messenger messenger = getMessenger(); + TestPlugin plugin = getPlugin(); + TestMessageListener listener = new TestMessageListener("test:foo", "test:bar".getBytes()); + Player player = TestPlayer.getInstance(); + PluginMessageListenerRegistration registration = messenger.registerIncomingPluginChannel(plugin, "test:foo", listener); + + assertTrue(registration.isValid()); + assertTrue(messenger.isIncomingChannelRegistered(plugin, "test:foo")); + messenger.dispatchIncomingMessage(player, "test:foo", "test:bar".getBytes()); + assertTrue(listener.hasReceived()); + + messenger.unregisterIncomingPluginChannel(plugin, "test:foo", listener); + listener.reset(); + + assertFalse(registration.isValid()); + assertFalse(messenger.isIncomingChannelRegistered(plugin, "test:foo")); + messenger.dispatchIncomingMessage(player, "test:foo", "test:bar".getBytes()); + assertFalse(listener.hasReceived()); + } + + @Test(expected = ReservedChannelException.class) + public void testReservedIncomingRegistration() { + Messenger messenger = getMessenger(); + TestPlugin plugin = getPlugin(); + + messenger.registerIncomingPluginChannel(plugin, "minecraft:register", new TestMessageListener("test:foo", "test:bar".getBytes())); + } + + @Test(expected = IllegalArgumentException.class) + public void testDuplicateIncomingRegistration() { + Messenger messenger = getMessenger(); + TestPlugin plugin = getPlugin(); + TestMessageListener listener = new TestMessageListener("test:foo", "test:bar".getBytes()); + + messenger.registerIncomingPluginChannel(plugin, "test:baz", listener); + messenger.registerIncomingPluginChannel(plugin, "test:baz", listener); + } + + @Test + public void testUnregisterIncomingPluginChannel_Plugin_String() { + Messenger messenger = getMessenger(); + TestPlugin plugin = getPlugin(); + TestMessageListener listener1 = new TestMessageListener("test:foo", "test:bar".getBytes()); + TestMessageListener listener2 = new TestMessageListener("test:baz", "test:qux".getBytes()); + Player player = TestPlayer.getInstance(); + PluginMessageListenerRegistration registration1 = messenger.registerIncomingPluginChannel(plugin, "test:foo", listener1); + PluginMessageListenerRegistration registration2 = messenger.registerIncomingPluginChannel(plugin, "test:baz", listener2); + + assertTrue(registration1.isValid()); + assertTrue(registration2.isValid()); + messenger.dispatchIncomingMessage(player, "test:foo", "test:bar".getBytes()); + messenger.dispatchIncomingMessage(player, "test:baz", "test:qux".getBytes()); + assertTrue(listener1.hasReceived()); + assertTrue(listener2.hasReceived()); + + messenger.unregisterIncomingPluginChannel(plugin, "test:foo"); + listener1.reset(); + listener2.reset(); + + assertFalse(registration1.isValid()); + assertTrue(registration2.isValid()); + messenger.dispatchIncomingMessage(player, "test:foo", "test:bar".getBytes()); + messenger.dispatchIncomingMessage(player, "test:baz", "test:qux".getBytes()); + assertFalse(listener1.hasReceived()); + assertTrue(listener2.hasReceived()); + } + + @Test + public void testUnregisterIncomingPluginChannel_Plugin() { + Messenger messenger = getMessenger(); + TestPlugin plugin = getPlugin(); + TestMessageListener listener1 = new TestMessageListener("test:foo", "test:bar".getBytes()); + TestMessageListener listener2 = new TestMessageListener("test:baz", "test:qux".getBytes()); + Player player = TestPlayer.getInstance(); + PluginMessageListenerRegistration registration1 = messenger.registerIncomingPluginChannel(plugin, "test:foo", listener1); + PluginMessageListenerRegistration registration2 = messenger.registerIncomingPluginChannel(plugin, "test:baz", listener2); + + assertTrue(registration1.isValid()); + assertTrue(registration2.isValid()); + messenger.dispatchIncomingMessage(player, "test:foo", "test:bar".getBytes()); + messenger.dispatchIncomingMessage(player, "test:baz", "test:qux".getBytes()); + assertTrue(listener1.hasReceived()); + assertTrue(listener2.hasReceived()); + + messenger.unregisterIncomingPluginChannel(plugin); + listener1.reset(); + listener2.reset(); + + assertFalse(registration1.isValid()); + assertFalse(registration2.isValid()); + messenger.dispatchIncomingMessage(player, "test:foo", "test:bar".getBytes()); + messenger.dispatchIncomingMessage(player, "test:baz", "test:qux".getBytes()); + assertFalse(listener1.hasReceived()); + assertFalse(listener2.hasReceived()); + } + + @Test + public void testGetOutgoingChannels() { + Messenger messenger = getMessenger(); + TestPlugin plugin1 = getPlugin(); + TestPlugin plugin2 = getPlugin(); + + assertEquals(messenger.getOutgoingChannels()); + + messenger.registerOutgoingPluginChannel(plugin1, "test:foo"); + messenger.registerOutgoingPluginChannel(plugin1, "test:bar"); + messenger.registerOutgoingPluginChannel(plugin2, "test:baz"); + messenger.registerOutgoingPluginChannel(plugin2, "test:baz"); + + assertEquals(messenger.getOutgoingChannels(), "test:foo", "test:bar", "test:baz"); + } + + @Test + public void testGetOutgoingChannels_Plugin() { + Messenger messenger = getMessenger(); + TestPlugin plugin1 = getPlugin(); + TestPlugin plugin2 = getPlugin(); + TestPlugin plugin3 = getPlugin(); + + messenger.registerOutgoingPluginChannel(plugin1, "test:foo"); + messenger.registerOutgoingPluginChannel(plugin1, "test:bar"); + messenger.registerOutgoingPluginChannel(plugin2, "test:baz"); + messenger.registerOutgoingPluginChannel(plugin2, "test:qux"); + + assertEquals(messenger.getOutgoingChannels(plugin1), "test:foo", "test:bar"); + assertEquals(messenger.getOutgoingChannels(plugin2), "test:baz", "test:qux"); + assertEquals(messenger.getOutgoingChannels(plugin3)); + } + + @Test + public void testGetIncomingChannels() { + Messenger messenger = getMessenger(); + TestPlugin plugin1 = getPlugin(); + TestPlugin plugin2 = getPlugin(); + + assertEquals(messenger.getIncomingChannels()); + + messenger.registerIncomingPluginChannel(plugin1, "test:foo", new TestMessageListener("test:foo", "test:bar".getBytes())); + messenger.registerIncomingPluginChannel(plugin1, "test:bar", new TestMessageListener("test:foo", "test:bar".getBytes())); + messenger.registerIncomingPluginChannel(plugin2, "test:baz", new TestMessageListener("test:foo", "test:bar".getBytes())); + messenger.registerIncomingPluginChannel(plugin2, "test:baz", new TestMessageListener("test:foo", "test:bar".getBytes())); + + assertEquals(messenger.getIncomingChannels(), "test:foo", "test:bar", "test:baz"); + } + + @Test + public void testGetIncomingChannels_Plugin() { + Messenger messenger = getMessenger(); + TestPlugin plugin1 = getPlugin(); + TestPlugin plugin2 = getPlugin(); + TestPlugin plugin3 = getPlugin(); + + messenger.registerIncomingPluginChannel(plugin1, "test:foo", new TestMessageListener("test:foo", "test:bar".getBytes())); + messenger.registerIncomingPluginChannel(plugin1, "test:bar", new TestMessageListener("test:foo", "test:bar".getBytes())); + messenger.registerIncomingPluginChannel(plugin2, "test:baz", new TestMessageListener("test:foo", "test:bar".getBytes())); + messenger.registerIncomingPluginChannel(plugin2, "test:qux", new TestMessageListener("test:foo", "test:bar".getBytes())); + + assertEquals(messenger.getIncomingChannels(plugin1), "test:foo", "test:bar"); + assertEquals(messenger.getIncomingChannels(plugin2), "test:baz", "test:qux"); + assertEquals(messenger.getIncomingChannels(plugin3)); + } + + @Test + public void testGetIncomingChannelRegistrations_Plugin() { + Messenger messenger = getMessenger(); + TestPlugin plugin1 = getPlugin(); + TestPlugin plugin2 = getPlugin(); + TestPlugin plugin3 = getPlugin(); + PluginMessageListenerRegistration registration1 = messenger.registerIncomingPluginChannel(plugin1, "test:foo", new TestMessageListener("test:foo", "test:bar".getBytes())); + PluginMessageListenerRegistration registration2 = messenger.registerIncomingPluginChannel(plugin1, "test:bar", new TestMessageListener("test:foo", "test:bar".getBytes())); + PluginMessageListenerRegistration registration3 = messenger.registerIncomingPluginChannel(plugin2, "test:baz", new TestMessageListener("test:foo", "test:bar".getBytes())); + PluginMessageListenerRegistration registration4 = messenger.registerIncomingPluginChannel(plugin2, "test:qux", new TestMessageListener("test:foo", "test:bar".getBytes())); + + assertEquals(messenger.getIncomingChannelRegistrations(plugin1), registration1, registration2); + assertEquals(messenger.getIncomingChannelRegistrations(plugin2), registration3, registration4); + assertEquals(messenger.getIncomingChannels(plugin3)); + } + + @Test + public void testGetIncomingChannelRegistrations_String() { + Messenger messenger = getMessenger(); + TestPlugin plugin1 = getPlugin(); + TestPlugin plugin2 = getPlugin(); + PluginMessageListenerRegistration registration1 = messenger.registerIncomingPluginChannel(plugin1, "test:foo", new TestMessageListener("test:foo", "test:bar".getBytes())); + PluginMessageListenerRegistration registration2 = messenger.registerIncomingPluginChannel(plugin1, "test:bar", new TestMessageListener("test:foo", "test:bar".getBytes())); + PluginMessageListenerRegistration registration3 = messenger.registerIncomingPluginChannel(plugin2, "test:foo", new TestMessageListener("test:foo", "test:bar".getBytes())); + PluginMessageListenerRegistration registration4 = messenger.registerIncomingPluginChannel(plugin2, "test:bar", new TestMessageListener("test:foo", "test:bar".getBytes())); + + assertEquals(messenger.getIncomingChannelRegistrations("test:foo"), registration1, registration3); + assertEquals(messenger.getIncomingChannelRegistrations("test:bar"), registration2, registration4); + assertEquals(messenger.getIncomingChannelRegistrations("test:baz")); + } + + @Test + public void testGetIncomingChannelRegistrations_Plugin_String() { + Messenger messenger = getMessenger(); + TestPlugin plugin1 = getPlugin(); + TestPlugin plugin2 = getPlugin(); + TestPlugin plugin3 = getPlugin(); + PluginMessageListenerRegistration registration1 = messenger.registerIncomingPluginChannel(plugin1, "test:foo", new TestMessageListener("test:foo", "test:bar".getBytes())); + PluginMessageListenerRegistration registration2 = messenger.registerIncomingPluginChannel(plugin1, "test:foo", new TestMessageListener("test:foo", "test:bar".getBytes())); + PluginMessageListenerRegistration registration3 = messenger.registerIncomingPluginChannel(plugin1, "test:bar", new TestMessageListener("test:foo", "test:bar".getBytes())); + PluginMessageListenerRegistration registration4 = messenger.registerIncomingPluginChannel(plugin2, "test:bar", new TestMessageListener("test:foo", "test:bar".getBytes())); + PluginMessageListenerRegistration registration5 = messenger.registerIncomingPluginChannel(plugin2, "test:baz", new TestMessageListener("test:foo", "test:bar".getBytes())); + PluginMessageListenerRegistration registration6 = messenger.registerIncomingPluginChannel(plugin2, "test:baz", new TestMessageListener("test:foo", "test:bar".getBytes())); + + assertEquals(messenger.getIncomingChannelRegistrations(plugin1, "test:foo"), registration1, registration2); + assertEquals(messenger.getIncomingChannelRegistrations(plugin1, "test:bar"), registration3); + assertEquals(messenger.getIncomingChannelRegistrations(plugin2, "test:bar"), registration4); + assertEquals(messenger.getIncomingChannelRegistrations(plugin2, "test:baz"), registration5, registration6); + assertEquals(messenger.getIncomingChannelRegistrations(plugin1, "test:baz")); + assertEquals(messenger.getIncomingChannelRegistrations(plugin3, "test:qux")); + } + + @Test(expected = IllegalArgumentException.class) + public void testInvalidChannel() { + Messenger messenger = getMessenger(); + TestPlugin plugin = getPlugin(); + + messenger.registerOutgoingPluginChannel(plugin, "foo"); + } + + @Test + public void testValidateAndCorrectChannel() { + Assert.assertEquals("bungeecord:main", StandardMessenger.validateAndCorrectChannel("BungeeCord")); + Assert.assertEquals("BungeeCord", StandardMessenger.validateAndCorrectChannel("bungeecord:main")); + } + + private static void assertEquals(Collection actual, T... expected) { + assertThat("Size of the array", actual.size(), is(expected.length)); + assertThat(actual, hasItems(expected)); + } +} diff --git a/api/src/test/java/org/bukkit/plugin/messaging/TestMessageListener.java b/api/src/test/java/org/bukkit/plugin/messaging/TestMessageListener.java new file mode 100644 index 000000000..98860ec19 --- /dev/null +++ b/api/src/test/java/org/bukkit/plugin/messaging/TestMessageListener.java @@ -0,0 +1,29 @@ +package org.bukkit.plugin.messaging; + +import org.bukkit.entity.Player; +import static org.junit.Assert.*; + +public class TestMessageListener implements PluginMessageListener { + private final String channel; + private final byte[] message; + private boolean received = false; + + public TestMessageListener(String channel, byte[] message) { + this.channel = channel; + this.message = message; + } + + public void onPluginMessageReceived(String channel, Player player, byte[] message) { + assertEquals(this.channel, channel); + assertArrayEquals(this.message, message); + this.received = true; + } + + public boolean hasReceived() { + return received; + } + + public void reset() { + received = false; + } +} diff --git a/api/src/test/java/org/bukkit/plugin/messaging/TestPlayer.java b/api/src/test/java/org/bukkit/plugin/messaging/TestPlayer.java new file mode 100644 index 000000000..71f59c5d3 --- /dev/null +++ b/api/src/test/java/org/bukkit/plugin/messaging/TestPlayer.java @@ -0,0 +1,50 @@ +package org.bukkit.plugin.messaging; + +import java.lang.reflect.Constructor; +import java.lang.reflect.InvocationHandler; +import java.lang.reflect.Method; +import java.lang.reflect.Proxy; +import java.util.HashMap; + +import org.bukkit.entity.Player; + + +public class TestPlayer implements InvocationHandler { + private static interface MethodHandler { + Object handle(TestPlayer server, Object[] args); + } + private static final Constructor constructor; + private static final HashMap methods = new HashMap(); + static { + try { + /* + methods.put(Player.class.getMethod("methodName"), + new MethodHandler() { + public Object handle(TestPlayer server, Object[] args) { + } + }); + */ + constructor = Proxy.getProxyClass(Player.class.getClassLoader(), Player.class).asSubclass(Player.class).getConstructor(InvocationHandler.class); + } catch (Throwable t) { + throw new Error(t); + } + } + + private TestPlayer() {}; + + public static Player getInstance() { + try { + return constructor.newInstance(new TestPlayer()); + } catch (Throwable t) { + throw new RuntimeException(t); + } + } + + public Object invoke(Object proxy, Method method, Object[] args) { + MethodHandler handler = methods.get(method); + if (handler != null) { + return handler.handle(this, args); + } + throw new UnsupportedOperationException(String.valueOf(method)); + } +} diff --git a/api/src/test/java/org/bukkit/util/BoundingBoxTest.java b/api/src/test/java/org/bukkit/util/BoundingBoxTest.java new file mode 100644 index 000000000..1332aa265 --- /dev/null +++ b/api/src/test/java/org/bukkit/util/BoundingBoxTest.java @@ -0,0 +1,206 @@ +package org.bukkit.util; + +import static org.hamcrest.Matchers.*; +import static org.junit.Assert.*; + +import java.util.Map; + +import org.bukkit.Location; +import org.bukkit.block.BlockFace; +import org.junit.Test; + +public class BoundingBoxTest { + + private static final double delta = 1.0 / 1000000; + + @Test + public void testConstruction() { + BoundingBox expected = new BoundingBox(-1, -1, -1, 1, 2, 3); + assertThat(expected.getMin(), is(new Vector(-1, -1, -1))); + assertThat(expected.getMax(), is(new Vector(1, 2, 3))); + assertThat(expected.getCenter(), is(new Vector(0.0D, 0.5D, 1.0D))); + assertThat(expected.getWidthX(), is(2.0D)); + assertThat(expected.getHeight(), is(3.0D)); + assertThat(expected.getWidthZ(), is(4.0D)); + assertThat(expected.getVolume(), is(24.0D)); + + assertThat(BoundingBox.of(new Vector(-1, -1, -1), new Vector(1, 2, 3)), is(expected)); + assertThat(BoundingBox.of(new Vector(1, 2, 3), new Vector(-1, -1, -1)), is(expected)); + assertThat(BoundingBox.of(new Location(null, -1, -1, -1), new Location(null, 1, 2, 3)), is(expected)); + assertThat(BoundingBox.of(new Vector(0.0D, 0.5D, 1.0D), 1.0D, 1.5D, 2.0D), is(expected)); + assertThat(BoundingBox.of(new Location(null, 0.0D, 0.5D, 1.0D), 1.0D, 1.5D, 2.0D), is(expected)); + } + + @Test + public void testContains() { + BoundingBox aabb = new BoundingBox(-1, -1, -1, 1, 2, 3); + assertThat(aabb.contains(-0.5D, 0.0D, 0.5D), is(true)); + assertThat(aabb.contains(-1.0D, -1.0D, -1.0D), is(true)); + assertThat(aabb.contains(1.0D, 2.0D, 3.0D), is(false)); + assertThat(aabb.contains(-1.0D, 1.0D, 4.0D), is(false)); + assertThat(aabb.contains(new Vector(-0.5D, 0.0D, 0.5D)), is(true)); + + assertThat(aabb.contains(new BoundingBox(-0.5D, -0.5D, -0.5D, 0.5D, 1.0D, 2.0D)), is(true)); + assertThat(aabb.contains(aabb), is(true)); + assertThat(aabb.contains(new BoundingBox(-1, -1, -1, 1, 1, 3)), is(true)); + assertThat(aabb.contains(new BoundingBox(-2, -1, -1, 1, 2, 3)), is(false)); + assertThat(aabb.contains(new Vector(-0.5D, -0.5D, -0.5D), new Vector(0.5D, 1.0D, 2.0D)), is(true)); + } + + @Test + public void testOverlaps() { + BoundingBox aabb = new BoundingBox(-1, -1, -1, 1, 2, 3); + assertThat(aabb.contains(aabb), is(true)); + assertThat(aabb.overlaps(new BoundingBox(-2, -2, -2, 0, 0, 0)), is(true)); + assertThat(aabb.overlaps(new BoundingBox(0.5D, 1.5D, 2.5D, 1, 2, 3)), is(true)); + assertThat(aabb.overlaps(new BoundingBox(0.5D, 1.5D, 2.5D, 2, 3, 4)), is(true)); + assertThat(aabb.overlaps(new BoundingBox(-2, -2, -2, -1, -1, -1)), is(false)); + assertThat(aabb.overlaps(new BoundingBox(1, 2, 3, 2, 3, 4)), is(false)); + assertThat(aabb.overlaps(new Vector(0.5D, 1.5D, 2.5D), new Vector(1, 2, 3)), is(true)); + } + + @Test + public void testDegenerate() { + BoundingBox aabb = new BoundingBox(0, 0, 0, 0, 0, 0); + assertThat(aabb.getWidthX(), is(0.0D)); + assertThat(aabb.getHeight(), is(0.0D)); + assertThat(aabb.getWidthZ(), is(0.0D)); + assertThat(aabb.getVolume(), is(0.0D)); + } + + @Test + public void testShift() { + BoundingBox aabb = new BoundingBox(0, 0, 0, 1, 1, 1); + assertThat(aabb.clone().shift(1, 2, 3), is(new BoundingBox(1, 2, 3, 2, 3, 4))); + assertThat(aabb.clone().shift(-1, -2, -3), is(new BoundingBox(-1, -2, -3, 0, -1, -2))); + assertThat(aabb.clone().shift(new Vector(1, 2, 3)), is(new BoundingBox(1, 2, 3, 2, 3, 4))); + assertThat(aabb.clone().shift(new Location(null, 1, 2, 3)), is(new BoundingBox(1, 2, 3, 2, 3, 4))); + } + + @Test + public void testUnion() { + BoundingBox aabb1 = new BoundingBox(0, 0, 0, 1, 1, 1); + assertThat(aabb1.clone().union(new BoundingBox(-2, -2, -2, -1, -1, -1)), is(new BoundingBox(-2, -2, -2, 1, 1, 1))); + assertThat(aabb1.clone().union(1, 2, 3), is(new BoundingBox(0, 0, 0, 1, 2, 3))); + assertThat(aabb1.clone().union(new Vector(1, 2, 3)), is(new BoundingBox(0, 0, 0, 1, 2, 3))); + assertThat(aabb1.clone().union(new Location(null, 1, 2, 3)), is(new BoundingBox(0, 0, 0, 1, 2, 3))); + } + + @Test + public void testIntersection() { + BoundingBox aabb = new BoundingBox(-1, -1, -1, 1, 2, 3); + assertThat(aabb.clone().intersection(new BoundingBox(-2, -2, -2, 4, 4, 4)), is(aabb)); + assertThat(aabb.clone().intersection(new BoundingBox(-2, -2, -2, 1, 1, 1)), is(new BoundingBox(-1, -1, -1, 1, 1, 1))); + } + + @Test + public void testExpansion() { + BoundingBox aabb = new BoundingBox(0, 0, 0, 2, 2, 2); + assertThat(aabb.clone().expand(1, 2, 3, 1, 2, 3), is(new BoundingBox(-1, -2, -3, 3, 4, 5))); + assertThat(aabb.clone().expand(-1, -2, -3, 1, 2, 3), is(new BoundingBox(1, 2, 3, 3, 4, 5))); + assertThat(aabb.clone().expand(1, 2, 3, -1, -2, -3), is(new BoundingBox(-1, -2, -3, 1, 0, -1))); + assertThat(aabb.clone().expand(-1, -2, -3, -0.5D, -0.5, -3), is(new BoundingBox(1, 1.5D, 1, 1.5D, 1.5D, 1))); + + assertThat(aabb.clone().expand(1, 2, 3), is(new BoundingBox(-1, -2, -3, 3, 4, 5))); + assertThat(aabb.clone().expand(-0.1, -0.5, -2), is(new BoundingBox(0.1D, 0.5D, 1, 1.9D, 1.5D, 1))); + assertThat(aabb.clone().expand(new Vector(1, 2, 3)), is(new BoundingBox(-1, -2, -3, 3, 4, 5))); + + assertThat(aabb.clone().expand(1), is(new BoundingBox(-1, -1, -1, 3, 3, 3))); + assertThat(aabb.clone().expand(-0.5D), is(new BoundingBox(0.5D, 0.5D, 0.5D, 1.5D, 1.5D, 1.5D))); + + assertThat(aabb.clone().expand(1, 0, 0, 0.5D), is(new BoundingBox(0, 0, 0, 2.5D, 2, 2))); + assertThat(aabb.clone().expand(1, 0, 0, -0.5D), is(new BoundingBox(0, 0, 0, 1.5D, 2, 2))); + assertThat(aabb.clone().expand(-1, 0, 0, 0.5D), is(new BoundingBox(-0.5D, 0, 0, 2, 2, 2))); + assertThat(aabb.clone().expand(-1, 0, 0, -0.5D), is(new BoundingBox(0.5D, 0, 0, 2, 2, 2))); + + assertThat(aabb.clone().expand(0, 1, 0, 0.5D), is(new BoundingBox(0, 0, 0, 2, 2.5D, 2))); + assertThat(aabb.clone().expand(0, 1, 0, -0.5D), is(new BoundingBox(0, 0, 0, 2, 1.5D, 2))); + assertThat(aabb.clone().expand(0, -1, 0, 0.5D), is(new BoundingBox(0, -0.5D, 0, 2, 2, 2))); + assertThat(aabb.clone().expand(0, -1, 0, -0.5D), is(new BoundingBox(0, 0.5D, 0, 2, 2, 2))); + + assertThat(aabb.clone().expand(0, 0, 1, 0.5D), is(new BoundingBox(0, 0, 0, 2, 2, 2.5D))); + assertThat(aabb.clone().expand(0, 0, 1, -0.5D), is(new BoundingBox(0, 0, 0, 2, 2, 1.5D))); + assertThat(aabb.clone().expand(0, 0, -1, 0.5D), is(new BoundingBox(0, 0, -0.5D, 2, 2, 2))); + assertThat(aabb.clone().expand(0, 0, -1, -0.5D), is(new BoundingBox(0, 0, 0.5D, 2, 2, 2))); + + assertThat(aabb.clone().expand(new Vector(1, 0, 0), 0.5D), is(new BoundingBox(0, 0, 0, 2.5D, 2, 2))); + assertThat(aabb.clone().expand(BlockFace.EAST, 0.5D), is(new BoundingBox(0, 0, 0, 2.5D, 2, 2))); + assertThat(aabb.clone().expand(BlockFace.NORTH_NORTH_WEST, 1.0D), is(aabb.clone().expand(BlockFace.NORTH_NORTH_WEST.getDirection(), 1.0D))); + assertThat(aabb.clone().expand(BlockFace.SELF, 1.0D), is(aabb)); + + BoundingBox expanded = aabb.clone().expand(BlockFace.NORTH_WEST, 1.0D); + assertThat(expanded.getWidthX(), is(closeTo(aabb.getWidthX() + Math.sqrt(0.5D), delta))); + assertThat(expanded.getWidthZ(), is(closeTo(aabb.getWidthZ() + Math.sqrt(0.5D), delta))); + assertThat(expanded.getHeight(), is(aabb.getHeight())); + + assertThat(aabb.clone().expandDirectional(1, 2, 3), is(new BoundingBox(0, 0, 0, 3, 4, 5))); + assertThat(aabb.clone().expandDirectional(-1, -2, -3), is(new BoundingBox(-1, -2, -3, 2, 2, 2))); + assertThat(aabb.clone().expandDirectional(new Vector(1, 2, 3)), is(new BoundingBox(0, 0, 0, 3, 4, 5))); + } + + @Test + public void testRayTrace() { + BoundingBox aabb = new BoundingBox(-1, -1, -1, 1, 1, 1); + + assertThat(aabb.rayTrace(new Vector(-2, 0, 0), new Vector(1, 0, 0), 10), + is(new RayTraceResult(new Vector(-1, 0, 0), BlockFace.WEST))); + assertThat(aabb.rayTrace(new Vector(2, 0, 0), new Vector(-1, 0, 0), 10), + is(new RayTraceResult(new Vector(1, 0, 0), BlockFace.EAST))); + + assertThat(aabb.rayTrace(new Vector(0, -2, 0), new Vector(0, 1, 0), 10), + is(new RayTraceResult(new Vector(0, -1, 0), BlockFace.DOWN))); + assertThat(aabb.rayTrace(new Vector(0, 2, 0), new Vector(0, -1, 0), 10), + is(new RayTraceResult(new Vector(0, 1, 0), BlockFace.UP))); + + assertThat(aabb.rayTrace(new Vector(0, 0, -2), new Vector(0, 0, 1), 10), + is(new RayTraceResult(new Vector(0, 0, -1), BlockFace.NORTH))); + assertThat(aabb.rayTrace(new Vector(0, 0, 2), new Vector(0, 0, -1), 10), + is(new RayTraceResult(new Vector(0, 0, 1), BlockFace.SOUTH))); + + assertThat(aabb.rayTrace(new Vector(0, 0, 0), new Vector(1, 0, 0), 10), + is(new RayTraceResult(new Vector(1, 0, 0), BlockFace.EAST))); + assertThat(aabb.rayTrace(new Vector(0, 0, 0), new Vector(-1, 0, 0), 10), + is(new RayTraceResult(new Vector(-1, 0, 0), BlockFace.WEST))); + + assertThat(aabb.rayTrace(new Vector(0, 0, 0), new Vector(0, 1, 0), 10), + is(new RayTraceResult(new Vector(0, 1, 0), BlockFace.UP))); + assertThat(aabb.rayTrace(new Vector(0, 0, 0), new Vector(0, -1, 0), 10), + is(new RayTraceResult(new Vector(0, -1, 0), BlockFace.DOWN))); + + assertThat(aabb.rayTrace(new Vector(0, 0, 0), new Vector(0, 0, 1), 10), + is(new RayTraceResult(new Vector(0, 0, 1), BlockFace.SOUTH))); + assertThat(aabb.rayTrace(new Vector(0, 0, 0), new Vector(0, 0, -1), 10), + is(new RayTraceResult(new Vector(0, 0, -1), BlockFace.NORTH))); + + assertThat(aabb.rayTrace(new Vector(-2, -2, -2), new Vector(1, 0, 0), 10), is(nullValue())); + assertThat(aabb.rayTrace(new Vector(-2, -2, -2), new Vector(0, 1, 0), 10), is(nullValue())); + assertThat(aabb.rayTrace(new Vector(-2, -2, -2), new Vector(0, 0, 1), 10), is(nullValue())); + + assertThat(aabb.rayTrace(new Vector(0, 0, -3), new Vector(1, 0, 1), 10), is(nullValue())); + assertThat(aabb.rayTrace(new Vector(0, 0, -2), new Vector(1, 0, 2), 10), + is(new RayTraceResult(new Vector(0.5D, 0, -1), BlockFace.NORTH))); + + // corner/edge hits yield unspecified block face: + assertThat(aabb.rayTrace(new Vector(2, 2, 2), new Vector(-1, -1, -1), 10), + anyOf(is(new RayTraceResult(new Vector(1, 1, 1), BlockFace.EAST)), + is(new RayTraceResult(new Vector(1, 1, 1), BlockFace.UP)), + is(new RayTraceResult(new Vector(1, 1, 1), BlockFace.SOUTH)))); + + assertThat(aabb.rayTrace(new Vector(-2, -2, -2), new Vector(1, 1, 1), 10), + anyOf(is(new RayTraceResult(new Vector(-1, -1, -1), BlockFace.WEST)), + is(new RayTraceResult(new Vector(-1, -1, -1), BlockFace.DOWN)), + is(new RayTraceResult(new Vector(-1, -1, -1), BlockFace.NORTH)))); + + assertThat(aabb.rayTrace(new Vector(0, 0, -2), new Vector(1, 0, 1), 10), + anyOf(is(new RayTraceResult(new Vector(1, 0, -1), BlockFace.NORTH)), + is(new RayTraceResult(new Vector(1, 0, -1), BlockFace.EAST)))); + } + + @Test + public void testSerialization() { + BoundingBox aabb = new BoundingBox(-1, -1, -1, 1, 1, 1); + Map serialized = aabb.serialize(); + BoundingBox deserialized = BoundingBox.deserialize(serialized); + assertThat(deserialized, is(aabb)); + } +} diff --git a/api/src/test/java/org/bukkit/util/StringUtilStartsWithTest.java b/api/src/test/java/org/bukkit/util/StringUtilStartsWithTest.java new file mode 100644 index 000000000..b85a2b21e --- /dev/null +++ b/api/src/test/java/org/bukkit/util/StringUtilStartsWithTest.java @@ -0,0 +1,86 @@ +package org.bukkit.util; + +import static org.hamcrest.Matchers.*; +import static org.junit.Assert.*; + +import java.util.List; + +import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.Parameterized; +import org.junit.runners.Parameterized.Parameter; +import org.junit.runners.Parameterized.Parameters; + +import com.google.common.collect.ImmutableList; + +@RunWith(Parameterized.class) +public class StringUtilStartsWithTest { + + @Parameters(name= "{index}: {0} startsWith {1} == {2}") + public static List data() { + return ImmutableList.of( + new Object[] { + "Apple", + "Apples", + false + }, + new Object[] { + "Apples", + "Apple", + true + }, + new Object[] { + "Apple", + "Apple", + true + }, + new Object[] { + "Apple", + "apples", + false + }, + new Object[] { + "apple", + "Apples", + false + }, + new Object[] { + "apple", + "apples", + false + }, + new Object[] { + "Apples", + "apPL", + true + }, + new Object[] { + "123456789", + "1234567", + true + }, + new Object[] { + "", + "", + true + }, + new Object[] { + "string", + "", + true + } + ); + } + + @Parameter(0) + public String base; + @Parameter(1) + public String prefix; + @Parameter(2) + public boolean result; + + @Test + public void testFor() { + assertThat(base + " starts with " + prefix + ": " + result, StringUtil.startsWithIgnoreCase(base, prefix), is(result)); + } +} diff --git a/api/src/test/java/org/bukkit/util/StringUtilTest.java b/api/src/test/java/org/bukkit/util/StringUtilTest.java new file mode 100644 index 000000000..9c8444c1c --- /dev/null +++ b/api/src/test/java/org/bukkit/util/StringUtilTest.java @@ -0,0 +1,61 @@ +package org.bukkit.util; + +import static org.hamcrest.Matchers.*; +import static org.junit.Assert.*; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; + +import org.junit.Test; + +import com.google.common.collect.ImmutableList; + +public class StringUtilTest { + + @Test(expected=NullPointerException.class) + public void nullPrefixTest() { + StringUtil.startsWithIgnoreCase("String", null); + } + + @Test(expected=IllegalArgumentException.class) + public void nullStringTest() { + StringUtil.startsWithIgnoreCase(null, "String"); + } + + @Test(expected=IllegalArgumentException.class) + public void nullCollectionTest() { + StringUtil.copyPartialMatches("Token", ImmutableList.of(), null); + } + + @Test(expected=IllegalArgumentException.class) + public void nullIterableTest() { + StringUtil.copyPartialMatches("Token", null, new ArrayList()); + } + + @Test(expected=IllegalArgumentException.class) + public void nullTokenTest() { + StringUtil.copyPartialMatches(null, ImmutableList.of(), new ArrayList()); + } + + @Test + public void copyTokenTest() { + String token = "ab"; + Iterable original = ImmutableList.of("ab12", "aC561", "AB5195", "Ab76", "", "a"); + List expected = ImmutableList.of("ab12", "AB5195", "Ab76" ); + List list = new ArrayList(); + assertThat(StringUtil.copyPartialMatches(token, original, list), is(expected)); + assertThat(StringUtil.copyPartialMatches(token, original, list), is(sameInstance(list))); + assertThat(list.size(), is(expected.size() * 2)); + } + + @Test(expected=UnsupportedOperationException.class) + public void copyUnsupportedTest() { + StringUtil.copyPartialMatches("token", ImmutableList.of("token1", "token2"), ImmutableList.of()); + } + + @Test(expected=IllegalArgumentException.class) + public void copyNullTest() { + StringUtil.copyPartialMatches("token", Arrays.asList("token1", "token2", null), new ArrayList()); + } +} diff --git a/api/src/test/java/org/bukkit/util/VectorTest.java b/api/src/test/java/org/bukkit/util/VectorTest.java new file mode 100644 index 000000000..6170f28c6 --- /dev/null +++ b/api/src/test/java/org/bukkit/util/VectorTest.java @@ -0,0 +1,117 @@ +package org.bukkit.util; + +import org.bukkit.block.BlockFace; +import org.junit.Test; +import static org.junit.Assert.*; + +public class VectorTest { + + @Test + public void testNormalisedVectors() { + assertFalse(new Vector(1, 0, 0).multiply(1.1).isNormalized()); + + assertTrue(new Vector(1, 1, 1).normalize().isNormalized()); + assertTrue(new Vector(1, 0, 0).isNormalized()); + } + + @Test(expected = IllegalArgumentException.class) + public void testNullVectorAxis() { + new Vector(0, 1, 0).rotateAroundAxis(null, Math.PI); + } + + @Test + public void testBypassingAxisVector() { + new Vector(0, 1, 0).rotateAroundNonUnitAxis(new Vector(1, 1, 1), Math.PI); // This will result some weird result, but there may be some use for it for some people + } + + @Test + public void testResizeAxis() { + Vector axis = new Vector(0, 10, 0); + assertEquals(BlockFace.EAST.getDirection().rotateAroundAxis(axis, Math.PI * 0.5), BlockFace.NORTH.getDirection()); + } + + /** + * As west to east are the x axis in Minecraft, rotating around it from up + * should lead to up -> south -> down -> north. + */ + @Test + public void testRotationAroundX() { + Vector vector = BlockFace.UP.getDirection(); + assertEquals(BlockFace.SOUTH.getDirection(), vector.clone().rotateAroundX(Math.PI * 0.5)); // Should rotate around x axis for 1/4 of a circle. + assertEquals(BlockFace.DOWN.getDirection(), vector.clone().rotateAroundX(Math.PI * 1.0)); // Should rotate around x axis for 2/4 of a circle. + assertEquals(BlockFace.NORTH.getDirection(), vector.clone().rotateAroundX(Math.PI * 1.5)); // Should rotate around x axis for 3/4 of a circle. + assertEquals(BlockFace.UP.getDirection(), vector.clone().rotateAroundX(Math.PI * 2.0)); // Should rotate around x axis for 4/4 of a circle. + } + + /** + * As up to down are the y axis in Minecraft, rotating around it from up + * should lead to east (positive x) -> south -> west -> north. + */ + @Test + public void testRotationAroundY() { + Vector vector = BlockFace.EAST.getDirection(); + assertEquals(BlockFace.NORTH.getDirection(), vector.clone().rotateAroundY(Math.PI * 0.5)); // Should rotate around x axis for 1/4 of a circle. + assertEquals(BlockFace.WEST.getDirection(), vector.clone().rotateAroundY(Math.PI * 1.0)); // Should rotate around x axis for 2/4 of a circle. + assertEquals(BlockFace.SOUTH.getDirection(), vector.clone().rotateAroundY(Math.PI * 1.5)); // Should rotate around x axis for 3/4 of a circle. + assertEquals(BlockFace.EAST.getDirection(), vector.clone().rotateAroundY(Math.PI * 2.0)); // Should rotate around x axis for 4/4 of a circle. + } + + /** + * As up to down are the y axis in Minecraft, rotating around it from up + * should lead to east (positive x) -> south -> west -> north. + */ + @Test + public void testRotationAroundYUsingCustomAxis() { + Vector vector = BlockFace.EAST.getDirection(); + Vector axis = BlockFace.UP.getDirection(); + assertEquals(BlockFace.NORTH.getDirection(), vector.clone().rotateAroundAxis(axis, Math.PI * 0.5)); // Should rotate around x axis for 1/4 of a circle. + assertEquals(BlockFace.WEST.getDirection(), vector.clone().rotateAroundAxis(axis, Math.PI * 1.0)); // Should rotate around x axis for 2/4 of a circle. + assertEquals(BlockFace.SOUTH.getDirection(), vector.clone().rotateAroundAxis(axis, Math.PI * 1.5)); // Should rotate around x axis for 3/4 of a circle. + assertEquals(BlockFace.EAST.getDirection(), vector.clone().rotateAroundAxis(axis, Math.PI * 2.0)); // Should rotate around x axis for 4/4 of a circle. + } + + /** + * As south to north are the z axis in Minecraft, rotating around it from up + * should lead to up (positive y) -> west -> down -> east. + */ + @Test + public void testRotationAroundZ() { + Vector vector = BlockFace.UP.getDirection(); + assertEquals(BlockFace.WEST.getDirection(), vector.clone().rotateAroundZ(Math.PI * 0.5)); // Should rotate around x axis for 1/4 of a circle. + assertEquals(BlockFace.DOWN.getDirection(), vector.clone().rotateAroundZ(Math.PI * 1.0)); // Should rotate around x axis for 2/4 of a circle. + assertEquals(BlockFace.EAST.getDirection(), vector.clone().rotateAroundZ(Math.PI * 1.5)); // Should rotate around x axis for 3/4 of a circle. + assertEquals(BlockFace.UP.getDirection(), vector.clone().rotateAroundZ(Math.PI * 2.0)); // Should rotate around x axis for 4/4 of a circle. + } + + @Test + public void testRotationAroundAxis() { + Vector axis = new Vector(1, 0, 1); + assertEquals(new Vector(0, 1, 0).rotateAroundNonUnitAxis(axis, Math.PI * 0.5), new Vector(-1, 0, 1)); + } + + @Test + public void testRotationAroundAxisNonUnit() { + Vector axis = new Vector(0, 2, 0); + Vector v = BlockFace.EAST.getDirection(); + + assertEquals(v.rotateAroundNonUnitAxis(axis, Math.PI * 0.5), BlockFace.NORTH.getDirection().multiply(2)); + } + + /** + * This will be a bit tricky to prove so we will try to simply see if the + * vectors have correct angle to each other This will work with any two + * vectors, as the rotation will keep the angle the same. + */ + @Test + public void testRotationAroundCustomAngle() { + Vector axis = new Vector(-30, 1, 2000).normalize(); + Vector v = new Vector(53, 12, 98); + + float a = v.angle(axis); + double stepSize = Math.PI / 21; + for (int i = 0; i < 42; i++) { + v.rotateAroundAxis(axis, stepSize); + assertEquals(a, v.angle(axis), Vector.getEpsilon()); + } + } +} diff --git a/api/src/test/java/org/bukkit/util/io/BukkitObjectStreamTest.java b/api/src/test/java/org/bukkit/util/io/BukkitObjectStreamTest.java new file mode 100644 index 000000000..d0af4a015 --- /dev/null +++ b/api/src/test/java/org/bukkit/util/io/BukkitObjectStreamTest.java @@ -0,0 +1,173 @@ +package org.bukkit.util.io; + +import static org.hamcrest.Matchers.*; +import static org.junit.Assert.*; + +import java.io.ByteArrayInputStream; +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.io.ObjectInputStream; +import java.io.ObjectOutputStream; +import java.util.List; + +import org.bukkit.Color; +import org.bukkit.FireworkEffect; +import org.bukkit.FireworkEffect.Type; +import org.bukkit.configuration.serialization.ConfigurationSerializable; +import org.bukkit.util.Vector; +import org.junit.Ignore; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.Parameterized; +import org.junit.runners.Parameterized.Parameter; +import org.junit.runners.Parameterized.Parameters; +import org.yaml.snakeyaml.external.biz.base64Coder.Base64Coder; + +import com.google.common.collect.ImmutableList; + +@RunWith(Parameterized.class) +public class BukkitObjectStreamTest { + + @Parameters(name= "{index}: {0}") + public static List data() { + return ImmutableList.of( + new Object[] { + Color.class.getName(), + "rO0ABXNyADZjb20uZ29vZ2xlLmNvbW1vbi5jb2xsZWN0LkltbXV0YWJsZUxpc3QkU2VyaWFsaXplZEZvcm0AAAAAAAAAAAIAAVsACGVsZW1lbnRzdAATW0xqYXZhL2xhbmcvT2JqZWN0O3hwdXIAE1tMamF2YS5sYW5nLk9iamVjdDuQzlifEHMpbAIAAHhwAAAABXNyABpvcmcuYnVra2l0LnV0aWwuaW8uV3JhcHBlcvJQR+zxEm8FAgABTAADbWFwdAAPTGphdmEvdXRpbC9NYXA7eHBzcgA1Y29tLmdvb2dsZS5jb21tb24uY29sbGVjdC5JbW11dGFibGVNYXAkU2VyaWFsaXplZEZvcm0AAAAAAAAAAAIAAlsABGtleXNxAH4AAVsABnZhbHVlc3EAfgABeHB1cQB+AAMAAAAEdAACPT10AANSRUR0AARCTFVFdAAFR1JFRU51cQB+AAMAAAAEdAAFQ29sb3JzcgARamF2YS5sYW5nLkludGVnZXIS4qCk94GHOAIAAUkABXZhbHVleHIAEGphdmEubGFuZy5OdW1iZXKGrJUdC5TgiwIAAHhwAAAA/3NxAH4AEQAAAP9zcQB+ABEAAAD/c3EAfgAFc3EAfgAIdXEAfgADAAAABHEAfgALcQB+AAxxAH4ADXEAfgAOdXEAfgADAAAABHEAfgAQc3EAfgARAAAAAHNxAH4AEQAAAIBzcQB+ABEAAACAc3EAfgAFc3EAfgAIdXEAfgADAAAABHEAfgALcQB+AAxxAH4ADXEAfgAOdXEAfgADAAAABHEAfgAQc3EAfgARAAAAgHNxAH4AEQAAAIBxAH4AGnNxAH4ABXNxAH4ACHVxAH4AAwAAAARxAH4AC3EAfgAMcQB+AA1xAH4ADnVxAH4AAwAAAARxAH4AEHNxAH4AEQAAAP9xAH4AGnEAfgAac3EAfgAFc3EAfgAIdXEAfgADAAAABHEAfgALcQB+AAxxAH4ADXEAfgAOdXEAfgADAAAABHEAfgAQc3EAfgARAAAA/3EAfgAac3EAfgARAAAApQ==", + ImmutableList.of( + Color.WHITE, + Color.TEAL, + Color.PURPLE, + Color.RED, + Color.ORANGE + ) + }, + new Object[] { + FireworkEffect.class.getName(), + "rO0ABXNyADZjb20uZ29vZ2xlLmNvbW1vbi5jb2xsZWN0LkltbXV0YWJsZUxpc3QkU2VyaWFsaXplZEZvcm0AAAAAAAAAAAIAAVsACGVsZW1lbnRzdAATW0xqYXZhL2xhbmcvT2JqZWN0O3hwdXIAE1tMamF2YS5sYW5nLk9iamVjdDuQzlifEHMpbAIAAHhwAAAAA3NyABpvcmcuYnVra2l0LnV0aWwuaW8uV3JhcHBlcvJQR+zxEm8FAgABTAADbWFwdAAPTGphdmEvdXRpbC9NYXA7eHBzcgA1Y29tLmdvb2dsZS5jb21tb24uY29sbGVjdC5JbW11dGFibGVNYXAkU2VyaWFsaXplZEZvcm0AAAAAAAAAAAIAAlsABGtleXNxAH4AAVsABnZhbHVlc3EAfgABeHB1cQB+AAMAAAAGdAACPT10AAdmbGlja2VydAAFdHJhaWx0AAZjb2xvcnN0AAtmYWRlLWNvbG9yc3QABHR5cGV1cQB+AAMAAAAGdAAIRmlyZXdvcmtzcgARamF2YS5sYW5nLkJvb2xlYW7NIHKA1Zz67gIAAVoABXZhbHVleHABc3EAfgATAHNxAH4AAHVxAH4AAwAAAAJzcQB+AAVzcQB+AAh1cQB+AAMAAAAEcQB+AAt0AANSRUR0AARCTFVFdAAFR1JFRU51cQB+AAMAAAAEdAAFQ29sb3JzcgARamF2YS5sYW5nLkludGVnZXIS4qCk94GHOAIAAUkABXZhbHVleHIAEGphdmEubGFuZy5OdW1iZXKGrJUdC5TgiwIAAHhwAAAAAHEAfgAicQB+ACJzcQB+AAVzcQB+AAh1cQB+AAMAAAAEcQB+AAtxAH4AG3EAfgAccQB+AB11cQB+AAMAAAAEcQB+AB9zcQB+ACAAAADAc3EAfgAgAAAAwHNxAH4AIAAAAMBzcQB+AAB1cQB+AAMAAAABc3EAfgAFc3EAfgAIdXEAfgADAAAABHEAfgALcQB+ABtxAH4AHHEAfgAddXEAfgADAAAABHEAfgAfc3EAfgAgAAAA/3NxAH4AIAAAAP9zcQB+ACAAAAD/dAAKQkFMTF9MQVJHRXNxAH4ABXNxAH4ACHVxAH4AAwAAAAZxAH4AC3EAfgAMcQB+AA1xAH4ADnEAfgAPcQB+ABB1cQB+AAMAAAAGcQB+ABJxAH4AFXEAfgAVc3EAfgAAdXEAfgADAAAAAXNxAH4ABXNxAH4ACHVxAH4AAwAAAARxAH4AC3EAfgAbcQB+ABxxAH4AHXVxAH4AAwAAAARxAH4AH3EAfgAic3EAfgAgAAAAgHEAfgAic3EAfgAAdXEAfgADAAAAAHQABEJBTExzcQB+AAVzcQB+AAh1cQB+AAMAAAAGcQB+AAtxAH4ADHEAfgANcQB+AA5xAH4AD3EAfgAQdXEAfgADAAAABnEAfgAScQB+ABRxAH4AFHNxAH4AAHVxAH4AAwAAAAFzcQB+AAVzcQB+AAh1cQB+AAMAAAAEcQB+AAtxAH4AG3EAfgAccQB+AB11cQB+AAMAAAAEcQB+AB9zcQB+ACAAAACAcQB+ACJxAH4AInEAfgA/dAAHQ1JFRVBFUg==", + ImmutableList.of( + FireworkEffect.builder() + .withColor(Color.BLACK, Color.SILVER) + .with(Type.BALL_LARGE) + .withFade(Color.WHITE) + .withFlicker() + .build(), + FireworkEffect.builder() + .withColor(Color.NAVY) + .build(), + FireworkEffect.builder() + .withColor(Color.MAROON) + .withTrail() + .withFlicker() + .with(Type.CREEPER) + .build() + ), + }, + new Object[] { + Vector.class.getName(), + "rO0ABXNyADZjb20uZ29vZ2xlLmNvbW1vbi5jb2xsZWN0LkltbXV0YWJsZUxpc3QkU2VyaWFsaXplZEZvcm0AAAAAAAAAAAIAAVsACGVsZW1lbnRzdAATW0xqYXZhL2xhbmcvT2JqZWN0O3hwdXIAE1tMamF2YS5sYW5nLk9iamVjdDuQzlifEHMpbAIAAHhwAAAABHNyABpvcmcuYnVra2l0LnV0aWwuaW8uV3JhcHBlcvJQR+zxEm8FAgABTAADbWFwdAAPTGphdmEvdXRpbC9NYXA7eHBzcgA1Y29tLmdvb2dsZS5jb21tb24uY29sbGVjdC5JbW11dGFibGVNYXAkU2VyaWFsaXplZEZvcm0AAAAAAAAAAAIAAlsABGtleXNxAH4AAVsABnZhbHVlc3EAfgABeHB1cQB+AAMAAAAEdAACPT10AAF4dAABeXQAAXp1cQB+AAMAAAAEdAAGVmVjdG9yc3IAEGphdmEubGFuZy5Eb3VibGWAs8JKKWv7BAIAAUQABXZhbHVleHIAEGphdmEubGFuZy5OdW1iZXKGrJUdC5TgiwIAAHhwAAAAAAAAAABzcQB+ABEAAAAAAAAAAHNxAH4AEQAAAAAAAAAAc3EAfgAFc3EAfgAIdXEAfgADAAAABHEAfgALcQB+AAxxAH4ADXEAfgAOdXEAfgADAAAABHEAfgAQc3EAfgARQIOFwo9cKPZzcQB+ABFAtCKcKPXCj3NxAH4AEUBzrpeNT987c3EAfgAFc3EAfgAIdXEAfgADAAAABHEAfgALcQB+AAxxAH4ADXEAfgAOdXEAfgADAAAABHEAfgAQc3EAfgARwEQTMzMzMzNzcQB+ABFASYAAAAAAAHNxAH4AEcCjqG3UQTVUc3EAfgAFc3EAfgAIdXEAfgADAAAABHEAfgALcQB+AAxxAH4ADXEAfgAOdXEAfgADAAAABHEAfgAQc3EAfgARQd/////AAABzcQB+ABHB4AAAAAAAAHNxAH4AEQAAAAAAAAAA", + ImmutableList.of( + new Vector(0, 0, 0), + new Vector(624.72, 5154.61, 314.912), + new Vector(-40.15, 51, -2516.21451), + new Vector(Integer.MAX_VALUE, Integer.MIN_VALUE, 0) + ) + }); + } + + @Parameter(0) + public String className; + + @Parameter(1) + public String preEncoded; + + @Parameter(2) + public List object; + + @Test + public void checkSerlialization() throws Throwable { + // If this test fails, you may start your trek to debug by commenting the '@Ignore' on the next method + // (and of course, you would read those comments too) + final ByteArrayOutputStream out = new ByteArrayOutputStream(); + ObjectOutputStream oos = null; + try { + oos = new BukkitObjectOutputStream(out); + oos.writeObject(object); + } finally { + if (oos != null) { + try { + oos.close(); + } catch (IOException e) { + } + } + } + + final byte[] preEncodedArray = Base64Coder.decode(preEncoded); + + final Object readBack; + final Object preEncoded; + + ObjectInputStream ois = null; + ObjectInputStream preois = null; + try { + ByteArrayInputStream in = new ByteArrayInputStream(out.toByteArray()); + ByteArrayInputStream preIn = new ByteArrayInputStream(preEncodedArray); + ois = new BukkitObjectInputStream(in); + preois = new BukkitObjectInputStream(preIn); + + readBack = ois.readObject(); + preEncoded = preois.readObject(); + } finally { + if (ois != null) { + try { + ois.close(); + } catch (IOException ex) { + } + } + if (preois != null) { + try { + preois.close(); + } catch (IOException ex) { + } + } + } + + assertThat(object, is(readBack)); + assertThat(object, is(preEncoded)); + } + + @Ignore + @Test + public void preEncoded() throws Throwable { + // This test is placed in the case that a necessary change is made to change the encoding format + // Just remove the ignore (or run manually) and it'll give you the new pre-encoded values + + // It really does not matter if the encoded array is different per system (hence why this test is set to not run), + // as long as all systems can deserialize it. + + // The entire reason the pre-encoded string was added is to make a build (test) fail if someone accidentally makes it not backward-compatible + + final ByteArrayOutputStream out = new ByteArrayOutputStream(); + ObjectOutputStream oos = null; + try { + oos = new BukkitObjectOutputStream(out); + oos.writeObject(object); + oos.flush(); + } finally { + if (oos != null) { + try { + oos.close(); + } catch (IOException e) { + } + } + } + + final String string = new String(Base64Coder.encode(out.toByteArray())); + try { + assertThat(preEncoded, is(string)); + } catch (Throwable t) { + System.out.println(className + ": \"" + string + "\""); + throw t; + } + } +} diff --git a/scripts/build.sh b/scripts/build.sh index c6a7ca9e2..6370f2515 100644 --- a/scripts/build.sh +++ b/scripts/build.sh @@ -31,8 +31,8 @@ echo "[Akarin] Ready to build" cd "$paperbasedir" if [ "$2" == "--fast" ] || [ "$3" == "--fast" ] || [ "$4" == "--fast" ]; then echo "[Akarin] Test and repatch has been skipped" - \cp -rf "$basedir/src/api/main" "$paperbasedir/Paper-API/src/" - \cp -rf "$basedir/src/api/pom.xml" "$paperbasedir/Paper-API/" + \cp -rf "$basedir/api/src/main" "$paperbasedir/Paper-API/src/" + \cp -rf "$basedir/api/src/pom.xml" "$paperbasedir/Paper-API/" \cp -rf "$basedir/src" "$paperbasedir/Paper-Server/" \cp -rf "$basedir/pom.xml" "$paperbasedir/Paper-Server/" mvn clean install -DskipTests @@ -40,20 +40,22 @@ echo "[Akarin] Ready to build" rm -rf Paper-API/src rm -rf Paper-Server/src ./paper patch - \cp -rf "$basedir/src/api/main" "$paperbasedir/Paper-API/src/" - \cp -rf "$basedir/src/api/pom.xml" "$paperbasedir/Paper-API/" + \cp -rf "$basedir/api/src/main" "$paperbasedir/Paper-API/src/" + \cp -rf "$basedir/api/src/pom.xml" "$paperbasedir/Paper-API/" \cp -rf "$basedir/src" "$paperbasedir/Paper-Server/" \cp -rf "$basedir/pom.xml" "$paperbasedir/Paper-Server/" mvn clean install -DskipTests fi - minecraftversion=$(cat "$paperworkdir/BuildData/info.json" | grep minecraftVersion | cut -d '"' -f 4) + minecraftversion=$(cat "$paperworkdir/BuildData/info.json" | grep minecraftVersion | cut -d '"' -f 4) rawjar="$paperbasedir/Paper-Server/target/akarin-$minecraftversion.jar" \cp -rf "$rawjar" "$basedir/akarin-$minecraftversion.jar" + rawapi="$paperbasedir/Paper-API/target/akarin-api-1.13.2-R0.1-SNAPSHOT.jar" + \cp -rf "$rawapi" "$basedir/akarin-api-1.13.2-R0.1-SNAPSHOT.jar" echo "" echo "[Akarin] Build successful" - echo "[Akarin] Migrated final jar to $basedir/akarin-$minecraftversion.jar" + echo "[Akarin] Migrated the final jar to $basedir/" ) )