diff --git a/CHANGELOG/kelp-v0.3.0.md b/CHANGELOG/kelp-v0.3.0.md new file mode 100644 index 00000000..74e531f0 --- /dev/null +++ b/CHANGELOG/kelp-v0.3.0.md @@ -0,0 +1,37 @@ +# v0.3.0 +> Release date: 27.02.2021 + +**The world update**: +* Add custom library to for world and location management: + * Add `KelpWorld` class replacing the normal `World` class by bukkit. It still does not offer all methods of a default bukkit world, but offers integration with other kelp world classes. + * Add `KelpLocation` offering a replacement for the normal bukkit `Location`. It offers some more methods for vector calculation for example. + * Add `KelpChunk`, which is a replacement for the normal bukkit `Chunk`. On top of the normal bukkit methods, it offers methods to work with neighbouring chunks. + * Add `KelpBlock` + * All the mentioned classes port methods from newer versions to older versions for example bone meal application for `KelpBlock` or checking whether a `KelpChunk` is a slime chunk. More method ports will follow. + * Add some util methods used by the kelp world classes: + * `WorldType` expresses whether a world is overworld, nether or the end + * `CardinalDirection` expresses in which direction a player or relative block is facing. It simplifies the work wit `yaw` + * `ExplosionPower` is basically just a wrapper for a float, but it contains some default values for default minecraft explosion types, which makes the code more readable. Instead of `4F`, you would write `ExplosionPower.TNT` + * Add functionality to force load chunks +* The `KelpPlayer`, `KelpEntity`, `KelpNpc` class as well as the particle library are now using the new location system instead of the normal bukkit system. +* Add `KelpDummyPlugin` which can be used to emulate a plugin (`KelpApplication`) when developing core features where providing a plugin instance is needed. +* Fix some material sub ids of the above mentioned ones. +* Add `MathUtils` class providing some useful mathematical functions +* When tagging an item of type `AIR`, the method does not return `null` anymore but the original item stack without a tag. This should avoid unexpected NPEs in the future. +* Documentation improvements +* Fix #45: Item description of `KelpItem` was not rendered. + + + + + + + + + + + + + + + diff --git a/README.md b/README.md index 89ae06cc..9f8785ba 100644 --- a/README.md +++ b/README.md @@ -125,13 +125,13 @@ There are version implementations for the following version implementations avai com.github.pxav.kelp core - 0.2.0 + 0.3.0 ``` ### Gradle ```shell script -compile group: 'com.github.pxav.kelp', name: 'core', version: '0.2.0' +compile group: 'com.github.pxav.kelp', name: 'core', version: '0.3.0' ``` ### Other build tools diff --git a/core/pom.xml b/core/pom.xml index 2d8fb227..f6ef3e8b 100644 --- a/core/pom.xml +++ b/core/pom.xml @@ -5,7 +5,7 @@ com.github.pxav.kelp parent - 0.2.0 + 0.3.0 4.0.0 @@ -139,7 +139,7 @@ org.spigotmc spigot-api - 1.16.1-R0.1-SNAPSHOT + 1.16.5-R0.1-SNAPSHOT provided @@ -158,7 +158,7 @@ org.bukkit craftbukkit - 1.14.4-R0.1-SNAPSHOT + 1.16.5-R0.1-SNAPSHOT provided diff --git a/core/src/main/java/de/pxav/kelp/core/KelpDummyPlugin.java b/core/src/main/java/de/pxav/kelp/core/KelpDummyPlugin.java new file mode 100644 index 00000000..1b659001 --- /dev/null +++ b/core/src/main/java/de/pxav/kelp/core/KelpDummyPlugin.java @@ -0,0 +1,18 @@ +package de.pxav.kelp.core; + +import de.pxav.kelp.core.application.KelpApplication; + +/** + * This class is used to pass a dummy plugin instance when developing + * inside the core module. + * + * For some things like {@link de.pxav.kelp.core.world.KelpChunk#addForceLoadFlag(Class)} you + * need to pass a {@link KelpApplication} class representing your plugin, but the kelp core + * obviously is no Kelp plugin, but a spigot plugin ({@link org.bukkit.plugin.java.JavaPlugin}). + * + * So if you ever run into such scenarios as a KelpCore developer, use this + * class. + * + * @author pxav + */ +public class KelpDummyPlugin extends KelpApplication {} diff --git a/core/src/main/java/de/pxav/kelp/core/KelpPlugin.java b/core/src/main/java/de/pxav/kelp/core/KelpPlugin.java index 660dd3ef..54bec763 100644 --- a/core/src/main/java/de/pxav/kelp/core/KelpPlugin.java +++ b/core/src/main/java/de/pxav/kelp/core/KelpPlugin.java @@ -35,7 +35,7 @@ * * @author pxav */ -@Plugin(name = "Kelp", version = "0.2.0") +@Plugin(name = "Kelp", version = "0.3.0") @Author("pxav") @Description("A cross version spigot framework.") @Singleton diff --git a/core/src/main/java/de/pxav/kelp/core/common/MathUtils.java b/core/src/main/java/de/pxav/kelp/core/common/MathUtils.java new file mode 100644 index 00000000..9d40cec1 --- /dev/null +++ b/core/src/main/java/de/pxav/kelp/core/common/MathUtils.java @@ -0,0 +1,56 @@ +package de.pxav.kelp.core.common; + +import java.util.concurrent.ThreadLocalRandom; + +/** + * A small collection of some mathematical utils for Kelp. + * This class is functional, so all methods can be accessed statically. + * + * @author pxav + */ +public class MathUtils { + + /** + * Checks if the given number x is even, which is + * equal to {@code x % 2 == 0} + * + * @param number The number to check. + * @return {@code true} if the number is even. + */ + public static boolean isEven(int number) { + return number % 2 == 0; + } + + /** + * Checks if the given number x is odd, which is + * equal to {@code x % 2 == 1} + * + * @param number The number to check. + * @return {@code true} if the number is odd. + */ + public static boolean isOdd(int number) { + return number % 2 != 0; + } + + /** + * Randomly calculates whether a certain chance has been + * fulfilled. If your {@code chance} is set to {@code 50}, + * there will be a 50% chance that this method will return + * {@code true}. If it is {@code 5}, then there will be a + * {@code 5} per cent chance that this method will return + * {@code true}. + * + * @param chance The chance to use for random calculation. + * @return Either true or false depending on your luck and the provided chance. + */ + public static boolean perCentChance(int chance) { + if (chance == 100) { + return true; + } + if (chance == 0) { + return false; + } + return ThreadLocalRandom.current().nextInt(0, 101) < chance; + } + +} diff --git a/core/src/main/java/de/pxav/kelp/core/entity/KelpEntity.java b/core/src/main/java/de/pxav/kelp/core/entity/KelpEntity.java index 7d6143fb..58ea851d 100644 --- a/core/src/main/java/de/pxav/kelp/core/entity/KelpEntity.java +++ b/core/src/main/java/de/pxav/kelp/core/entity/KelpEntity.java @@ -1,10 +1,10 @@ package de.pxav.kelp.core.entity; import de.pxav.kelp.core.entity.version.EntityVersionTemplate; -import org.bukkit.Bukkit; +import de.pxav.kelp.core.world.KelpLocation; +import de.pxav.kelp.core.world.KelpWorld; import org.bukkit.Location; import org.bukkit.Server; -import org.bukkit.World; import org.bukkit.entity.Entity; import org.bukkit.event.player.PlayerTeleportEvent; import org.bukkit.util.Vector; @@ -61,12 +61,21 @@ public KelpEntity entityType(KelpEntityType entityType) { return this; } - public Location getInitialLocation() { + public Location getInitialBukkitLocation() { return initialLocation; } - public KelpEntity initialLocation(Location currentLocation) { - this.initialLocation = currentLocation; + public KelpLocation getInitialLocation() { + return KelpLocation.from(initialLocation); + } + + public KelpEntity initialLocation(KelpLocation currentLocation) { + this.initialLocation = currentLocation.getBukkitLocation(); + return this; + } + + public KelpEntity initialLocation(Location location) { + this.initialLocation = location; return this; } @@ -104,7 +113,7 @@ public Entity toBukkitEntity() { * * @return The current location of the current entity. */ - public Location getLocation() { + public KelpLocation getLocation() { return entityVersionTemplate.getLocation(toBukkitEntity()); } @@ -162,8 +171,8 @@ public boolean isOnGround() { * * @return The world where the entity is currently located. */ - public World getWorld() { - return entityVersionTemplate.getWorld(toBukkitEntity()); + public KelpWorld getWorld() { + return KelpWorld.from(getLocation().getWorldName()); } /** @@ -183,7 +192,7 @@ public KelpEntity setRotation(float yaw, float pitch) { * * @param to The location you want the entity to be teleported to. */ - public KelpEntity teleport(Location to) { + public KelpEntity teleport(KelpLocation to) { entityVersionTemplate.teleport(toBukkitEntity(), to, PlayerTeleportEvent.TeleportCause.PLUGIN); return this; } @@ -198,42 +207,7 @@ public KelpEntity teleport(Location to) { * @param z The exact value of the location's z axis. */ public KelpEntity teleport(double x, double y, double z) { - World world = entityVersionTemplate.getWorld(toBukkitEntity()); - Location to = new Location(world, x, y, z); - teleport(to); - return this; - } - - /** - * Teleports the entity to the location at the given - * coordinates. As there is no world passed, this method - * will use the current world of the entity. - * - * @param x The block value of the location's x axis. - * @param y The block value of the location's y axis. - * @param z The block value of the location's z axis. - */ - public KelpEntity teleport(int x, int y, int z) { - World world = entityVersionTemplate.getWorld(toBukkitEntity()); - Location to = new Location(world, x, y, z); - teleport(to); - return this; - } - - /** - * Teleports the entity to the location at the given - * coordinates. As there is no world passed, this method - * will use the current world of the entity. - * - * @param x The block value of the location's x axis. - * @param y The block value of the location's y axis. - * @param z The block value of the location's z axis. - * @param yaw The yaw rotation of the entity. - * @param pitch The location's pitch - */ - public KelpEntity teleport(int x, int y, int z, float yaw, float pitch) { - World world = entityVersionTemplate.getWorld(toBukkitEntity()); - Location to = new Location(world, x, y, z, yaw, pitch); + KelpLocation to = KelpLocation.from(getWorld(), x, y, z); teleport(to); return this; } @@ -250,8 +224,7 @@ public KelpEntity teleport(int x, int y, int z, float yaw, float pitch) { * @param pitch The location's pitch */ public KelpEntity teleport(double x, double y, double z, float yaw, float pitch) { - World world = entityVersionTemplate.getWorld(toBukkitEntity()); - Location to = new Location(world, x, y, z, yaw, pitch); + KelpLocation to = KelpLocation.from(getWorld(), x, y, z, yaw, pitch); teleport(to); return this; } @@ -267,40 +240,8 @@ public KelpEntity teleport(double x, double y, double z, float yaw, float pitch) * @param yaw The yaw rotation of the entity. * @param pitch The location's pitch */ - public KelpEntity teleport(World world, double x, double y, double z, float yaw, float pitch) { - Location to = new Location(world, x, y, z, yaw, pitch); - teleport(to); - return this; - } - - /** - * Teleports the entity to the location at the given - * coordinates. - * - * @param world The world where the entity should be teleported to. - * @param x The block value of the location's x axis. - * @param y The block value of the location's y axis. - * @param z The block value of the location's z axis. - * @param yaw The yaw rotation of the entity. - * @param pitch The location's pitch - */ - public KelpEntity teleport(World world, int x, int y, int z, float yaw, float pitch) { - Location to = new Location(world, x, y, z, yaw, pitch); - teleport(to); - return this; - } - - /** - * Teleports the entity to the location at the given - * coordinates. - * - * @param world The world where the entity should be teleported to. - * @param x The block value of the location's x axis. - * @param y The block value of the location's y axis. - * @param z The block value of the location's z axis. - */ - public KelpEntity teleport(World world, int x, int y, int z) { - Location to = new Location(world, x, y, z); + public KelpEntity teleport(KelpWorld world, double x, double y, double z, float yaw, float pitch) { + KelpLocation to = KelpLocation.from(world, x, y, z, yaw, pitch); teleport(to); return this; } @@ -314,8 +255,8 @@ public KelpEntity teleport(World world, int x, int y, int z) { * @param y The exact value of the location's y axis. * @param z The exact value of the location's z axis. */ - public KelpEntity teleport(World world, double x, double y, double z) { - Location to = new Location(world, x, y, z); + public KelpEntity teleport(KelpWorld world, double x, double y, double z) { + KelpLocation to = KelpLocation.from(world, x, y, z); teleport(to); return this; } @@ -332,39 +273,7 @@ public KelpEntity teleport(World world, double x, double y, double z) { * @param pitch The location's pitch */ public KelpEntity teleport(String worldName, double x, double y, double z, float yaw, float pitch) { - Location to = new Location(Bukkit.getWorld(worldName), x, y, z, yaw, pitch); - teleport(to); - return this; - } - - /** - * Teleports the entity to the location at the given - * coordinates. - * - * @param worldName The name of the world where the entity should be teleported to. - * @param x The block value of the location's x axis. - * @param y The block value of the location's y axis. - * @param z The block value of the location's z axis. - * @param yaw The yaw rotation of the entity. - * @param pitch The location's pitch - */ - public KelpEntity teleport(String worldName, int x, int y, int z, float yaw, float pitch) { - Location to = new Location(Bukkit.getWorld(worldName), x, y, z, yaw, pitch); - teleport(to); - return this; - } - - /** - * Teleports the entity to the location at the given - * coordinates. - * - * @param worldName The name of the world where the entity should be teleported to. - * @param x The block value of the location's x axis. - * @param y The block value of the location's y axis. - * @param z The block value of the location's z axis. - */ - public KelpEntity teleport(String worldName, int x, int y, int z) { - Location to = new Location(Bukkit.getWorld(worldName), x, y, z); + KelpLocation to = KelpLocation.from(worldName, x, y, z, yaw, pitch); teleport(to); return this; } @@ -379,7 +288,7 @@ public KelpEntity teleport(String worldName, int x, int y, int z) { * @param z The exact value of the location's z axis. */ public KelpEntity teleport(String worldName, double x, double y, double z) { - Location to = new Location(Bukkit.getWorld(worldName), x, y, z); + KelpLocation to = KelpLocation.from(worldName, x, y, z); teleport(to); return this; } diff --git a/core/src/main/java/de/pxav/kelp/core/entity/LivingKelpEntity.java b/core/src/main/java/de/pxav/kelp/core/entity/LivingKelpEntity.java index e75b5c60..7dab04a2 100644 --- a/core/src/main/java/de/pxav/kelp/core/entity/LivingKelpEntity.java +++ b/core/src/main/java/de/pxav/kelp/core/entity/LivingKelpEntity.java @@ -2,6 +2,7 @@ import de.pxav.kelp.core.entity.version.EntityVersionTemplate; import de.pxav.kelp.core.entity.version.LivingEntityVersionTemplate; +import de.pxav.kelp.core.world.KelpLocation; import org.bukkit.Location; import org.bukkit.entity.LivingEntity; @@ -43,7 +44,7 @@ public LivingEntity toBukkitLivingEntity() { return bukkitLivingEntity; } - public Location getEyeLocation() { + public KelpLocation getEyeLocation() { return this.livingEntityVersionTemplate.getEyeLocation(bukkitLivingEntity); } diff --git a/core/src/main/java/de/pxav/kelp/core/entity/type/DroppedItemEntity.java b/core/src/main/java/de/pxav/kelp/core/entity/type/DroppedItemEntity.java index ca58ba1b..2b17d9d0 100644 --- a/core/src/main/java/de/pxav/kelp/core/entity/type/DroppedItemEntity.java +++ b/core/src/main/java/de/pxav/kelp/core/entity/type/DroppedItemEntity.java @@ -1,10 +1,13 @@ package de.pxav.kelp.core.entity.type; +import de.pxav.kelp.core.KelpPlugin; import de.pxav.kelp.core.entity.KelpEntity; import de.pxav.kelp.core.entity.KelpEntityType; +import de.pxav.kelp.core.entity.version.EntityTypeVersionTemplate; import de.pxav.kelp.core.entity.version.EntityVersionTemplate; import de.pxav.kelp.core.inventory.item.KelpItem; import org.bukkit.Location; +import org.bukkit.entity.Item; /** * A class description goes here. @@ -16,6 +19,10 @@ public class DroppedItemEntity extends KelpEntity { private KelpItem item; private ItemDropType itemDropType; + public static DroppedItemEntity from(Item item) { + return (DroppedItemEntity) KelpPlugin.getInjector().getInstance(EntityTypeVersionTemplate.class).getKelpEntity(item); + } + public DroppedItemEntity() {} public DroppedItemEntity(EntityVersionTemplate entityVersionTemplate, Object entity, int entityId, Location location, KelpItem item) { diff --git a/core/src/main/java/de/pxav/kelp/core/entity/type/ItemDropType.java b/core/src/main/java/de/pxav/kelp/core/entity/type/ItemDropType.java index 4799ce97..b79bd5ff 100644 --- a/core/src/main/java/de/pxav/kelp/core/entity/type/ItemDropType.java +++ b/core/src/main/java/de/pxav/kelp/core/entity/type/ItemDropType.java @@ -1,13 +1,27 @@ package de.pxav.kelp.core.entity.type; /** - * A class description goes here. + * This enum expresses how an item should be dropped. + * There are different modes an item can be dropped. * * @author pxav */ public enum ItemDropType { + /** + * Drops the item at the provided location without any + * random spread or offset. It is guaranteed that the item will + * land at the given location. + */ NORMAL, + + /** + * Drops the item with a natural random offset. That means + * the location you provide for an item to land at won't be the + * exact location where it spawns but minecraft will calculate a + * random location within a small radius and drop it there, which + * gives that natural-looking effect. + */ NATURAL } diff --git a/core/src/main/java/de/pxav/kelp/core/entity/version/EntityVersionTemplate.java b/core/src/main/java/de/pxav/kelp/core/entity/version/EntityVersionTemplate.java index 60731272..08435e61 100644 --- a/core/src/main/java/de/pxav/kelp/core/entity/version/EntityVersionTemplate.java +++ b/core/src/main/java/de/pxav/kelp/core/entity/version/EntityVersionTemplate.java @@ -4,6 +4,7 @@ import de.pxav.kelp.core.entity.KelpEntity; import de.pxav.kelp.core.version.KelpVersion; import de.pxav.kelp.core.version.SinceKelpVersion; +import de.pxav.kelp.core.world.KelpLocation; import org.bukkit.Location; import org.bukkit.Server; import org.bukkit.World; @@ -46,7 +47,7 @@ public abstract class EntityVersionTemplate { * @param entity The entity whose location you want to get. * @return The location of the given entity. */ - public abstract Location getLocation(Entity entity); + public abstract KelpLocation getLocation(Entity entity); /** * Sets the entity's velocity to the given vector. @@ -102,14 +103,6 @@ public abstract class EntityVersionTemplate { */ public abstract boolean isOnGround(Entity entity); - /** - * Gets the current world of the entity. - * - * @param entity The entity whose world you want to get. - * @return The world where the entity is currently located. - */ - public abstract World getWorld(Entity entity); - /** * Sets the rotation of the given entity. This does not * affect the location x, y and z axes. @@ -128,7 +121,7 @@ public abstract class EntityVersionTemplate { * @param teleportCause The cause for the teleportation. * @return {@code true} if the teleport action was successful. */ - public abstract boolean teleport(Entity entity, Location location, PlayerTeleportEvent.TeleportCause teleportCause); + public abstract boolean teleport(Entity entity, KelpLocation location, PlayerTeleportEvent.TeleportCause teleportCause); /** * Gets all entities within the given radius centered around diff --git a/core/src/main/java/de/pxav/kelp/core/entity/version/LivingEntityVersionTemplate.java b/core/src/main/java/de/pxav/kelp/core/entity/version/LivingEntityVersionTemplate.java index b7eba9e4..79427daf 100644 --- a/core/src/main/java/de/pxav/kelp/core/entity/version/LivingEntityVersionTemplate.java +++ b/core/src/main/java/de/pxav/kelp/core/entity/version/LivingEntityVersionTemplate.java @@ -1,6 +1,7 @@ package de.pxav.kelp.core.entity.version; import de.pxav.kelp.core.application.KelpVersionTemplate; +import de.pxav.kelp.core.world.KelpLocation; import org.bukkit.Location; import org.bukkit.entity.LivingEntity; @@ -12,6 +13,6 @@ @KelpVersionTemplate public abstract class LivingEntityVersionTemplate { - public abstract Location getEyeLocation(LivingEntity livingEntity); + public abstract KelpLocation getEyeLocation(LivingEntity livingEntity); } diff --git a/core/src/main/java/de/pxav/kelp/core/inventory/item/KelpItem.java b/core/src/main/java/de/pxav/kelp/core/inventory/item/KelpItem.java index 0d5d5e92..1d014a1a 100644 --- a/core/src/main/java/de/pxav/kelp/core/inventory/item/KelpItem.java +++ b/core/src/main/java/de/pxav/kelp/core/inventory/item/KelpItem.java @@ -298,6 +298,10 @@ public ItemStack getItemStack() { itemStack = itemVersionTemplate.setDisplayName(itemStack, displayName); } + if (this.itemDescription != null && !this.itemDescription.isEmpty()) { + itemVersionTemplate.setLore(itemStack, itemDescription); + } + // make the item unbreakable if needed. if (this.unbreakable) { itemStack = itemVersionTemplate.makeUnbreakable(itemStack); diff --git a/core/src/main/java/de/pxav/kelp/core/inventory/material/KelpMaterial.java b/core/src/main/java/de/pxav/kelp/core/inventory/material/KelpMaterial.java index b0d7ac2e..53704d77 100644 --- a/core/src/main/java/de/pxav/kelp/core/inventory/material/KelpMaterial.java +++ b/core/src/main/java/de/pxav/kelp/core/inventory/material/KelpMaterial.java @@ -1,6 +1,8 @@ package de.pxav.kelp.core.inventory.material; +import de.pxav.kelp.core.KelpPlugin; import de.pxav.kelp.core.version.KelpVersion; +import org.bukkit.Material; import java.util.ArrayList; import java.util.Collection; @@ -930,7 +932,7 @@ public enum KelpMaterial { LAPIS_BLOCK(KelpVersion.MC_1_8_0), LAPIS_LAZULI(KelpVersion.MC_1_8_0), LAPIS_ORE(KelpVersion.MC_1_8_0), - LARGE_FERN(KelpVersion.MC_1_8_0), + LARGE_FERN_LOWER(KelpVersion.MC_1_8_0), LAVA(KelpVersion.MC_1_8_0), LAVA_BUCKET(KelpVersion.MC_1_8_0), LEAD(KelpVersion.MC_1_8_0), @@ -1135,6 +1137,27 @@ public KelpVersion since() { return since; } + public static KelpMaterial from(Material bukkitMaterial) { + MaterialRepository repository = KelpPlugin.getInjector().getInstance(MaterialRepository.class); + return repository.getKelpMaterial(bukkitMaterial.toString()); + } + + public static KelpMaterial from(Material bukkitMaterial, int subId) { + MaterialRepository repository = KelpPlugin.getInjector().getInstance(MaterialRepository.class); + return repository.getKelpMaterial(bukkitMaterial.toString(), subId); + } + + public static MaterialContainer convert(KelpMaterial kelpMaterial) { + MaterialRepository repository = KelpPlugin.getInjector().getInstance(MaterialRepository.class); + return repository.getBukkitMaterial(kelpMaterial); + } + + public static Material convertUnsafe(KelpMaterial kelpMaterial) { + MaterialRepository repository = KelpPlugin.getInjector().getInstance(MaterialRepository.class); + String[] materialData = repository.getMaterial(kelpMaterial).split(":"); + return Material.valueOf(materialData[0]); + } + /** * Returns a collection of materials included in newer versions than * the given one. So if you input 1.12 for example, only materials diff --git a/core/src/main/java/de/pxav/kelp/core/inventory/version/ItemVersionTemplate.java b/core/src/main/java/de/pxav/kelp/core/inventory/version/ItemVersionTemplate.java index a0312c66..22b970b9 100644 --- a/core/src/main/java/de/pxav/kelp/core/inventory/version/ItemVersionTemplate.java +++ b/core/src/main/java/de/pxav/kelp/core/inventory/version/ItemVersionTemplate.java @@ -7,6 +7,8 @@ import org.bukkit.inventory.ItemFlag; import org.bukkit.inventory.ItemStack; +import java.util.List; + /** * A class description goes here. * @@ -23,7 +25,7 @@ public abstract class ItemVersionTemplate { public abstract ItemStack setDisplayName(ItemStack itemStack, String displayName); - public abstract ItemStack setLore(ItemStack itemStack, String itemLore); + public abstract ItemStack setLore(ItemStack itemStack, List itemLore); public abstract ItemStack makeUnbreakable(ItemStack itemStack); diff --git a/core/src/main/java/de/pxav/kelp/core/npc/KelpNpc.java b/core/src/main/java/de/pxav/kelp/core/npc/KelpNpc.java index 8a1293a5..0c642fec 100644 --- a/core/src/main/java/de/pxav/kelp/core/npc/KelpNpc.java +++ b/core/src/main/java/de/pxav/kelp/core/npc/KelpNpc.java @@ -12,6 +12,7 @@ import de.pxav.kelp.core.npc.activity.NpcActivity; import de.pxav.kelp.core.npc.version.NpcVersionTemplate; import de.pxav.kelp.core.player.KelpPlayer; +import de.pxav.kelp.core.world.KelpLocation; import org.bukkit.Bukkit; import org.bukkit.Location; @@ -34,7 +35,7 @@ public class KelpNpc { private KelpPlayer player; - private Location currentLocation; + private KelpLocation currentLocation; private KelpItem itemInHand; private int entityId; @@ -99,7 +100,7 @@ public static KelpNpc create() { ); } - public KelpNpc location(Location location) { + public KelpNpc location(KelpLocation location) { this.currentLocation = location; return this; } @@ -411,7 +412,7 @@ public KelpNpc onInteract(Consumer event) { return this; } - public KelpNpc sleep(Location bedLocation) { + public KelpNpc sleep(KelpLocation bedLocation) { this.sleeping = true; npcVersionTemplate.sleep(this, bedLocation); return this; @@ -441,7 +442,7 @@ public KelpNpc wakeUp() { * @param target The location where the NPC should look to. * @return An instance of the current NPC object. */ - public KelpNpc lookTo(Location target) { + public KelpNpc lookTo(KelpLocation target) { double xDiff = target.getX() - currentLocation.getX(); double yDiff = target.getY() - currentLocation.getY(); double zDiff = target.getZ() - currentLocation.getZ(); @@ -464,7 +465,7 @@ public void moveRelativeDistance(double x, double y, double z, float absoluteYaw npcVersionTemplate.moveRelativeDistance(this, player.getBukkitPlayer(), x, y, z, absoluteYaw, absolutePitch); } - public void moveTo(Location target) { + public void moveTo(KelpLocation target) { double x = target.getX() - currentLocation.getX(); double y = target.getY() - currentLocation.getY(); double z = target.getZ() - currentLocation.getZ(); @@ -472,7 +473,7 @@ public void moveTo(Location target) { this.location(target); } - public void teleport(Location location) { + public void teleport(KelpLocation location) { this.currentLocation = location; npcVersionTemplate.teleport(this, currentLocation); } @@ -789,7 +790,7 @@ public KelpNpcMeta getNpcMeta() { return npcMeta; } - public Location getLocation() { + public KelpLocation getLocation() { return currentLocation; } diff --git a/core/src/main/java/de/pxav/kelp/core/npc/activity/AutoSpawnActivity.java b/core/src/main/java/de/pxav/kelp/core/npc/activity/AutoSpawnActivity.java index 7e891c7e..cba43e30 100644 --- a/core/src/main/java/de/pxav/kelp/core/npc/activity/AutoSpawnActivity.java +++ b/core/src/main/java/de/pxav/kelp/core/npc/activity/AutoSpawnActivity.java @@ -2,6 +2,7 @@ import de.pxav.kelp.core.npc.KelpNpc; import de.pxav.kelp.core.player.KelpPlayer; +import de.pxav.kelp.core.world.KelpLocation; import org.bukkit.Location; public class AutoSpawnActivity extends NpcActivity { @@ -20,7 +21,7 @@ public AutoSpawnActivity distanceThreshold(double distanceThreshold) { @Override public void onTick(KelpNpc kelpNpc) { KelpPlayer player = kelpNpc.getPlayer(); - Location npcLocation = kelpNpc.getLocation(); + KelpLocation npcLocation = kelpNpc.getLocation(); if (player.getLocation().distanceSquared(npcLocation) <= (distanceThreshold * distanceThreshold) && !kelpNpc.isSpawned()) { diff --git a/core/src/main/java/de/pxav/kelp/core/npc/activity/LookToActivity.java b/core/src/main/java/de/pxav/kelp/core/npc/activity/LookToActivity.java index d800035f..cca8d6d7 100644 --- a/core/src/main/java/de/pxav/kelp/core/npc/activity/LookToActivity.java +++ b/core/src/main/java/de/pxav/kelp/core/npc/activity/LookToActivity.java @@ -1,19 +1,20 @@ package de.pxav.kelp.core.npc.activity; import de.pxav.kelp.core.npc.KelpNpc; +import de.pxav.kelp.core.world.KelpLocation; import org.bukkit.Location; import java.util.function.Supplier; public class LookToActivity extends NpcActivity { - private Supplier target; + private Supplier target; public static LookToActivity create() { return new LookToActivity(); } - public LookToActivity target(Supplier target) { + public LookToActivity target(Supplier target) { this.target = target; return this; } diff --git a/core/src/main/java/de/pxav/kelp/core/npc/activity/WalkToDirectionActivity.java b/core/src/main/java/de/pxav/kelp/core/npc/activity/WalkToDirectionActivity.java index 6cf62d8a..7631e21a 100644 --- a/core/src/main/java/de/pxav/kelp/core/npc/activity/WalkToDirectionActivity.java +++ b/core/src/main/java/de/pxav/kelp/core/npc/activity/WalkToDirectionActivity.java @@ -2,6 +2,7 @@ import de.pxav.kelp.core.npc.KelpNpc; import de.pxav.kelp.core.npc.MovementSpeed; +import de.pxav.kelp.core.world.KelpLocation; import org.bukkit.Location; import org.bukkit.util.Vector; @@ -10,7 +11,7 @@ public class WalkToDirectionActivity extends NpcActivity { private Vector direction; private double index = .01; - private Location startLocation; - private Location target; + private KelpLocation startLocation; + private KelpLocation target; private boolean lookToTarget = true; private boolean lastTick = false; @@ -19,7 +20,7 @@ public static WalkToTargetActivity create() { return new WalkToTargetActivity(); } - public WalkToTargetActivity target(Location target) { + public WalkToTargetActivity target(KelpLocation target) { this.target = target; return this; } @@ -47,14 +48,13 @@ public void onTick(KelpNpc kelpNpc) { } direction.multiply(index); - Location newLocation = startLocation.clone().add(direction); + KelpLocation newLocation = startLocation.clone().add(direction); kelpNpc.moveTo(newLocation); if (this.lookToTarget) { kelpNpc.lookTo(target); } - direction.normalize(); if (kelpNpc.getLocation().distance(target) < 1) { diff --git a/core/src/main/java/de/pxav/kelp/core/npc/version/NpcVersionTemplate.java b/core/src/main/java/de/pxav/kelp/core/npc/version/NpcVersionTemplate.java index d6312a4a..f66f5304 100644 --- a/core/src/main/java/de/pxav/kelp/core/npc/version/NpcVersionTemplate.java +++ b/core/src/main/java/de/pxav/kelp/core/npc/version/NpcVersionTemplate.java @@ -5,6 +5,7 @@ import de.pxav.kelp.core.npc.KelpNpc; import de.pxav.kelp.core.npc.KelpNpcMeta; import de.pxav.kelp.core.npc.NpcAnimation; +import de.pxav.kelp.core.world.KelpLocation; import org.bukkit.Location; import org.bukkit.entity.Player; @@ -52,7 +53,7 @@ public abstract void moveRelativeDistance(KelpNpc npc, float absoluteYaw, float absolutePitch); - public abstract void teleport(KelpNpc npc, Location location); + public abstract void teleport(KelpNpc npc, KelpLocation location); public abstract void updateCustomName(KelpNpc npc); @@ -62,7 +63,7 @@ public abstract void moveRelativeDistance(KelpNpc npc, public abstract void makeCorpse(KelpNpc npc); - public abstract void sleep(KelpNpc npc, Location bedLocation); + public abstract void sleep(KelpNpc npc, KelpLocation bedLocation); public abstract void wakeUp(KelpNpc npc); @@ -87,6 +88,7 @@ public abstract void moveRelativeDistance(KelpNpc npc, * this very player. */ public abstract void refreshMetadata(KelpNpc npc, Player player); + public abstract void updateTab(KelpNpc npc, @Nullable GameProfile gameProfile); } diff --git a/core/src/main/java/de/pxav/kelp/core/particle/effect/ParticleLineEffect.java b/core/src/main/java/de/pxav/kelp/core/particle/effect/ParticleLineEffect.java index b5633d99..bf476c74 100644 --- a/core/src/main/java/de/pxav/kelp/core/particle/effect/ParticleLineEffect.java +++ b/core/src/main/java/de/pxav/kelp/core/particle/effect/ParticleLineEffect.java @@ -2,6 +2,7 @@ import de.pxav.kelp.core.particle.type.ParticleType; import de.pxav.kelp.core.player.KelpPlayer; +import de.pxav.kelp.core.world.KelpLocation; import org.bukkit.Location; import org.bukkit.util.Vector; @@ -15,8 +16,8 @@ public class ParticleLineEffect extends ParticleEffect { private ParticleType particleType; - private Location firstPoint; - private Location secondPoint; + private KelpLocation firstPoint; + private KelpLocation secondPoint; private double particleDensity; ParticleLineEffect(ParticleEffectRepository particleEffectRepository) { @@ -28,12 +29,12 @@ public ParticleLineEffect particleType(ParticleType particleType) { return this; } - public ParticleLineEffect firstPoint(Location firstPoint) { + public ParticleLineEffect firstPoint(KelpLocation firstPoint) { this.firstPoint = firstPoint; return this; } - public ParticleLineEffect secondPoint(Location secondPoint) { + public ParticleLineEffect secondPoint(KelpLocation secondPoint) { this.secondPoint = secondPoint; return this; } @@ -50,7 +51,7 @@ public ParticleLineEffect changeLocationBy(double x, double y, double z) { @Override protected void playAnimationOnce(Collection player) { - Location firstPointBackup = firstPoint.clone(); + KelpLocation firstPointBackup = firstPoint.clone(); Vector line = secondPoint.clone().toVector().subtract(firstPointBackup.toVector()); double length = line.length(); diff --git a/core/src/main/java/de/pxav/kelp/core/player/KelpPlayer.java b/core/src/main/java/de/pxav/kelp/core/player/KelpPlayer.java index 82f2eabe..39b94a1c 100644 --- a/core/src/main/java/de/pxav/kelp/core/player/KelpPlayer.java +++ b/core/src/main/java/de/pxav/kelp/core/player/KelpPlayer.java @@ -1,6 +1,7 @@ package de.pxav.kelp.core.player; import com.google.common.base.Preconditions; +import de.pxav.kelp.core.KelpPlugin; import de.pxav.kelp.core.entity.KelpEntityType; import de.pxav.kelp.core.entity.LivingKelpEntity; import de.pxav.kelp.core.entity.version.EntityVersionTemplate; @@ -23,6 +24,7 @@ import de.pxav.kelp.core.sidebar.SidebarRepository; import de.pxav.kelp.core.sidebar.type.KelpSidebar; import de.pxav.kelp.core.sound.KelpSound; +import de.pxav.kelp.core.world.KelpLocation; import org.bukkit.Bukkit; import org.bukkit.Location; import org.bukkit.entity.Player; @@ -114,6 +116,23 @@ public KelpPlayer(Player bukkitPlayer, this.anvilPromptVersionTemplate = anvilPromptVersionTemplate; } + public static KelpPlayer from(UUID player) { + KelpPlayerRepository repository = KelpPlugin.getInjector().getInstance(KelpPlayerRepository.class); + return repository.getKelpPlayer(player); + } + + public static KelpPlayer from(String name) { + Player player = Bukkit.getPlayer(name); + if (player == null) { + return null; + } + return KelpPlayer.from(player); + } + + public static KelpPlayer from(Player player) { + return KelpPlayer.from(player.getUniqueId()); + } + public SignPrompt openSignPrompt() { return new SignPrompt(this.getBukkitPlayer(), this.signPromptVersionTemplate); } @@ -229,26 +248,26 @@ public KelpPlayer forceInventoryClose() { } public KelpPlayer playSound(KelpSound sound) { - playerVersionTemplate.playSound(bukkitPlayer, sound, playerVersionTemplate.getLocation(bukkitPlayer), 3, 0); + playerVersionTemplate.playSound(bukkitPlayer, sound, getLocation(), 3, 0); return this; } - public KelpPlayer playSound(KelpSound sound, Location location) { + public KelpPlayer playSound(KelpSound sound, KelpLocation location) { playerVersionTemplate.playSound(bukkitPlayer, sound, location, 3, 0); return this; } public KelpPlayer playSound(KelpSound sound, float volume) { - playerVersionTemplate.playSound(bukkitPlayer, sound, playerVersionTemplate.getLocation(bukkitPlayer), volume, 0); + playerVersionTemplate.playSound(bukkitPlayer, sound, getLocation(), volume, 0); return this; } public KelpPlayer playSound(KelpSound sound, float volume, float pitch) { - playerVersionTemplate.playSound(bukkitPlayer, sound, playerVersionTemplate.getLocation(bukkitPlayer), volume, pitch); + playerVersionTemplate.playSound(bukkitPlayer, sound, getLocation(), volume, pitch); return this; } - public KelpPlayer playSound(KelpSound sound, Location location, float volume, float pitch) { + public KelpPlayer playSound(KelpSound sound, KelpLocation location, float volume, float pitch) { playerVersionTemplate.playSound(bukkitPlayer, sound, location, volume, pitch); return this; } @@ -313,7 +332,7 @@ public KelpPlayer spawnParticle(ParticleType particleType, boolean longDistance, return this; } - public KelpPlayer spawnParticle(ParticleType particleType, boolean longDistance, Location location, float offsetX, float offsetY, float offsetZ, int count, float particleData, Object generalData) { + public KelpPlayer spawnParticle(ParticleType particleType, boolean longDistance, KelpLocation location, float offsetX, float offsetY, float offsetZ, int count, float particleData, Object generalData) { particleVersionTemplate.spawnParticle(this, particleType, longDistance, @@ -351,12 +370,6 @@ public String getName() { return bukkitPlayer.getName(); } -// @Override -// public KelpPlayer teleport(Location location) { -// playerVersionTemplate.teleport(bukkitPlayer, location); -// return this; -// } - public KelpPlayer setHealth(int health) { playerVersionTemplate.setHealth(bukkitPlayer, health); return this; @@ -408,7 +421,7 @@ public KelpPlayer setTabListName(String tabListName) { // TODO TAB LIST HEADER AND FOOTER - public KelpPlayer setCompassTarget(Location target) { + public KelpPlayer setCompassTarget(KelpLocation target) { playerVersionTemplate.setCompassTarget(bukkitPlayer, target); return this; } diff --git a/core/src/main/java/de/pxav/kelp/core/player/KelpPlayerRepository.java b/core/src/main/java/de/pxav/kelp/core/player/KelpPlayerRepository.java index 8ce8f7a4..4e0605f1 100644 --- a/core/src/main/java/de/pxav/kelp/core/player/KelpPlayerRepository.java +++ b/core/src/main/java/de/pxav/kelp/core/player/KelpPlayerRepository.java @@ -236,7 +236,7 @@ private KelpPlayer newKelpPlayerFrom(Player bukkitPlayer) { entityVersionTemplate, livingEntityVersionTemplate, playerVersionTemplate.getUniqueId(bukkitPlayer), - entityVersionTemplate.getLocation(bukkitPlayer), + entityVersionTemplate.getLocation(bukkitPlayer).getBukkitLocation(), entityVersionTemplate.getEntityId(bukkitPlayer) ); } diff --git a/core/src/main/java/de/pxav/kelp/core/player/PlayerVersionTemplate.java b/core/src/main/java/de/pxav/kelp/core/player/PlayerVersionTemplate.java index ff033cd2..7cb9938d 100644 --- a/core/src/main/java/de/pxav/kelp/core/player/PlayerVersionTemplate.java +++ b/core/src/main/java/de/pxav/kelp/core/player/PlayerVersionTemplate.java @@ -5,7 +5,7 @@ import de.pxav.kelp.core.player.bossbar.BossBarStyle; import de.pxav.kelp.core.player.message.InteractiveMessage; import de.pxav.kelp.core.sound.KelpSound; -import org.bukkit.*; +import de.pxav.kelp.core.world.KelpLocation; import org.bukkit.entity.Player; import org.bukkit.inventory.ItemStack; @@ -70,7 +70,7 @@ public abstract class PlayerVersionTemplate { * @param volume How loud the sound should be. * @param pitch How strong the sound should be pitched. */ - public abstract void playSound(Player player, KelpSound sound, Location location, float volume, float pitch); + public abstract void playSound(Player player, KelpSound sound, KelpLocation location, float volume, float pitch); /** * Sets the player's health. @@ -88,20 +88,6 @@ public abstract class PlayerVersionTemplate { */ public abstract UUID getUniqueId(Player player); - /** - * @param player The player whose location you want to get - * @return The location the player is currently at. - */ - public abstract Location getLocation(Player player); - - /** - * Teleports the player to the given location. - * - * @param player The player you want to teleport. - * @param location The location you want to teleport the player to. - */ - public abstract void teleport(Player player, Location location); - /** * Checks if the player is currently stuck in a cobweb. * @@ -250,7 +236,7 @@ public abstract class PlayerVersionTemplate { * @param player The player whose target location you want to update. * @param target The location, where the compass should point to. */ - public abstract void setCompassTarget(Player player, Location target); + public abstract void setCompassTarget(Player player, KelpLocation target); /** * Gets the compass target of the player. @@ -260,9 +246,7 @@ public abstract class PlayerVersionTemplate { * @param player The player whose compass target you want to set. * @return The target location of the player's compass. */ - public abstract Location getCompassTarget(Player player); - - // TODO send raw message, handle conversations (Bukkit conversation API) + public abstract KelpLocation getCompassTarget(Player player); /** * Kicks the given player from the server. @@ -875,7 +859,7 @@ public abstract class PlayerVersionTemplate { * @param player The player you want to get the bed spawn location of. * @return The spawn location, {@code null} if the player has not slept or location is invalid. */ - public abstract Location getBedSpawnLocation(Player player); + public abstract KelpLocation getBedSpawnLocation(Player player); /** * Sends the given player a message into their chat. diff --git a/core/src/main/java/de/pxav/kelp/core/world/KelpBlock.java b/core/src/main/java/de/pxav/kelp/core/world/KelpBlock.java new file mode 100644 index 00000000..2ec689d1 --- /dev/null +++ b/core/src/main/java/de/pxav/kelp/core/world/KelpBlock.java @@ -0,0 +1,318 @@ +package de.pxav.kelp.core.world; + +import de.pxav.kelp.core.KelpPlugin; +import de.pxav.kelp.core.inventory.material.KelpMaterial; +import de.pxav.kelp.core.world.util.CardinalDirection; +import de.pxav.kelp.core.world.version.BlockVersionTemplate; +import org.bukkit.block.Block; +import org.bukkit.block.BlockFace; +import org.bukkit.util.Vector; + +/** + * This class represents a block of a world. A block is the smallest unit + * a world consists of. Blocks are grouped together in 16x16 areas of the + * world (-> {@link KelpChunk}), which can be loaded and unloaded for + * optimal performance usage. A block can have a {@link KelpMaterial} + * which defines its hardness, collision and other properties. + * + * @author pxav + */ +public class KelpBlock { + + private Block bukkitBlock; + private BlockVersionTemplate versionTemplate; + + KelpBlock(Block bukkitBlock, BlockVersionTemplate versionTemplate) { + this.bukkitBlock = bukkitBlock; + this.versionTemplate = versionTemplate; + } + + public static KelpBlock from(Block bukkitBlock) { + return new KelpBlock( + bukkitBlock, + KelpPlugin.getInjector().getInstance(BlockVersionTemplate.class) + ); + } + + /** + * Gets the {@link KelpWorld} this block is located in. + * + * @return The world this block is located in. + */ + public KelpWorld getWorld() { + return KelpWorld.from(getBukkitBlock().getWorld()); + } + + /** + * Gets the name of the world this block is located in. + * + * @return The name of the world this block is located in. + */ + public String getWorldName() { + return getBukkitBlock().getWorld().getName(); + } + + /** + * Gets the {@link KelpChunk} the given block is located in. + * + * @return The {@link KelpChunk} the given block is located in. + */ + public KelpChunk getChunk() { + return versionTemplate.getChunk(this); + } + + /** + * Gets the {@link KelpLocation} of the given block in its {@link KelpWorld}. + * + * @return The {@link KelpLocation} of this world. + */ + public KelpLocation getLocation() { + return versionTemplate.getLocation(this); + } + + /** + * Gets the block which is blow this block. This means it + * gets the block which has the same x and z coordinates, but a + * lower y coordinate. + * + * @return The block below this block. + */ + public KelpBlock getBlockBelow() { + return getLocation().subtractY(1).getBlock(); + } + + /** + * Gets the block which is above this block. This means it + * gets the block which has the same x and z coordinates, but a + * higher y coordinate. + * + * @return The block above this block. + */ + public KelpBlock getBlockAbove() { + return getLocation().addY(1).getBlock(); + } + + /** + * Gets the block which is south of this block in the same y-axis. + * + * @return The southern block of this block. + */ + public KelpBlock getSouthernBlock() { + return getLocation().addZ(1).getBlock(); + } + + /** + * Gets the block which is north of this block in the same y-axis. + * + * @return The northern block of this block. + */ + public KelpBlock getNorthernBlock() { + return getLocation().subtractZ(1).getBlock(); + } + + /** + * Gets the block which is west of this block in the same y-axis. + * + * @return The western block of this block. + */ + public KelpBlock getWesternBlock() { + return getLocation().subtractX(1).getBlock(); + } + + /** + * Gets the block which is east of this block in the same y-axis. + * + * @return The eastern block of this block. + */ + public KelpBlock getEasternBlock() { + return getLocation().addX(1).getBlock(); + } + + /** + * Gets the block which is north east of this block in the same y-axis. + * + * @return The north eastern block of this block. + */ + public KelpBlock getNorthEasternBlock() { + return getLocation().add(1, 0, -1).getBlock(); + } + + /** + * Gets the block which is north west of this block in the same y-axis. + * + * @return The north western block of this block. + */ + public KelpBlock getNorthWesternBlock() { + return getLocation().add(-1, 0, -1).getBlock(); + } + + /** + * Gets the block which is south west of this block in the same y-axis. + * + * @return The south western block of this block. + */ + public KelpBlock getSouthWesternBlock() { + return getLocation().add(-1, 0, 1).getBlock(); + } + + /** + * Gets the block which is south east of this block in the same y-axis. + * + * @return The south eastern block of this block. + */ + public KelpBlock getSouthEasternBlock() { + return getLocation().add(1, 0, 1).getBlock(); + } + + /** + * Gets the block relative to this block in a specific + * {@link CardinalDirection}. + * + * @param direction The direction of the block you want to get. + * @return The block relative to this block in the given direction. + */ + public KelpBlock getRelative(CardinalDirection direction) { + switch (direction) { + case NORTH: + return getNorthernBlock(); + case NORTH_EAST: + return getNorthEasternBlock(); + case EAST: + return getEasternBlock(); + case SOUTH_EAST: + return getSouthEasternBlock(); + case SOUTH: + return getSouthernBlock(); + case SOUTH_WEST: + return getSouthWesternBlock(); + case WEST: + return getWesternBlock(); + case NORTH_WEST: + return getNorthWesternBlock(); + } + return null; + } + + /** + * Gets the block in front of this block relative to the given direction. + * + * @param direction A {@link Vector} indicating the direction + * to determine which block is actually in front. + * @return The block in front of this block. + */ + public KelpBlock getFrontBlock(Vector direction) { + KelpLocation location = getLocation(); + location.setDirection(direction); + + return getRelative(location.getCardinalDirection()); + } + + /** + * Gets the block behind this block relative to the given direction. + * + * @param direction A {@link Vector} indicating the direction + * to determine which block is actually behind. + * @return The block behind this block. + */ + public KelpBlock getBackBlock(Vector direction) { + KelpLocation location = getLocation(); + location.setDirection(direction.multiply(-1)); + + return getRelative(location.getCardinalDirection()); + } + + /** + * Gets the x-coordinate of this block in the world grid. + * + * @return The block's x-coordinate. + */ + public int getX() { + return bukkitBlock.getX(); + } + + /** + * Gets the y-coordinate of this block in the world grid. + * + * @return The block's y-coordinate. + */ + public int getY() { + return bukkitBlock.getY(); + } + + /** + * Gets the z-coordinate of this block in the world grid. + * + * @return The block's z-coordinate. + */ + public int getZ() { + return bukkitBlock.getZ(); + } + + /** + * Gets the material of the current block. The material defines + * the block's general behavior including its hardness or + * collision. + * + * @return The {@link KelpMaterial} the given block is made of. + */ + public KelpMaterial getMaterial() { + return versionTemplate.getMaterial(this); + } + + /** + * Sets the material of the given block to the given {@link KelpMaterial}. + * So if you had an {@link KelpMaterial#AIR} block for example, + * + * @param material The new material you want the block to have. + */ + public void setMaterial(KelpMaterial material) { + versionTemplate.setMaterial(this, material); + } + + /** + * Simulates the application of bone meal on a block. + * This means if the block is a sapling for example, it + * might grow to a tree or grass might spawn random flowers + * and so on. + * + * This method by default applies the bone meal on the upper side of a block. */ + public void applyBoneMeal() { + versionTemplate.applyBoneMeal(this, BlockFace.UP); + } + + /** + * Simulates the application of bone meal on a block. + * This means if the block is a sapling for example, it + * might grow to a tree or grass might spawn random flowers + * and so on. + * + * @param blockFace The face of the block to apply the bone meal on. + */ + public void applyBoneMeal(BlockFace blockFace) { + versionTemplate.applyBoneMeal(this, blockFace); + } + + /** + * Checks whether bone meal will have an effect on this block. + * This means it will check whether the block is a sapling + * or a plant that will grow if you right click it with bone + * meal. More information about that can be found + * in this wiki + * article + * + * @return {@code true} whether bone meal is applicable. + */ + public boolean canApplyBoneMeal() { + return versionTemplate.canApplyBoneMeal(this); + } + + /** + * Converts this block to a bukkit {@link Block}. + * + * @return The bukkit block instance of this block. + */ + public Block getBukkitBlock() { + return bukkitBlock; + } + +} diff --git a/core/src/main/java/de/pxav/kelp/core/world/KelpChunk.java b/core/src/main/java/de/pxav/kelp/core/world/KelpChunk.java new file mode 100644 index 00000000..9a35c3db --- /dev/null +++ b/core/src/main/java/de/pxav/kelp/core/world/KelpChunk.java @@ -0,0 +1,172 @@ +package de.pxav.kelp.core.world; + +import de.pxav.kelp.core.KelpPlugin; +import de.pxav.kelp.core.application.KelpApplication; +import de.pxav.kelp.core.player.KelpPlayer; +import de.pxav.kelp.core.world.version.ChunkVersionTemplate; +import org.bukkit.Chunk; + +import java.util.Collection; +import java.util.Set; + +public class KelpChunk { + + private Chunk bukkitChunk; + private ChunkVersionTemplate versionTemplate; + + KelpChunk(Chunk bukkitChunk, ChunkVersionTemplate versionTemplate) { + this.bukkitChunk = bukkitChunk; + this.versionTemplate = versionTemplate; + } + + public static KelpChunk from(Chunk bukkitChunk) { + return new KelpChunk( + bukkitChunk, + KelpPlugin.getInjector().getInstance(ChunkVersionTemplate.class) + ); + } + + public KelpWorld getWorld() { + return versionTemplate.getWorld(this); + } + + public int getX() { + return versionTemplate.getX(this); + } + + public int getZ() { + return versionTemplate.getZ(this); + } + + public KelpBlock getBlockAt(KelpLocation location) { + return versionTemplate.getBlockAt(this, location); + } + + public boolean contains(KelpLocation location) { + return this.versionTemplate.contains(this, location); + } + + public boolean contains(KelpBlock block) { + return this.versionTemplate.contains(this, block.getLocation()); + } + + public boolean isSlimeChunk() { + return versionTemplate.isSlimeChunk(this); + } + + public Collection getPlayers() { + return versionTemplate.getPlayers(this); + } + + public KelpLocation getCenter(int height) { + return KelpLocation.from(getWorld().getName(), getX() << 4, height, getZ() << 4).add(7, 0, 7); + } + + public KelpChunk getWestEnclosedChunk() { + return getNorthWesternBlock(10).getLocation().add(-2, 0, 2).getChunk(); + } + + public KelpChunk getNorthEnclosedChunk() { + return getNorthEasternBlock(10).getLocation().add(-2, 0, -2).getChunk(); + } + + public KelpChunk getEastEnclosedChunk() { + return getNorthEasternBlock(10).getLocation().add(2, 0, 2).getChunk(); + } + + public KelpChunk getSouthEnclosedChunk() { + return getSouthWesternBlock(10).getLocation().add(2, 0, 2).getChunk(); + } + + public KelpChunk getSouthWestEnclosedChunk() { + return getSouthWesternBlock(10).getLocation().add(-2, 0, 2).getChunk(); + } + + public KelpChunk getNorthWestEnclosedChunk() { + return getNorthWesternBlock(10).getLocation().add(-2, 0, -2).getChunk(); + } + + public KelpChunk getNorthEastEnclosedChunk() { + return getNorthEasternBlock(10).getLocation().add(2, 0, -2).getChunk(); + } + + public KelpChunk getSouthEastEnclosedChunk() { + return getSouthEasternBlock(10).getLocation().add(2, 0, 2).getChunk(); + } + + public KelpBlock getNorthEasternBlock(int height) { + KelpLocation location = KelpLocation.from( + getWorld().getName(), + getX() * 16 + 15, + height, + getZ() * 16 + ); + return getWorld().getBlockAt(location); + } + + public KelpBlock getNorthWesternBlock(int height) { + KelpLocation location = KelpLocation.from( + getWorld().getName(), + getX() * 16, + height, + getZ() * 16 + ); + return getWorld().getBlockAt(location); + } + + public KelpBlock getSouthEasternBlock(int height) { + KelpLocation location = KelpLocation.from( + getWorld().getName(), + getX() * 16 + 15, + height, + getZ() * 16 + 15 + ); + return getWorld().getBlockAt(location); + } + + public KelpBlock getSouthWesternBlock(int height) { + KelpLocation location = KelpLocation.from( + getWorld().getName(), + getX() * 16, + height, + getZ() * 16 + 15 + ); + return getWorld().getBlockAt(location); + } + + public void load() { + versionTemplate.load(this); + } + + public void unload() { + versionTemplate.unload(this); + } + + public boolean isLoaded() { + return versionTemplate.isLoaded(this); + } + + public void addForceLoadFlag(Class plugin) { + versionTemplate.addForceLoadFlag(this, plugin); + } + + public void removeForceLoadFlag(Class plugin) { + versionTemplate.removeForceLoadFlag(this, plugin); + } + + public Set> getForceLoadingPlugins() { + return versionTemplate.getForceLoadFlagPlugins(this); + } + + public boolean equals(KelpChunk compareTo) { + return compareTo.getX() == getX() && compareTo.getZ() == getZ(); + } + + public boolean equals(Chunk compareTo) { + return compareTo.getX() == getX() && compareTo.getZ() == getZ(); + } + + public Chunk getBukkitChunk() { + return bukkitChunk; + } +} diff --git a/core/src/main/java/de/pxav/kelp/core/world/KelpLocation.java b/core/src/main/java/de/pxav/kelp/core/world/KelpLocation.java new file mode 100644 index 00000000..47d0f749 --- /dev/null +++ b/core/src/main/java/de/pxav/kelp/core/world/KelpLocation.java @@ -0,0 +1,899 @@ +package de.pxav.kelp.core.world; + +import com.google.common.base.Preconditions; +import de.pxav.kelp.core.world.util.CardinalDirection; +import org.bukkit.Bukkit; +import org.bukkit.Location; +import org.bukkit.World; +import org.bukkit.util.NumberConversions; +import org.bukkit.util.Vector; + +import java.io.Serializable; + +/** + * A {@code KelpLocation} is a point in a 3D-world. It basically is a replacement + * for the default {@link Location} of bukkit with some additional useful methods. + * + * You can easily convert those location types: + * - From a bukkit location to a KelpLocation: {@link #from(Location)} + * - From a KelpLocation to a bukkit location: {@link #getBukkitLocation()} + * + * @author pxav + */ +public class KelpLocation implements Serializable, Cloneable { + + // name of the location's world + // + // There is no need to store the entire KelpWorld object, + // which is why it's not saved for performance reasons. + private String worldName = "world"; + + // axis of the location + private double x = 0.0D; + private double y = 0.0D; + private double z = 0.0D; + private float yaw = 0.0F; + private float pitch = 0.0F; + + public static KelpLocation from(Location location) { + KelpLocation kelpLocation = new KelpLocation(); + + kelpLocation.setX(location.getX()); + kelpLocation.setY(location.getY()); + kelpLocation.setZ(location.getZ()); + kelpLocation.setYaw(location.getYaw()); + kelpLocation.setPitch(location.getPitch()); + + kelpLocation.setWorldName(location.getWorld().getName()); + + return kelpLocation; + } + + public static KelpLocation from(String worldName, double x, double y, double z) { + Preconditions.checkNotNull(worldName); + KelpLocation location = new KelpLocation(); + + location.setX(x); + location.setY(y); + location.setZ(z); + location.setWorldName(worldName); + + return location; + } + + public static KelpLocation from(String worldName, double x, double y, double z, float yaw, float pitch) { + Preconditions.checkNotNull(worldName); + KelpLocation location = new KelpLocation(); + + location.setX(x); + location.setY(y); + location.setZ(z); + location.setYaw(yaw); + location.setPitch(pitch); + location.setWorldName(worldName); + + return location; + } + + public static KelpLocation from(KelpWorld world, double x, double y, double z, float yaw, float pitch) { + return from(world.getName(), x, y, z, yaw, pitch); + } + + public static KelpLocation from(KelpWorld world, double x, double y, double z) { + return from(world.getName(), x, y, z); + } + + public static KelpLocation from(World world, double x, double y, double z, float yaw, float pitch) { + return from(world.getName(), x, y, z, yaw, pitch); + } + + public static KelpLocation from(World world, double x, double y, double z) { + return from(world.getName(), x, y, z); + } + + public static KelpLocation create() { + return new KelpLocation(); + } + + /** + * Gets the name of the world this location is valid for. + * + * @return The name of the world of this location. + */ + public String getWorldName() { + return worldName; + } + + /** + * Sets the name of the world of this location. + * Note that changing a location's world while keeping + * the same coordinates, might lead to unintended world generation + * when working with the location. This might cause lag to the server. + * + * @param worldName The name of the new world for this location. + */ + public void setWorldName(String worldName) { + this.worldName = worldName; + } + + /** + * Gets the locations exact value on the world's X-axis. + * + * @return The locations value on the X-axis. + */ + public double getX() { + return x; + } + + /** + * Sets the location's exact value on the world's X-axis. + * + * @param x The new value on the world's X-axis for the location. + */ + public void setX(double x) { + this.x = x; + } + + /** + * Gets the locations exact value on the world's Y-axis. + * + * @return The locations value on the Y-axis. + */ + public double getY() { + return y; + } + + /** + * Sets the location's exact value on the world's Y-axis (height). + * + * @param y The new value on the world's Y-axis for the location. + */ + public void setY(double y) { + this.y = y; + } + + /** + * Gets the locations exact value on the world's Z-axis. + * + * @return The locations value on the Z-axis. + */ + public double getZ() { + return z; + } + + /** + * Sets the location's exact value on the world's Y-axis. + * + * @param z The new value on the world's Y-axis for the location. + */ + public void setZ(double z) { + this.z = z; + } + + /** + * Gets the locations yaw. The yaw is part of the facing. + * If you are using this location as a block location, this value + * is likely {@code 0}, but if you are using it with a {@link de.pxav.kelp.core.player.KelpPlayer} + * or any other entity, this indicates the angle the player is looking at left and right + * (rotation around the y-axis). + * + * If the player is looking straight west, it is {@code 0F} and towards west it rises up to + * {@code 180F} in the north direction and up to {@code -180F} if you rotate in the other + * direction. + * + * You can calculate the yaw from a {@link Vector} if you need to using {@link #setDirection(Vector)}, + * which will also set the {@code pitch}. + * + * @return The locations yaw (rotation around the y-axis). + */ + public float getYaw() { + return yaw; + } + + /** + * Sets the location's yaw (rotation around the y-Axis). + * + * @param yaw The new yaw to set for this location. + */ + public void setYaw(float yaw) { + this.yaw = yaw; + } + + /** + * Gets the pitch for this location. The pitch is the angle in which a player is looking + * up or down. If a player is looking right into the sky, his pitch is {@code -90F}, if + * they are looking at the ground their pitch is {@code 90F} and looking straight on stands + * for {@code 0F}. Block locations usually don't have a pitch (= 0F). + * + * You can calculate the pitch from a {@link Vector} if you need to using {@link #setDirection(Vector)}, + * which will also set the {@code yaw}. + * + * @return The current location's pitch. + */ + public float getPitch() { + return pitch; + } + + /** + * Sets the locations pitch. This is the angle in which the entity is facing up or down. + * + * @param pitch The new pitch for the location. + */ + public void setPitch(float pitch) { + this.pitch = pitch; + } + + /** + * Gets the block X-location, which is a rounded value of the exact x-axis. + * Instead of {@code -456.223183497}, this will simply return {@code -456} + * as any more detail would be unnecessary when working with simple blocks. + * + * @return The block x-axis value of this location. + */ + public int getBlockX() { + return Location.locToBlock(this.x); + } + + /** + * Gets the block Y-location, which is a rounded value of the exact y-axis. + * Instead of {@code -87.223183497}, this will simply return {@code 87} + * as any more detail would be unnecessary when working with simple blocks. + * + * @return The block y-axis value of this location. + */ + public int getBlockY() { + return Location.locToBlock(this.y); + } + + /** + * Gets the block Z-location, which is a rounded value of the exact Z-axis. + * Instead of {@code -456.223183497}, this will simply return {@code -456} + * as any more detail would be unnecessary when working with simple blocks. + * + * @return The block Z-axis value of this location. + */ + public int getBlockZ() { + return Location.locToBlock(this.z); + } + + /** + * Sets the block X-value of this location. + * + * This will overwrite the exact x-value as well, + * so be careful if you still need the exact value. + * + * @param x The new block x-value of this location. + */ + public void setBlockX(double x) { + this.x = Location.locToBlock(this.x); + } + + /** + * Sets the block Y-value of this location. + * + * This will overwrite the exact x-value as well, + * so be careful if you still need the exact value. + * + * @param y The new block y-value of this location. + */ + public void setBlockY(double y) { + this.y = Location.locToBlock(this.y); + } + + /** + * Sets the block Z-value of this location. + * + * This will overwrite the exact x-value as well, + * so be careful if you still need the exact value. + * + * @param z The new block z-value of this location. + */ + public void setBlockZ(double z) { + this.z = Location.locToBlock(this.z); + } + + /** + * Gets the {@link KelpChunk} at the current Location. + * + * @return The chunk at the current location. + */ + public KelpChunk getChunk() { + return KelpChunk.from(getBukkitLocation().getChunk()); + } + + /** + * Gets the {@link KelpBlock} at the current Location. + * + * @return The block at the current location. + */ + public KelpBlock getBlock() { + return KelpBlock.from(getBukkitLocation().getBlock()); + } + + /** + * Adds the given values to the current axis values. + * + * Note that this method will overwrite the old values. + * If you still need them, {@link #clone()} the instance before + * and then add the values. + * + * @param x The x-coordinate to add. + * @param y The x-coordinate to add. + * @param z The x-coordinate to add. + * @return The current location object with the added values. + */ + public KelpLocation add(double x, double y, double z) { + this.x += x; + this.y += y; + this.z += z; + return this; + } + + /** + * Only adds the x-coordinate of this location. + * Other coordinates are not affected by this method. + * + * Note that this method will overwrite the old the value. + * If you still need them, {@link #clone()} the instance before + * and then add the values. + * + * @param x The x-coordinate to add. + * @return The current location object with the added value. + */ + public KelpLocation addX(double x) { + this.x += x; + return this; + } + + /** + * Only adds the y-coordinate of this location. + * Other coordinates are not affected by this method. + * + * Note that this method will overwrite the old the value. + * If you still need them, {@link #clone()} the instance before + * and then add the values. + * + * @param y The y-coordinate to add. + * @return The current location object with the added value. + */ + public KelpLocation addY(double y) { + this.y += y; + return this; + } + + /** + * Only adds the z-coordinate of this location. + * Other coordinates are not affected by this method. + * + * Note that this method will overwrite the old the value. + * If you still need them, {@link #clone()} the instance before + * and then add the values. + * + * @param z The z-coordinate to add. + * @return The current location object with the added value. + */ + public KelpLocation addZ(double z) { + this.z += z; + return this; + } + + /** + * Subtracts the given values from the current axis values. + * + * Note that this method will overwrite the old values. + * If you still need them, {@link #clone()} the instance before + * and then subtract the values. + * + * @param x The x-coordinate to subtract. + * @param y The x-coordinate to subtract. + * @param z The x-coordinate to subtract. + * @return The current location object with the subtracted values. + */ + public KelpLocation subtract(double x, double y, double z) { + this.x -= x; + this.y -= y; + this.z -= z; + return this; + } + + /** + * Only subtracts the x-coordinate of this location. + * Other values are not affected by this method. + * + * Note that this method will overwrite the old value. + * If you still need it, {@link #clone()} the instance before + * and then subtract the value. + * + * @param x The x-coordinate to subtract. + * @return The current location object with the subtracted value. + */ + public KelpLocation subtractX(double x) { + this.x -= x; + return this; + } + + /** + * Only subtracts the y-coordinate of this location. + * Other values are not affected by this method. + * + * Note that this method will overwrite the old value. + * If you still need it, {@link #clone()} the instance before + * and then subtract the value. + * + * @param y The y-coordinate to subtract. + * @return The current location object with the subtracted value. + */ + public KelpLocation subtractY(double y) { + this.y -= y; + return this; + } + + /** + * Only subtracts the z-coordinate of this location. + * Other values are not affected by this method. + * + * Note that this method will overwrite the old value. + * If you still need it, {@link #clone()} the instance before + * and then subtract the value. + * + * @param z The z-coordinate to subtract. + * @return The current location object with the subtracted value. + */ + public KelpLocation subtractZ(double z) { + this.z -= z; + return this; + } + + /** + * Converts the locations {@code yaw} and {@code pitch} to a {@link Vector}. + * This vector will point into the facing of the current location and will + * have the default length of {@code 1}. + * + * @return A vector pointing in this location's facing. + */ + public Vector getDirection() { + Vector vector = new Vector(); + + double rotY = this.getPitch(); + vector.setY(-Math.sin(Math.toRadians(rotY))); + + double xz = Math.cos(Math.toRadians(rotY)); + double rotX = this.getYaw(); + vector.setX(-xz * Math.sin(Math.toRadians(rotX))); + vector.setZ(xz * Math.cos(Math.toRadians(rotX))); + + return vector; + } + + /** + * Gets the location in front of this location. The front is defined + * by the location's facing (yaw & pitch), so doing that with a block location + * does not make sense. Use {@link KelpBlock#getFrontBlock(Vector)} for this instead. + * + * As this method uses the pitch as well the front location returned by this method + * might be below this location if the facing is looking down or above this location + * if the facing up. If you want to get the front location on the same y-level, + * use one of the 'keep-height' methods such as {@link #getFrontLocationKeepHeight()}. + * + * @return The location in front of this location. + */ + public KelpLocation getFrontLookLocation() { + return this.clone().add(getDirection()); + } + + /** + * Gets the location behind this location. The back is defined + * by the location's facing (yaw & pitch), so doing that with a block location + * does not make sense. Use {@link KelpBlock#getBackBlock(Vector)} for this instead. + * + * As this method uses the pitch as well the back location returned by this method + * might be below this location if the facing is looking up or above this location + * if the facing down. If you want to get the back location on the same y-level, + * use one of the 'keep-height' methods such as {@link #getBackLocationKeepHeight()}. + * + * @return The location in behind this location. + */ + public KelpLocation getBackLookLocation() { + return this.clone().subtract(getDirection()); + } + + /** + * Gets the location behind this location. In this case, only the location's + * yaw is used for calculation, which means it does not matter if the player + * is currently looking up or down. The location will keep on the same y-level. + * + * If you want the pitch of the player to be respected as well in the calculation, + * then use {@link #getBackLookLocation()}. + * + * @return The location in behind this location on the same y-level. + */ + public KelpLocation getBackLocationKeepHeight() { + double yawRadians = Math.PI * getYaw() / 180; + return this.add(Math.sin(yawRadians), 0, -Math.cos(yawRadians)); + } + + /** + * Gets the location in front of this location. In this case, only the location's + * yaw is used for calculation, which means it does not matter if the player + * is currently looking up or down. The location will keep on the same y-level. + * + * If you want the pitch of the player to be respected as well in the calculation, + * then use {@link #getFrontLookLocation()}. + * + * @return The location in in front of this location on the same y-level. + */ + public KelpLocation getFrontLocationKeepHeight() { + double yawRadians = Math.PI * getYaw() / 180; + return this.subtract(Math.sin(yawRadians), 0, -Math.cos(yawRadians)); + } + + /** + * Gets the location behind this location with the given distance. In this case, only the location's + * yaw is used for calculation, which means it does not matter if the player + * is currently looking up or down. The location will keep on the same y-level. + * + * While {@link #getBackLocationKeepHeight()} will simply take the location one block behind + * this location, you can pass the distance from the original location here as well. + * {@code 3} for example will get the location three blocks behind this location. + * + * If you want the pitch of the player to be respected as well in the calculation, + * then use {@link #getBackLookLocation()}. + * + * @param distance How many blocks behind this location should the new location be? + * @return The location in behind this location on the same y-level. + */ + public KelpLocation getBackLocationKeepHeight(double distance) { + double yawRadians = Math.PI * getYaw() / 180; + return this.add(distance * Math.sin(yawRadians), 0, -distance * Math.cos(yawRadians)); + } + + /** + * Gets the location in front of this location with the given distance. In this case, only the location's + * yaw is used for calculation, which means it does not matter if the player + * is currently looking up or down. The location will keep on the same y-level. + * + * While {@link #getFrontLocationKeepHeight()} will simply take the location one block in front of + * this location, you can pass the distance from the original location here as well. + * {@code 3} for example will get the location three blocks in front of this location. + * + * If you want the pitch of the player to be respected as well in the calculation, + * then use {@link #getFrontLookLocation()} ()}. + * + * @param distance How many blocks in front of this location should the new location be? + * @return The location in in front of this location on the same y-level. + */ + public KelpLocation getFrontLocationKeepHeight(double distance) { + double yawRadians = Math.PI * getYaw() / 180; + return this.subtract(distance * Math.sin(yawRadians), 0, -distance * Math.cos(yawRadians)); + } + + /** + * Gets the {@link CardinalDirection} of this location's facing. For this, only the location's + * yaw will be used. For more information about carinal direction, check out the documentation + * of {@link CardinalDirection} + * + * @return The cardinal direction based on this location's facing. + */ + public CardinalDirection getCardinalDirection() { + double rotation = (getYaw() - 180) % 360; + + if (rotation < 0) { + rotation += 360.0; + } + + if (0 <= rotation && rotation < 22.5) { + return CardinalDirection.NORTH; + } else if (22.5 <= rotation && rotation < 67.5) { + return CardinalDirection.NORTH_EAST; + } else if (67.5 <= rotation && rotation < 112.5) { + return CardinalDirection.EAST; + } else if (112.5 <= rotation && rotation < 157.5) { + return CardinalDirection.SOUTH_EAST; + } else if (157.5 <= rotation && rotation < 202.5) { + return CardinalDirection.SOUTH; + } else if (202.5 <= rotation && rotation < 247.5) { + return CardinalDirection.SOUTH_WEST; + } else if (247.5 <= rotation && rotation < 292.5) { + return CardinalDirection.WEST; + } else if (292.5 <= rotation && rotation < 337.5) { + return CardinalDirection.NORTH_WEST; + } else if (337.5 <= rotation && rotation < 360.0) { + return CardinalDirection.NORTH; + } else { + return null; + } + } + + /** + * Sets the yaw and pitch value of this location based on any + * vector. The length of the vector is ignored for this operation. + * + * @param vector The vector to calculate the yaw and pitch value of. + * @return The current location object with the new yaw and pitch. + */ + public KelpLocation setDirection(Vector vector) { + double _2PI = 6.283185307179586D; + double x = vector.getX(); + double z = vector.getZ(); + if (x == 0.0D && z == 0.0D) { + this.pitch = (float)(vector.getY() > 0.0D ? -90 : 90); + } else { + double theta = Math.atan2(-x, z); + this.yaw = (float)Math.toDegrees((theta + _2PI) % _2PI); + double x2 = NumberConversions.square(x); + double z2 = NumberConversions.square(z); + double xz = Math.sqrt(x2 + z2); + this.pitch = (float)Math.toDegrees(Math.atan(-vector.getY() / xz)); + } + return this; + } + + /** + * Creates a new {@link Vector} representing this location. + * + * @return A new vectors containing the coordinates of this location. + */ + public Vector toVector() { + return new Vector(this.x, this.y, this.z); + } + + /** + * Adds another location to this location. This will add all axis + * of the locations but not the facing. + * + * @param location The location to add to this location. + * @throws IllegalArgumentException if the worlds of the both locations are not the same. + * @return The new location with the added values. + */ + public KelpLocation add(KelpLocation location) { + Preconditions.checkNotNull(location, "Cannot add 'null' to a KelpLocation"); + if (!location.getWorldName().equalsIgnoreCase(getWorldName())) { + throw new IllegalArgumentException("Cannot add two locations of differing worlds!"); + } + + this.x += location.getX(); + this.y += location.getY(); + this.z += location.getZ(); + return this; + } + + /** + * Subtracts another location from this location. This will subtract all axis + * of the locations but not the facing. + * + * @param location The location to subtract from this location. + * @throws IllegalArgumentException if the worlds of the both locations are not the same. + * @return The new location with the subtracted values. + */ + public KelpLocation subtract(KelpLocation location) { + Preconditions.checkNotNull(location, "Cannot subtract 'null' from a KelpLocation"); + if (!location.getWorldName().equalsIgnoreCase(getWorldName())) { + throw new IllegalArgumentException("Cannot subtract two locations of differing worlds!"); + } + + this.x -= location.getX(); + this.y -= location.getY(); + this.z -= location.getZ(); + return this; + } + + /** + * Adds any vector to this location. + * + * @param vector Vector to add the axis of. + * @return The new location with the added values. + */ + public KelpLocation add(Vector vector) { + this.x += vector.getX(); + this.y += vector.getY(); + this.z += vector.getZ(); + return this; + } + + /** + * Subtracts any vector from this location. + * + * @param vector Vector to subtract the axis from. + * @return The new location with the subtracted values. + */ + public KelpLocation subtract(Vector vector) { + this.x -= vector.getX(); + this.y -= vector.getY(); + this.z -= vector.getZ(); + return this; + } + + /** + * Returns the magnitude of this location defined as {@code squareRootOf(x^2 + y^2 + z^2)}. + * The magnitude can be used to compare two locations with each other. A larger magnitude means + * the location is more right/up in the grid. As this method uses performance heavy square root + * operations, it is recommended to use {@link #magnitudeSquared()} instead of this method where possible. + * + * @return The location's magnitude. + */ + public double magnitude() { + return Math.sqrt(this.magnitudeSquared()); + } + + /** + * Returns the squared magnitude of this location defined as {@code (x^2 + y^2 + z^2)}. + * The magnitude can be used to compare two locations with each other. A larger magnitude means + * the location is more right/up in the grid. + * + * @return The location's magnitude value squared. + */ + public double magnitudeSquared() { + return NumberConversions.square(this.x) + NumberConversions.square(this.y) + NumberConversions.square(this.z); + } + + /** + * Calculates the squared distance to another location in blocks^2. + * + * @param to The location to calculate the distance to. + * @return The squared distance between the blocks. + */ + public double distanceSquared(KelpLocation to) { + Preconditions.checkNotNull(to, "Cannot measure distance between 'null' and a KelpLocation"); + if (!to.getWorldName().equalsIgnoreCase(worldName)) { + throw new IllegalArgumentException("Cannot measure distance between KelpLocations from differing worlds"); + } + return NumberConversions.square( + this.x - to.x) + + NumberConversions.square(this.y - to.y) + + NumberConversions.square(this.z - to.z + ); + } + + /** + * Calculates the distance to another location in blocks. + * Note that this method uses heavy square root operations to calculate + * the distance and it is recommended to use {@link #distanceSquared(KelpLocation)} + * where possible. + * + * @param to The location to measure the distance to. + * @return The distance between the two given locations. + */ + public double distance(KelpLocation to) { + return Math.sqrt(this.distanceSquared(to)); + } + + /** + * Multiplies all the location's axis with a specific + * multiplier. This does not include yaw and pitch. + * + * @param multiplier The multiplier to multiply with. + * @return The current location with the new values. + */ + public KelpLocation multiply(double multiplier) { + this.x *= multiplier; + this.y *= multiplier; + this.z *= multiplier; + return this; + } + + /** + * Only multiplies the x axis with a specific multiplier. + * + * @param multiplier The multiplier to multiply the x-axis with. + * @return The current location with the updated values. + */ + public KelpLocation multiplyX(double multiplier) { + this.x *= multiplier; + return this; + } + + /** + * Only multiplies the z axis with a specific multiplier. + * + * @param multiplier The multiplier to multiply the z-axis with. + * @return The current location with the updated values. + */ + public KelpLocation multiplyZ(double multiplier) { + this.z *= multiplier; + return this; + } + + /** + * Multiplies only the x and z axis of this location with the given multiplier. + * + * @param multiplier The multiplier to multiply x and z with. + * @return The current location with the updated values. + */ + public KelpLocation multiplyXZ(double multiplier) { + this.x *= multiplier; + this.z *= multiplier; + return this; + } + + /** + * Multiplies this location with a {@link Vector}. + * This means the x, y, and z axis are multiplied with + * the vector's values. + * + * @param multiplier The vector to multiply with. + * @return The current location with the updated values. + */ + public KelpLocation multiply(Vector multiplier) { + this.x *= multiplier.getX(); + this.y *= multiplier.getY(); + this.z *= multiplier.getZ(); + return this; + } + + /** + * Multiplies this location with another. This means + * the x, y and z values are multiplied with the other + * x, y and z values. + * + * @param multiplier The location to multiply with. + * @return The current location with the updated axis. + */ + public KelpLocation multiply(KelpLocation multiplier) { + this.x *= multiplier.getX(); + this.y *= multiplier.getY(); + this.z *= multiplier.getZ(); + return this; + } + + /** + * Zeros all axis of the location. This sets the x, y, and z + * coordinate to {@code 0}. + * + * @return The new location value with all axis set to 0. + */ + public KelpLocation zeroAxis() { + setX(0.0D); + setY(0.0D); + setZ(0.0D); + return this; + } + + /** + * Zeros all values of the location except the world name. + * This sets all coordinate values (x, y, z) as well as facing + * values to {@code 0}. + * + * @return The new location with all numbers set to 0. + */ + public KelpLocation zeroAll() { + zeroAxis(); + return zeroLook(); + } + + /** + * Zeros the look values of the location. + * This means the yaw and pitch are set to {@code 0F}. + * + * @return The new location object with the updated yaw and pitch values. + */ + public KelpLocation zeroLook() { + setYaw(0.0F); + setPitch(0.0F); + return this; + } + + /** + * Creates an identical copy of this location object. + * + * @return The new location object with the same data as this object. + */ + @Override + public KelpLocation clone() { + try { + return (KelpLocation) super.clone(); + } catch (CloneNotSupportedException e) { + e.printStackTrace(); + return null; + } + } + + /** + * Converts this {@code KelpLocation} into a default bukkit {@link Location}. + * + * @return The newly created bukkit location. + */ + public Location getBukkitLocation() { + return new Location(Bukkit.getWorld(worldName), x, y, z, yaw, pitch); + } + +} diff --git a/core/src/main/java/de/pxav/kelp/core/world/KelpWorld.java b/core/src/main/java/de/pxav/kelp/core/world/KelpWorld.java new file mode 100644 index 00000000..5d9b4870 --- /dev/null +++ b/core/src/main/java/de/pxav/kelp/core/world/KelpWorld.java @@ -0,0 +1,616 @@ +package de.pxav.kelp.core.world; + +import de.pxav.kelp.core.KelpPlugin; +import de.pxav.kelp.core.entity.type.DroppedItemEntity; +import de.pxav.kelp.core.entity.type.ItemDropType; +import de.pxav.kelp.core.inventory.item.KelpItem; +import de.pxav.kelp.core.player.KelpPlayer; +import de.pxav.kelp.core.world.util.ExplosionPower; +import de.pxav.kelp.core.world.util.WorldType; +import de.pxav.kelp.core.world.version.WorldVersionTemplate; +import org.bukkit.Bukkit; +import org.bukkit.Difficulty; +import org.bukkit.World; + +import java.util.Collection; +import java.util.UUID; + +/** + * This class represents a world on the server in which entities and players + * can move, blocks can be spawned and generated and chunks can be loaded + * and unloaded. + * + * A world is usually saved in the server's main folder and has to + * be loaded manually before it can be used (except the default {@code 'world'} of + * bukkit). + * + * This class is a version-independent replacement for the normal {@link World} + * provided by bukkit. + * + * @author pxav + */ +public class KelpWorld { + + private String name; + private WorldType worldType; + private World bukkitWorld; + + private WorldVersionTemplate versionTemplate; + + public KelpWorld(World bukkitWorld, WorldVersionTemplate versionTemplate) { + this.bukkitWorld = bukkitWorld; + this.versionTemplate = versionTemplate; + + this.name = bukkitWorld.getName(); + } + + public static KelpWorld from(String worldName) { + World world = Bukkit.getWorld(worldName); + if (world == null) { + return null; + } + return KelpWorld.from(world); + } + + public static KelpWorld from(World world) { + return new KelpWorld( + world, + KelpPlugin.getInjector().getInstance(WorldVersionTemplate.class) + ); + } + + /** + * Converts this world into a bukkit {@link World} instance. + * + * @return The bukkit instance of this world. + */ + public World getBukkitWorld() { + return bukkitWorld; + } + + public WorldType getWorldType() { + if (this.worldType == null) { + this.worldType = versionTemplate.getWorldType(this); + } + return worldType; + } + + /** + * Gets the {@link KelpBlock} at the given location of the world. + * + * @param location The exact location of the block you want to get. + * @return The {@link KelpBlock} object at the given location. + * If the block's material is {@link de.pxav.kelp.core.inventory.material.KelpMaterial#AIR}, + * the block won't be {@code null} but of type {@code AIR} + */ + public KelpBlock getBlockAt(KelpLocation location) { + return versionTemplate.getBlockAt(this, location); + } + + /** + * Gets the chunk at the given location in this world. + * A chunk is a 16x16x256 (320 if you are on 1.17+) + * cuboid region minecraft uses to generate and handle the world. More information + * can be found in {@link KelpChunk}. + * + * @param location The location where the chunk is located. + * @return The chunk at the given location. + */ + public KelpChunk getChunkAt(KelpLocation location) { + return versionTemplate.getChunkAt(this, location); + } + + /** + * Gets the name of this world in the bukkit world registration. + * + * @return This world's name. + */ + public String getName() { + return name; + } + + /** + * Gets the spawn location of the given world. The spawn location is the + * center of a radius in which players can spawn when initially joining a + * new world. + * + * @return The world's spawn location. + */ + public KelpLocation getSpawnLocation() { + return versionTemplate.getSpawnLocation(this); + } + + /** + * Checks whether this world is able to generate structures such as temples or villages. + * + * @return {@code true} if the world can generate structures + */ + public boolean canGenerateStructures() { + return versionTemplate.canGenerateStructures(this); + } + + /** + * Creates an explosion at the given location. This explosion will be + * destructive, which means it will actually destroy blocks, but not + * ignite any fire. + * + * @param location The center of the explosion + * @param power The power of the explosion. More information on explosion powers can be + * found in {@link ExplosionPower}. This has no effect on the fact whether + * fire is ignited or blocks are broken. + * @return + */ + public KelpWorld createDestructiveExplosion(KelpLocation location, ExplosionPower power) { + versionTemplate.createExplosion(this, location, power, true, false); + return this; + } + + /** + * Creates an explosion at the given location. This explosion is only + * an effect, which means it won't break any blocks nor ignite fire. + * + * @param location The center of the explosion + * @param power The power of the explosion. More information on explosion powers can be + * found in {@link ExplosionPower}. This has no effect on the fact whether + * fire is ignited or blocks are broken. + * @return + */ + public KelpWorld createEffectExplosion(KelpLocation location, ExplosionPower power) { + versionTemplate.createExplosion(this, location, power, false, false); + return this; + } + + /** + * Creates an explosion at the given location. This explosion will be + * destructive, which means it will break blocks. It will furthermore ignite fire + * at some locations within the explosion radius. + * + * @param location The center of the explosion + * @param power The power of the explosion. More information on explosion powers can be + * found in {@link ExplosionPower}. This has no effect on the fact whether + * fire is ignited or blocks are broken. + * @return + */ + public KelpWorld createDestructiveFireExplosion(KelpLocation location, ExplosionPower power) { + versionTemplate.createExplosion(this, location, power, true, true); + return this; + } + + /** + * Creates an explosion at the given location. This explosion will not + * break any blocks but ignite fire within a specific radius of the center + * location. + * + * @param location The center of the explosion + * @param power The power of the explosion. More information on explosion powers can be + * found in {@link ExplosionPower}. This has no effect on the fact whether + * fire is ignited or blocks are broken. + * @return + */ + public KelpWorld createFireExplosion(KelpLocation location, ExplosionPower power) { + versionTemplate.createExplosion(this, location, power, false, true); + return this; + } + + /** + * Drops an item on the given world at the given location. This item will be + * visible for all players on the world. The drop will land on the given + * location as there won't be any natural offset by default. If you want that, + * use {@link #dropItemNaturally(KelpLocation, KelpItem)} instead. + * + * @param location The location from where the item should drop + * @param item The item you want to drop + * @return The item entity that has been spawned at the given location. + */ + public DroppedItemEntity dropItem(KelpLocation location, KelpItem item) { + return versionTemplate.dropItem(this, location, item, ItemDropType.NORMAL); + } + + /** + * Drops an item on the given world at the given location. This item will be + * visible for all players on the world. The drop will land on the given + * location as there won't be any natural offset by default. If you want that, + * use + * + * @param location The location from where the item should drop + * @param item The item you want to drop + * @return The item entity that has been spawned at the given location. + */ + public DroppedItemEntity dropItemNaturally(KelpLocation location, KelpItem item) { + return versionTemplate.dropItem(this, location, item, ItemDropType.NATURAL); + } + + /** + * Checks whether animals can spawn naturally on this world. + * + * @return {@code true} if animals can spawn by default. + */ + public boolean animalsAllowed() { + return versionTemplate.animalsAllowed(this); + } + + /** + * Checks whether monsters such as creepers or zombies + * can spawn naturally on this world. + * + * @return {@code true} if monsters can spawn by default. + */ + public boolean monstersAllowed() { + return versionTemplate.monstersAllowed(this); + } + + /** + * Gets the highest block at a given location. + * + * @param location The location containing the y-axis for the block check. + * @return The highest {@link KelpBlock} at the provided location of the world. + */ + public KelpBlock getHighestBlockAt(KelpLocation location) { + return versionTemplate.getHighestBlockAt(this, location); + } + + /** + * Gets a collection of all {@link KelpChunk}s that are currently loaded in + * this world. This includes forced loaded chunks as well as naturally loaded chunks. + * + * @return The collection of all chunks currently loaded in this world. + */ + public Collection getLoadedChunks() { + return versionTemplate.getLoadedChunks(this); + } + + /** + * Gets the difficulty of this current world. The difficulty gives an + * indication of how difficult survival gameplay is. {@code Peaceful} means + * players cannot become hungry and monsters won't spawn, while {@code hard} + * says that lots of monsters will spawn and players lose saturation really + * fast. + * + * @return The current difficulty of the world. + */ + public Difficulty getDifficulty() { + return versionTemplate.getDifficulty(this); + } + + /** + * Gets the humidity at a specific location in the world. + * The humidity is dependent on the biome at the given location. + * + * @param location The location to check the humidity at. + * @return The humidity value at the given location in the given world. + */ + public double getHumidityAt(KelpLocation location) { + return versionTemplate.getHumidityAt(this, location); + } + + /** + * Checks whether the spawn chunks should be kept in memory although + * no players are currently in the spawn area. + * + * @return {@code true} if the spawn chunks are kept in memory. + */ + public boolean shouldKeepSpawnInMemory() { + return versionTemplate.shouldKeepSpawnInMemory(this); + } + + /** + * Changes whether the spawn chunks of a world should be kept + * loaded/in memory although no players are currently in the spawn + * area. This might improve performance if you are commonly spawning + * new players to a world (such as in a server lobby). + * + * @param keep {@code true} if the spawn chunks should be kept loaded over the server runtime. + */ + public KelpWorld keepSpawnInMemory(boolean keep) { + versionTemplate.setKeepSpawnInMemory(this, keep); + return this; + } + + /** + * Gets the full time of a world. This is the full absolute + * in-game daytime. + * + * @return The full (absolute) time of the given world. + */ + public long getFullTime() { + return versionTemplate.getFullTime(this); + } + + /** + * Gets the full in-game time of this world from the initial world generation. + * + * @return The game time that has passed on the given world. + */ + public long getGameTime() { + return versionTemplate.getGameTime(this); + } + + /** + * Gets the relative in-game time of the given world. + * This time is calculated from the world's full time: + * {@code #getFullTime() % 24000L}. + * + * This time cannot be negative. If a negative value is returned from + * the above operation, a full day will be added to this value. + * + * @return The relative in-game time of the given world. + */ + public long getTime() { + return versionTemplate.getTime(this); + } + + /** + * Gets all players that are currently on the given world. + * + * @return A collection of all players that are currently on this world. + */ + public Collection getPlayers() { + return versionTemplate.getPlayers(this); + } + + /** + * Gets the current pvp mode of the world. If players are allowed to + * fight each other, this will return {@code true}. + * + * @return {@code true} if pvp is enabled on this world. + */ + public boolean pvpEnabled() { + return versionTemplate.isPvPEnabled(this); + } + + /** + * Allows PVP in this world. This allows players to fight + * against each other. + * + * @return + */ + public KelpWorld enablePVP() { + versionTemplate.setPVP(this, true); + return this; + } + + /** + * Allows PVP in this world. This prohibits players to fight + * against each other. + * + * @return + */ + public KelpWorld disablePVP() { + versionTemplate.setPVP(this, false); + return this; + } + + /** + * Sets the current pvp mode of the world. If players should be allowed + * to fight each other, set this to {@code true}. + * + * @param allow {@code true} if players should be able to fight each other. + */ + public KelpWorld allowPVP(boolean allow) { + versionTemplate.setPVP(this, allow); + return this; + } + + /** + * Gets the seed of the world generator of this world. A seed is a constant value + * used by the {@code Perlin noise} world generation algorithm to randomize + * biomes and structures. Seeds are usually compatible across different versions, but + * might not output the same result in differing versions due to new structures and + * biomes that have been added over time. + * + * @return The world's seed. + */ + public long getSeed() { + return versionTemplate.getSeed(this); + } + + /** + * Gets the sea level of this world. + * + * @return The world's sea level. + */ + public int getSeaLevel() { + return versionTemplate.getSeaLevel(this); + } + + /** + * Gets the temperature of a world at a specific location. + * Temperatures are depending on the biome and exact location (height specifically). + * A temperature indicates whether it can snow or rain at a specific location + * or whether there is any precipitation at all. The desert biome for example + * has a relatively high temperature of {@code 2.0d}, while Snowy Beach has {@code 0.05} + * for example. + * + * @param location The location you want to get the temperature at. + * @return The exact temperature at the given location in the current world. + */ + public double getTemperatureAt(KelpLocation location) { + return versionTemplate.getTemperatureAt(this, location); + } + + /** + * Gets the duration of the current thunder in the world in ticks. + * + * @return The thunder duration in ticks. + */ + public int getThunderDuration() { + return versionTemplate.getThunderDuration(this); + } + + /** + * Gets the unique id of this world. + * + * @return The world's {@link UUID}. + */ + public UUID getUUID() { + return versionTemplate.getUUID(this); + } + + /** + * Gets the duration of the current weather state in ticks. + * This counts for all weather types (including clear weather) + * + * @return The current weather duration in ticks. + */ + public int getWeatherDuration() { + return versionTemplate.getWeatherDuration(this); + } + + /** + * Checks whether there is a storm in the given world. + * + * @return {@code true} whether there is currently a storm in the given world. + */ + public boolean hasStorm() { + return versionTemplate.hasStorm(this); + } + + /** + * Checks if auto-save is enabled in this world. + * This would save the world to the server folder in a regular interval. + * + * @return {@code true} if auto-save is enabled. + */ + public boolean hasAutoSave() { + return versionTemplate.hasAutoSave(this); + } + + /** + * Checks whether there is clear weather in the given world. + * If there is clear whether, methods such as {@link #hasStorm()} + * are automatically {@code false}. + * + * @return {@code true} whether there is clear weather, {@code false} if there is any thunder or precipitation. + */ + public boolean isWeatherClear() { + return versionTemplate.isWeatherClear(this); + } + + /** + * Checks if there currently is a thunderstorm in the given world. + * + * @return {@code true} weather there currently is a thunder in the world. + */ + public boolean isThundering() { + return versionTemplate.isThundering(this); + } + + /** + * Sets the difficulty of the current world. The difficulty gives an + * indication of how difficult survival gameplay is. {@code Peaceful} means + * players cannot become hungry and monsters won't spawn, while {@code hard} + * says that lots of monsters will spawn and players lose saturation really + * fast. + * + * @param difficulty The difficulty you want to set. + */ + public KelpWorld setDifficulty(Difficulty difficulty) { + versionTemplate.setDifficulty(this, difficulty); + return this; + } + + /** + * Sets the full time of the current world. The full time is the absolute in-game + * daytime of a world. With this method you can also rewind the time, which could + * eventually break redstone clocks or other scheduled events, which is why + * you might want to use {@link #setTime(long)} depending on your use-case. + * + * @param fullTime The full time to set for this world. + */ + public KelpWorld setFullTime(long fullTime) { + versionTemplate.setFullTime(this, fullTime); + return this; + } + + /** + * Sets the spawn location of this world. This is the location, where + * players will spawn when they first join the world. + * + * By default, there is a 30x30 radius around the location, where players + * spawn randomly. + * + * @param spawnLocation The spawn location you want to set. + */ + public KelpWorld setSpawnLocation(KelpLocation spawnLocation) { + versionTemplate.setSpawnLocation(this, spawnLocation); + return this; + } + + /** + * Sets the relative in-game time of the given world. + * If you set a value that is smaller than the current time value, + * the server will automatically skip to the next day. Rewind + * is not possible with this method for safety reasons. If you need + * to do it though, use {@link #setFullTime(long)}. + * + * @param time The relative in-game time to set. + */ + public KelpWorld setTime(long time) { + versionTemplate.setTime(this, time); + return this; + } + + /** + * Sets the duration of the current thunderstorm in the world in ticks. + * + * @param duration The duration in ticks the thunder should take. + */ + public KelpWorld setThunderDuration(int duration) { + versionTemplate.setThunderDuration(this, duration); + return this; + } + + /** + * Sets the thunder state of a world. When set to {@code true} a new thunder + * will start or the current storm will continue. + * + * @param thundering {@code true} if you want to start a thunderstorm. {@code false} if you want to end the thunder. + */ + public KelpWorld setThundering(boolean thundering) { + versionTemplate.setThundering(this, thundering); + return this; + } + + /** + * Sets the storm state of a world. When set to {@code true} a new storm + * will start or the current storm will continue. + * + * @param storm {@code true} if you want to start a storm. {@code false} if you want to end the storm. + */ + public KelpWorld setStorm(boolean storm) { + versionTemplate.setStorm(this, storm); + return this; + } + + /** + * Spawns a lightning in the given world at the given location. This + * lightning will be visible for all players who are in the world and in range. + * + * This method will cause block and entity damage and might ignite fires at + * the point where the lightning strikes. If you just want to spawn the effect, + * use {@link #strikeLightningEffect(KelpLocation)}. + * + * @param location The exact location where the lightning should strike. + */ + public KelpWorld strikeLightning(KelpLocation location) { + versionTemplate.strikeLightning(this, location, false); + return this; + } + + /** + * Spawns a strike lightning in the given world at the given location. This + * lightning will be visible for all players who are in the world and in range. + * + * This method does not spawn fire at the location where the lightning + * strikes, nor does it damage or transform any entities (from pig to pigman + * for example). It simply spawns the effect of a lightning. If you want the + * lightning to make damange, use {@link #strikeLightning(KelpLocation)} + * + * @param location The exact location where the lightning should strike. + */ + public KelpWorld strikeLightningEffect(KelpLocation location) { + versionTemplate.strikeLightning(this, location, true); + return this; + } + +} diff --git a/core/src/main/java/de/pxav/kelp/core/world/util/CardinalDirection.java b/core/src/main/java/de/pxav/kelp/core/world/util/CardinalDirection.java new file mode 100644 index 00000000..f11fb41b --- /dev/null +++ b/core/src/main/java/de/pxav/kelp/core/world/util/CardinalDirection.java @@ -0,0 +1,37 @@ +package de.pxav.kelp.core.world.util; + +import de.pxav.kelp.core.world.KelpLocation; +import org.bukkit.util.Vector; + +/** + * This util class is used to express cardinal directions + * of any {@link de.pxav.kelp.core.world.KelpLocation}. + * + * Cardinal directions depend on the rotation on the y-axis + * and are therefore calculated using a location's {@code yaw}. + * If you use the {@link KelpLocation#getCardinalDirection()} method, + * the given location has to have a {@code yaw} to be able to convert + * it to a {@code CardinalDirection}. + * + * You can also use a {@link org.bukkit.util.Vector} to determine + * the yaw, which can be done with {@link KelpLocation#setDirection(Vector)}. + * + * Cardinal directions can be used to express the facing of a player or + * a relative block for example. Given a block, you could use the direction + * with {@link de.pxav.kelp.core.world.KelpBlock#getRelative(CardinalDirection)} + * to get the neighbouring block in a specific direction. + * + * @author pxav + */ +public enum CardinalDirection { + + NORTH, + NORTH_EAST, + EAST, + SOUTH_EAST, + SOUTH, + SOUTH_WEST, + WEST, + NORTH_WEST; + +} diff --git a/core/src/main/java/de/pxav/kelp/core/world/util/ExplosionPower.java b/core/src/main/java/de/pxav/kelp/core/world/util/ExplosionPower.java new file mode 100644 index 00000000..55c03534 --- /dev/null +++ b/core/src/main/java/de/pxav/kelp/core/world/util/ExplosionPower.java @@ -0,0 +1,69 @@ +package de.pxav.kelp.core.world.util; + +/** + * This class is used to determine the power of an explosion. + * Depending on which type of explosion is performed, those types + * have different powers. {@code TNT} for example has a power of {@code 4}, + * while a creeper has just {@code 3}. + * + * This class basically just holds the float value for the power of the + * explosion, while providing a bunch of default values for some default + * Minecraft explosion types. They can be accessed with: + * {@code ExplosionPower.EXPLOSION_NAME}. + * + * To create a custom explosion power, you could use the static factory + * {@code ExplosionPower.custom(float power)}. + * + * The power can be passed when creating an explosion from + * {@link de.pxav.kelp.core.world.KelpWorld} for example. + * + * @author pxav + */ +public class ExplosionPower { + + public static final ExplosionPower CREEPER = new ExplosionPower(3F); + public static final ExplosionPower CHARGED_CREEPER = new ExplosionPower(6F); + + public static final ExplosionPower TNT = new ExplosionPower(4F); + public static final ExplosionPower UNDERWATER_TNT = new ExplosionPower(4F); + + public static final ExplosionPower WITHER_HALF_HEALTH = new ExplosionPower(8F); + public static final ExplosionPower WITHER_SPAWN = new ExplosionPower(7F); + public static final ExplosionPower WITHER_KILL = new ExplosionPower(7F); + public static final ExplosionPower BLACK_WITHER_SKULL = new ExplosionPower(1F); + public static final ExplosionPower DANGEROUS_WITHER_SKULL = new ExplosionPower(1F); + + public static final ExplosionPower BED_NETHER = new ExplosionPower(5F); + public static final ExplosionPower BED_END = new ExplosionPower(5F); + + public static final ExplosionPower END_CRYSTAL = new ExplosionPower(6F); + public static final ExplosionPower RESPAWN_ANCHOR = new ExplosionPower(5F); + public static final ExplosionPower GHAST_FIREBALL = new ExplosionPower(1F); + + /** + * Allows you to create an explosion with a custom power, which is not + * provided by the default list by minecraft. + * + * @param power The power of your custom explosion. + * @return The {@link ExplosionPower} object holding the given {@code power} + */ + public static ExplosionPower custom(float power) { + return new ExplosionPower(power); + } + + // the actual power of the explosion + private final float power; + + ExplosionPower(float power) { + this.power = power; + } + + /** + * Gets the explosion power of this explosion type. + * @return The explosion power value held by this object. + */ + public float getPower() { + return power; + } + +} diff --git a/core/src/main/java/de/pxav/kelp/core/world/util/WorldType.java b/core/src/main/java/de/pxav/kelp/core/world/util/WorldType.java new file mode 100644 index 00000000..95f9a900 --- /dev/null +++ b/core/src/main/java/de/pxav/kelp/core/world/util/WorldType.java @@ -0,0 +1,29 @@ +package de.pxav.kelp.core.world.util; + +/** + * Represents the generator type of a {@link de.pxav.kelp.core.world.KelpWorld}. + * This can be either a normal overworld or the nether or the end. + * This is the equivalent to the normal {@link org.bukkit.World.Environment} provided + * by bukkit. + * + * @author pxav + */ +public enum WorldType { + + /** + * The current world is an overworld. + */ + NORMAL, + + /** + * The current world is the end. This also changes the sky background + * to the purple background you might know. + */ + THE_END, + + /** + * The current world is the nether. + */ + THE_NETHER + +} diff --git a/core/src/main/java/de/pxav/kelp/core/world/version/BlockVersionTemplate.java b/core/src/main/java/de/pxav/kelp/core/world/version/BlockVersionTemplate.java new file mode 100644 index 00000000..acf50caf --- /dev/null +++ b/core/src/main/java/de/pxav/kelp/core/world/version/BlockVersionTemplate.java @@ -0,0 +1,77 @@ +package de.pxav.kelp.core.world.version; + +import de.pxav.kelp.core.application.KelpVersionTemplate; +import de.pxav.kelp.core.inventory.material.KelpMaterial; +import de.pxav.kelp.core.world.KelpBlock; +import de.pxav.kelp.core.world.KelpChunk; +import de.pxav.kelp.core.world.KelpLocation; +import org.bukkit.block.BlockFace; + +/** + * This version template is used for version-dependent operations + * related to bukkit's default {@link org.bukkit.block.Block}. + * + * @author pxav + */ +@KelpVersionTemplate +public abstract class BlockVersionTemplate { + + /** + * Gets the {@link KelpChunk} the given block is located in. + * + * @param block The block you want to get the chunk of. + * @return The {@link KelpChunk} the given block is located in. + */ + public abstract KelpChunk getChunk(KelpBlock block); + + /** + * Gets the {@link KelpLocation} of the given block. This also + * contains information like the current world. + * + * @param block The block you want to get the location of. + * @return The {@link KelpLocation} of this world. + */ + public abstract KelpLocation getLocation(KelpBlock block); + + /** + * Gets the material of the current block. + * + * @param block The block you want to get the material of. + * @return The {@link KelpMaterial} the given block is made of. + */ + public abstract KelpMaterial getMaterial(KelpBlock block); + + /** + * Sets the material of the given block to the given {@link KelpMaterial}. + * So if you had an {@link KelpMaterial#AIR} block for example, + * + * @param block The block you want to change the material of. + * @param material The new material you want the block to have. + */ + public abstract void setMaterial(KelpBlock block, KelpMaterial material); + + /** + * Checks whether bone meal will have an effect on this block. + * This means it will check whether the block is a sapling + * or a plant that will grow if you right click it with bone + * meal. More information about that can be found + * in this wiki + * article + * + * @param block The block you want to check the application state of. + * @return {@code true} whether bone meal is applicable. + */ + public abstract boolean canApplyBoneMeal(KelpBlock block); + + /** + * Simulates the application of bone meal on a block. + * This means if the block is a sapling for example, it + * might grow to a tree or grass might spawn random flowers + * and so on. + * + * @param block The block you want to simulate the application of. + * @param blockFace The face of the block to apply the bone meal to. + */ + public abstract void applyBoneMeal(KelpBlock block, BlockFace blockFace); + +} diff --git a/core/src/main/java/de/pxav/kelp/core/world/version/ChunkVersionTemplate.java b/core/src/main/java/de/pxav/kelp/core/world/version/ChunkVersionTemplate.java new file mode 100644 index 00000000..ce5a0e29 --- /dev/null +++ b/core/src/main/java/de/pxav/kelp/core/world/version/ChunkVersionTemplate.java @@ -0,0 +1,171 @@ +package de.pxav.kelp.core.world.version; + +import de.pxav.kelp.core.application.KelpApplication; +import de.pxav.kelp.core.application.KelpVersionTemplate; +import de.pxav.kelp.core.player.KelpPlayer; +import de.pxav.kelp.core.world.KelpBlock; +import de.pxav.kelp.core.world.KelpChunk; +import de.pxav.kelp.core.world.KelpLocation; +import de.pxav.kelp.core.world.KelpWorld; + +import java.util.Collection; +import java.util.Set; + +/** + * This version template is used to handle version-dependent operations + * of {@link KelpChunk}s. + * + * @author pxav + */ +@KelpVersionTemplate +public abstract class ChunkVersionTemplate { + + /** + * Determines whether the given location is inside the given chunk. + * + * @param chunk The chunk you want to check. + * @param location The location that should be located in the chunk. + * @return {@code true} if the location is inside the chunk. + */ + public abstract boolean contains(KelpChunk chunk, KelpLocation location); + + /** + * Gets a block at the given location inside the chunk. The passed location + * may not be from outside the chunk! + * + * @param chunk The chunk you want to get a block of. + * @param location The location where the block is located. + * @return The block at the given location. + */ + public abstract KelpBlock getBlockAt(KelpChunk chunk, KelpLocation location); + + //todo getEntities() + + /** + * Gets a collection of all players that are currently inside the chunk. + * + * @param chunk The chunk you want to get the players of. + * @return A collection of all players that are currently inside the chunk. + */ + public abstract Collection getPlayers(KelpChunk chunk); + + /** + * Gets the world the given chunk is located in. + * + * @param chunk The chunk you want to get the world of. + * @return The world the given chunk is located in. + */ + public abstract KelpWorld getWorld(KelpChunk chunk); + + /** + * Gets the X-coordinate in the chunk's world of the given + * chunk. This can be used to compare and identify chunks. + * + * Note that this value does not return the absolute X-block- + * coordinate where the chunk begins, but it returns the X + * block value divided by 16. If you are at x=-35, then this + * would return 2, because it is bigger than 32 (16*2) but smaller + * than 48 (16*3). + * + * @param chunk The chunk you want to get the X-coordinate of. + * @return The X coordinate on the world's chunk grid. + */ + public abstract int getX(KelpChunk chunk); + + /** + * Gets the Z-coordinate in the chunk's world of the given + * chunk. This can be used to compare and identify chunks. + * + * Note that this value does not return the absolute Z-block- + * coordinate where the chunk begins, but it returns the Z + * block value divided by 16. If you are at z=-35, then this + * would return 2, because it is bigger than 32 (16*2) but smaller + * than 48 (16*3). + * + * @param chunk The chunk you want to get the Z-coordinate of. + * @return The Z coordinate on the world's chunk grid. + */ + public abstract int getZ(KelpChunk chunk); + + /** + * Checks whether the given chunk is currently loaded. That + * means it checks whether tick operations are currently ran on + * this chunk. + * + * @param chunk The chunk you want to check if its loaded. + * @return {@code true} if the chunk is currently loaded. + */ + public abstract boolean isLoaded(KelpChunk chunk); + + /** + * Loads the chunk. This will make the chunk passable for players and + * tick operations such as redstone clocks or crop growing will be performed + * again. To save performance, chunks are unloaded by bukkit if they are not used. + * If you need to prevent that you can use {@link #addForceLoadFlag(KelpChunk, Class)} + * to keep the chunk loaded until you manually unload it again. But please + * keep in mind that this might have bad performance impact. + * + * @param chunk The chunk to be loaded. If the chunk has not been loaded before, + * the world will be generated at that point. + */ + public abstract void load(KelpChunk chunk); + + /** + * Unloads the chunk. That means tick operations in this + * chunk will no longer be performed. Redstone clocks will stop running + * and crops will stop growing. This can be reversed at any time using + * {@link #load(KelpChunk)}. + * + * @param chunk The chunk you want to unload. + */ + public abstract void unload(KelpChunk chunk); + + /** + * Adds a force load flag to the given chunk. A force load flag means that + * the chunk cannot be unloaded by bukkit randomly, but keeps loaded until you + * revert that action by yourself ({@link #removeForceLoadFlag(KelpChunk, Class)}). + * + * If you call this method, {@link #unload(KelpChunk)} won't have an effect + * as Kelp will immediately load the chunk again if there is a flag to keep + * it loaded. So if you want to unload the chunk, call {@link #removeForceLoadFlag(KelpChunk, Class)} + * first. + * + * @param chunk The chunk you want to keep loaded. + * @param plugin The plugin that should keep the chunk loaded. + * This is important when you remove your flag, + * but another plugin still relies on the chunk to be loaded, + * the chunk will be kept loaded until the other plugin(s) + * unload the chunk as well. + */ + public abstract void addForceLoadFlag(KelpChunk chunk, Class plugin); + + /** + * Removes a force load flag from the given chunk again. A force load flag means that + * the chunk cannot be unloaded by bukkit randomly, but keeps loaded until you + * revert that action by yourself using this method. Such a flag can be assigned + * to a chunk using {@link #addForceLoadFlag(KelpChunk, Class)}. + * + * @param chunk The chunk to remove the force load flag of. + * @param plugin The plugin that removes their flag. If another plugin has still + * loaded this chunk, it won't be unloaded until all plugins have removed + * their flag. + */ + public abstract void removeForceLoadFlag(KelpChunk chunk, Class plugin); + + /** + * Gets all {@link KelpApplication}s that are currently forcing this chunk to keep loaded. + * + * @param chunk The chunk you want to get the plugins of. + * @return A set of all plugin main classes that force the chunk to keep loaded. + */ + public abstract Set> getForceLoadFlagPlugins(KelpChunk chunk); + + /** + * Determines whether slime entities can spawn in the given chunk. + * + * @param chunk The chunk you want to check. + * @return {@code true} if slime entities can spawn in that chunk. + */ + public abstract boolean isSlimeChunk(KelpChunk chunk); + +} diff --git a/core/src/main/java/de/pxav/kelp/core/world/version/LocationVersionTemplate.java b/core/src/main/java/de/pxav/kelp/core/world/version/LocationVersionTemplate.java new file mode 100644 index 00000000..4310ec99 --- /dev/null +++ b/core/src/main/java/de/pxav/kelp/core/world/version/LocationVersionTemplate.java @@ -0,0 +1,7 @@ +package de.pxav.kelp.core.world.version; + +import de.pxav.kelp.core.application.KelpVersionTemplate; + +@KelpVersionTemplate +public abstract class LocationVersionTemplate { +} diff --git a/core/src/main/java/de/pxav/kelp/core/world/version/WorldVersionTemplate.java b/core/src/main/java/de/pxav/kelp/core/world/version/WorldVersionTemplate.java new file mode 100644 index 00000000..c8e2bb2e --- /dev/null +++ b/core/src/main/java/de/pxav/kelp/core/world/version/WorldVersionTemplate.java @@ -0,0 +1,417 @@ +package de.pxav.kelp.core.world.version; + +import de.pxav.kelp.core.application.KelpVersionTemplate; +import de.pxav.kelp.core.entity.type.DroppedItemEntity; +import de.pxav.kelp.core.entity.type.ItemDropType; +import de.pxav.kelp.core.inventory.item.KelpItem; +import de.pxav.kelp.core.player.KelpPlayer; +import de.pxav.kelp.core.world.*; +import de.pxav.kelp.core.world.util.ExplosionPower; +import de.pxav.kelp.core.world.util.WorldType; +import org.bukkit.Difficulty; + +import java.util.Collection; +import java.util.UUID; + +/** + * This class is used to handle version dependent operations + * with {@link KelpWorld}s. + * + * @author pxav + */ +@KelpVersionTemplate +public abstract class WorldVersionTemplate { + + /** + * Gets the spawn location of the given world. The spawn location is the + * center of a radius in which players can spawn when initially joining a + * new world. + * + * @param world The world you want to get the spawn location of. + * @return The world's spawn location. + */ + public abstract KelpLocation getSpawnLocation(KelpWorld world); + + /** + * Gets the chunk at the given location. A chunk is a 16x16x(256 or 320 depending on your version) + * cuboid region minecraft uses to generate and handle the world. More information + * can be found in {@link KelpChunk}. + * + * @param world The world from which you want to get the chunk of. + * @param location The location where the chunk is located. + * @return The chunk in the given world at the given location. + */ + public abstract KelpChunk getChunkAt(KelpWorld world, KelpLocation location); + + /** + * Checks whether this world is able to generate structures such as temples or villages. + * + * @param world The world you want to check. + * @return {@code true} if the world can generate structures + */ + public abstract boolean canGenerateStructures(KelpWorld world); + + /** + * Creates an explosion at the given location. + * + * @param world The world to spawn the explosion at. + * @param location The center of the explosion + * @param power The power of the explosion. More information on explosion powers can be + * found in {@link ExplosionPower} + * @param breakBlocks Whether the explosion should be able to break blocks. + * @param igniteFire Whether the explosion should be able to ignite a fire within its radius. + */ + public abstract void createExplosion(KelpWorld world, KelpLocation location, ExplosionPower power, boolean breakBlocks, boolean igniteFire); + + /** + * Drops an item on the given world at the given location. This item will be + * visible for all players on the world. + * + * @param world The world to spawn the item on. + * @param location The location from where the item should drop + * @param item The item you want to drop + * @param dropType Whether the item should be dropped normally or naturally. + * More information about the difference can be found in {@link ItemDropType} + * @return The item entity that has been spawned at the given location. + */ + public abstract DroppedItemEntity dropItem(KelpWorld world, KelpLocation location, KelpItem item, ItemDropType dropType); + + /** + * Checks whether animals can spawn naturally on this world. + * + * @param world The world you want to check animal spawns for. + * @return {@code true} if animals can spawn by default. + */ + public abstract boolean animalsAllowed(KelpWorld world); + + /** + * Checks whether monsters such as creepers or zombies + * can spawn naturally on this world. + * + * @param world The world you want to check monster spawns for. + * @return {@code true} if monsters can spawn by default. + */ + public abstract boolean monstersAllowed(KelpWorld world); + + /** + * Gets the {@link KelpBlock} at the given location of the world. + * + * @param world The world you want to get the block of. + * @param location The exact location of the block you want to get. + * @return The {@link KelpBlock} object at the given location. + * If the block's material is {@link de.pxav.kelp.core.inventory.material.KelpMaterial#AIR}, + * the block won't be {@code null} but of type {@code AIR} + */ + public abstract KelpBlock getBlockAt(KelpWorld world, KelpLocation location); + + /** + * Gets the highest block at a given location. + * + * @param world The world to check the location in. + * @param location The location containing the y-axis for the block check. + * @return The highest {@link KelpBlock} at the provided location of the world. + */ + public abstract KelpBlock getHighestBlockAt(KelpWorld world, KelpLocation location); + + /** + * Gets a collection of all {@link KelpChunk}s that are currently loaded in + * the given world. This includes forced loaded chunks as well as naturally loaded chunks. + * + * @param world The world to get the loaded chunks of. + * @return The collection of all chunks currently loaded in this world. + */ + public abstract Collection getLoadedChunks(KelpWorld world); + + /** + * Gets the difficulty of the current world. The difficulty gives an + * indication of how difficult survival gameplay is. {@code Peaceful} means + * players cannot become hungry and monsters won't spawn, while {@code hard} + * says that lots of monsters will spawn and players lose saturation really + * fast. + * + * @param world The world to get the difficulty of. + * @return The current difficulty of the world. + */ + public abstract Difficulty getDifficulty(KelpWorld world); + + /** + * Gets the environment/world type. The world type says which dimension + * is currently used by the world generator. This also affects the fog effects + * of the world. While in overworld ({@code NORMAL}) there won't be any + * special background or fog, the background of an {@code END} world + * will be purple for example. + * + * This is similar to the {@code #getEnvironment()} method of a normal + * bukkit world. + * + * @param world The world to get the world type of. + * @return The type of the given world. + */ + public abstract WorldType getWorldType(KelpWorld world); + + /** + * Gets the humidity at a specific location in the world. + * The humidity is dependent on the biome at the given location. + * + * @param world The world you want to check the humidity in. + * @param location The location to check the humidity at. + * @return The humidity value at the given location in the given world. + */ + public abstract double getHumidityAt(KelpWorld world, KelpLocation location); + + /** + * Checks whether the spawn chunks should be kept in memory although + * no players are currently in the spawn area. + * + * @param world The world you want to check the spawn chunks of. + * @return {@code true} if the spawn chunks are kept in memory. + */ + public abstract boolean shouldKeepSpawnInMemory(KelpWorld world); + + /** + * Changes whether the spawn chunks of a world should be kept + * loaded/in memory although no players are currently in the spawn + * area. This might improve performance if you are commonly spawning + * new players to a world (such as in a server lobby). + * + * @param world The world you want to set the spawn chunk settings of. + * @param keep {@code true} if the spawn chunks should be kept loaded over the server runtime. + */ + public abstract void setKeepSpawnInMemory(KelpWorld world, boolean keep); + + /** + * Gets the full time of a world. This is the full absolute + * in-game daytime. + * + * @param world The world you want to get the time of. + * @return The full (absolute) time of the given world. + */ + public abstract long getFullTime(KelpWorld world); + + /** + * Gets the full in-game time of this world from the initial world generation. + * + * @param world The world you want to get the game time of. + * @return The game time that has passed on the given world. + */ + public abstract long getGameTime(KelpWorld world); + + /** + * Gets the relative in-game time of the given world. + * This time is calculated from the world's full time: + * {@code #getFullTime() % 24000L}. + * + * This time cannot be negative. If a negative value is returned from + * the above operation, a full day will be added to this value. + * + * @param world The world you want to get the relative time of. + * @return The relative in-game time of the given world. + */ + public abstract long getTime(KelpWorld world); + + /** + * Gets all players that are currently on the given world. + * + * @param world The world you want to get the players of. + * @return A collection of all players that are currently on this world. + */ + public abstract Collection getPlayers(KelpWorld world); + + /** + * Gets the current pvp mode of the world. If players are allowed to + * fight each other, this will return {@code true}. + * + * @param world The world you want to check the pvp setting of. + * @return {@code true} if pvp is enabled on this world. + */ + public abstract boolean isPvPEnabled(KelpWorld world); + + /** + * Sets the current pvp mode of the world. If players should be allowed + * to fight each other, set this to {@code true}. + * + * @param world The world you want to set the pvp setting of. + * @param pvp {@code true} if players should be able to fight each other. + */ + public abstract void setPVP(KelpWorld world, boolean pvp); + + /** + * Gets the seed of the world generator of this world. A seed is a constant value + * used by the {@code Perlin noise} world generation algorithm to randomize + * biomes and structures. Seeds are usually compatible across different versions, but + * might not output the same result in differing versions due to new structures and + * biomes that have been added over time. + * + * @param world The world you want to get the seed of. + * @return The world's seed. + */ + public abstract long getSeed(KelpWorld world); + + /** + * Gets the sea level of this world. + * + * @param world The world you want to get the sea level of. + * @return The world's sea level. + */ + public abstract int getSeaLevel(KelpWorld world); + + /** + * Gets the temperature of a world at a specific location. + * Temperatures are depending on the biome and exact location (height specifically). + * A temperature indicates whether it can snow or rain at a specific location + * or whether there is any precipitation at all. The desert biome for example + * has a relatively high temperature of {@code 2.0d}, while Snowy Beach has {@code 0.05} + * for example. + * + * @param world The world you want to get the temperature in. + * @param location The location you want to get the temperature at. + * @return The exact temperature at the given location in the current world. + */ + public abstract double getTemperatureAt(KelpWorld world, KelpLocation location); + + /** + * Gets the duration of the current thunder in the world in ticks. + * + * @param world The world you want to get the thunder duration of. + * @return The thunder duration in ticks. + */ + public abstract int getThunderDuration(KelpWorld world); + + /** + * Gets the unique id of this world. + * + * @param world This world's unique id. + * @return The world's {@link UUID}. + */ + public abstract UUID getUUID(KelpWorld world); + + /** + * Gets the duration of the current weather state in ticks. + * This counts for all weather types (including clear weather) + * + * @param world The world to get the weather duration of. + * @return The current weather duration in ticks. + */ + public abstract int getWeatherDuration(KelpWorld world); + + /** + * Checks whether there is a storm in the given world. + * + * @param world The world you want to check. + * @return {@code true} whether there is currently a storm in the given world. + */ + public abstract boolean hasStorm(KelpWorld world); + + /** + * Checks if auto-save is enabled in this world. + * This would save the world to the server folder in a regular interval. + * + * @param world The world you want to get the auto-save mode of. + * @return {@code true} if auto-save is enabled. + */ + public abstract boolean hasAutoSave(KelpWorld world); + + /** + * Checks whether there is clear weather in the given world. + * If there is clear whether, methods such as {@link #hasStorm(KelpWorld)} + * are automatically {@code false}. + * + * @param world The world you want to check the weather of. + * @return {@code true} whether there is clear weather, {@code false} if there is any thunder or precipitation. + */ + public abstract boolean isWeatherClear(KelpWorld world); + + /** + * Checks if there currently is a thunderstorm in the given world. + * + * @param world The world you want to check the weather of. + * @return {@code true} weather there currently is a thunder in the world. + */ + public abstract boolean isThundering(KelpWorld world); + + /** + * Sets the difficulty of the current world. The difficulty gives an + * indication of how difficult survival gameplay is. {@code Peaceful} means + * players cannot become hungry and monsters won't spawn, while {@code hard} + * says that lots of monsters will spawn and players lose saturation really + * fast. + * + * @param world The world you want to set the difficulty of. + * @param difficulty The difficulty you want to set. + */ + public abstract void setDifficulty(KelpWorld world, Difficulty difficulty); + + /** + * Sets the full time of the current world. The full time is the absolute in-game + * daytime of a world. With this method you can also rewind the time, which could + * eventually break redstone clocks or other scheduled events, which is why + * you might want to use {@link #setTime(KelpWorld, long)} depending on your use-case. + * + * @param world The world to set the full time of. + * @param fullTime The full time to set for this world. + */ + public abstract void setFullTime(KelpWorld world, long fullTime); + + /** + * Sets the spawn location of this world. This is the location, where + * players will spawn when they first join the world. + * + * By default, there is a 30x30 radius around the location, where players + * spawn randomly. + * + * @param world The world you want to set the spawn location of. + * @param location The spawn location you want to set. + */ + public abstract void setSpawnLocation(KelpWorld world, KelpLocation location); + + /** + * Sets the storm state of a world. When set to {@code true} a new storm + * will start or the current storm will continue. + * + * @param world The world you want to set the storm state of. + * @param storm {@code true} if you want to start a storm. {@code false} if you want to end the storm. + */ + public abstract void setStorm(KelpWorld world, boolean storm); + + /** + * Sets the thunder state of a world. When set to {@code true} a new thunder + * will start or the current storm will continue. + * + * @param world The world you want to set the thunder state of. + * @param thunder {@code true} if you want to start a thunderstorm. {@code false} if you want to end the thunder. + */ + public abstract void setThundering(KelpWorld world, boolean thunder); + + /** + * Sets the duration of the current thunderstorm in the world in ticks. + * + * @param world The world you want to set the thunder duration of. + * @param duration The duration in ticks the thunder should take. + */ + public abstract void setThunderDuration(KelpWorld world, int duration); + + /** + * Sets the relative in-game time of the given world. + * If you set a value that is smaller than the current time value, + * the server will automatically skip to the next day. Rewind + * is not possible with this method for safety reasons. If you need + * to do it though, use {@link #setFullTime(KelpWorld, long)}. + * + * @param world The world you want to set the time of. + * @param time The relative in-game time to set. + */ + public abstract void setTime(KelpWorld world, long time); + + /** + * Spawns a lightning in the given world at the given location. This + * lightning will be visible for all players who are in the world and in range. + * + * @param world The world you want to spawn the lightning on. + * @param location The exact location where the lightning should strike. + * @param effect Whether the lightning should just be an effect or be a real lightning. + * A lightning effect does not cause fire, nor damage, nor pigs are transformed to pigmen, etc. + * If you set this to {@code false} a normal lightning will spawn and cause damage to players. + */ + public abstract void strikeLightning(KelpWorld world, KelpLocation location, boolean effect); + +} diff --git a/kelp-sql/pom.xml b/kelp-sql/pom.xml index 49c64d42..0a6f5860 100644 --- a/kelp-sql/pom.xml +++ b/kelp-sql/pom.xml @@ -5,7 +5,7 @@ parent com.github.pxav.kelp - 0.2.0 + 0.3.0 4.0.0 @@ -20,7 +20,7 @@ com.github.pxav.kelp core - 0.2.0 + 0.3.0 provided diff --git a/pom.xml b/pom.xml index 30440dd9..ff12e826 100644 --- a/pom.xml +++ b/pom.xml @@ -7,7 +7,7 @@ com.github.pxav.kelp parent pom - 0.2.0 + 0.3.0 Kelp A cross-version spigot framework to avoid boilerplate code and make your plugin compatible with multiple spigot versions easily diff --git a/testing-module/pom.xml b/testing-module/pom.xml index da89802d..5e6f5a07 100644 --- a/testing-module/pom.xml +++ b/testing-module/pom.xml @@ -5,7 +5,7 @@ parent com.github.pxav.kelp - 0.2.0 + 0.3.0 4.0.0 @@ -45,7 +45,7 @@ com.github.pxav.kelp core - 0.2.0 + 0.3.0 provided diff --git a/v1_8_implementation/pom.xml b/v1_8_implementation/pom.xml index 1a9dc224..14d88634 100644 --- a/v1_8_implementation/pom.xml +++ b/v1_8_implementation/pom.xml @@ -5,7 +5,7 @@ com.github.pxav.kelp parent - 0.2.0 + 0.3.0 4.0.0 @@ -90,7 +90,7 @@ com.github.pxav.kelp core - 0.2.0 + 0.3.0 provided diff --git a/v1_8_implementation/src/main/java/de/pxav/kelp/implementation1_8/entity/VersionedEntity.java b/v1_8_implementation/src/main/java/de/pxav/kelp/implementation1_8/entity/VersionedEntity.java index 8a932bf1..dbd8732c 100644 --- a/v1_8_implementation/src/main/java/de/pxav/kelp/implementation1_8/entity/VersionedEntity.java +++ b/v1_8_implementation/src/main/java/de/pxav/kelp/implementation1_8/entity/VersionedEntity.java @@ -8,6 +8,8 @@ import de.pxav.kelp.core.version.KelpVersion; import de.pxav.kelp.core.version.SinceKelpVersion; import de.pxav.kelp.core.version.Versioned; +import de.pxav.kelp.core.world.KelpLocation; +import de.pxav.kelp.core.world.KelpWorld; import net.minecraft.server.v1_8_R3.Entity; import org.bukkit.Location; import org.bukkit.Server; @@ -46,20 +48,20 @@ public class VersionedEntity extends EntityVersionTemplate { */ @Override public void spawnEntity(KelpEntity entity) { - CraftWorld craftWorld = (CraftWorld) entity.getInitialLocation().getWorld(); + CraftWorld craftWorld = (CraftWorld) entity.getInitialBukkitLocation().getWorld(); if (entity.getEntityType() == KelpEntityType.DROPPED_ITEM) { DroppedItemEntity kelpEntity = (DroppedItemEntity) entity; if (kelpEntity.getItemDropType() == ItemDropType.NATURAL) { - craftWorld.dropItemNaturally(entity.getInitialLocation(), kelpEntity.getItem().getItemStack()); + craftWorld.dropItemNaturally(entity.getInitialBukkitLocation(), kelpEntity.getItem().getItemStack()); } else { - craftWorld.dropItem(entity.getInitialLocation(), kelpEntity.getItem().getItemStack()); + craftWorld.dropItem(entity.getInitialBukkitLocation(), kelpEntity.getItem().getItemStack()); } return; } Entity minecraftEntity = (Entity) entity.getMinecraftEntity(); - ((Entity) entity.getMinecraftEntity()).setPositionRotation(entity.getInitialLocation().getX(), entity.getInitialLocation().getY(), entity.getInitialLocation().getZ(), entity.getInitialLocation().getYaw(), entity.getInitialLocation().getPitch()); + ((Entity) entity.getMinecraftEntity()).setPositionRotation(entity.getInitialBukkitLocation().getX(), entity.getInitialBukkitLocation().getY(), entity.getInitialBukkitLocation().getZ(), entity.getInitialBukkitLocation().getYaw(), entity.getInitialBukkitLocation().getPitch()); craftWorld.addEntity(minecraftEntity, CreatureSpawnEvent.SpawnReason.CUSTOM); } @@ -76,8 +78,8 @@ public org.bukkit.entity.Entity toBukkitEntity(Object minecraftEntity) { * @return The location of the given entity. */ @Override - public Location getLocation(org.bukkit.entity.Entity entity) { - return entity.getLocation(); + public KelpLocation getLocation(org.bukkit.entity.Entity entity) { + return KelpLocation.from(entity.getLocation()); } /** @@ -136,17 +138,6 @@ public boolean isOnGround(org.bukkit.entity.Entity entity) { return entity.isOnGround(); } - /** - * Gets the current world of the entity. - * - * @param entity The entity whose world you want to get. - * @return The world where the entity is currently located. - */ - @Override - public World getWorld(org.bukkit.entity.Entity entity) { - return entity.getWorld(); - } - /** * Sets the rotation of the given entity. This does not * affect the location x, y and z axes. @@ -175,8 +166,8 @@ public void setRotation(org.bukkit.entity.Entity entity, float yaw, float pitch) * @return {@code true} if the teleport action was successful. */ @Override - public boolean teleport(org.bukkit.entity.Entity entity, Location location, PlayerTeleportEvent.TeleportCause teleportCause) { - return entity.teleport(location, teleportCause); + public boolean teleport(org.bukkit.entity.Entity entity, KelpLocation location, PlayerTeleportEvent.TeleportCause teleportCause) { + return entity.teleport(location.getBukkitLocation(), teleportCause); } /** diff --git a/v1_8_implementation/src/main/java/de/pxav/kelp/implementation1_8/entity/VersionedLivingEntity.java b/v1_8_implementation/src/main/java/de/pxav/kelp/implementation1_8/entity/VersionedLivingEntity.java index f3e47ee7..c2652e0d 100644 --- a/v1_8_implementation/src/main/java/de/pxav/kelp/implementation1_8/entity/VersionedLivingEntity.java +++ b/v1_8_implementation/src/main/java/de/pxav/kelp/implementation1_8/entity/VersionedLivingEntity.java @@ -2,6 +2,7 @@ import de.pxav.kelp.core.entity.version.LivingEntityVersionTemplate; import de.pxav.kelp.core.version.Versioned; +import de.pxav.kelp.core.world.KelpLocation; import org.bukkit.Location; import org.bukkit.entity.LivingEntity; @@ -12,8 +13,10 @@ */ @Versioned public class VersionedLivingEntity extends LivingEntityVersionTemplate { + @Override - public Location getEyeLocation(LivingEntity livingEntity) { - return livingEntity.getEyeLocation(); + public KelpLocation getEyeLocation(LivingEntity livingEntity) { + return KelpLocation.from(livingEntity.getEyeLocation()); } + } diff --git a/v1_8_implementation/src/main/java/de/pxav/kelp/implementation1_8/inventory/VersionedItem.java b/v1_8_implementation/src/main/java/de/pxav/kelp/implementation1_8/inventory/VersionedItem.java index 672b826c..2cc75797 100644 --- a/v1_8_implementation/src/main/java/de/pxav/kelp/implementation1_8/inventory/VersionedItem.java +++ b/v1_8_implementation/src/main/java/de/pxav/kelp/implementation1_8/inventory/VersionedItem.java @@ -14,6 +14,8 @@ import org.bukkit.inventory.ItemStack; import org.bukkit.inventory.meta.ItemMeta; +import java.util.List; + /** * A class description goes here. * @@ -81,8 +83,11 @@ public ItemStack setDisplayName(ItemStack itemStack, String displayName) { } @Override - public ItemStack setLore(ItemStack itemStack, String itemLore) { - return null; + public ItemStack setLore(ItemStack itemStack, List itemLore) { + ItemMeta itemMeta = itemStack.getItemMeta(); + itemMeta.setLore(itemLore); + itemStack.setItemMeta(itemMeta); + return itemStack; } @Override diff --git a/v1_8_implementation/src/main/java/de/pxav/kelp/implementation1_8/inventory/VersionedItemTag.java b/v1_8_implementation/src/main/java/de/pxav/kelp/implementation1_8/inventory/VersionedItemTag.java index 3c28d7ae..448892c1 100644 --- a/v1_8_implementation/src/main/java/de/pxav/kelp/implementation1_8/inventory/VersionedItemTag.java +++ b/v1_8_implementation/src/main/java/de/pxav/kelp/implementation1_8/inventory/VersionedItemTag.java @@ -43,7 +43,7 @@ public ItemStack tagItem(ItemStack itemStack, String key, String value) { Preconditions.checkNotNull(value); if (itemStack.getType() == Material.AIR) { this.logAirError(); - return null; + return itemStack; } net.minecraft.server.v1_8_R3.ItemStack nmsItemStack = CraftItemStack.asNMSCopy(itemStack); @@ -58,13 +58,16 @@ public ItemStack tagItem(ItemStack itemStack, String key, String value) { @Override public ItemStack removeTag(ItemStack itemStack, String key) { Preconditions.checkNotNull(itemStack); + if (itemStack.getType() == Material.AIR) { this.logAirError(); - return null; + return itemStack; } net.minecraft.server.v1_8_R3.ItemStack nmsItemStack = CraftItemStack.asNMSCopy(itemStack); - NBTTagCompound nbtTagCompound = nmsItemStack.getTag(); + NBTTagCompound nbtTagCompound = nmsItemStack.getTag() == null + ? new NBTTagCompound() + : nmsItemStack.getTag(); nbtTagCompound.remove(key); return CraftItemStack.asBukkitCopy(nmsItemStack); } diff --git a/v1_8_implementation/src/main/java/de/pxav/kelp/implementation1_8/inventory/VersionedMaterial.java b/v1_8_implementation/src/main/java/de/pxav/kelp/implementation1_8/inventory/VersionedMaterial.java index 2613c335..0b4d2aa6 100644 --- a/v1_8_implementation/src/main/java/de/pxav/kelp/implementation1_8/inventory/VersionedMaterial.java +++ b/v1_8_implementation/src/main/java/de/pxav/kelp/implementation1_8/inventory/VersionedMaterial.java @@ -579,7 +579,7 @@ public void defineDefaults() { materialRepository.addMaterial(KelpMaterial.LAPIS_BLOCK, Material.LAPIS_BLOCK.toString()); materialRepository.addMaterial(KelpMaterial.LAPIS_LAZULI, Material.INK_SACK.toString() + ":4"); materialRepository.addMaterial(KelpMaterial.LAPIS_ORE, Material.LAPIS_ORE.toString()); - materialRepository.addMaterial(KelpMaterial.LARGE_FERN, Material.DOUBLE_PLANT.toString() + ":3"); + materialRepository.addMaterial(KelpMaterial.LARGE_FERN_LOWER, Material.DOUBLE_PLANT.toString() + ":3"); materialRepository.addMaterial(KelpMaterial.LAVA, Material.LAVA.toString()); materialRepository.addMaterial(KelpMaterial.LAVA_BUCKET, Material.LAVA_BUCKET.toString()); materialRepository.addMaterial(KelpMaterial.LEAD, Material.LEASH.toString()); @@ -709,7 +709,7 @@ public void defineDefaults() { materialRepository.addMaterial(KelpMaterial.SUGAR_CANE, Material.SUGAR_CANE.toString()); materialRepository.addMaterial(KelpMaterial.SUGAR_CANE_BLOCK, Material.SUGAR_CANE_BLOCK.toString()); materialRepository.addMaterial(KelpMaterial.SUNFLOWER, Material.DOUBLE_PLANT.toString()); - materialRepository.addMaterial(KelpMaterial.TALL_GRASS, Material.LONG_GRASS.toString()); + materialRepository.addMaterial(KelpMaterial.TALL_GRASS, Material.DOUBLE_PLANT.toString() + ":2"); materialRepository.addMaterial(KelpMaterial.TNT, Material.TNT.toString()); materialRepository.addMaterial(KelpMaterial.TNT_MINECART, Material.EXPLOSIVE_MINECART.toString()); materialRepository.addMaterial(KelpMaterial.TORCH, Material.TORCH.toString()); diff --git a/v1_8_implementation/src/main/java/de/pxav/kelp/implementation1_8/npc/VersionedNpc.java b/v1_8_implementation/src/main/java/de/pxav/kelp/implementation1_8/npc/VersionedNpc.java index 98044f6e..0689e389 100644 --- a/v1_8_implementation/src/main/java/de/pxav/kelp/implementation1_8/npc/VersionedNpc.java +++ b/v1_8_implementation/src/main/java/de/pxav/kelp/implementation1_8/npc/VersionedNpc.java @@ -14,6 +14,7 @@ import de.pxav.kelp.core.npc.version.NpcVersionTemplate; import de.pxav.kelp.core.reflect.ReflectionUtil; import de.pxav.kelp.core.version.Versioned; +import de.pxav.kelp.core.world.KelpLocation; import net.minecraft.server.v1_8_R3.*; import org.bukkit.Bukkit; import org.bukkit.Location; @@ -170,7 +171,7 @@ public void moveRelativeDistance(KelpNpc npc, Player player, double x, double y, } @Override - public void teleport(KelpNpc npc, Location location) { + public void teleport(KelpNpc npc, KelpLocation location) { CraftPlayer craftPlayer = (CraftPlayer) npc.getPlayer().getBukkitPlayer(); PlayerConnection playerConnection = craftPlayer.getHandle().playerConnection; @@ -235,7 +236,8 @@ public void updateCustomName(KelpNpc npc) { @Override public void updateTitleLines(KelpNpc npc) { CraftPlayer player = (CraftPlayer) npc.getPlayer().getBukkitPlayer(); - WorldServer nmsWorld = ((CraftWorld) npc.getLocation().getWorld()).getHandle(); + CraftWorld craftWorld = (CraftWorld) Bukkit.getWorld(npc.getLocation().getWorldName()); + WorldServer nmsWorld = craftWorld.getHandle(); npc.getNpcMeta().getArmorStandEntityIds().forEach(current -> { PacketPlayOutEntityDestroy armorStandDestroyPacket = new PacketPlayOutEntityDestroy(current); @@ -325,7 +327,7 @@ public void makeCorpse(KelpNpc npc) { CraftPlayer player = (CraftPlayer) npc.getPlayer().getBukkitPlayer(); Location bedLocation = new Location( - npc.getLocation().getWorld(), + Bukkit.getWorld(npc.getLocation().getWorldName()), npc.getLocation().getX(), npc.getLocation().getY(), npc.getLocation().getZ()); @@ -345,7 +347,7 @@ public void makeCorpse(KelpNpc npc) { } @Override - public void sleep(KelpNpc npc, Location bedLocation) { + public void sleep(KelpNpc npc, KelpLocation bedLocation) { CraftPlayer player = (CraftPlayer) npc.getPlayer().getBukkitPlayer(); BlockPosition blockPosition = new BlockPosition( @@ -366,8 +368,11 @@ public void wakeUp(KelpNpc npc) { playAnimation(npc, NpcAnimation.LEAVE_BED); if (npc.isCorpse()) { - Material serverMaterial = npc.getLocation().getBlock().getType(); - player.sendBlockChange(npc.getLocation(), serverMaterial, npc.getLocation().getBlock().getData()); + Material serverMaterial = npc.getLocation().getBlock().getBukkitBlock().getType(); + player.sendBlockChange( + npc.getLocation().getBukkitLocation(), + serverMaterial, + npc.getLocation().getBlock().getBukkitBlock().getData()); } // walk back to initial location for a more smooth transition diff --git a/v1_8_implementation/src/main/java/de/pxav/kelp/implementation1_8/player/VersionedPlayer.java b/v1_8_implementation/src/main/java/de/pxav/kelp/implementation1_8/player/VersionedPlayer.java index 2911ee83..bd3fd2e3 100644 --- a/v1_8_implementation/src/main/java/de/pxav/kelp/implementation1_8/player/VersionedPlayer.java +++ b/v1_8_implementation/src/main/java/de/pxav/kelp/implementation1_8/player/VersionedPlayer.java @@ -17,6 +17,7 @@ import de.pxav.kelp.core.sound.KelpSound; import de.pxav.kelp.core.sound.SoundRepository; import de.pxav.kelp.core.version.Versioned; +import de.pxav.kelp.core.world.KelpLocation; import net.md_5.bungee.api.ChatColor; import net.md_5.bungee.api.chat.*; import net.minecraft.server.v1_8_R3.*; @@ -150,7 +151,7 @@ public void sendTabHeaderAndFooter(Player player, String header, String footer) * @param pitch How strong the sound should be pitched. */ @Override - public void playSound(Player player, KelpSound sound, Location location, float volume, float pitch) { + public void playSound(Player player, KelpSound sound, KelpLocation location, float volume, float pitch) { Sound bukkitSound = Sound.valueOf(soundRepository.getSound(sound)); player.playSound(player.getLocation(), bukkitSound, volume, pitch); } @@ -168,17 +169,6 @@ public void setHealth(Player player, int health) { player.setHealth(health); } - /** - * Teleports the player to the given location. - * - * @param player The player you want to teleport. - * @param location The location you want to teleport the player to. - */ - @Override - public void teleport(Player player, Location location) { - player.teleport(location); - } - /** * @param player The player whose UUID you want to get. * @return The player's uuid. @@ -187,15 +177,6 @@ public UUID getUniqueId(Player player) { return player.getUniqueId(); } - /** - * @param player The player whose location you want to get - * @return The location the player is currently at. - */ - @Override - public Location getLocation(Player player) { - return null; - } - /** * Checks if the player is currently located in water. * @@ -378,8 +359,8 @@ public void setPlayerListFooter(Player player, String footer) { * @param target The location, where the compass should point to. */ @Override - public void setCompassTarget(Player player, Location target) { - player.setCompassTarget(target); + public void setCompassTarget(Player player, KelpLocation target) { + player.setCompassTarget(target.getBukkitLocation()); } /** @@ -391,8 +372,8 @@ public void setCompassTarget(Player player, Location target) { * @return The target location of the player's compass. */ @Override - public Location getCompassTarget(Player player) { - return player.getCompassTarget(); + public KelpLocation getCompassTarget(Player player) { + return KelpLocation.from(player.getCompassTarget()); } /** @@ -1135,8 +1116,8 @@ public void setWhitelisted(Player player, boolean whitelisted) { * @return The spawn location, {@code null} if the player has not slept or location is invalid. */ @Override - public Location getBedSpawnLocation(Player player) { - return player.getBedSpawnLocation(); + public KelpLocation getBedSpawnLocation(Player player) { + return KelpLocation.from(player.getBedSpawnLocation()); } @Override diff --git a/v1_8_implementation/src/main/java/de/pxav/kelp/implementation1_8/world/ForcedChunkLoader.java b/v1_8_implementation/src/main/java/de/pxav/kelp/implementation1_8/world/ForcedChunkLoader.java new file mode 100644 index 00000000..1a97f29a --- /dev/null +++ b/v1_8_implementation/src/main/java/de/pxav/kelp/implementation1_8/world/ForcedChunkLoader.java @@ -0,0 +1,154 @@ +package de.pxav.kelp.implementation1_8.world; + +import com.google.common.collect.Sets; +import de.pxav.kelp.core.application.KelpApplication; +import de.pxav.kelp.core.logger.KelpLogger; +import de.pxav.kelp.core.logger.LogLevel; +import de.pxav.kelp.core.scheduler.synchronize.ServerMainThread; +import de.pxav.kelp.core.world.KelpChunk; +import org.bukkit.Chunk; +import org.bukkit.event.EventHandler; +import org.bukkit.event.world.ChunkUnloadEvent; + +import javax.inject.Inject; +import javax.inject.Singleton; +import java.util.Map; +import java.util.Set; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.ConcurrentMap; + +/** + * This class is responsible for keeping chunks loaded if + * they have a flag assigned by {@link KelpChunk#addForceLoadFlag(Class)}. + * + * Minecraft unloads chunks by default to save performance, which prevents + * crops from growing, etc. If you want to disable that, you can use this class. + * + * But you don't have to use it directly, it is recommended to use the + * interface provided by {@link KelpChunk}. + * + * @author pxav + */ +@Singleton +public class ForcedChunkLoader { + + private ConcurrentMap>> forceLoadedChunks = new ConcurrentHashMap<>(); + + private KelpLogger logger; + + @Inject + public ForcedChunkLoader(KelpLogger logger) { + this.logger = logger; + } + + /** + * Adds a chunk to the force load list. This will automatically load the + * chunk if it is not already loaded. + * + * @param plugin The plugin that added this loading flag. + * @param kelpChunk The chunk that should be kept loaded. + */ + public void forceLoadChunk(Class plugin, KelpChunk kelpChunk) { + Chunk chunk = kelpChunk.getBukkitChunk(); + Set> plugins = forceLoadedChunks.getOrDefault(chunk, Sets.newHashSet()); + plugins.add(plugin); + forceLoadedChunks.put(chunk, plugins); + + // load the plugin sync, as it is not thread-safe + ServerMainThread.RunParallel.run(chunk::load); + } + + /** + * Removes a force load flag from the given chunk by a specific plugin. + * The chunk might still be kept loaded if another plugin is loading it. + * + * @param plugin The plugin that removed the load flag. + * @param kelpChunk The chunk that should be removed. + */ + public void removeForceLoadFlag(Class plugin, KelpChunk kelpChunk) { + Chunk chunk = kelpChunk.getBukkitChunk(); + Set> plugins = forceLoadedChunks.getOrDefault(chunk, Sets.newHashSet()); + plugins.remove(plugin); + + // if this was the last plugin keeping that chunk, remove the chunk from the list entirely + if (plugins.isEmpty()) { + forceLoadedChunks.remove(chunk); + return; + } + forceLoadedChunks.put(chunk, plugins); + } + + /** + * Removes all force load flags for a plugin. Then, this plugin won't load + * any chunks anymore. But the chunks might still be loaded if another plugin + * loads them. + * + * @param plugin The plugin to remove from the list and eventually unload the chunks of. + */ + public void removeForceLoadsFor(Class plugin) { + for (Map.Entry>> entry : forceLoadedChunks.entrySet()) { + Set> plugins = entry.getValue(); + + if (plugins.contains(plugin)) { + plugins.remove(plugin); + if (plugins.isEmpty()) { + forceLoadedChunks.remove(entry.getKey()); + } + } + + } + } + + /** + * Gets all chunks that are force loaded by a specific plugin. + * + * @param plugin The plugin you want to get the force loaded chunks of. + * @return The set of chunks that are loaded by the given plugin. + */ + public Set getForceLoadedChunksFor(Class plugin) { + Set output = Sets.newHashSet(); + for (Map.Entry>> entry : forceLoadedChunks.entrySet()) { + Set> plugins = entry.getValue(); + + if (plugins.contains(plugin)) { + output.add(KelpChunk.from(entry.getKey())); + } + + } + return output; + } + + /** + * Gets all plugins force loading a specific {@link KelpChunk}. + * + * @param chunk The chunk whose plugin should be queried. + * @return The plugins loading the given chunk. + */ + public Set> getForceLoadingPluginsOf(KelpChunk chunk) { + return this.forceLoadedChunks.getOrDefault(chunk.getBukkitChunk(), Sets.newHashSet()); + } + + /** + * This listener method is triggered everytime the server + * tries to unload a chunk. + * + * If that chunk is force loaded by any plugin, the unload event + * is canceled and the chunk is kept loaded. + * + * @param event The detailed event data + */ + @EventHandler + public void handleChunkUnload(ChunkUnloadEvent event) { + Chunk chunk = event.getChunk(); + + // if chunk in list, then cancel the event + for (Chunk current : forceLoadedChunks.keySet()) { + if (current.getX() == chunk.getX() && current.getZ() == chunk.getZ()) { + event.setCancelled(true); + logger.log(LogLevel.DEBUG, "Chunk " + chunk.getX() + ":" + chunk.getZ() + " has been prevented from unloading."); + } + } + + } + +} diff --git a/v1_8_implementation/src/main/java/de/pxav/kelp/implementation1_8/world/VersionedBlock.java b/v1_8_implementation/src/main/java/de/pxav/kelp/implementation1_8/world/VersionedBlock.java new file mode 100644 index 00000000..bce35e79 --- /dev/null +++ b/v1_8_implementation/src/main/java/de/pxav/kelp/implementation1_8/world/VersionedBlock.java @@ -0,0 +1,338 @@ +package de.pxav.kelp.implementation1_8.world; + +import de.pxav.kelp.core.common.MathUtils; +import de.pxav.kelp.core.inventory.item.KelpItem; +import de.pxav.kelp.core.inventory.material.KelpMaterial; +import de.pxav.kelp.core.inventory.material.MaterialContainer; +import de.pxav.kelp.core.version.Versioned; +import de.pxav.kelp.core.world.KelpLocation; +import de.pxav.kelp.core.world.version.BlockVersionTemplate; +import de.pxav.kelp.core.world.KelpBlock; +import de.pxav.kelp.core.world.KelpChunk; +import net.minecraft.server.v1_8_R3.BlockPosition; +import net.minecraft.server.v1_8_R3.BlockSapling; +import net.minecraft.server.v1_8_R3.ItemDye; +import net.minecraft.server.v1_8_R3.ItemStack; +import org.bukkit.Material; +import org.bukkit.TreeType; +import org.bukkit.block.Block; +import org.bukkit.block.BlockFace; +import org.bukkit.block.BlockState; +import org.bukkit.craftbukkit.v1_8_R3.CraftWorld; +import org.bukkit.craftbukkit.v1_8_R3.block.CraftBlock; +import org.bukkit.craftbukkit.v1_8_R3.inventory.CraftItemStack; + +import java.util.concurrent.ThreadLocalRandom; + +/** + * This class implements all version-dependent methods + * of a {@link KelpBlock}. + * + * @author pxav + */ +@Versioned +public class VersionedBlock extends BlockVersionTemplate { + + /** + * Gets the {@link KelpChunk} the given block is located in. + * + * @param block The block you want to get the chunk of. + * @return The {@link KelpChunk} the given block is located in. + */ + @Override + public KelpChunk getChunk(KelpBlock block) { + return KelpChunk.from(block.getBukkitBlock().getChunk()); + } + + /** + * Gets the {@link KelpLocation} of the given block. This also + * contains information like the current world. + * + * @param block The block you want to get the location of. + * @return The {@link KelpLocation} of this world. + */ + @Override + public KelpLocation getLocation(KelpBlock block) { + return KelpLocation.from(block.getBukkitBlock().getLocation()); + } + + /** + * Gets the material of the current block. + * + * @param block The block you want to get the material of. + * @return The {@link KelpMaterial} the given block is made of. + */ + @Override + public KelpMaterial getMaterial(KelpBlock block) { + // if block has a sub id + if (block.getBukkitBlock().getData() != 0) { + return KelpMaterial.from(block.getBukkitBlock().getType(), block.getBukkitBlock().getData()); + } + + // if the block has no special data to obey + return KelpMaterial.from(block.getBukkitBlock().getType()); + } + + /** + * Sets the material of the given block to the given {@link KelpMaterial}. + * So if you had an {@link KelpMaterial#AIR} block for example, + * + * @param block The block you want to change the material of. + * @param material The new material you want the block to have. + */ + @Override + public void setMaterial(KelpBlock block, KelpMaterial material) { + MaterialContainer newMaterial = KelpMaterial.convert(material); + block.getBukkitBlock().setType(newMaterial.getBukkitMaterial()); + if (newMaterial.getSubId() != 0) { + block.getBukkitBlock().setData( + (byte) newMaterial.getSubId() + ); + } + } + + /** + * Checks whether bone meal will have an effect on this block. + * This means it will check whether the block is a sapling + * or a plant that will grow if you right click it with bone + * meal. More information about that can be found + * in this wiki + * article + * + * @param block The block you want to check the application state of. + * @return {@code true} whether bone meal is applicable. + */ + @Override + public boolean canApplyBoneMeal(KelpBlock block) { + // kelp material checks! + return false; + } + + /** + * Simulates the application of bone meal on a block. + * This means if the block is a sapling for example, it + * might grow to a tree or grass might spawn random flowers + * and so on. + * + * @param kBlock The block you want to simulate the application of. + * @param blockFace The face of the block to apply the bone meal to. + */ + @Override + public void applyBoneMeal(KelpBlock kBlock, BlockFace blockFace) { + Block block = kBlock.getBukkitBlock(); + + // cause those plant types to grow by 2 to 5 stages. + if (kBlock.getMaterial() == KelpMaterial.POTATOES + || kBlock.getMaterial() == KelpMaterial.CARROTS + || craftBlock(kBlock).getType() == Material.CROPS + || craftBlock(kBlock).getType() == Material.MELON_STEM + || craftBlock(kBlock).getType() == Material.PUMPKIN_STEM) { + + // generate a random growth value between 2 and 5 which + // is equal to the natural bone meal growth + byte data = kBlock.getBukkitBlock().getData(); + byte newData = (byte) (data + ThreadLocalRandom.current().nextInt(2, 5)); + + // a crop can only grow up to age 7, so set + // it back there if the value should exceed that number + if (newData > 7) { + newData = (byte) 7; + } + + // update the block data + kBlock.getBukkitBlock().setData(newData); + return; + } + + // drop item if player clicks on sunflower, lilac, peony or rose bush + if (kBlock.getBukkitBlock().getType() == Material.DOUBLE_PLANT) { + byte data = block.getData(); + KelpMaterial material; + + // if the block is the upper part of a plant, get + // the lower part to fetch the exact item dropped by + // this plant + if (data == 10) { + KelpBlock baseBlock = kBlock.getBlockBelow(); + byte baseData = baseBlock.getBukkitBlock().getData(); + material = getDoublePlantMaterial(baseData); + + // if the block is the lower part of the plant + // immediately get the item material + } else { + material = getDoublePlantMaterial(data); + } + + // if there could not be any material fetched, + // return. + if (material == KelpMaterial.AIR) { + return; + } + + kBlock.getWorld().dropItemNaturally( + kBlock.getLocation(), + KelpItem.create() + .material(material) + .allowInteractions() + ); + return; + } + + // grow small fern and grass to tall gras and large fern + if (kBlock.getBukkitBlock().getType() == Material.LONG_GRASS + && kBlock.getBlockAbove().getMaterial() == KelpMaterial.AIR) { + + byte data = kBlock.getBukkitBlock().getData(); + + // if it is small grass, build both tall grass blocks + if (data == 1) { + block.setType(Material.DOUBLE_PLANT); + block.setData((byte) 2); + + Block above = kBlock.getBlockAbove().getBukkitBlock(); + above.setType(Material.DOUBLE_PLANT); + above.setData((byte) 10); + } + + // if it is small fern, build both large fern blocks + if (data == 2) { + block.setType(Material.DOUBLE_PLANT); + block.setData((byte) 3); + + Block above = kBlock.getBlockAbove().getBukkitBlock(); + above.setType(Material.DOUBLE_PLANT); + above.setData((byte) 10); + } + return; + } + + // if a player clicks on a sapling there is a 45% chance + // that it will grow by 1 stage. When it is on the second stage, + // a real tree will grow + if (kBlock.getBukkitBlock().getType() == Material.SAPLING) { + byte data = kBlock.getBukkitBlock().getData(); + boolean grow = MathUtils.perCentChance(45); + + if (!grow) { + return; + } + + boolean spawned = false; + + // if the id is below 8, the tree is too young to + // grow immediately, so it is set to the next growth stage. + + // if the id is higher than or equal to 8, try spawning + // the tree + switch (data) { + case 0: // oak + kBlock.getBukkitBlock().setData((byte) 8); + break; + case 1: // spruce + kBlock.getBukkitBlock().setData((byte) 9); + break; + case 2: // birch + kBlock.getBukkitBlock().setData((byte) 10); + break; + case 3: // jungle + kBlock.getBukkitBlock().setData((byte) 11); + break; + case 4: // acacia + kBlock.getBukkitBlock().setData((byte) 12); + break; + case 5: // dark oak + kBlock.getBukkitBlock().setData((byte) 13); + break; + case 8: + block.setType(Material.AIR); + TreeType type = MathUtils.perCentChance(10) ? TreeType.BIG_TREE : TreeType.TREE; + spawned = kBlock.getBukkitBlock().getWorld().generateTree(kBlock.getBukkitBlock().getLocation(), type); + break; + case 9: + block.setType(Material.AIR); + spawned = kBlock.getBukkitBlock().getWorld().generateTree(kBlock.getBukkitBlock().getLocation(), TreeType.REDWOOD); + break; + case 10: + block.setType(Material.AIR); + spawned = kBlock.getBukkitBlock().getWorld().generateTree(kBlock.getBukkitBlock().getLocation(), TreeType.BIRCH); + break; + case 11: + block.setType(Material.AIR); + spawned = kBlock.getBukkitBlock().getWorld().generateTree(kBlock.getBukkitBlock().getLocation(), TreeType.JUNGLE); + break; + case 12: + block.setType(Material.AIR); + spawned = kBlock.getBukkitBlock().getWorld().generateTree(kBlock.getBukkitBlock().getLocation(), TreeType.ACACIA); + break; + case 13: + block.setType(Material.AIR); + spawned = kBlock.getBukkitBlock().getWorld().generateTree(kBlock.getBukkitBlock().getLocation(), TreeType.DARK_OAK); + break; + } + + // if there was not enough space for the tree, reset it + if (!spawned && data >= 8) { + block.setType(Material.SAPLING); + block.setData(data); + } + + return; + } + + // default case when player interacts with grass + // for example + ItemStack nmsStack = CraftItemStack.asNMSCopy( + new org.bukkit.inventory.ItemStack(Material.INK_SACK, 1, (short) 15) + ); + + CraftWorld world = (CraftWorld) kBlock.getWorld().getBukkitWorld(); + + ItemDye.a(nmsStack, world.getHandle(), + new BlockPosition( + kBlock.getX(), + kBlock.getY() - 1, + kBlock.getZ() + ) + ); + + } + + /** + * Converts the block data of a flower type double plant and converts + * it to the corresponding item which is dropped by the plant. + * + * @param data The data of the plant (either sub id of an item or + * data of a block) + * @return The kelp material of the plant. + */ + private KelpMaterial getDoublePlantMaterial(byte data) { + KelpMaterial material = KelpMaterial.AIR; + switch (data) { + case 0: + material = KelpMaterial.SUNFLOWER; + break; + case 1: + material = KelpMaterial.LILAC; + break; + case 4: + material = KelpMaterial.ROSE_BUSH; + break; + case 5: + material = KelpMaterial.PEONY; + break; + } + return material; + } + + /** + * Converts the given kelp block to a craftbukkit + * block allowing for better NMS integration. + * + * @param block The block to convert + * @return The final craftbukkit block + */ + private CraftBlock craftBlock(KelpBlock block) { + return (CraftBlock) block.getBukkitBlock(); + } + +} diff --git a/v1_8_implementation/src/main/java/de/pxav/kelp/implementation1_8/world/VersionedChunk.java b/v1_8_implementation/src/main/java/de/pxav/kelp/implementation1_8/world/VersionedChunk.java new file mode 100644 index 00000000..e387bb23 --- /dev/null +++ b/v1_8_implementation/src/main/java/de/pxav/kelp/implementation1_8/world/VersionedChunk.java @@ -0,0 +1,263 @@ +package de.pxav.kelp.implementation1_8.world; + +import com.google.common.collect.Lists; +import de.pxav.kelp.core.application.KelpApplication; +import de.pxav.kelp.core.inventory.material.KelpMaterial; +import de.pxav.kelp.core.player.KelpPlayer; +import de.pxav.kelp.core.version.Versioned; +import de.pxav.kelp.core.world.*; +import de.pxav.kelp.core.world.version.ChunkVersionTemplate; +import net.minecraft.server.v1_8_R3.EntityHuman; +import org.bukkit.craftbukkit.v1_8_R3.CraftChunk; +import org.bukkit.entity.Player; + +import javax.inject.Inject; +import javax.inject.Singleton; +import java.util.*; + +/** + * Implements all version-dependent operations of {@link KelpChunk}. + * + * @author pxav + */ +@Versioned +@Singleton +public class VersionedChunk extends ChunkVersionTemplate { + + @Inject private ForcedChunkLoader forcedChunkLoader; + + /** + * Determines whether the given location is inside the given chunk. + * + * @param chunk The chunk you want to check. + * @param location The location that should be located in the chunk. + * @return {@code true} if the location is inside the chunk. + */ + @Override + public boolean contains(KelpChunk chunk, KelpLocation location) { + KelpLocation firstPosition = chunk.getNorthEasternBlock(0).getLocation(); + KelpLocation secondPosition = chunk.getSouthWesternBlock(256).getLocation(); + + double maxX = Math.max(firstPosition.getX(), secondPosition.getX()); + double minX = Math.min(firstPosition.getX(), secondPosition.getX()); + + double maxY = Math.max(firstPosition.getY(), secondPosition.getY()); + double minY = Math.min(firstPosition.getY(), secondPosition.getY()); + + double maxZ = Math.max(firstPosition.getZ(), secondPosition.getZ()); + double minZ = Math.min(firstPosition.getZ(), secondPosition.getZ()); + + if(location.getX() <= maxX && location.getX() >= minX) { + if(location.getY() <= maxY && location.getY() >= minY) + return location.getZ() <= maxZ && location.getZ() >= minZ; + } + + return false; + } + + /** + * Gets a block at the given location inside the chunk. The passed location + * may not be from outside the chunk! + * + * @param chunk The chunk you want to get a block of. + * @param location The location where the block is located. + * @return The block at the given location. + */ + @Override + public KelpBlock getBlockAt(KelpChunk chunk, KelpLocation location) { + return KelpBlock.from(chunk.getBukkitChunk().getBlock( + location.getBlockX(), + location.getBlockY(), + location.getBlockZ()) + ); + } + + /** + * Gets a collection of all players that are currently inside the chunk. + * + * @param chunk The chunk you want to get the players of. + * @return A collection of all players that are currently inside the chunk. + */ + @Override + public Collection getPlayers(KelpChunk chunk) { + Collection output = Lists.newArrayList(); + net.minecraft.server.v1_8_R3.Chunk nmsChunk = craftChunk(chunk).getHandle(); + + Arrays.stream(nmsChunk.entitySlices).forEach(entitySlice -> { + Object[] entityArray = entitySlice.toArray(); + + for (Object entityObject : entityArray) { + if (entityObject instanceof EntityHuman) { + EntityHuman entityHuman = (EntityHuman) entityObject; + Player player = (Player) entityHuman.getBukkitEntity(); + KelpPlayer kelpPlayer = KelpPlayer.from(player); + output.add(kelpPlayer); + } + } + + }); + + return output; + } + + /** + * Gets the world the given chunk is located in. + * + * @param chunk The chunk you want to get the world of. + * @return The world the given chunk is located in. + */ + @Override + public KelpWorld getWorld(KelpChunk chunk) { + return KelpWorld.from(chunk.getBukkitChunk().getWorld()); + } + + /** + * Gets the X-coordinate in the chunk's world of the given + * chunk. This can be used to compare and identify chunks. + * + * Note that this value does not return the absolute X-block- + * coordinate where the chunk begins, but it returns the X + * block value divided by 16. If you are at x=-35, then this + * would return 2, because it is bigger than 32 (16*2) but smaller + * than 48 (16*3). + * + * @param chunk The chunk you want to get the X-coordinate of. + * @return The X coordinate on the world's chunk grid. + */ + @Override + public int getX(KelpChunk chunk) { + return chunk.getBukkitChunk().getX(); + } + + /** + * Gets the Z-coordinate in the chunk's world of the given + * chunk. This can be used to compare and identify chunks. + * + * Note that this value does not return the absolute Z-block- + * coordinate where the chunk begins, but it returns the Z + * block value divided by 16. If you are at z=-35, then this + * would return 2, because it is bigger than 32 (16*2) but smaller + * than 48 (16*3). + * + * @param chunk The chunk you want to get the Z-coordinate of. + * @return The Z coordinate on the world's chunk grid. + */ + @Override + public int getZ(KelpChunk chunk) { + return chunk.getBukkitChunk().getZ(); + } + + /** + * Checks whether the given chunk is currently loaded. That + * means it checks whether tick operations are currently ran on + * this chunk. + * + * @param chunk The chunk you want to check if its loaded. + * @return {@code true} if the chunk is currently loaded. + */ + @Override + public boolean isLoaded(KelpChunk chunk) { + return chunk.getBukkitChunk().isLoaded(); + } + + /** + * Loads the chunk. This will make the chunk passable for players and + * tick operations such as redstone clocks or crop growing will be performed + * again. To save performance, chunks are unloaded by bukkit if they are not used. + * If you need to prevent that you can use {@link #addForceLoadFlag(KelpChunk, Class)} + * to keep the chunk loaded until you manually unload it again. But please + * keep in mind that this might have bad performance impact. + * + * @param chunk The chunk to be loaded. If the chunk has not been loaded before, + * the world will be generated at that point. + */ + @Override + public void load(KelpChunk chunk) { + chunk.getBukkitChunk().load(); + } + + /** + * Unloads the chunk. That means tick operations in this + * chunk will no longer be performed. Redstone clocks will stop running + * and crops will stop growing. This can be reversed at any time using + * {@link #load(KelpChunk)}. + * + * @param chunk The chunk you want to unload. + */ + @Override + public void unload(KelpChunk chunk) { + chunk.getBukkitChunk().unload(); + } + + /** + * Adds a force load flag to the given chunk. A force load flag means that + * the chunk cannot be unloaded by bukkit randomly, but keeps loaded until you + * revert that action by yourself ({@link #removeForceLoadFlag(KelpChunk, Class)}). + * + * If you call this method, {@link #unload(KelpChunk)} won't have an effect + * as Kelp will immediately load the chunk again if there is a flag to keep + * it loaded. So if you want to unload the chunk, call {@link #removeForceLoadFlag(KelpChunk, Class)} + * first. + * + * @param chunk The chunk you want to keep loaded. + * @param plugin The plugin that should keep the chunk loaded. + * This is important when you remove your flag, + * but another plugin still relies on the chunk to be loaded, + * the chunk will be kept loaded until the other plugin(s) + * unload the chunk as well. + */ + @Override + public void addForceLoadFlag(KelpChunk chunk, Class plugin) { + forcedChunkLoader.forceLoadChunk(plugin, chunk); + } + + /** + * Removes a force load flag from the given chunk again. A force load flag means that + * the chunk cannot be unloaded by bukkit randomly, but keeps loaded until you + * revert that action by yourself using this method. Such a flag can be assigned + * to a chunk using {@link #addForceLoadFlag(KelpChunk, Class)}. + * + * @param chunk The chunk to remove the force load flag of. + * @param plugin The plugin that removes their flag. If another plugin has still + * loaded this chunk, it won't be unloaded until all plugins have removed + * their flag. + */ + @Override + public void removeForceLoadFlag(KelpChunk chunk, Class plugin) { + forcedChunkLoader.removeForceLoadFlag(plugin, chunk); + } + + /** + * Gets all {@link KelpApplication}s that are currently forcing this chunk to keep loaded. + * + * @param chunk The chunk you want to get the plugins of. + * @return A set of all plugin main classes that force the chunk to keep loaded. + */ + @Override + public Set> getForceLoadFlagPlugins(KelpChunk chunk) { + return forcedChunkLoader.getForceLoadingPluginsOf(chunk); + } + + /** + * Determines whether slime entities can spawn in the given chunk. + * + * @param chunk The chunk you want to check. + * @return {@code true} if slime entities can spawn in that chunk. + */ + @Override + public boolean isSlimeChunk(KelpChunk chunk) { + long seed = chunk.getBukkitChunk().getWorld().getSeed(); + int x = chunk.getX(); + int y = chunk.getZ(); + return (new Random( + seed + + ((long) x * x * 4987142) + (x * 5947611L) + + (long) y * y * 4392871L + (y * 389711L) ^ 987234911L) + ).nextInt(10) == 0; + } + + private CraftChunk craftChunk(KelpChunk chunk) { + return (CraftChunk) chunk.getBukkitChunk(); + } + +} diff --git a/v1_8_implementation/src/main/java/de/pxav/kelp/implementation1_8/world/VersionedLocation.java b/v1_8_implementation/src/main/java/de/pxav/kelp/implementation1_8/world/VersionedLocation.java new file mode 100644 index 00000000..1b9e6fee --- /dev/null +++ b/v1_8_implementation/src/main/java/de/pxav/kelp/implementation1_8/world/VersionedLocation.java @@ -0,0 +1,8 @@ +package de.pxav.kelp.implementation1_8.world; + +import de.pxav.kelp.core.version.Versioned; +import de.pxav.kelp.core.world.version.LocationVersionTemplate; + +@Versioned +public class VersionedLocation extends LocationVersionTemplate { +} diff --git a/v1_8_implementation/src/main/java/de/pxav/kelp/implementation1_8/world/VersionedWorld.java b/v1_8_implementation/src/main/java/de/pxav/kelp/implementation1_8/world/VersionedWorld.java new file mode 100644 index 00000000..1cfce2e2 --- /dev/null +++ b/v1_8_implementation/src/main/java/de/pxav/kelp/implementation1_8/world/VersionedWorld.java @@ -0,0 +1,603 @@ +package de.pxav.kelp.implementation1_8.world; + +import com.google.common.collect.Lists; +import de.pxav.kelp.core.entity.type.DroppedItemEntity; +import de.pxav.kelp.core.entity.type.ItemDropType; +import de.pxav.kelp.core.inventory.item.KelpItem; +import de.pxav.kelp.core.player.KelpPlayer; +import de.pxav.kelp.core.version.Versioned; +import de.pxav.kelp.core.world.*; +import de.pxav.kelp.core.world.util.ExplosionPower; +import de.pxav.kelp.core.world.util.WorldType; +import de.pxav.kelp.core.world.version.WorldVersionTemplate; +import net.minecraft.server.v1_8_R3.EntityHuman; +import org.bukkit.Chunk; +import org.bukkit.Difficulty; +import org.bukkit.Location; +import org.bukkit.World; +import org.bukkit.craftbukkit.v1_8_R3.CraftWorld; +import org.bukkit.entity.HumanEntity; +import org.bukkit.entity.Player; + +import java.util.Collection; +import java.util.UUID; + +/** + * This class implements all version-dependent methods + * of {@link KelpWorld}. + * + * @author pxav + */ +@Versioned +public class VersionedWorld extends WorldVersionTemplate { + + /** + * Gets the spawn location of the given world. The spawn location is the + * center of a radius in which players can spawn when initially joining a + * new world. + * + * @param world The world you want to get the spawn location of. + * @return The world's spawn location. + */ + @Override + public KelpLocation getSpawnLocation(KelpWorld world) { + return KelpLocation.from(world.getBukkitWorld().getSpawnLocation()); + } + + /** + * Gets the chunk at the given location. A chunk is a 16x16x(256 or 320 depending on your version) + * cuboid region minecraft uses to generate and handle the world. More information + * can be found in {@link KelpChunk}. + * + * @param world The world from which you want to get the chunk of. + * @param location The location where the chunk is located. + * @return The chunk in the given world at the given location. + */ + @Override + public KelpChunk getChunkAt(KelpWorld world, KelpLocation location) { + return KelpChunk.from(world.getBukkitWorld().getChunkAt(location.getBukkitLocation())); + } + + /** + * Checks whether this world is able to generate structures such as temples or villages. + * + * @param world The world you want to check. + * @return {@code true} if the world can generate structures + */ + @Override + public boolean canGenerateStructures(KelpWorld world) { + return world.getBukkitWorld().canGenerateStructures(); + } + + /** + * Creates an explosion at the given location. + * + * @param world The world to spawn the explosion at. + * @param location The center of the explosion + * @param power The power of the explosion. More information on explosion powers can be + * found in {@link ExplosionPower} + * @param breakBlocks Whether the explosion should be able to break blocks. + * @param igniteFire Whether the explosion should be able to ignite a fire within its radius. + */ + @Override + public void createExplosion(KelpWorld world, KelpLocation location, ExplosionPower power, boolean breakBlocks, boolean igniteFire) { + Location bukkitLocation = location.getBukkitLocation(); + world.getBukkitWorld().createExplosion( + bukkitLocation.getX(), + bukkitLocation.getY(), + bukkitLocation.getZ(), + power.getPower(), + igniteFire, + breakBlocks + ); + } + + /** + * Drops an item on the given world at the given location. This item will be + * visible for all players on the world. + * + * @param world The world to spawn the item on. + * @param location The location from where the item should drop + * @param item The item you want to drop + * @param dropType Whether the item should be dropped normally or naturally. + * More information about the difference can be found in {@link ItemDropType} + * @return The item entity that has been spawned at the given location. + */ + @Override + public DroppedItemEntity dropItem(KelpWorld world, KelpLocation location, KelpItem item, ItemDropType dropType) { + if (dropType == ItemDropType.NORMAL) { + return DroppedItemEntity.from(world.getBukkitWorld() + .dropItem( + location.getBukkitLocation(), + item.getItemStack()) + ); + } else if (dropType == ItemDropType.NATURAL) { + return DroppedItemEntity.from(world.getBukkitWorld() + .dropItemNaturally( + location.getBukkitLocation(), + item.getItemStack()) + ); + } + return null; + } + + /** + * Checks whether animals can spawn naturally on this world. + * + * @param world The world you want to check animal spawns for. + * @return {@code true} if animals can spawn by default. + */ + @Override + public boolean animalsAllowed(KelpWorld world) { + return world.getBukkitWorld().getAllowAnimals(); + } + + /** + * Checks whether monsters such as creepers or zombies + * can spawn naturally on this world. + * + * @param world The world you want to check monster spawns for. + * @return {@code true} if monsters can spawn by default. + */ + @Override + public boolean monstersAllowed(KelpWorld world) { + return world.getBukkitWorld().getAllowMonsters(); + } + + /** + * Gets the {@link KelpBlock} at the given location of the world. + * + * @param world The world you want to get the block of. + * @param location The exact location of the block you want to get. + * @return The {@link KelpBlock} object at the given location. + * If the block's material is {@link de.pxav.kelp.core.inventory.material.KelpMaterial#AIR}, + * the block won't be {@code null} but of type {@code AIR} + */ + @Override + public KelpBlock getBlockAt(KelpWorld world, KelpLocation location) { + return KelpBlock.from(world.getBukkitWorld().getBlockAt(location.getBukkitLocation())); + } + + /** + * Gets the highest block at a given location. + * + * @param world The world to check the location in. + * @param location The location containing the y-axis for the block check. + * @return The highest {@link KelpBlock} at the provided location of the world. + */ + @Override + public KelpBlock getHighestBlockAt(KelpWorld world, KelpLocation location) { + return KelpBlock.from(world.getBukkitWorld().getHighestBlockAt(location.getBukkitLocation())); + } + + /** + * Gets a collection of all {@link KelpChunk}s that are currently loaded in + * the given world. This includes forced loaded chunks as well as naturally loaded chunks. + * + * @param world The world to get the loaded chunks of. + * @return The collection of all chunks currently loaded in this world. + */ + @Override + public Collection getLoadedChunks(KelpWorld world) { + Collection chunks = Lists.newArrayList(); + for (Chunk loadedChunk : world.getBukkitWorld().getLoadedChunks()) { + chunks.add(KelpChunk.from(loadedChunk)); + } + return chunks; + } + + /** + * Gets the difficulty of the current world. The difficulty gives an + * indication of how difficult survival gameplay is. {@code Peaceful} means + * players cannot become hungry and monsters won't spawn, while {@code hard} + * says that lots of monsters will spawn and players lose saturation really + * fast. + * + * @param world The world to get the difficulty of. + * @return The current difficulty of the world. + */ + @Override + public Difficulty getDifficulty(KelpWorld world) { + return world.getBukkitWorld().getDifficulty(); + } + + /** + * Gets the environment/world type. The world type says which dimension + * is currently used by the world generator. This also affects the fog effects + * of the world. While in overworld ({@code NORMAL}) there won't be any + * special background or fog, the background of an {@code END} world + * will be purple for example. + * + * This is similar to the {@code #getEnvironment()} method of a normal + * bukkit world. + * + * @param world The world to get the world type of. + * @return The type of the given world. + */ + @Override + public WorldType getWorldType(KelpWorld world) { + World.Environment environment = world.getBukkitWorld().getEnvironment(); + switch (environment) { + case NETHER: + return WorldType.THE_NETHER; + case THE_END: + return WorldType.THE_END; + case NORMAL: + return WorldType.NORMAL; + } + return WorldType.NORMAL; + } + + /** + * Gets the humidity at a specific location in the world. + * The humidity is dependent on the biome at the given location. + * + * @param world The world you want to check the humidity in. + * @param location The location to check the humidity at. + * @return The humidity value at the given location in the given world. + */ + @Override + public double getHumidityAt(KelpWorld world, KelpLocation location) { + return world.getBukkitWorld().getHumidity((int) location.getX(), (int) location.getZ()); + } + + /** + * Checks whether the spawn chunks should be kept in memory although + * no players are currently in the spawn area. + * + * @param world The world you want to check the spawn chunks of. + * @return {@code true} if the spawn chunks are kept in memory. + */ + @Override + public boolean shouldKeepSpawnInMemory(KelpWorld world) { + return world.getBukkitWorld().getKeepSpawnInMemory(); + } + + /** + * Changes whether the spawn chunks of a world should be kept + * loaded/in memory although no players are currently in the spawn + * area. This might improve performance if you are commonly spawning + * new players to a world (such as in a server lobby). + * + * @param world The world you want to set the spawn chunk settings of. + * @param keep {@code true} if the spawn chunks should be kept loaded over the server runtime. + */ + @Override + public void setKeepSpawnInMemory(KelpWorld world, boolean keep) { + world.getBukkitWorld().setKeepSpawnInMemory(keep); + } + + /** + * Gets the full time of a world. This is the full absolute + * in-game daytime. + * + * @param world The world you want to get the time of. + * @return The full (absolute) time of the given world. + */ + @Override + public long getFullTime(KelpWorld world) { + return world.getBukkitWorld().getFullTime(); + } + + /** + * Gets the full in-game time of this world from the initial world generation. + * + * @param world The world you want to get the game time of. + * @return The game time that has passed on the given world. + */ + @Override + public long getGameTime(KelpWorld world) { + return ((CraftWorld)world.getBukkitWorld()).getHandle().getWorldData().getTime(); + } + + /** + * Gets the relative in-game time of the given world. + * This time is calculated from the world's full time: + * {@code #getFullTime() % 24000L}. + * + * This time cannot be negative. If a negative value is returned from + * the above operation, a full day will be added to this value. + * + * @param world The world you want to get the relative time of. + * @return The relative in-game time of the given world. + */ + @Override + public long getTime(KelpWorld world) { + return world.getBukkitWorld().getTime(); + } + + /** + * Gets all players that are currently on the given world. + * + * @param world The world you want to get the players of. + * @return A collection of all players that are currently on this world. + */ + @Override + public Collection getPlayers(KelpWorld world) { + Collection output = Lists.newArrayList(); + for (EntityHuman human : craftWorld(world).getHandle().players) { + HumanEntity bukkitEntity = human.getBukkitEntity(); + if (bukkitEntity instanceof Player) { + output.add(KelpPlayer.from((Player) bukkitEntity)); + } + } + return output; + } + + /** + * Gets the current pvp mode of the world. If players are allowed to + * fight each other, this will return {@code true}. + * + * @param world The world you want to check the pvp setting of. + * @return {@code true} if pvp is enabled on this world. + */ + @Override + public boolean isPvPEnabled(KelpWorld world) { + return world.getBukkitWorld().getPVP(); + } + + /** + * Sets the current pvp mode of the world. If players should be allowed + * to fight each other, set this to {@code true}. + * + * @param world The world you want to set the pvp setting of. + * @param pvp {@code true} if players should be able to fight each other. + */ + @Override + public void setPVP(KelpWorld world, boolean pvp) { + world.getBukkitWorld().setPVP(pvp); + } + + /** + * Gets the seed of the world generator of this world. A seed is a constant value + * used by the {@code Perlin noise} world generation algorithm to randomize + * biomes and structures. Seeds are usually compatible across different versions, but + * might not output the same result in differing versions due to new structures and + * biomes that have been added over time. + * + * @param world The world you want to get the seed of. + * @return The world's seed. + */ + @Override + public long getSeed(KelpWorld world) { + return world.getBukkitWorld().getSeed(); + } + + /** + * Gets the sea level of this world. + * + * @param world The world you want to get the sea level of. + * @return The world's sea level. + */ + @Override + public int getSeaLevel(KelpWorld world) { + return world.getBukkitWorld().getSeaLevel(); + } + + /** + * Gets the temperature of a world at a specific location. + * Temperatures are depending on the biome and exact location (height specifically). + * A temperature indicates whether it can snow or rain at a specific location + * or whether there is any precipitation at all. The desert biome for example + * has a relatively high temperature of {@code 2.0d}, while Snowy Beach has {@code 0.05} + * for example. + * + * @param world The world you want to get the temperature in. + * @param location The location you want to get the temperature at. + * @return The exact temperature at the given location in the current world. + */ + @Override + public double getTemperatureAt(KelpWorld world, KelpLocation location) { + return world.getBukkitWorld().getTemperature((int) location.getX(), (int) location.getZ()); + } + + /** + * Gets the duration of the current thunder in the world in ticks. + * + * @param world The world you want to get the thunder duration of. + * @return The thunder duration in ticks. + */ + @Override + public int getThunderDuration(KelpWorld world) { + return world.getBukkitWorld().getThunderDuration(); + } + + /** + * Gets the unique id of this world. + * + * @param world This world's unique id. + * @return The world's {@link UUID}. + */ + @Override + public UUID getUUID(KelpWorld world) { + return world.getBukkitWorld().getUID(); + } + + /** + * Gets the duration of the current weather state in ticks. + * This counts for all weather types (including clear weather) + * + * @param world The world to get the weather duration of. + * @return The current weather duration in ticks. + */ + @Override + public int getWeatherDuration(KelpWorld world) { + return world.getBukkitWorld().getWeatherDuration(); + } + + /** + * Checks whether there is a storm in the given world. + * + * @param world The world you want to check. + * @return {@code true} whether there is currently a storm in the given world. + */ + @Override + public boolean hasStorm(KelpWorld world) { + return world.getBukkitWorld().hasStorm(); + } + + /** + * Checks if auto-save is enabled in this world. + * This would save the world to the server folder in a regular interval. + * + * @param world The world you want to get the auto-save mode of. + * @return {@code true} if auto-save is enabled. + */ + @Override + public boolean hasAutoSave(KelpWorld world) { + return world.getBukkitWorld().isAutoSave(); + } + + /** + * Checks whether there is clear weather in the given world. + * If there is clear whether, methods such as {@link #hasStorm(KelpWorld)} + * are automatically {@code false}. + * + * @param world The world you want to check the weather of. + * @return {@code true} whether there is clear weather, {@code false} if there is any thunder or precipitation. + */ + @Override + public boolean isWeatherClear(KelpWorld world) { + return !(world.getBukkitWorld().hasStorm() || world.getBukkitWorld().isThundering()); + } + + /** + * Checks if there currently is a thunderstorm in the given world. + * + * @param world The world you want to check the weather of. + * @return {@code true} weather there currently is a thunder in the world. + */ + @Override + public boolean isThundering(KelpWorld world) { + return world.getBukkitWorld().isThundering(); + } + + /** + * Sets the difficulty of the current world. The difficulty gives an + * indication of how difficult survival gameplay is. {@code Peaceful} means + * players cannot become hungry and monsters won't spawn, while {@code hard} + * says that lots of monsters will spawn and players lose saturation really + * fast. + * + * @param world The world you want to set the difficulty of. + * @param difficulty The difficulty you want to set. + */ + @Override + public void setDifficulty(KelpWorld world, Difficulty difficulty) { + world.getBukkitWorld().setDifficulty(difficulty); + } + + /** + * Sets the full time of the current world. The full time is the absolute in-game + * daytime of a world. With this method you can also rewind the time, which could + * eventually break redstone clocks or other scheduled events, which is why + * you might want to use {@link #setTime(KelpWorld, long)} depending on your use-case. + * + * @param world The world to set the full time of. + * @param fullTime The full time to set for this world. + */ + @Override + public void setFullTime(KelpWorld world, long fullTime) { + world.getBukkitWorld().setFullTime(fullTime); + } + + /** + * Sets the spawn location of this world. This is the location, where + * players will spawn when they first join the world. + * + * By default, there is a 30x30 radius around the location, where players + * spawn randomly. + * + * @param world The world you want to set the spawn location of. + * @param location The spawn location you want to set. + */ + @Override + public void setSpawnLocation(KelpWorld world, KelpLocation location) { + world.getBukkitWorld().setSpawnLocation( + location.getBukkitLocation().getBlockX(), + location.getBukkitLocation().getBlockY(), + location.getBukkitLocation().getBlockZ() + ); + } + + /** + * Sets the storm state of a world. When set to {@code true} a new storm + * will start or the current storm will continue. + * + * @param world The world you want to set the storm state of. + * @param storm {@code true} if you want to start a storm. {@code false} if you want to end the storm. + */ + @Override + public void setStorm(KelpWorld world, boolean storm) { + world.getBukkitWorld().setStorm(storm); + } + + /** + * Sets the thunder state of a world. When set to {@code true} a new thunder + * will start or the current storm will continue. + * + * @param world The world you want to set the thunder state of. + * @param thunder {@code true} if you want to start a thunderstorm. {@code false} if you want to end the thunder. + */ + @Override + public void setThundering(KelpWorld world, boolean thunder) { + world.getBukkitWorld().setThundering(thunder); + } + + /** + * Sets the duration of the current thunderstorm in the world in ticks. + * + * @param world The world you want to set the thunder duration of. + * @param duration The duration in ticks the thunder should take. + */ + @Override + public void setThunderDuration(KelpWorld world, int duration) { + world.getBukkitWorld().setThunderDuration(duration); + } + + /** + * Sets the relative in-game time of the given world. + * If you set a value that is smaller than the current time value, + * the server will automatically skip to the next day. Rewind + * is not possible with this method for safety reasons. If you need + * to do it though, use {@link #setFullTime(KelpWorld, long)}. + * + * @param world The world you want to set the time of. + * @param time The relative in-game time to set. + */ + @Override + public void setTime(KelpWorld world, long time) { + world.getBukkitWorld().setTime(time); + } + + /** + * Spawns a lightning in the given world at the given location. This + * lightning will be visible for all players who are in the world and in range. + * + * @param world The world you want to spawn the lightning on. + * @param location The exact location where the lightning should strike. + * @param effect Whether the lightning should just be an effect or be a real lightning. + * A lightning effect does not cause fire, nor damage, nor pigs are transformed to pigmen, etc. + * If you set this to {@code false} a normal lightning will spawn and cause damage to players. + */ + @Override + public void strikeLightning(KelpWorld world, KelpLocation location, boolean effect) { + if (effect) { + world.getBukkitWorld().strikeLightningEffect(location.getBukkitLocation()); + return; + } + world.getBukkitWorld().strikeLightning(location.getBukkitLocation()); + } + + /** + * Converts a given {@link KelpWorld} to a craft bukkit world. + * This allows easier NMS integration into the implementation. + * + * @param world The world to convert. + * @return The final craft world + */ + private CraftWorld craftWorld(KelpWorld world) { + return ((CraftWorld) world.getBukkitWorld()); + } + +} diff --git a/v_1_14_implementation/pom.xml b/v_1_14_implementation/pom.xml index 6d7cc8f1..cf257cab 100644 --- a/v_1_14_implementation/pom.xml +++ b/v_1_14_implementation/pom.xml @@ -5,7 +5,7 @@ parent com.github.pxav.kelp - 0.2.0 + 0.3.0 4.0.0 @@ -57,7 +57,7 @@ com.github.pxav.kelp core - 0.2.0 + 0.3.0 org.spigotmc diff --git a/v_1_14_implementation/src/main/java/de/pxav/kelp/implementation1_14/inventory/VersionedItem.java b/v_1_14_implementation/src/main/java/de/pxav/kelp/implementation1_14/inventory/VersionedItem.java index f696cb00..1caf2868 100644 --- a/v_1_14_implementation/src/main/java/de/pxav/kelp/implementation1_14/inventory/VersionedItem.java +++ b/v_1_14_implementation/src/main/java/de/pxav/kelp/implementation1_14/inventory/VersionedItem.java @@ -7,6 +7,8 @@ import org.bukkit.inventory.ItemFlag; import org.bukkit.inventory.ItemStack; +import java.util.List; + /** * A class description goes here. * @@ -34,7 +36,7 @@ public ItemStack setDisplayName(ItemStack itemStack, String displayName) { } @Override - public ItemStack setLore(ItemStack itemStack, String itemLore) { + public ItemStack setLore(ItemStack itemStack, List itemLore) { return null; }