diff --git a/CHANGELOG/kelp-v0.1.0.md b/CHANGELOG/kelp-v0.1.0.md new file mode 100644 index 00000000..bde9df21 --- /dev/null +++ b/CHANGELOG/kelp-v0.1.0.md @@ -0,0 +1,23 @@ +# v0.1.0 +> Release date: 28.01.2021 + +**The sidebar update**: +* Rework sidebar system: + * The old, annotation-based sidebar system using `@CreateSidebar` is not available anymore. Plugins that have been using this system will have to upgrade. + * Rework component system (stateful & stateless) + * Add new component `StatefulListComponent` to display dynamic lists in sidebars + * Developers can now choose between lazy updating (manipulating team prefixes, flicker free) or normal updating (resetting the scoreboard), which was not possible with the old system. + * Move version dependent scoreboard code out of the core module to the version modules (scoreboard team handling, etc.) + * Rework animation system by replacing it with a cluster-based system + * Add new events for handling sidebars: + * `KelpSidebarRenderEvent` + * `KelpSidebarUpdateEvent` + * `KelpSidebarRemoveEvent` + * Create new event base: `KelpPlayerEvent`, which should replace the `PlayerEvent` for better integration of `KelpPlayer` into event handling. So if you create anything player-related with custom events, use this class instead. + * Sidebar components and sidebar objects are now created using static factory methods. If you like the old approach more, you can still rely on the old factory classes such as `SidebarComponentFactory` + * Sidebars can now be properly removed/hidden from a player +* Documentation improvements in `KelpPlayer` and all newly added classes. +* `TextAnimations` can now be created using static factory methods. +* Add `ksidebar` command in testing-module to demonstrate some sidebar components. +* Refactor testing-module by assigning dedicated packages to each feature to test +* Fix bug that `TimeConverter` did not convert milliseconds <-> ticks correctly \ No newline at end of file diff --git a/README.md b/README.md index aa5f70ca..5cb0661d 100644 --- a/README.md +++ b/README.md @@ -93,7 +93,8 @@ playerConnection.sendPacket(spawnPacket); - **Particle engine:** Easily create custom and prebuilt particle effects - **Schedulers**: Create sync and async schedulers and make use of powerful thread synchronization tools - **Events**: Custom events for NPCs, Inventories and more as well as new listener techniques - +- **Prompts**: Interact with your player by prompting input from chat, anvils ans signs. +- **and more** to discover in the [Wiki](https://github.com/PXAV/kelp/wiki) ## Support & Requirements @@ -119,28 +120,23 @@ There are version implementations for the following version implementations avai ## Downloading -#### Maven +### Maven ```xml com.github.pxav.kelp core - 0.0.4 + 0.1.0 ``` ### Gradle ```shell script -implementation 'com.github.pxav.kelp:core:0.0.4' +compile group: 'com.github.pxav.kelp', name: 'core', version: '0.1.0' ``` -### Bazel -```shell script -maven_jar( - name = "core", - artifact = "com.github.pxav.kelp:core:0.0.4", - sha1 = "4743f29c20f3b033de5fe8c1eddb374511fa31d8", -) -``` +### Other build tools +The dependency can be found here including suggestions for other build tools. +[Kelp Mavenrepository](https://mvnrepository.com/artifact/com.github.pxav.kelp/core/) ### Pre-built files If you are a server owner who simply needs the jar files or a developer who does not use a built tool like Maven, you can simply download the pre-built jar files from the [Releaes page](https://github.com/PXAV/kelp/releases). There you can find all versions, but it's recommended to use the latest. diff --git a/core/pom.xml b/core/pom.xml index 5d4767cb..0d5a39d2 100644 --- a/core/pom.xml +++ b/core/pom.xml @@ -5,7 +5,7 @@ com.github.pxav.kelp parent - 0.0.5 + 0.1.0 4.0.0 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 f31f2fe0..1dca462b 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.0.4") +@Plugin(name = "Kelp", version = "0.1.0") @Author("pxav") @Description("A cross version spigot framework.") @Singleton @@ -100,9 +100,6 @@ public void onEnable() { injector.getInstance(EventHandlerRegistration.class).initialize(this.getClass().getPackage().getName()); injector.getInstance(KelpEventRepository.class).detectSubscriptions(this.getClass().getPackage().getName()); - injector.getInstance(SidebarRepository.class).loadSidebars(this.getClass().getPackage().getName()); - injector.getInstance(SidebarRepository.class).schedule(); - injector.getInstance(KelpCommandRepository.class).loadCommands(this.getClass().getPackage().getName()); injector.getInstance(KelpNpcRepository.class).startScheduler(); @@ -130,7 +127,8 @@ public void onDisable() { injector.getInstance(KelpApplicationRepository.class).disableApplications(); injector.getInstance(KelpNpcRepository.class).stopScheduler(); - injector.getInstance(SidebarRepository.class).interruptAnimations(); + injector.getInstance(SidebarRepository.class).stopAllClusters(); + logger().log("[SIDEBAR] Removed all animation clusters."); injector.getInstance(ParticleEffectRepository.class).stopAllTimers(); injector.getInstance(KelpSchedulerRepository.class).interruptAll(); diff --git a/core/src/main/java/de/pxav/kelp/core/animation/BuildingTextAnimation.java b/core/src/main/java/de/pxav/kelp/core/animation/BuildingTextAnimation.java index ea3a2643..83981c9f 100644 --- a/core/src/main/java/de/pxav/kelp/core/animation/BuildingTextAnimation.java +++ b/core/src/main/java/de/pxav/kelp/core/animation/BuildingTextAnimation.java @@ -1,5 +1,6 @@ package de.pxav.kelp.core.animation; +import de.pxav.kelp.core.KelpPlugin; import de.pxav.kelp.core.common.StringUtils; import java.util.ArrayList; @@ -32,6 +33,12 @@ public BuildingTextAnimation(StringUtils stringUtils) { this.stringUtils = stringUtils; } + public static BuildingTextAnimation create() { + return new BuildingTextAnimation( + KelpPlugin.getInjector().getInstance(StringUtils.class) + ); + } + public BuildingTextAnimation text(String text) { this.text = text; return this; diff --git a/core/src/main/java/de/pxav/kelp/core/animation/CustomTextAnimation.java b/core/src/main/java/de/pxav/kelp/core/animation/CustomTextAnimation.java index 74d48447..34f50dc7 100644 --- a/core/src/main/java/de/pxav/kelp/core/animation/CustomTextAnimation.java +++ b/core/src/main/java/de/pxav/kelp/core/animation/CustomTextAnimation.java @@ -23,6 +23,10 @@ public class CustomTextAnimation implements TextAnimation { public CustomTextAnimation() {} + public static CustomTextAnimation create() { + return new CustomTextAnimation(); + } + public CustomTextAnimation addStates(String... states) { this.states.addAll(Arrays.asList(states)); return this; diff --git a/core/src/main/java/de/pxav/kelp/core/animation/FloatingTextAnimation.java b/core/src/main/java/de/pxav/kelp/core/animation/FloatingTextAnimation.java index 139aaa90..81504a98 100644 --- a/core/src/main/java/de/pxav/kelp/core/animation/FloatingTextAnimation.java +++ b/core/src/main/java/de/pxav/kelp/core/animation/FloatingTextAnimation.java @@ -16,8 +16,10 @@ public class FloatingTextAnimation implements TextAnimation { private boolean slideIn; private SlideDirection slideDirection; - public FloatingTextAnimation() { + public FloatingTextAnimation() {} + public static FloatingTextAnimation create() { + return new FloatingTextAnimation(); } public FloatingTextAnimation text(String text) { @@ -74,7 +76,6 @@ private List slideAnimation(SlideDirection slideDirection) { @Override public List states() { - return Lists.newArrayList(); } diff --git a/core/src/main/java/de/pxav/kelp/core/animation/StaticTextAnimation.java b/core/src/main/java/de/pxav/kelp/core/animation/StaticTextAnimation.java index 9adb0ff3..34e9325b 100644 --- a/core/src/main/java/de/pxav/kelp/core/animation/StaticTextAnimation.java +++ b/core/src/main/java/de/pxav/kelp/core/animation/StaticTextAnimation.java @@ -1,6 +1,5 @@ package de.pxav.kelp.core.animation; -import java.util.Arrays; import java.util.Collections; import java.util.List; @@ -19,6 +18,10 @@ public final class StaticTextAnimation implements TextAnimation { StaticTextAnimation() {} + public static StaticTextAnimation create() { + return new StaticTextAnimation(); + } + public StaticTextAnimation text(String text) { this.text = text; return this; diff --git a/core/src/main/java/de/pxav/kelp/core/event/kelpevent/KelpPlayerEvent.java b/core/src/main/java/de/pxav/kelp/core/event/kelpevent/KelpPlayerEvent.java new file mode 100644 index 00000000..b01509da --- /dev/null +++ b/core/src/main/java/de/pxav/kelp/core/event/kelpevent/KelpPlayerEvent.java @@ -0,0 +1,35 @@ +package de.pxav.kelp.core.event.kelpevent; + +import de.pxav.kelp.core.player.KelpPlayer; +import org.bukkit.event.Event; + +/** + * Represents a specific type of event. It is basically a replacement + * class for bukkit's normal {@link org.bukkit.event.player.PlayerEvent}, but + * does not hold a normal {@link org.bukkit.entity.Player} instance. Instead + * the {@code #getPlayer()} method returns a {@link KelpPlayer} to offer better + * integration of custom Kelp events into your plugins. You don't need to + * manually fetch the player anymore with the {@link de.pxav.kelp.core.player.KelpPlayerRepository} + * but can directly retrieve it from the event. + * + * @author pxav + */ +public abstract class KelpPlayerEvent extends Event { + + protected KelpPlayer player; + + public KelpPlayerEvent(KelpPlayer who) { + this.player = who; + } + + /** + * Gets the player who has triggered the event or + * the player who should be handled by this event. + * + * @return The player of this event. + */ + public final KelpPlayer getPlayer() { + return this.player; + } + +} diff --git a/core/src/main/java/de/pxav/kelp/core/event/kelpevent/sidebar/KelpSidebarRemoveEvent.java b/core/src/main/java/de/pxav/kelp/core/event/kelpevent/sidebar/KelpSidebarRemoveEvent.java new file mode 100644 index 00000000..cf565055 --- /dev/null +++ b/core/src/main/java/de/pxav/kelp/core/event/kelpevent/sidebar/KelpSidebarRemoveEvent.java @@ -0,0 +1,32 @@ +package de.pxav.kelp.core.event.kelpevent.sidebar; + +import de.pxav.kelp.core.event.kelpevent.KelpPlayerEvent; +import de.pxav.kelp.core.player.KelpPlayer; +import org.bukkit.event.HandlerList; + +/** + * This event is triggered when any sidebar is removed from a player + * and the corresponding animation schedulers, etc. are stopped. It + * does only handle {@link de.pxav.kelp.core.sidebar.type.KelpSidebar<>} ano + * no default bukkit scoreboards. + * + * @author pxav + */ +public class KelpSidebarRemoveEvent extends KelpPlayerEvent { + + private static final HandlerList handlers = new HandlerList(); + + public KelpSidebarRemoveEvent(KelpPlayer who) { + super(who); + } + + public static HandlerList getHandlerList() { + return handlers; + } + + @Override + public HandlerList getHandlers() { + return handlers; + } + +} diff --git a/core/src/main/java/de/pxav/kelp/core/event/kelpevent/sidebar/KelpSidebarRenderEvent.java b/core/src/main/java/de/pxav/kelp/core/event/kelpevent/sidebar/KelpSidebarRenderEvent.java new file mode 100644 index 00000000..1fb05af7 --- /dev/null +++ b/core/src/main/java/de/pxav/kelp/core/event/kelpevent/sidebar/KelpSidebarRenderEvent.java @@ -0,0 +1,43 @@ +package de.pxav.kelp.core.event.kelpevent.sidebar; + +import de.pxav.kelp.core.event.kelpevent.KelpPlayerEvent; +import de.pxav.kelp.core.player.KelpPlayer; +import de.pxav.kelp.core.sidebar.type.KelpSidebar; +import org.bukkit.event.HandlerList; + +/** + * This event is triggered when a {@link KelpSidebar} is rendered to a player, + * which means that the sidebar is displayed to them for the first time. + * + * @author pxav + */ +public class KelpSidebarRenderEvent extends KelpPlayerEvent { + + private static final HandlerList handlers = new HandlerList(); + + private KelpSidebar sidebar; + + public KelpSidebarRenderEvent(KelpPlayer who, KelpSidebar sidebar) { + super(who); + this.sidebar = sidebar; + } + + public static HandlerList getHandlerList() { + return handlers; + } + + /** + * Gets the instance of the sidebar that has been rendered to the player. + * + * @return The current scoreboard object. + */ + public KelpSidebar getSidebar() { + return sidebar; + } + + @Override + public HandlerList getHandlers() { + return handlers; + } + +} diff --git a/core/src/main/java/de/pxav/kelp/core/event/kelpevent/sidebar/KelpSidebarUpdateEvent.java b/core/src/main/java/de/pxav/kelp/core/event/kelpevent/sidebar/KelpSidebarUpdateEvent.java new file mode 100644 index 00000000..7183f7ec --- /dev/null +++ b/core/src/main/java/de/pxav/kelp/core/event/kelpevent/sidebar/KelpSidebarUpdateEvent.java @@ -0,0 +1,58 @@ +package de.pxav.kelp.core.event.kelpevent.sidebar; + +import de.pxav.kelp.core.event.kelpevent.KelpPlayerEvent; +import de.pxav.kelp.core.player.KelpPlayer; +import de.pxav.kelp.core.sidebar.type.KelpSidebar; +import org.bukkit.event.HandlerList; + +/** + * This event is triggered when any {@link KelpSidebar<>} is updated no + * matter if it is a lazy or normal update. Title updates are not + * included. + * + * @author pxav + */ +public class KelpSidebarUpdateEvent extends KelpPlayerEvent { + + private static final HandlerList handlers = new HandlerList(); + + private boolean lazyUpdate; + private KelpSidebar sidebar; + + public KelpSidebarUpdateEvent(KelpPlayer who, KelpSidebar sidebar, boolean lazyUpdate) { + super(who); + this.sidebar = sidebar; + this.lazyUpdate = lazyUpdate; + } + + public static HandlerList getHandlerList() { + return handlers; + } + + /** + * Checks if the update was performed lazy, which means that + * no scores have been added/deleted from the board. + * + * @return {@code true} if the update was lazy. + */ + public boolean isLazyUpdate() { + return lazyUpdate; + } + + /** + * Gets an instance of the scoreboard that has been updated. + * It can be casted to the specific type such as {@link de.pxav.kelp.core.sidebar.type.AnimatedSidebar} + * for example if needed. + * + * @return The current sidebar object. + */ + public KelpSidebar getSidebar() { + return sidebar; + } + + @Override + public HandlerList getHandlers() { + return handlers; + } + +} 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 34ee9b2c..4f6da573 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 @@ -5,6 +5,7 @@ import de.pxav.kelp.core.entity.LivingKelpEntity; import de.pxav.kelp.core.entity.version.EntityVersionTemplate; import de.pxav.kelp.core.entity.version.LivingEntityVersionTemplate; +import de.pxav.kelp.core.event.kelpevent.sidebar.KelpSidebarRemoveEvent; import de.pxav.kelp.core.inventory.KelpInventoryRepository; import de.pxav.kelp.core.inventory.type.KelpInventory; import de.pxav.kelp.core.particle.type.ParticleType; @@ -20,9 +21,13 @@ import de.pxav.kelp.core.player.prompt.sign.SignPrompt; import de.pxav.kelp.core.player.prompt.sign.SignPromptVersionTemplate; import de.pxav.kelp.core.sidebar.SidebarRepository; +import de.pxav.kelp.core.sidebar.type.KelpSidebar; import de.pxav.kelp.core.sound.KelpSound; +import org.bukkit.Bukkit; import org.bukkit.Location; import org.bukkit.entity.Player; +import org.bukkit.scoreboard.DisplaySlot; +import org.bukkit.scoreboard.Scoreboard; import java.util.Collection; import java.util.UUID; @@ -78,9 +83,10 @@ public class KelpPlayer extends LivingKelpEntity { private String tabListHeader; private String tabListFooter; + private KelpSidebar kelpSidebar; + public KelpPlayer(Player bukkitPlayer, PlayerVersionTemplate playerVersionTemplate, - SidebarRepository sidebarRepository, KelpInventoryRepository inventoryRepository, KelpPlayerRepository kelpPlayerRepository, ParticleVersionTemplate particleVersionTemplate, @@ -101,7 +107,6 @@ public KelpPlayer(Player bukkitPlayer, bukkitPlayer); this.bukkitPlayer = bukkitPlayer; this.playerVersionTemplate = playerVersionTemplate; - this.sidebarRepository = sidebarRepository; this.inventoryRepository = inventoryRepository; this.particleVersionTemplate = particleVersionTemplate; this.signPromptVersionTemplate = signPromptVersionTemplate; @@ -122,26 +127,50 @@ public SimpleChatPrompt openSimpleChatPrompt() { } /** - * Opens a kelp sidebar with the given identifier. This method - * only applies to sidebars created using an annotation. + * Checks if the player has any scoreboard with content stored in any + * objective type ({@code SIDEBAR, PLAYER_LIST}, etc.) * - * @param identifier The identifier of the sidebar you want to show - * to the player - * @return the current instance of the player. + * @return {@code true} if the player has a scoreboard with an objective. */ - public KelpPlayer openKelpSidebar(String identifier) { - this.sidebarRepository.openSidebar(identifier, bukkitPlayer); - return this; + public boolean hasScoreboard() { + Scoreboard scoreboard = bukkitPlayer.getScoreboard(); + return scoreboard.getObjective(DisplaySlot.SIDEBAR) != null + || scoreboard.getObjective(DisplaySlot.BELOW_NAME) != null + || scoreboard.getObjective(DisplaySlot.PLAYER_LIST) != null; } /** - * Makes the current sidebar of the player disappear. + * Removes the {@link de.pxav.kelp.core.sidebar.type.KelpSidebar} from the player. + * This hides the sidebar from the screen, but also stops all schedulers connected + * to it (such as title animation). This method does not effect other parts + * of the scoreboard such as the tab list. + */ + public void removeSidebar() { + setSidebarInternally(null); + Bukkit.getPluginManager().callEvent(new KelpSidebarRemoveEvent(this)); + playerVersionTemplate.removeSidebar(bukkitPlayer); + } + + /** + * Caches the sidebar object of the player locally. This does not + * render nor update the given sidebar. It simply changes the internal + * sidebar object which can then be retrieved to update it for + * example using {@link #getCurrentSidebar()}. * - * @return the current instance of the player. + * @param sidebar The current sidebar of the player. */ - public KelpPlayer removeKelpSidebar() { - this.sidebarRepository.removeSidebar(bukkitPlayer); - return this; + public void setSidebarInternally(KelpSidebar sidebar) { + this.kelpSidebar = sidebar; + } + + /** + * Gets the sidebar the player is currently seeing. + * Will return {@code null} of the player has no sidebar. + * + * @return The current sidebar of the player. + */ + public KelpSidebar getCurrentSidebar() { + return kelpSidebar; } /** @@ -342,6 +371,13 @@ public KelpPlayer chat(String message) { return this; } + public KelpPlayer clearChat() { + for (int i = 0; i < 103; i++) { + sendMessage(" "); + } + return this; + } + public boolean mayFly() { return playerVersionTemplate.getAllowFlight(bukkitPlayer); } 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 d93c061d..8ce8f7a4 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 @@ -56,9 +56,18 @@ public class KelpPlayerRepository { private ChatPromptVersionTemplate chatPromptVersionTemplate; @Inject - public KelpPlayerRepository(PlayerVersionTemplate playerVersionTemplate, SidebarRepository sidebarRepository, KelpInventoryRepository inventoryRepository, KelpLogger logger, EntityVersionTemplate entityVersionTemplate, LivingEntityVersionTemplate livingEntityVersionTemplate, ParticleVersionTemplate particleVersionTemplate, SignPromptVersionTemplate signPromptVersionTemplate, AnvilPromptVersionTemplate anvilPromptVersionTemplate, ChatPromptVersionTemplate chatPromptVersionTemplate) { + public KelpPlayerRepository(PlayerVersionTemplate playerVersionTemplate, + //SidebarRepository sidebarRepository, + KelpInventoryRepository inventoryRepository, + KelpLogger logger, + EntityVersionTemplate entityVersionTemplate, + LivingEntityVersionTemplate livingEntityVersionTemplate, + ParticleVersionTemplate particleVersionTemplate, + SignPromptVersionTemplate signPromptVersionTemplate, + AnvilPromptVersionTemplate anvilPromptVersionTemplate, + ChatPromptVersionTemplate chatPromptVersionTemplate) { this.playerVersionTemplate = playerVersionTemplate; - this.sidebarRepository = sidebarRepository; + //this.sidebarRepository = sidebarRepository; this.inventoryRepository = inventoryRepository; this.logger = logger; this.entityVersionTemplate = entityVersionTemplate; @@ -217,7 +226,7 @@ public void removeKelpPlayer(UUID uuid) { private KelpPlayer newKelpPlayerFrom(Player bukkitPlayer) { return new KelpPlayer(bukkitPlayer, playerVersionTemplate, - sidebarRepository, + //sidebarRepository, inventoryRepository, this, particleVersionTemplate, 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 fe98e01b..ff033cd2 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 @@ -935,4 +935,12 @@ public abstract class PlayerVersionTemplate { */ public abstract void sendInteractiveMessage(Player player, InteractiveMessage interactiveMessage); + /** + * If the player currently sees a sidebar, it will be hidden for the given + * player. This mostly happens by replacing it with a new, empty scoreboard. + * + * @param player The player whose sidebar you want to hide/remove. + */ + public abstract void removeSidebar(Player player); + } diff --git a/core/src/main/java/de/pxav/kelp/core/scheduler/TimeConverter.java b/core/src/main/java/de/pxav/kelp/core/scheduler/TimeConverter.java index ad42cdfe..0dd872c2 100644 --- a/core/src/main/java/de/pxav/kelp/core/scheduler/TimeConverter.java +++ b/core/src/main/java/de/pxav/kelp/core/scheduler/TimeConverter.java @@ -49,7 +49,7 @@ public static int secondsToTicks(int seconds) { public static int getTicks(int value, TimeUnit timeUnit) { switch (timeUnit) { case MILLISECONDS: - return secondsToTicks(value * 1000); + return value / 50; case SECONDS: return secondsToTicks(value); case MINUTES: diff --git a/core/src/main/java/de/pxav/kelp/core/sidebar/CreateSidebar.java b/core/src/main/java/de/pxav/kelp/core/sidebar/CreateSidebar.java deleted file mode 100644 index a9a4f9cb..00000000 --- a/core/src/main/java/de/pxav/kelp/core/sidebar/CreateSidebar.java +++ /dev/null @@ -1,57 +0,0 @@ -package de.pxav.kelp.core.sidebar; - -import java.lang.annotation.ElementType; -import java.lang.annotation.Retention; -import java.lang.annotation.RetentionPolicy; -import java.lang.annotation.Target; - -/** - * This annotation is used to mark methods - * which provide a sidebar that can be used - * and opened. - * - * @author pxav - */ -@Target(ElementType.METHOD) -@Retention(RetentionPolicy.RUNTIME) -public @interface CreateSidebar { - - /** - * Each sidebar must have a unique identifier. - * This identifier is used to for example - * open the sidebar, etc. - * - * @return The unique identifier string. - */ - String identifier(); - - int switchInterval() default 100; - - /** - * If your sidebar title is animated - * you should specify an interval in which - * the sidebar title should be updated. - * The default value is 1000 milliseconds. - * - * @return The interval in milliseconds. - */ - int titleAnimationInterval() default 1000; - - /** - * Should your sidebar be handled asynchronously? - * This only applies for general updates, the title updates - * (if your sidebar is animated) are always asynchronous. - * - * @return {@code true} if sidebar updates should happen async. - */ - boolean async() default true; - - /** - * This attribute describes whether the scoreboard - * should be displayed automatically when a player - * joins the server. - * @return {@code true} if it should be the default scoreboard. - */ - boolean setOnJoin() default false; - -} diff --git a/core/src/main/java/de/pxav/kelp/core/sidebar/SidebarRepository.java b/core/src/main/java/de/pxav/kelp/core/sidebar/SidebarRepository.java old mode 100644 new mode 100755 index b33b1bda..9f46547a --- a/core/src/main/java/de/pxav/kelp/core/sidebar/SidebarRepository.java +++ b/core/src/main/java/de/pxav/kelp/core/sidebar/SidebarRepository.java @@ -1,342 +1,170 @@ package de.pxav.kelp.core.sidebar; -import com.google.common.base.Preconditions; import com.google.common.collect.Maps; -import com.google.inject.Inject; -import com.google.inject.Injector; -import com.google.inject.Singleton; +import com.google.common.collect.Sets; +import de.pxav.kelp.core.animation.TextAnimation; +import de.pxav.kelp.core.event.kelpevent.sidebar.KelpSidebarRemoveEvent; +import de.pxav.kelp.core.player.KelpPlayer; +import de.pxav.kelp.core.player.KelpPlayerRepository; +import de.pxav.kelp.core.scheduler.KelpSchedulerRepository; +import de.pxav.kelp.core.scheduler.type.SchedulerFactory; import de.pxav.kelp.core.sidebar.type.AnimatedSidebar; -import de.pxav.kelp.core.logger.KelpLogger; -import de.pxav.kelp.core.logger.LogLevel; -import de.pxav.kelp.core.reflect.MethodCriterion; -import de.pxav.kelp.core.reflect.MethodFinder; -import de.pxav.kelp.core.sidebar.type.KelpSidebar; -import org.bukkit.Bukkit; -import org.bukkit.entity.Player; +import de.pxav.kelp.core.sidebar.version.SidebarUpdaterVersionTemplate; +import org.bukkit.event.EventHandler; +import org.bukkit.event.player.PlayerQuitEvent; -import java.lang.reflect.InvocationTargetException; -import java.lang.reflect.Method; -import java.util.Arrays; -import java.util.Map; -import java.util.concurrent.ExecutorService; -import java.util.concurrent.Executors; -import java.util.concurrent.ScheduledExecutorService; -import java.util.concurrent.TimeUnit; +import javax.inject.Inject; +import javax.inject.Singleton; +import java.util.List; +import java.util.Set; +import java.util.UUID; +import java.util.concurrent.ConcurrentHashMap; /** - * This repository class is used to manage your sidebars. - * You can open, close and update sidebars using the - * unique identifier which is passed in the {@code CreateSidebar} - * annotation. + * A class description goes here. * * @author pxav */ @Singleton public class SidebarRepository { - // saves the methods which build the sidebar. Identifier -> Method - private final Map methods = Maps.newHashMap(); + private ConcurrentHashMap animationStates; + private ConcurrentHashMap> clusters; + private ConcurrentHashMap clusterTasks; + private ConcurrentHashMap playerTasks; + private ConcurrentHashMap animations; - // should the sidebar be updated asynchronously? Identifier -> async? - private final Map asyncMode = Maps.newHashMap(); - - // The sidebar which is currently opened by a player. Player -> Sidebar identifier - private final Map playerSidebars = Maps.newHashMap(); - - // The schedulers for the title animation of animated sidebars. Identifier -> Scheduler - private final Map titleScheduler = Maps.newHashMap(); - - // The current state of animation for each player. Player -> State - private final Map animationStates = Maps.newHashMap(); - - // When should the next animation state be called? Identifier -> Time in millis - private final Map titleAnimationInterval = Maps.newHashMap(); - - // the identifier of the scoreboard which should be set on join. - private String defaultScoreboard = "NONE"; - - private MethodFinder methodFinder; - private KelpLogger kelpLogger; - private Injector injector; - private ExecutorService executorService; + private KelpSchedulerRepository schedulerRepository; + private SchedulerFactory schedulerFactory; + private SidebarUpdaterVersionTemplate updaterVersionTemplate; + private KelpPlayerRepository playerRepository; @Inject - public SidebarRepository(MethodFinder methodFinder, - KelpLogger kelpLogger, - Injector injector, - ExecutorService executorService) { - this.methodFinder = methodFinder; - this.kelpLogger = kelpLogger; - this.injector = injector; - this.executorService = executorService; + public SidebarRepository(KelpSchedulerRepository schedulerRepository, + SchedulerFactory schedulerFactory, + SidebarUpdaterVersionTemplate updaterVersionTemplate, + KelpPlayerRepository playerRepository) { + this.animationStates = new ConcurrentHashMap<>(); + this.clusters = new ConcurrentHashMap<>(); + this.clusterTasks = new ConcurrentHashMap<>(); + this.playerTasks = new ConcurrentHashMap<>(); + this.animations = new ConcurrentHashMap<>(); + this.schedulerFactory = schedulerFactory; + this.schedulerRepository = schedulerRepository; + this.updaterVersionTemplate = updaterVersionTemplate; + this.playerRepository = playerRepository; } - /** - * Searches for methods annotated with a {@code CreateSidebar} annotation - * and saves as a sidebar. - * - * @param packageNames The packages in which you want to search. - * @see CreateSidebar - */ - public void loadSidebars(String... packageNames) { - kelpLogger.log("[SIDEBAR] Loading sidebars in " + Arrays.toString(packageNames)); - this.methodFinder.filter(packageNames, MethodCriterion.annotatedWith(CreateSidebar.class)) - .forEach(method -> { - CreateSidebar annotation = method.getAnnotation(CreateSidebar.class); - String identifier = annotation.identifier(); - if (identifier.equalsIgnoreCase("NONE")) { - kelpLogger.log(LogLevel.ERROR, "[SIDEBAR] Sidebar identifier 'NONE' is not allowed, " + - "because it's reserved for the system. Please choose another name."); - return; - } - - if (!identifierAvailable(identifier)) { - kelpLogger.log(LogLevel.ERROR, "[SIDEBAR] Sidebar identifier " + identifier - + " is already in use, but identifiers must be unique!" + - " Please change the identifier and reload the system."); - return; - } - - methods.put(identifier, method); - asyncMode.put(identifier, annotation.async()); - if (annotation.titleAnimationInterval() <= 0) { - kelpLogger.log(LogLevel.ERROR, "[SIDEBAR] Animation interval of sidebar '" + identifier - + "' is smaller than or equal to 0. Please change the delay to at least 1."); - return; - } - titleAnimationInterval.put(identifier, annotation.titleAnimationInterval()); - - if (defaultScoreboard.equalsIgnoreCase("NONE")) { - defaultScoreboard = annotation.identifier(); - } - - kelpLogger.log("[SIDEBAR] Sidebar " + identifier + " successfully loaded!"); - }); - kelpLogger.log("[SIDEBAR] Loading process complete. Loaded " + methods.size() + " sidebars in total so far."); - } + public void addAnimatedSidebar(AnimatedSidebar sidebar, KelpPlayer player) { - /** - * Starts the schedulers for the scoreboard animations. - * - * Each sidebar gets an own scheduler which uses the interval - * which is passed in the {@code CreateSidebar} annotation, - * while animation states are linked to each player individually, - * because players can have different animations in the same sidebar - * when for example their name is displayed in the title: - * §apxav -> 4 states - * §aOpi_CAN -> 7 states - */ - public void schedule() { - kelpLogger.log("[SIDEBAR] Enabling animation schedulers."); - for (Map.Entry entry : Maps.newHashMap(this.titleAnimationInterval).entrySet()) { - String identifier = entry.getKey(); + animationStates.put(player.getUUID(), 0); + animations.put(player.getUUID(), sidebar.getTitle()); - // check if the current sidebar is really an animated one. - // if not, remove it from the collection and continue with the next one. - if (!this.isAnimated(identifier)) { - this.titleAnimationInterval.remove(identifier); - continue; + // add player to cluster or create a new cluster if needed + if (sidebar.getClusterId() != null) { + if (!clusters.containsKey(sidebar.getClusterId())) { + this.addCluster(sidebar.getClusterId(), sidebar.getTitleAnimationInterval()); } - - // create a new thread containing a new scheduler - ScheduledExecutorService scheduledExecutorService = Executors.newSingleThreadScheduledExecutor(); - - - // schedule using the time passed in the annotation. - scheduledExecutorService.scheduleAtFixedRate(() -> { - try { - - // iterate all players on the server. - for (Player player : Bukkit.getOnlinePlayers()) { - if (!animationStates.containsKey(player)) { - continue; - } - - int state = this.animationStates.get(player); - - if (this.playerSidebars.containsKey(player) - && !this.playerSidebars.get(player).equalsIgnoreCase(identifier)) - continue; - - // load the sidebar for the player - AnimatedSidebar sidebar = (AnimatedSidebar) getSidebar(identifier, player); - Preconditions.checkNotNull(sidebar); - - // update the state. If the state index it out of bounds, reset it to 0. - animationStates.put(player, animationStates.get(player) + 1); - if (state >= sidebar.maxStates() - 1) { - this.animationStates.put(player, 0); - } - - // finally update the title. - sidebar.updateTitleOnly(player, state); - - } - - } catch (Exception e) { - e.printStackTrace(); - } - }, 200L, titleAnimationInterval.get(identifier), TimeUnit.MILLISECONDS); - - // save the scheduler object in the map so that it can be canceled later. - this.titleScheduler.put(identifier, scheduledExecutorService); - } - } - - /** - * Iterates through all sidebars and cancels the animation - * scheduler if existing. - */ - public void interruptAnimations() { - for (Map.Entry entry : this.titleScheduler.entrySet()) { - entry.getValue().shutdown(); + this.addPlayerToCluster(sidebar.getClusterId(), player); + } else { + // if the sidebar does not have clusters, an individual scheduler + // has to be set up + UUID task = schedulerFactory.newRepeatingScheduler() + .async() + .every(sidebar.getTitleAnimationInterval()) + .milliseconds() + .run(taskId -> { + String updateTo = incrementAnimationState(player.getUUID()); + updaterVersionTemplate.updateTitleOnly(updateTo, player); + }); + playerTasks.put(player.getUUID(), task); } - kelpLogger.log("[SIDEBAR] Interrupted all animation schedulers."); - } - - /** - * Open the given sidebar for the given player. - * - * @param identifier The identifier of the desired sidebar. - * @param player The player who should see the sidebar. - */ - public void openSidebar(String identifier, Player player) { - checkAvailability(identifier); - KelpSidebar sidebar = getSidebar(identifier, player); - Preconditions.checkNotNull(sidebar); - - playerSidebars.put(player, identifier); - animationStates.put(player, 0); - sidebar.renderAndOpenSidebar(player); } - /** - * Updates the sidebar of a player. - * The type ((a-)sync) depends on the value - * passed in the {@code CreateSidebar} annotation - * and will be selected automatically. - * - * @param player The player whose sidebar you want to update. - */ - public void updateSidebar(Player player) { - String identifier = playerSidebars.get(player); - if (isAsync(identifier)) { - this.updateSidebarAsynchronously(player); + public void removeAnimatedSidebar(KelpPlayer player) { + if (playerTasks.containsKey(player.getUUID())) { + UUID task = playerTasks.get(player.getUUID()); + schedulerRepository.interruptScheduler(task); + this.playerTasks.remove(player.getUUID()); } else { - this.updateSidebarSynchronously(player); + Maps.newHashMap(this.clusters).forEach((clusterId, playerSet) + -> playerSet.stream() + .filter(uuid -> player.getUUID() == uuid) + .findFirst() + .ifPresent(uuid -> { + playerSet.remove(uuid); + if (playerSet.isEmpty()) { + stopCluster(clusterId); + return; + } + clusters.put(clusterId, playerSet); + })); } + this.animationStates.remove(player.getUUID()); + this.animations.remove(player.getUUID()); } - /** - * Updates the sidebar of the given player inside the - * main thread of the server. - * - * @param player The player whose sidebar you want to update. - */ - public void updateSidebarSynchronously(Player player) { - String identifier = playerSidebars.get(player); - checkAvailability(identifier); - - KelpSidebar kelpSidebar = getSidebar(identifier, player); - Preconditions.checkNotNull(kelpSidebar); - kelpSidebar.update(player); + public void stopAllClusters() { + Maps.newHashMap(this.clusters).forEach((clusterId, playerSet) + -> stopCluster(clusterId)); } - /** - * Updates the sidebar of the given player in a - * separate thread. - * - * @param player The player whose sidebar you want to update. - */ - public void updateSidebarAsynchronously(Player player) { - this.executorService.execute(() -> { - String identifier = playerSidebars.get(player); - checkAvailability(identifier); - - KelpSidebar kelpSidebar = getSidebar(identifier, player); - Preconditions.checkNotNull(kelpSidebar); - kelpSidebar.update(player); - }); + @EventHandler + public void handleClusterRemove(PlayerQuitEvent event) { + KelpPlayer player = playerRepository.getKelpPlayer(event.getPlayer()); + if (animationStates.containsKey(player.getUUID())) { + removeAnimatedSidebar(player); + } } - /** - * Removes a player from all lists in the cache and clears - * its sidebar. - * - * @param player The player whose sidebar should be removed. - */ - public void removeSidebar(Player player) { - this.playerSidebars.remove(player); - this.animationStates.remove(player); + @EventHandler + public void handleSidebarRemove(KelpSidebarRemoveEvent event) { + // stops all schedulers and removes the player from the list when + // their sidebar is removed. + this.removeAnimatedSidebar(event.getPlayer()); } - /** - * Invokes the creation method of the sidebar with the - * given identifier and returns the result. - * - * @param identifier The identifier of the sidebar you want to get. - * @param player Each sidebar method needs a player as parameter - * to also load player-specific data as well. - * So you need to give the player who should be passed - * as parameter. - * @return The final sidebar object. - */ - private KelpSidebar getSidebar(String identifier, Player player) { - if (this.identifierAvailable(identifier)) return null; - - try { - Method method = this.methods.get(identifier); - return (KelpSidebar) method.invoke(injector.getInstance(method.getDeclaringClass()), player); - } catch (IllegalAccessException | InvocationTargetException ignore) {} - return null; + private void addPlayerToCluster(String clusterId, KelpPlayer player) { + Set players = clusters.get(clusterId); + players.add(player.getUUID()); + clusters.put(clusterId, players); } - /** - * Checks whether the requested sidebar is animated. - * This means if the sidebar is of type {@code AnimatedSidebar} - * ano not just {@code SimpleSidebar} for example. - * - * @param identifier The identifier of the sidebar you want to check. - * @return {@code true} if the sidebar is of type {@code AnimatedSidebar}. - * @see AnimatedSidebar - */ - private boolean isAnimated(String identifier) { - checkAvailability(identifier); - Method method = this.methods.get(identifier); - return method.getReturnType() == AnimatedSidebar.class; + private void addCluster(String clusterId, int interval) { + clusters.put(clusterId, Sets.newHashSet()); + UUID task = schedulerFactory.newRepeatingScheduler() + .async() + .every(interval) + .milliseconds() + .run(taskId -> clusters.get(clusterId).forEach(current -> { + String updateTo = incrementAnimationState(current); + updaterVersionTemplate.updateTitleOnly(updateTo, playerRepository.getKelpPlayer(current)); + })); + clusterTasks.put(clusterId, task); } - /** - * Checks if the requested identifier exits in the cache. - * If this is false an error message is sent to the log. - * - * @param identifier The identifier you want to check. - */ - private void checkAvailability(String identifier) { - if (identifierAvailable(identifier)) { - kelpLogger.log(LogLevel.ERROR, "Cannot access sidebar: " + - " Sidebar with identifier " + identifier + " does not exist."); + private String incrementAnimationState(UUID uuid) { + List states = animations.get(uuid).states(); + int current = animationStates.get(uuid); + int max = states.size(); + + current += 1; + if (current == max) { + current = 0; } - } - /** - * @param identifier The identifier you want to check. - * @return {@code true} if the identifier is not in use already. - */ - private boolean identifierAvailable(String identifier) { - return !methods.containsKey(identifier); + animationStates.put(uuid, current); + return states.get(current); } - /** - * @param identifier The identifier of the sidebar you want to check. - * @return {@code true} if the sidebar should be handled asynchronously. - */ - private boolean isAsync(String identifier) { - return this.asyncMode.get(identifier); + private void stopCluster(String clusterId) { + if (this.clusterTasks.get(clusterId) == null) { + return; + } + schedulerRepository.interruptScheduler(clusterTasks.get(clusterId)); + clusterTasks.remove(clusterId); + clusters.remove(clusterId); } - public String getDefaultScoreboard() { - return defaultScoreboard; - } } diff --git a/core/src/main/java/de/pxav/kelp/core/sidebar/SidebarStateListener.java b/core/src/main/java/de/pxav/kelp/core/sidebar/SidebarStateListener.java deleted file mode 100644 index 55c9212e..00000000 --- a/core/src/main/java/de/pxav/kelp/core/sidebar/SidebarStateListener.java +++ /dev/null @@ -1,63 +0,0 @@ -package de.pxav.kelp.core.sidebar; - -import com.google.inject.Inject; -import com.google.inject.Singleton; -import org.bukkit.entity.Player; -import org.bukkit.event.EventHandler; -import org.bukkit.event.player.PlayerJoinEvent; -import org.bukkit.event.player.PlayerQuitEvent; - -/** - * This class handles the states of sidebars. - * It gives default sidebars on every player join - * and removes any type of sidebar on every player - * quit. - * - * This avoids useless calculation for the server. - * - * @author pxav - */ -@Singleton -public class SidebarStateListener { - - private SidebarRepository sidebarRepository; - - @Inject - public SidebarStateListener(SidebarRepository sidebarRepository) { - this.sidebarRepository = sidebarRepository; - } - - /** - * This event is triggered when a player joins the server. - * In this case it gives the player the default sidebar, - * if there has been defined one (with 'setOnJoin' to true - * in the {@code @CreateSidebar} annotation). - * - * @param event Instance of the current event. - */ - @EventHandler - public void onPlayerJoin(PlayerJoinEvent event) { - Player player = event.getPlayer(); - - // if a scoreboard has 'setOnJoin' set to true, it will be shown to the player - if (!sidebarRepository.getDefaultScoreboard().equalsIgnoreCase("NONE")) { - sidebarRepository.openSidebar(sidebarRepository.getDefaultScoreboard(), player); - } - - } - - /** - * This event is triggered when a player quits the server. - * In this case it removes the sidebar for every player so - * that the server does not do useless updates on their - * sidebar. - * - * @param event Instance of the current event. - */ - @EventHandler - public void onPlayerQuit(PlayerQuitEvent event) { - Player player = event.getPlayer(); - sidebarRepository.removeSidebar(player); - } - -} diff --git a/core/src/main/java/de/pxav/kelp/core/sidebar/SidebarUtils.java b/core/src/main/java/de/pxav/kelp/core/sidebar/SidebarUtils.java old mode 100644 new mode 100755 index cd03c5a1..4257eda8 --- a/core/src/main/java/de/pxav/kelp/core/sidebar/SidebarUtils.java +++ b/core/src/main/java/de/pxav/kelp/core/sidebar/SidebarUtils.java @@ -22,12 +22,10 @@ @Singleton public class SidebarUtils { - private KelpLogger logger; private StringUtils stringUtils; @Inject - public SidebarUtils(KelpLogger logger, StringUtils stringUtils) { - this.logger = logger; + public SidebarUtils(StringUtils stringUtils) { this.stringUtils = stringUtils; } @@ -79,7 +77,6 @@ public void setTeamData(String text, Team team) { * @return The final generation result. */ public String randomEmptyEntry(Scoreboard scoreboard) { - int index = ThreadLocalRandom.current().nextInt(1); int colorAmount = ThreadLocalRandom.current().nextInt(3); StringBuilder stringBuilder = new StringBuilder(); Collection forbidden = usedEntries(scoreboard); diff --git a/core/src/main/java/de/pxav/kelp/core/sidebar/component/EmptyLineComponent.java b/core/src/main/java/de/pxav/kelp/core/sidebar/component/EmptyLineComponent.java old mode 100644 new mode 100755 index 41a34e09..ab6b3df1 --- a/core/src/main/java/de/pxav/kelp/core/sidebar/component/EmptyLineComponent.java +++ b/core/src/main/java/de/pxav/kelp/core/sidebar/component/EmptyLineComponent.java @@ -1,15 +1,19 @@ package de.pxav.kelp.core.sidebar.component; +import com.google.common.collect.Maps; +import de.pxav.kelp.core.KelpPlugin; import de.pxav.kelp.core.sidebar.SidebarUtils; import org.bukkit.scoreboard.DisplaySlot; import org.bukkit.scoreboard.Objective; import org.bukkit.scoreboard.Scoreboard; import org.bukkit.scoreboard.Team; +import java.util.Map; + /** * This sidebar component can be used to generate empty lines. - * You could also write them manually as a {@code SimpleTextComponent}, - * but this way makes it more maintainable, because you don't + * You could also write them manually as a {@code StatelessTextComponent} + * for example, this way however makes it more maintainable, because you don't * have to choose color codes manually: * * To display an empty line in a scoreboard you have to use invisible @@ -22,34 +26,45 @@ * * @author pxav */ -public class EmptyLineComponent implements SimpleSidebarComponent { +public class EmptyLineComponent extends SidebarComponent { + // the line to place the component in the sidebar private int line; - private SidebarUtils sidebarUtils; - - public EmptyLineComponent(SidebarUtils sidebarUtils) { - this.sidebarUtils = sidebarUtils; + public static EmptyLineComponent create() { + return new EmptyLineComponent(); } - public EmptyLineComponent score(int line) { + /** + * Sets the line number of the component to be placed in the sidebar. + * Please note that this line id represents an absolute position and + * should therefore be unique. No components should have the same line + * number. + * + * @param line The line of the sidebar, where the component should + * be visible. + * @return The current component instance for fluent builder design. + */ + public EmptyLineComponent line(int line) { this.line = line; return this; } + /** + * Renders all the information provided in the component + * to a map containing the final information to be rendered + * to the sidebar (the lines where the text should be placed and + * the text to write there). + * + * @return A map, where the key is the absolute line where + * the component should be placed in the sidebar and + * the value is the actual text for that line. + */ @Override - public void render(Scoreboard parent) { - String entry = sidebarUtils.randomEmptyEntry(parent); - Objective objective = parent.getObjective(DisplaySlot.SIDEBAR); - - objective.getScore(entry).setScore(line); - - Team team = parent.registerNewTeam("entry_" + line); - team.addEntry(entry); - sidebarUtils.setTeamData(sidebarUtils.randomEmptyEntry(parent), team); + public Map render() { + Map output = Maps.newHashMap(); + output.put(line, " "); + return output; } - @Override - public void update(Scoreboard parent) {} - } diff --git a/core/src/main/java/de/pxav/kelp/core/sidebar/component/LineSeparatorComponent.java b/core/src/main/java/de/pxav/kelp/core/sidebar/component/LineSeparatorComponent.java old mode 100644 new mode 100755 index 45084bc8..ee285813 --- a/core/src/main/java/de/pxav/kelp/core/sidebar/component/LineSeparatorComponent.java +++ b/core/src/main/java/de/pxav/kelp/core/sidebar/component/LineSeparatorComponent.java @@ -1,5 +1,6 @@ package de.pxav.kelp.core.sidebar.component; +import com.google.common.collect.Maps; import de.pxav.kelp.core.sidebar.SidebarUtils; import org.bukkit.ChatColor; import org.bukkit.scoreboard.DisplaySlot; @@ -7,6 +8,8 @@ import org.bukkit.scoreboard.Scoreboard; import org.bukkit.scoreboard.Team; +import java.util.Map; + /** * This scoreboard component is used to easily create line separators. * With these you can simply create separators between paragraphs of your @@ -22,89 +25,117 @@ * * @author pxav */ -public class LineSeparatorComponent implements SimpleSidebarComponent { +public class LineSeparatorComponent extends SidebarComponent { private int line; private int length; - private char symbol; + private String symbol; private ChatColor[] colors; - private SidebarUtils sidebarUtils; - - LineSeparatorComponent(SidebarUtils sidebarUtils) { - this.sidebarUtils = sidebarUtils; - + public LineSeparatorComponent() { this.length = SeparatorLength.FULL; - this.symbol = '-'; + this.symbol = "-"; this.colors = new ChatColor[] {ChatColor.DARK_GRAY, ChatColor.STRIKETHROUGH}; } - public LineSeparatorComponent score(int line) { + public static LineSeparatorComponent create() { + return new LineSeparatorComponent(); + } + + /** + * Sets the line number of the component to be placed in the sidebar. + * Please note that this line id represents an absolute position and + * should therefore be unique. No components should have the same line + * number. + * + * @param line The line of the sidebar, where the component should + * be visible. + * @return The current component instance for fluent builder design. + */ + public LineSeparatorComponent line(int line) { this.line = line; return this; } + /** + * Sets the symbol to be repeated n times by the component, while + * {@code n} is equal to the length set by {@link #length(int)} + * + * @param symbol The symbol you want to set. + * @return Instance of the current component for more fluent builder design. + */ public LineSeparatorComponent symbol(char symbol) { + this.symbol = String.valueOf(symbol); + return this; + } + + /** + * Sets the symbol to be repeated n times by the component, while + * {@code n} is equal to the length set by {@link #length(int)} + * + * @param symbol The symbol you want to set. + * @return Instance of the current component for more fluent builder design. + */ + public LineSeparatorComponent symbol(String symbol) { this.symbol = symbol; return this; } + /** + * The colors in which the line separator symbols should be displayed. + * You can also apply style codes such as {@link ChatColor#STRIKETHROUGH} + * here. + * + * @param colors The colors to be displayed. + * @return Instance of the current component for more fluent builder design. + */ public LineSeparatorComponent color(ChatColor... colors) { this.colors = colors; return this; } + /** + * Sets the amount of times the given symbol should be repeated. + * + * @param length The length of your line separator. + * @return Instance of the current component for more fluent builder design. + */ public LineSeparatorComponent length(int length) { this.length = length; return this; } + /** + * Renders all the information provided in the component + * to a map containing the final information to be rendered + * to the sidebar (the lines where the text should be placed and + * the text to write there). + * + * @return A map, where the key is the absolute line where + * the component should be placed in the sidebar and + * the value is the actual text for that line. + */ @Override - public void render(Scoreboard parent) { - String entry = sidebarUtils.randomEmptyEntry(parent); - Objective objective = parent.getObjective(DisplaySlot.SIDEBAR); - - objective.getScore(entry).setScore(line); - - Team team = parent.registerNewTeam("entry_" + line); - team.addEntry(entry); - update(parent); - } - - @Override - public void update(Scoreboard parent) { - Team team = parent.getTeam("entry_" + line); - StringBuilder prefix = new StringBuilder(); - StringBuilder suffix = new StringBuilder(); - int totalLength = this.length + colors.length; + public Map render() { + Map output = Maps.newHashMap(); + StringBuilder builder = new StringBuilder(); for (ChatColor color : colors) { - prefix.append(color); - if (totalLength > 16) { - suffix.append(color); - } + builder.append(color); } for (int i = 0; i < length; i++) { - prefix.append(symbol); - if (totalLength > 16) { - suffix.append(symbol); - } - } - - if (prefix.toString().length() > 16) { - if (suffix.toString().length() > 16) { - team.setSuffix(suffix.toString().substring(0, 16)); - } else { - team.setSuffix(suffix.toString()); - } - team.setPrefix(prefix.toString().substring(0, 16)); - return; + builder.append(symbol); } - team.setPrefix(prefix.toString()); + output.put(line, builder.toString()); + return output; } + /** + * Contains some static values for possible length of + * a line separator. + */ public static class SeparatorLength { public static final int FULL = 30; public static final int HALF = 15; diff --git a/core/src/main/java/de/pxav/kelp/core/sidebar/component/SidebarComponent.java b/core/src/main/java/de/pxav/kelp/core/sidebar/component/SidebarComponent.java new file mode 100755 index 00000000..c3016a3a --- /dev/null +++ b/core/src/main/java/de/pxav/kelp/core/sidebar/component/SidebarComponent.java @@ -0,0 +1,27 @@ +package de.pxav.kelp.core.sidebar.component; + +import java.util.Map; + +/** + * This superclass is inherited by every sidebar component. + * It contains a {@link #render()} method converting the information + * provided in your component to the final content that is + * displayed in the sidebar. + * + * @author pxav + */ +public abstract class SidebarComponent { + + /** + * Renders all the information provided in the component + * to a map containing the final information to be rendered + * to the sidebar (the lines where the text should be placed and + * the text to write there). + * + * @return A map, where the key is the absolute line where + * the component should be placed in the sidebar and + * the value is the actual text for that line. + */ + public abstract Map render(); + +} diff --git a/core/src/main/java/de/pxav/kelp/core/sidebar/component/SidebarComponentFactory.java b/core/src/main/java/de/pxav/kelp/core/sidebar/component/SidebarComponentFactory.java old mode 100644 new mode 100755 index 450b3dac..17761aa1 --- a/core/src/main/java/de/pxav/kelp/core/sidebar/component/SidebarComponentFactory.java +++ b/core/src/main/java/de/pxav/kelp/core/sidebar/component/SidebarComponentFactory.java @@ -27,88 +27,4 @@ public SidebarComponentFactory(KelpLogger kelpLogger, SidebarUtils sidebarUtils) this.sidebarUtils = sidebarUtils; } - public SimpleTextComponent simpleTextComponent() { - return new SimpleTextComponent(sidebarUtils, kelpLogger); - } - - public SimpleTextComponent simpleTextComponent(String text) { - return new SimpleTextComponent(sidebarUtils, kelpLogger) - .text(text); - } - - public SimpleTextComponent simpleTextComponent(String text, int line) { - return new SimpleTextComponent(sidebarUtils, kelpLogger) - .text(text) - .line(line); - } - - public EmptyLineComponent emptyLineComponent() { - return new EmptyLineComponent(sidebarUtils); - } - - public EmptyLineComponent emptyLineComponent(int line) { - return new EmptyLineComponent(sidebarUtils).score(line); - } - - public LineSeparatorComponent lineSeparatorComponent() { - return new LineSeparatorComponent(sidebarUtils); - } - - public LineSeparatorComponent lineSeparatorComponent(int line) { - return new LineSeparatorComponent(sidebarUtils).score(line); - } - - public LineSeparatorComponent lineSeparatorComponent(char symbol) { - return new LineSeparatorComponent(sidebarUtils).symbol(symbol); - } - - public LineSeparatorComponent lineSeparatorComponent(int line, char symbol) { - return new LineSeparatorComponent(sidebarUtils) - .symbol(symbol) - .score(line); - } - - public LineSeparatorComponent lineSeparatorComponent(char symbol, ChatColor... colors) { - return new LineSeparatorComponent(sidebarUtils).symbol(symbol).color(colors); - } - - public LineSeparatorComponent lineSeparatorComponent(int line, char symbol, ChatColor... colors) { - return new LineSeparatorComponent(sidebarUtils) - .symbol(symbol) - .color(colors) - .score(line); - } - - public LineSeparatorComponent lineSeparatorComponent(ChatColor... colors) { - return new LineSeparatorComponent(sidebarUtils) - .color(colors); - } - - public LineSeparatorComponent lineSeparatorComponent(int line, ChatColor... colors) { - return new LineSeparatorComponent(sidebarUtils) - .color(colors) - .score(line); - } - - public LineSeparatorComponent lineSeparatorComponent(char symbol, int length, ChatColor... colors) { - return new LineSeparatorComponent(sidebarUtils) - .color(colors) - .symbol(symbol) - .length(length); - } - - public LineSeparatorComponent lineSeparatorComponent(int line, char symbol, int length, ChatColor... colors) { - return new LineSeparatorComponent(sidebarUtils) - .color(colors) - .symbol(symbol) - .length(length) - .score(line); - } - - public LineSeparatorComponent lineSeparatorComponent(int line, int length) { - return new LineSeparatorComponent(sidebarUtils) - .score(line) - .length(length); - } - } diff --git a/core/src/main/java/de/pxav/kelp/core/sidebar/component/SimpleSidebarComponent.java b/core/src/main/java/de/pxav/kelp/core/sidebar/component/SimpleSidebarComponent.java deleted file mode 100644 index 1582f425..00000000 --- a/core/src/main/java/de/pxav/kelp/core/sidebar/component/SimpleSidebarComponent.java +++ /dev/null @@ -1,32 +0,0 @@ -package de.pxav.kelp.core.sidebar.component; - -import org.bukkit.scoreboard.Scoreboard; - -/** - * This interface is the template for all sidebar - * components. It provides methods to render and update - * the component. - * - * @author pxav - */ -public interface SimpleSidebarComponent { - - /** - * This method creates and renders the component - * to the given scoreboard. - * So it will create a new team and set the content. - * - * @param parent The scoreboard you want to render the component on. - */ - void render(Scoreboard parent); - - /** - * Updates the component, which means that no - * new team will be created, but the existing one - * will be updated to avoid flickering. - * - * @param parent The scoreboard on which the component should be rendered. - */ - void update(Scoreboard parent); - -} diff --git a/core/src/main/java/de/pxav/kelp/core/sidebar/component/SimpleTextComponent.java b/core/src/main/java/de/pxav/kelp/core/sidebar/component/SimpleTextComponent.java deleted file mode 100644 index a814d66c..00000000 --- a/core/src/main/java/de/pxav/kelp/core/sidebar/component/SimpleTextComponent.java +++ /dev/null @@ -1,79 +0,0 @@ -package de.pxav.kelp.core.sidebar.component; - -import de.pxav.kelp.core.sidebar.SidebarUtils; -import de.pxav.kelp.core.logger.KelpLogger; -import de.pxav.kelp.core.logger.LogLevel; -import org.bukkit.scoreboard.DisplaySlot; -import org.bukkit.scoreboard.Objective; -import org.bukkit.scoreboard.Scoreboard; -import org.bukkit.scoreboard.Team; - -/** - * This scoreboard component is used to display - * custom text on your sidebar. - * - * @author pxav - */ -public class SimpleTextComponent implements SimpleSidebarComponent { - - private String text; - private int line; - - private SidebarUtils sidebarUtils; - private KelpLogger logger; - - public SimpleTextComponent(SidebarUtils sidebarUtils, KelpLogger logger) { - this.sidebarUtils = sidebarUtils; - this.logger = logger; - } - - public SimpleTextComponent text(String text) { - this.text = text; - return this; - } - - public SimpleTextComponent line(int line) { - this.line = line; - return this; - } - - /** - * This method creates and renders the component - * to the given scoreboard. - * So it will create a new team and set the content. - * - * @param parent The scoreboard you want to render the component on. - */ - @Override - public void render(Scoreboard parent) { - String entry = sidebarUtils.randomEmptyEntry(parent); - Objective objective = parent.getObjective(DisplaySlot.SIDEBAR); - - objective.getScore(entry).setScore(line); - - Team team = parent.registerNewTeam("entry_" + line); - team.addEntry(entry); - update(parent); - } - - /** - * Updates the component, which means that no - * new team will be created, but the existing one - * will be updated to avoid flickering. - * - * @param parent The scoreboard on which the component should be rendered. - */ - @Override - public void update(Scoreboard parent) { - String teamName = "entry_" + line; - Team team = parent.getTeam(teamName); - - if (team == null) { - logger.log(LogLevel.ERROR, "Cannot update component at score " + line + ", " - + "because there is no entry assigned to this score."); - return; - } - sidebarUtils.setTeamData(text, team); - } - -} diff --git a/core/src/main/java/de/pxav/kelp/core/sidebar/component/StatefulListComponent.java b/core/src/main/java/de/pxav/kelp/core/sidebar/component/StatefulListComponent.java new file mode 100644 index 00000000..94300508 --- /dev/null +++ b/core/src/main/java/de/pxav/kelp/core/sidebar/component/StatefulListComponent.java @@ -0,0 +1,246 @@ +package de.pxav.kelp.core.sidebar.component; + +import com.google.common.collect.Lists; +import com.google.common.collect.Maps; + +import java.util.Iterator; +import java.util.List; +import java.util.Map; +import java.util.function.Supplier; + +/** + * This component is used to automate list displays in sidebars. + * Imagine a JumpLeague plugin for example where a list of top players + * and their progression is displayed in the sidebar. Then it would be + * useful to be able to
+ * - limit your displayed list to a specific size (to avoid having a cluttered + * scoreboard when you have 20+ players in a round)
+ * - display dynamic content (using a {@link Supplier} for example) instead of static text
+ * - keeping the list size constant for a more convenient design (when players quit for example)
+ * + * All this is supported by this component. + * + * By default this component does not support {@code lazyUpdates}, because + * the number of lines set by it may vary. To avoid this use a limiter + * {@link #limitTo(int)} and {@link #enableAutoFill()}. + * + * @author pxav + */ +public class StatefulListComponent extends SidebarComponent { + + private Supplier> list; + private int startLine; + private int maxLine; + private boolean enableLimiter; + private boolean ascendingOrder; + private boolean autoFill; + + public StatefulListComponent() { + // set default values + this.list = Lists::newArrayList; + this.startLine = 0; + this.maxLine = 0; + this.enableLimiter = false; + this.ascendingOrder = false; + } + + public static StatefulListComponent create() { + return new StatefulListComponent(); + } + + /** + * The list to be displayed in the sidebar. The order of your list + * should be in the order of how they should be finally displayed. + * depending on whether you sort them with {@link #ascendingOrder()} or + * {@link #descendingOrder()}. + * + * @param listSupplier The supplier providing the list to be displayed. + * @return Instance of the current component for more fluent builder design. + */ + public StatefulListComponent list(Supplier> listSupplier) { + this.list = listSupplier; + return this; + } + + /** + * Defines the line in the scoreboard where the first list element should + * be placed and from where the list items should be placed. If you selected + * {@link #ascendingOrder()} the number for the next list item will be incremented + * and if you choose {@link #descendingOrder()} the number will be decremented. + * + * Please note that the given line ids are absolute and you should check whether + * no components are in range of your list items, which could cause weird behaviour. + * + * @param startLine The line from where the list should start. + * @return Instance of the current component for more fluent builder design. + */ + public StatefulListComponent startFrom(int startLine) { + this.startLine = startLine; + return this; + } + + /** + * Enables the line limiter for the list. So there will be only as many + * list items in the sidebar as you input here. Generally this is recommended + * if you are not completely sure how many list items will be displayed. Then + * you can make sure that those list items do not interfere with any other + * items. + * + * Please make sure that the number is reachable if you set it. If your + * list order is ascending and this number is smaller than your start line, + * this number will have no effect. + * + * @param maxLine The last line where a list item can be displayed. + * @return Instance of the current component for more fluent builder design. + */ + public StatefulListComponent limitTo(int maxLine) { + this.maxLine = maxLine; + this.enableLimiter = true; + return this; + } + + /** + * Normally, this component is not compatible with {@code lazyUpdating}, because + * the amount of lines covered by it may vary with each update. To avoid this, enable + * the limiter with {@link #limitTo(int)} and enable auto fill by calling this method. + * + * Auto fill makes sure that if the amount of list items is smaller than the given + * maximum, more empty lines are inserted to keep the total line amount constant. + * This allows you to create flicker free sidebars using this component. + * + * @return Instance of the current component for more fluent builder design. + */ + public StatefulListComponent enableAutoFill() { + this.autoFill = true; + return this; + } + + /** + * Disables the auto fill feature described in {@link #enableAutoFill()}. + * + * @return Instance of the current component for more fluent builder design. + */ + public StatefulListComponent disableAutoFill() { + this.autoFill = false; + return this; + } + + /** + * Disables the item limiter described in {@link #limitTo(int)}. If you have + * auto fill enabled, this will also disable auto fill! + * + * @return Instance of the current component for more fluent builder design. + */ + public StatefulListComponent disableLimiter() { + this.enableLimiter = false; + return this; + } + + /** + * Sets the list item order to {@code ascending}. This means that + * if your start line is 10 for example, the second item will be placed + * on line 11, the third one on 12, and so one. + * + * @return Instance of the current component for more fluent builder design. + */ + public StatefulListComponent ascendingOrder() { + this.ascendingOrder = true; + return this; + } + + /** + * Sets the list item order to {@code descending}. This means that + * if your start line is 10 for example, the second item will be placed + * on line 9, the third one on 8, and so one. + * + * @return Instance of the current component for more fluent builder design. + */ + public StatefulListComponent descendingOrder() { + this.ascendingOrder = false; + return this; + } + + /** + * Renders all the information provided in the component + * to a map containing the final information to be rendered + * to the sidebar (the lines where the text should be placed and + * the text to write there). + * + * @return A map, where the key is the absolute line where + * the component should be placed in the sidebar and + * the value is the actual text for that line. + */ + @Override + public Map render() { + Map output = Maps.newHashMap(); + List lines = list.get(); + + Iterator iterator = lines.iterator(); + + // before iterating through the list, the plugin checks whether + // the list should be placed in ascending or descending order, which + // is important to check whether indexes have to be decremented or incremented. + if (ascendingOrder) { + for (int i = startLine; i < Integer.MAX_VALUE; i++) { + + if (!iterator.hasNext()) { + // if the iterator has arrived at the last item it has to check + // whether there are enough lines to fill the minimum required area + // if such an area is set with autoFill. It will then add any missing + // lines. + if (enableLimiter && autoFill && i <= maxLine) { + for (int fillIndex = i; fillIndex <= maxLine; fillIndex++) { + output.put(fillIndex, " "); + } + return output; + } + return output; + } + if (enableLimiter && i == maxLine) { + return output; + } + output.put(i, iterator.next()); + } + } else { + for (int i = startLine; i < Integer.MAX_VALUE; i--) { + + // here you can find pretty much the same as described above + // but for the descending list order. The major difference is that + // the numbers are inverted here. (++ -> -- for example) + if (!iterator.hasNext()) { + if (enableLimiter && autoFill && i >= maxLine) { + for (int fillIndex = i; fillIndex >= maxLine; fillIndex--) { + output.put(fillIndex, " "); + } + return output; + } + return output; + } + if (enableLimiter && i == maxLine) { + return output; + } + output.put(i, iterator.next()); + } + } + + // if the list is empty, there have been no checks whether it is + // necessary to fill up empty lines. So this is done here. If the + // limiter and auto fill mode are enabled, the plugin will add as + // many lines as needed to fill the space. + if (output.isEmpty() && enableLimiter && autoFill) { + if (ascendingOrder) { + for (int fillIndex = startLine; fillIndex <= maxLine; fillIndex++) { + output.put(fillIndex, " "); + } + } else { + for (int fillIndex = startLine; fillIndex >= maxLine; fillIndex--) { + output.put(fillIndex, " "); + } + } + + } + + return output; + } + +} diff --git a/core/src/main/java/de/pxav/kelp/core/sidebar/component/StatefulTextComponent.java b/core/src/main/java/de/pxav/kelp/core/sidebar/component/StatefulTextComponent.java new file mode 100755 index 00000000..c08f668d --- /dev/null +++ b/core/src/main/java/de/pxav/kelp/core/sidebar/component/StatefulTextComponent.java @@ -0,0 +1,75 @@ +package de.pxav.kelp.core.sidebar.component; + +import com.google.common.collect.Maps; + +import java.util.Map; +import java.util.function.Supplier; + +/** + * This component allows you to display dynamic content + * on sidebars. While the {@link StatelessTextComponent} can only display + * static, non-updatable content, this component can be updated. + * Example use cases for that would be displaying the player's + * coins or a timer. + * + * @author pxav + */ +public class StatefulTextComponent extends SidebarComponent { + + // absolute position to place the component + private int line; + + private Supplier text; + + public StatefulTextComponent() { + text = () -> "StatefulTextComponent"; + } + + public static StatefulTextComponent create() { + return new StatefulTextComponent(); + } + + /** + * Sets the line number of the component to be placed in the sidebar. + * Please note that this line id represents an absolute position and + * should therefore be unique. No components should have the same line + * number. + * + * @param line The line of the sidebar, where the component should + * be visible. + * @return The current component instance for fluent builder design. + */ + public StatefulTextComponent line(int line) { + this.line = line; + return this; + } + + /** + * Sets the text to be displayed in the component. + * + * @param text The dynamic text to be displayed. + * @return Instance of the current component for more fluent builder design. + */ + public StatefulTextComponent text(Supplier text) { + this.text = text; + return this; + } + + /** + * Renders all the information provided in the component + * to a map containing the final information to be rendered + * to the sidebar (the lines where the text should be placed and + * the text to write there). + * + * @return A map, where the key is the absolute line where + * the component should be placed in the sidebar and + * the value is the actual text for that line. + */ + @Override + public Map render() { + Map output = Maps.newHashMap(); + output.put(line, String.valueOf(text.get())); + return output; + } + +} diff --git a/core/src/main/java/de/pxav/kelp/core/sidebar/component/StatelessTextComponent.java b/core/src/main/java/de/pxav/kelp/core/sidebar/component/StatelessTextComponent.java new file mode 100755 index 00000000..52f71306 --- /dev/null +++ b/core/src/main/java/de/pxav/kelp/core/sidebar/component/StatelessTextComponent.java @@ -0,0 +1,70 @@ +package de.pxav.kelp.core.sidebar.component; + +import com.google.common.collect.Maps; +import de.pxav.kelp.core.KelpPlugin; +import de.pxav.kelp.core.sidebar.SidebarUtils; + +import java.util.Map; + +/** + * This component can display static text in a sidebar. So unlike + * the {@link StatefulListComponent} the text will not change after an + * update. This is useful to display static information such as a link + * to your website, etc. + * + * @author pxav + */ +public class StatelessTextComponent extends SidebarComponent { + + private String text = "SimpleTextComponent"; + private int line; + + public static StatelessTextComponent create() { + return new StatelessTextComponent(); + } + + /** + * Sets the text to be displayed by this component. This text cannot be changed + * once it has been set. + * + * @param text The static text to be displayed. + * @return Instance of the current component for more fluent builder design. + */ + public StatelessTextComponent text(String text) { + this.text = text; + return this; + } + + /** + * Sets the line number of the component to be placed in the sidebar. + * Please note that this line id represents an absolute position and + * should therefore be unique. No components should have the same line + * number. + * + * @param line The line of the sidebar, where the component should + * be visible. + * @return The current component instance for fluent builder design. + */ + public StatelessTextComponent line(int line) { + this.line = line; + return this; + } + + /** + * Renders all the information provided in the component + * to a map containing the final information to be rendered + * to the sidebar (the lines where the text should be placed and + * the text to write there). + * + * @return A map, where the key is the absolute line where + * the component should be placed in the sidebar and + * the value is the actual text for that line. + */ + @Override + public Map render() { + Map output = Maps.newHashMap(); + output.put(this.line, this.text); + return output; + } + +} diff --git a/core/src/main/java/de/pxav/kelp/core/sidebar/type/AnimatedSidebar.java b/core/src/main/java/de/pxav/kelp/core/sidebar/type/AnimatedSidebar.java old mode 100644 new mode 100755 index 6e160945..77c3df06 --- a/core/src/main/java/de/pxav/kelp/core/sidebar/type/AnimatedSidebar.java +++ b/core/src/main/java/de/pxav/kelp/core/sidebar/type/AnimatedSidebar.java @@ -1,97 +1,136 @@ package de.pxav.kelp.core.sidebar.type; -import com.google.common.collect.Lists; +import de.pxav.kelp.core.KelpPlugin; import de.pxav.kelp.core.animation.TextAnimation; -import de.pxav.kelp.core.sidebar.component.SimpleSidebarComponent; +import de.pxav.kelp.core.player.KelpPlayer; import de.pxav.kelp.core.sidebar.version.SidebarVersionTemplate; -import org.bukkit.Bukkit; -import org.bukkit.entity.Player; -import org.bukkit.scoreboard.Scoreboard; -import org.bukkit.scoreboard.ScoreboardManager; - -import java.util.List; /** - * This is a more complex type of sidebar which can - * hold animations like an animated title. + * This is a specific type of sidebar which enables you + * to animate titles. Those animations can be clustered, which means + * that if you have the same update interval for different sidebars, + * you can cluster them into a group like {@code lobby_scoreboard} for + * example to reduce the total amount of schedulers required. * - * Furthermore you can add simple components. + * Apart from that it is the same as {@link SimpleSidebar}, so you can + * also hold normal sidebar components and update them, etc. * * @author pxav */ -public class AnimatedSidebar extends KelpSidebar { +public class AnimatedSidebar extends KelpSidebar { - private TextAnimation titleAnimation; - private List simpleComponents; + private TextAnimation title; + private int titleAnimationInterval = 200; + private String clusterId = null; - private ScoreboardManager scoreboardManager; private SidebarVersionTemplate sidebarVersionTemplate; - AnimatedSidebar(SidebarVersionTemplate sidebarVersionTemplate) { - this.scoreboardManager = Bukkit.getScoreboardManager(); + public AnimatedSidebar(SidebarVersionTemplate sidebarVersionTemplate) { this.sidebarVersionTemplate = sidebarVersionTemplate; - this.simpleComponents = Lists.newArrayList(); } - public AnimatedSidebar addComponent(SimpleSidebarComponent components) { - this.simpleComponents.add(components); + public static AnimatedSidebar create() { + return new AnimatedSidebar( + KelpPlugin.getInjector().getInstance(SidebarVersionTemplate.class) + ); + } + + /** + * Sets the title animation to be displayed at the top of the sidebar. + * + * @param title The title animation you want to display. + * @return Instance of the current component for more fluent builder design. + */ + public AnimatedSidebar title(TextAnimation title) { + this.title = title; return this; } - public AnimatedSidebar withTitle(TextAnimation titleAnimation) { - this.titleAnimation = titleAnimation; + /** + * Sets the interval of the title animation. The given value is the period + * in milliseconds it takes between each update to the next animation state. + * + * @param titleAnimationInterval The update interval in milliseconds. + * @return Instance of the current component for more fluent builder design. + */ + public AnimatedSidebar titleAnimationInterval(int titleAnimationInterval) { + this.titleAnimationInterval = titleAnimationInterval; return this; } - public void updateTitleOnly(Player player, int state) { - Scoreboard scoreboard = player.getScoreboard(); - scoreboard.getObjective("main").setDisplayName(titleAnimation.states().get(state)); + /** + * Sets the cluster id for the current sidebar. This is an optional + * step, but it is recommended doing that if your sidebar is displayed + * to more than 2 or 3 players. It wraps all player sidebars into a + * single scheduler to save server performance instead of creating an + * individual scheduler for each player. + * + * If the given cluster does not exist, it will be created automatically by Kelp. + * + * @param clusterId The id of the cluster you want to add the sidebar to. + * @return Instance of the current component for more fluent builder design. + */ + public AnimatedSidebar clusterId(String clusterId) { + this.clusterId = clusterId; + return this; } - @Override - public Scoreboard renderSidebar(Player player) { - Scoreboard scoreboard = scoreboardManager.getNewScoreboard(); - - scoreboard.getTeams().forEach(current -> { - if (current.getName().startsWith("entry_")) { - current.unregister(); - } - }); - - if (scoreboard.getObjective("main") == null) { - sidebarVersionTemplate.createObjective(scoreboard, "main", titleAnimation.states().get(0)); - } - - for (SimpleSidebarComponent component : this.simpleComponents) { - component.render(scoreboard); - } - return scoreboard; + /** + * Gets the cluster id of the current scoreboard. + * + * @return The current cluster id. {@code null} if no cluster id has been set. + */ + public String getClusterId() { + return clusterId; } - @Override - public Scoreboard renderAndOpenSidebar(Player player) { - Scoreboard scoreboard = renderSidebar(player); - player.setScoreboard(scoreboard); - return scoreboard; + /** + * Gets the title of the sidebar as a {@link TextAnimation}. + * + * @return The sidebar's title animation. + */ + public TextAnimation getTitle() { + return title; } - @Override - public Scoreboard update(Player player) { - Scoreboard scoreboard = player.getScoreboard(); + /** + * Gets the interval in which the sidebar's title is updated in milliseconds. + * + * @return The update interval of the sidebar's title. + */ + public int getTitleAnimationInterval() { + return titleAnimationInterval; + } - for (SimpleSidebarComponent component : this.simpleComponents) { - component.update(scoreboard); - } - return scoreboard; + @Override + public void render(KelpPlayer player) { + sidebarVersionTemplate.renderSidebar(this, player); } @Override - public void hideSidebar(Player player) { + public void update(KelpPlayer player) { + sidebarVersionTemplate.updateSidebar(this, player); + } + /** + * Performs a lazy update on the sidebar. A lazy update does not remove all entries/lines + * from a scoreboard first, like it is done by {@link #update(KelpPlayer)}. It only used the + * existing entries in the sidebar, which means that you cannot use it if you know that the amount + * of lines in the sidebar might change with an update. + * + * However this update method is completely free from any flickering effects and it is + * not as performance heavy as a normal update. So if you can, you should prefer this update + * method over a normal update. + * + * @param player The player who should see the updated sidebar. + */ + public void lazyUpdate(KelpPlayer player) { + sidebarVersionTemplate.lazyUpdate(this, player); } - public int maxStates() { - return this.titleAnimation == null ? 0 : this.titleAnimation.states().size(); + @Override + public void remove(KelpPlayer player) { + } } diff --git a/core/src/main/java/de/pxav/kelp/core/sidebar/type/KelpSidebar.java b/core/src/main/java/de/pxav/kelp/core/sidebar/type/KelpSidebar.java old mode 100644 new mode 100755 index 84471ffa..d87cff55 --- a/core/src/main/java/de/pxav/kelp/core/sidebar/type/KelpSidebar.java +++ b/core/src/main/java/de/pxav/kelp/core/sidebar/type/KelpSidebar.java @@ -1,55 +1,97 @@ package de.pxav.kelp.core.sidebar.type; -import org.bukkit.entity.Player; -import org.bukkit.scoreboard.Scoreboard; +import com.google.common.collect.Lists; +import de.pxav.kelp.core.player.KelpPlayer; +import de.pxav.kelp.core.sidebar.component.SidebarComponent; + +import java.util.Collection; /** - * This class basically is the template for each sidebar. - * If you want to make a custom sidebar, - * your class has to inherit from this class. + * This class represents a template for Kelp sidebars. * - * It provides essential methods used by the repository. * + * @param The type of the current sidebar. This is needed to keep + * up a more fluent builder design as methods such as {@link #addComponent(SidebarComponent)} + * do not return a general sidebar anymore but the specific type of sidebar you are + * currently working with. * @author pxav */ -public abstract class KelpSidebar { +public abstract class KelpSidebar { + + protected Collection components = Lists.newArrayList(); + + /** + * Adds a new component to the sidebar. When you add it, + * it should already contain all its properties. + * + * @param component The component to be added. + * @return + */ + public T addComponent(SidebarComponent component) { + this.components.add(component); + return (T) this; + } + + /** + * Removes a specific component from the sidebar permanently. + * It won't be displayed anymore until you add it again or + * display another component instead. + * + * @param component The component to be removed. + * @return + */ + public T removeComponent(SidebarComponent component) { + this.components.remove(component); + return (T) this; + } + + /** + * Removes all existing components of the sidebar permanently. + * + * @return + */ + public T clearComponents() { + this.components.clear(); + return (T) this; + } /** - * Renders the sidebar and its components. - * That means that no existing scoreboard is modified, - * but a new one will be created as well as - * a new objective. - * This method does not open the sidebar automatically. + * Gets a collection of all components the sidebar is currently holding. * - * @param player The player for which the sidebar should be rendered. - * @return The final sidebar obeject. + * @return A collection containing all components. */ - public abstract Scoreboard renderSidebar(Player player); + public Collection getComponents() { + return this.components; + } /** - * Renders and opens the sidebar. - * This method basically executes the {@code #renderSidebar(player)} method - * above and directly sets the scoreboard for the given player. + * Renders the sidebar to a specific player. This means it displays + * it for the first time (so only use this method if the player does + * not already see this sidebar to avoid flicker effects or similar behaviour). * - * @param player The player who should see the scoreboard. - * @return The render result. + * @param player The player to render the sidebar to. */ - public abstract Scoreboard renderAndOpenSidebar(Player player); + public abstract void render(KelpPlayer player); /** - * Updates the given scoreboard without creating a new - * one. + * Updates all components of the sidebar. That means that all existing entries/lines + * are removed and the components are added to an empty scoreboard again. So you + * should only use this method if you know that the amount of lines will change or + * certain components might (dis)appear after an update. If you do a simple update, + * that only updates some text values you might want to use a {@code lazyUpdate} as this + * is safe from any flicker effects unlike this method, which can cause flicker on heavily + * loaded servers. * - * @param player The player whose scoreboard you want to update. - * @return The final scoreboard with the updated data. + * @param player The player who should see the updated sidebar. */ - public abstract Scoreboard update(Player player); + public abstract void update(KelpPlayer player); /** - * Makes the sidebar disappear from the player's screen. + * Removes the sidebar from the given player, which means that the player + * won't be able to see it anymore. * - * @param player The player whose sidebar should disappear. + * @param player The player you want to hide the sidebar from. */ - public abstract void hideSidebar(Player player); + public abstract void remove(KelpPlayer player); } diff --git a/core/src/main/java/de/pxav/kelp/core/sidebar/type/SidebarFactory.java b/core/src/main/java/de/pxav/kelp/core/sidebar/type/SidebarFactory.java old mode 100644 new mode 100755 index c1e69831..148cc655 --- a/core/src/main/java/de/pxav/kelp/core/sidebar/type/SidebarFactory.java +++ b/core/src/main/java/de/pxav/kelp/core/sidebar/type/SidebarFactory.java @@ -2,10 +2,8 @@ import com.google.inject.Inject; import com.google.inject.Singleton; -import de.pxav.kelp.core.sidebar.component.SidebarComponentFactory; -import de.pxav.kelp.core.logger.KelpLogger; +import de.pxav.kelp.core.sidebar.version.SidebarUpdaterVersionTemplate; import de.pxav.kelp.core.sidebar.version.SidebarVersionTemplate; -import org.bukkit.plugin.java.JavaPlugin; /** * This class can be used to easily build @@ -22,24 +20,18 @@ @Singleton public class SidebarFactory { - private KelpLogger logger; - private JavaPlugin javaPlugin; private SidebarVersionTemplate sidebarVersionTemplate; - private SidebarComponentFactory sidebarComponentFactory; + private SidebarUpdaterVersionTemplate updaterVersionTemplate; @Inject - public SidebarFactory(KelpLogger logger, - JavaPlugin javaPlugin, - SidebarVersionTemplate sidebarVersionTemplate, - SidebarComponentFactory sidebarComponentFactory) { - this.logger = logger; - this.javaPlugin = javaPlugin; + public SidebarFactory(SidebarVersionTemplate sidebarVersionTemplate, + SidebarUpdaterVersionTemplate updaterVersionTemplate) { this.sidebarVersionTemplate = sidebarVersionTemplate; - this.sidebarComponentFactory = sidebarComponentFactory; + this.updaterVersionTemplate = updaterVersionTemplate; } public SimpleSidebar newSimpleSidebar() { - return new SimpleSidebar(logger, javaPlugin, sidebarVersionTemplate, sidebarComponentFactory); + return new SimpleSidebar(sidebarVersionTemplate, updaterVersionTemplate); } public AnimatedSidebar newAnimatedSidebar() { diff --git a/core/src/main/java/de/pxav/kelp/core/sidebar/type/SimpleSidebar.java b/core/src/main/java/de/pxav/kelp/core/sidebar/type/SimpleSidebar.java old mode 100644 new mode 100755 index f11e5a82..bc0a2e01 --- a/core/src/main/java/de/pxav/kelp/core/sidebar/type/SimpleSidebar.java +++ b/core/src/main/java/de/pxav/kelp/core/sidebar/type/SimpleSidebar.java @@ -1,17 +1,11 @@ package de.pxav.kelp.core.sidebar.type; -import com.google.common.collect.Lists; -import de.pxav.kelp.core.sidebar.component.SidebarComponentFactory; -import de.pxav.kelp.core.sidebar.component.SimpleSidebarComponent; +import de.pxav.kelp.core.KelpPlugin; +import de.pxav.kelp.core.player.KelpPlayer; +import de.pxav.kelp.core.sidebar.version.SidebarUpdaterVersionTemplate; import de.pxav.kelp.core.sidebar.version.SidebarVersionTemplate; -import de.pxav.kelp.core.logger.KelpLogger; -import org.bukkit.Bukkit; -import org.bukkit.entity.Player; -import org.bukkit.plugin.java.JavaPlugin; -import org.bukkit.scoreboard.Scoreboard; -import org.bukkit.scoreboard.ScoreboardManager; -import java.util.Collection; +import java.util.function.Supplier; /** * This class represents the most simple type @@ -22,94 +16,102 @@ * * @author pxav */ -public class SimpleSidebar extends KelpSidebar { +public class SimpleSidebar extends KelpSidebar { - // the title of the scoreboard (cannot be animated) - private String title; + private Supplier title; - // the components that should be displayed. - private Collection components; - - private KelpLogger logger; - private JavaPlugin javaPlugin; private SidebarVersionTemplate sidebarVersionTemplate; - private SidebarComponentFactory sidebarComponentFactory; - - private ScoreboardManager scoreboardManager; + private SidebarUpdaterVersionTemplate updaterVersionTemplate; - SimpleSidebar(KelpLogger logger, - JavaPlugin javaPlugin, - SidebarVersionTemplate sidebarVersionTemplate, - SidebarComponentFactory sidebarComponentFactory) { - this.logger = logger; - this.javaPlugin = javaPlugin; + public SimpleSidebar(SidebarVersionTemplate sidebarVersionTemplate, + SidebarUpdaterVersionTemplate updaterVersionTemplate) { this.sidebarVersionTemplate = sidebarVersionTemplate; - this.sidebarComponentFactory = sidebarComponentFactory; + this.updaterVersionTemplate = updaterVersionTemplate; + } - this.components = Lists.newArrayList(); - this.scoreboardManager = Bukkit.getScoreboardManager(); + public static SimpleSidebar create() { + return new SimpleSidebar( + KelpPlugin.getInjector().getInstance(SidebarVersionTemplate.class), + KelpPlugin.getInjector().getInstance(SidebarUpdaterVersionTemplate.class) + ); } - public SimpleSidebar withTitle(String title) { + /** + * Sets the title of the sidebar. This method takes a {@link Supplier}, which + * allows you to create dynamic titles that can update every time you call + * {@link #updateTitleOnly(KelpPlayer)}. + * + * If you rather want a static title, choose {@link #staticTitle(String)} + * + * @param title The title do display at the top of the sidebar. + * @return Instance of the current component for more fluent builder design. + */ + public SimpleSidebar title(Supplier title) { this.title = title; return this; } - public SimpleSidebar insertLine(int line, String text) { - this.components.add(sidebarComponentFactory.simpleTextComponent(text, line)); + /** + * Sets the title of the sidebar. This method takes a normal {@code String}, which + * means that the title is static and won't change if you call {@link #updateTitleOnly(KelpPlayer)} + * + * If you rather want a dynamic title, choose {@link #title(Supplier)} instead. + * + * @param title The title do display at the top of the sidebar. + * @return Instance of the current component for more fluent builder design. + */ + public SimpleSidebar staticTitle(String title) { + this.title = () -> title; return this; } - public SimpleSidebar addComponent(SimpleSidebarComponent sidebarComponent) { - this.components.add(sidebarComponent); - return this; + /** + * Gets the {@link Supplier} holding the current title - no matter if static or dynamic. + * @return The current title of the sidebar. + */ + public Supplier getTitle() { + return title; } @Override - public Scoreboard renderSidebar(Player player) { - Scoreboard scoreboard = scoreboardManager.getNewScoreboard(); - - scoreboard.getTeams().forEach(current -> { - if (current.getName().startsWith("entry_")) { - current.unregister(); - } - }); - - if (scoreboard.getObjective("main") == null) { - sidebarVersionTemplate.createObjective(scoreboard, "main", this.title); - } - - for (SimpleSidebarComponent component : this.components) { - component.render(scoreboard); - } - - return scoreboard; + public void render(KelpPlayer player) { + sidebarVersionTemplate.renderSidebar(this, player); } - @Override - public Scoreboard renderAndOpenSidebar(Player player) { - Scoreboard scoreboard = this.renderSidebar(player); - player.setScoreboard(scoreboard); - return scoreboard; + /** + * Updates the title of the sidebar without loading to changing any + * of its components. + * + * @param player The player you want to show the title update to. + */ + public void updateTitleOnly(KelpPlayer player) { + updaterVersionTemplate.updateTitleOnly(title.get(), player); } @Override - public Scoreboard update(Player player) { - Scoreboard scoreboard = player.getScoreboard(); - - for (SimpleSidebarComponent component : this.components) { - component.update(scoreboard); - } + public void update(KelpPlayer player) { + sidebarVersionTemplate.updateSidebar(this, player); + } - return scoreboard; + /** + * Performs a lazy update on the sidebar. A lazy update does not remove all entries/lines + * from a scoreboard first, like it is done by {@link #update(KelpPlayer)}. It only used the + * existing entries in the sidebar, which means that you cannot use it if you know that the amount + * of lines in the sidebar might change with an update. + * + * However this update method is completely free from any flickering effects and it is + * not as performance heavy as a normal update. So if you can, you should prefer this update + * method over a normal update. + * + * @param player The player who should see the updated sidebar. + */ + public void lazyUpdate(KelpPlayer player) { + sidebarVersionTemplate.lazyUpdate(this, player); } @Override - public void hideSidebar(Player player) { - Bukkit.getScheduler().runTaskLater(javaPlugin, () -> { - Scoreboard emptyScoreboard = scoreboardManager.getNewScoreboard(); - player.setScoreboard(emptyScoreboard); - }, 1L); + public void remove(KelpPlayer player) { + } } diff --git a/core/src/main/java/de/pxav/kelp/core/sidebar/version/SidebarUpdaterVersionTemplate.java b/core/src/main/java/de/pxav/kelp/core/sidebar/version/SidebarUpdaterVersionTemplate.java new file mode 100644 index 00000000..b51270fe --- /dev/null +++ b/core/src/main/java/de/pxav/kelp/core/sidebar/version/SidebarUpdaterVersionTemplate.java @@ -0,0 +1,21 @@ +package de.pxav.kelp.core.sidebar.version; + +import de.pxav.kelp.core.application.KelpVersionTemplate; +import de.pxav.kelp.core.player.KelpPlayer; + +/** + * This version template is responsible for specific update operations + * other than simply updating specific components. This includes updating + * a sidebar's title for example. + */ +@KelpVersionTemplate +public abstract class SidebarUpdaterVersionTemplate { + + /** + * Updates the title of sidebar of the given player to the given string. + * @param to The title to update to. + * @param player The player whose sidebar's title should be updated. + */ + public abstract void updateTitleOnly(String to, KelpPlayer player); + +} diff --git a/core/src/main/java/de/pxav/kelp/core/sidebar/version/SidebarVersionTemplate.java b/core/src/main/java/de/pxav/kelp/core/sidebar/version/SidebarVersionTemplate.java old mode 100644 new mode 100755 index f38ef309..152e8a7b --- a/core/src/main/java/de/pxav/kelp/core/sidebar/version/SidebarVersionTemplate.java +++ b/core/src/main/java/de/pxav/kelp/core/sidebar/version/SidebarVersionTemplate.java @@ -1,20 +1,15 @@ package de.pxav.kelp.core.sidebar.version; import de.pxav.kelp.core.application.KelpVersionTemplate; +import de.pxav.kelp.core.player.KelpPlayer; +import de.pxav.kelp.core.sidebar.component.SidebarComponent; +import de.pxav.kelp.core.sidebar.type.AnimatedSidebar; +import de.pxav.kelp.core.sidebar.type.KelpSidebar; import org.bukkit.entity.Player; import org.bukkit.scoreboard.Objective; import org.bukkit.scoreboard.Scoreboard; /** - * This basically is a template for version-specific implementations. - * If you want to create a version specific implementation of scoreboards - * you need to make your class inherit from this abstract class - * and type the content of the given methods. - * - * Kelp always detects if there is an implementation installed and - * automatically binds it to this class so that it can be injected - * in your constructor. So you can always depend on this class if - * you need version specific sidebar code. * * @author pxav */ @@ -22,36 +17,38 @@ public abstract class SidebarVersionTemplate { /** - * @return A new, empty scoreboard. - */ - public abstract Scoreboard createScoreboard(); - - /** - * Creates a scoreboard objective. + * Renders the sidebar to a specific player. This means it displays + * it for the first time (so only use this method if the player does + * not already see this sidebar to avoid flicker effects or similar behaviour). * - * @param parent The scoreboard in which the objective should be wrapped. - * @param identifier The name/identifier of the objective. - * @param title The display name/title of the objective. - * @return The final scoreboard objective. + * @param sidebar The sidebar to render to the given player. + * @param player The player to render the sidebar to. */ - public abstract Objective createObjective(Scoreboard parent, - String identifier, - String title); + public abstract void renderSidebar(KelpSidebar sidebar, KelpPlayer player); /** - * Open the screboard for a player. + * Performs a lazy update on the sidebar. A lazy update does not remove all entries/lines + * from a scoreboard first, like it is done by {@link #updateSidebar(KelpSidebar, KelpPlayer)}. It only uses the + * existing entries in the sidebar, which means that you cannot use it if you know that the amount + * of lines in the sidebar might change with an update. + * + * However this update method is completely free from any flickering effects and it is + * not as performance heavy as a normal update. So if you can, you should prefer this update + * method over a normal update. * - * @param scoreboard The actual scoreboard you want to open. - * @param player The player who should see the scoreboard. + * @param sidebar The sidebar to update. + * @param kelpPlayer The player who should see the updated sidebar. */ - public abstract void openScoreboard(Scoreboard scoreboard, Player player); + public abstract void lazyUpdate(KelpSidebar sidebar, KelpPlayer kelpPlayer); /** - * Close / hide the scoreboard for the player again. + * Performs a full-update on the given sidebar, which means that all existing + * contents are removed and then new contents are applied. This method is safe + * against changing amounts of lines. * - * @param player The player whose scoreboard - * should become invisible. + * @param sidebar The sidebar you want to update. + * @param player The player who should see the updates. */ - public abstract void closeScoreboard(Player player); + public abstract void updateSidebar(KelpSidebar sidebar, KelpPlayer player); } diff --git a/kelp-sql/pom.xml b/kelp-sql/pom.xml index 2dac1ee2..b1a3bd0f 100644 --- a/kelp-sql/pom.xml +++ b/kelp-sql/pom.xml @@ -5,7 +5,7 @@ parent com.github.pxav.kelp - 0.0.5 + 0.1.0 4.0.0 @@ -20,7 +20,7 @@ com.github.pxav.kelp core - 0.0.5 + 0.1.0 provided diff --git a/pom.xml b/pom.xml index 409ceb8f..b8301a6a 100644 --- a/pom.xml +++ b/pom.xml @@ -7,7 +7,7 @@ com.github.pxav.kelp parent pom - 0.0.5 + 0.1.0 Kelp A cross-version spigot framework to avoid boilerplate code and make your plugin compatible with multiple spigot versions easily @@ -92,7 +92,11 @@ central-deploy @@ -146,7 +150,7 @@ sign-artifacts - install + verify sign @@ -231,20 +235,6 @@ - - org.apache.maven.plugins - maven-gpg-plugin - 1.5 - - - sign-artifacts - install - - sign - - - - org.apache.maven.plugins maven-jar-plugin @@ -252,7 +242,6 @@ - \ No newline at end of file diff --git a/testing-module/pom.xml b/testing-module/pom.xml index 155ed166..67fdd6c4 100644 --- a/testing-module/pom.xml +++ b/testing-module/pom.xml @@ -5,7 +5,7 @@ parent com.github.pxav.kelp - 0.0.5 + 0.1.0 4.0.0 @@ -45,7 +45,7 @@ com.github.pxav.kelp core - 0.0.5 + 0.1.0 provided diff --git a/testing-module/src/main/java/de/pxav/kelp/testing/command/kbossbar/TestBossBarCommand.java b/testing-module/src/main/java/de/pxav/kelp/testing/bossbar/KBossBarCommand.java similarity index 98% rename from testing-module/src/main/java/de/pxav/kelp/testing/command/kbossbar/TestBossBarCommand.java rename to testing-module/src/main/java/de/pxav/kelp/testing/bossbar/KBossBarCommand.java index de064102..6e63a7a3 100644 --- a/testing-module/src/main/java/de/pxav/kelp/testing/command/kbossbar/TestBossBarCommand.java +++ b/testing-module/src/main/java/de/pxav/kelp/testing/bossbar/KBossBarCommand.java @@ -1,4 +1,4 @@ -package de.pxav.kelp.testing.command.kbossbar; +package de.pxav.kelp.testing.bossbar; import de.pxav.kelp.core.command.CreateCommand; import de.pxav.kelp.core.command.ExecutorType; @@ -14,7 +14,7 @@ * @author pxav */ @CreateCommand(name = "kbossbar", executorType = ExecutorType.PLAYER_ONLY) -public class TestBossBarCommand extends KelpCommand { +public class KBossBarCommand extends KelpCommand { @Override public void onCommandRegister() { diff --git a/testing-module/src/main/java/de/pxav/kelp/testing/command/kmaterial/GiveKelpMaterialCommands.java b/testing-module/src/main/java/de/pxav/kelp/testing/inventory/GiveMaterialCommand.java similarity index 88% rename from testing-module/src/main/java/de/pxav/kelp/testing/command/kmaterial/GiveKelpMaterialCommands.java rename to testing-module/src/main/java/de/pxav/kelp/testing/inventory/GiveMaterialCommand.java index f8037e5b..9cb3827f 100644 --- a/testing-module/src/main/java/de/pxav/kelp/testing/command/kmaterial/GiveKelpMaterialCommands.java +++ b/testing-module/src/main/java/de/pxav/kelp/testing/inventory/GiveMaterialCommand.java @@ -1,4 +1,4 @@ -package de.pxav.kelp.testing.command.kmaterial; +package de.pxav.kelp.testing.inventory; import com.google.inject.Inject; import de.pxav.kelp.core.command.CreateSubCommand; @@ -18,14 +18,14 @@ * * @author pxav */ -@CreateSubCommand(name = "give", executorType = ExecutorType.PLAYER_ONLY, parentCommand = KelpMaterialCommand.class) -public class GiveKelpMaterialCommands extends KelpCommand { +@CreateSubCommand(name = "give", executorType = ExecutorType.PLAYER_ONLY, parentCommand = KMaterialCommand.class) +public class GiveMaterialCommand extends KelpCommand { private DebugCommandConfig config; private KelpItemFactory itemFactory; @Inject - public GiveKelpMaterialCommands(DebugCommandConfig config, KelpItemFactory itemFactory) { + public GiveMaterialCommand(DebugCommandConfig config, KelpItemFactory itemFactory) { this.config = config; this.itemFactory = itemFactory; } diff --git a/testing-module/src/main/java/de/pxav/kelp/testing/command/kmaterial/KelpMaterialCommand.java b/testing-module/src/main/java/de/pxav/kelp/testing/inventory/KMaterialCommand.java similarity index 85% rename from testing-module/src/main/java/de/pxav/kelp/testing/command/kmaterial/KelpMaterialCommand.java rename to testing-module/src/main/java/de/pxav/kelp/testing/inventory/KMaterialCommand.java index 1ace59ce..6e9b9623 100644 --- a/testing-module/src/main/java/de/pxav/kelp/testing/command/kmaterial/KelpMaterialCommand.java +++ b/testing-module/src/main/java/de/pxav/kelp/testing/inventory/KMaterialCommand.java @@ -1,4 +1,4 @@ -package de.pxav.kelp.testing.command.kmaterial; +package de.pxav.kelp.testing.inventory; import com.google.inject.Inject; import com.google.inject.Singleton; @@ -15,12 +15,12 @@ */ @Singleton @CreateCommand(name = "kmaterial", executorType = ExecutorType.PLAYER_ONLY) -public class KelpMaterialCommand extends KelpCommand { +public class KMaterialCommand extends KelpCommand { private DebugCommandConfig config; @Inject - public KelpMaterialCommand(DebugCommandConfig config) { + public KMaterialCommand(DebugCommandConfig config) { this.config = config; } diff --git a/testing-module/src/main/java/de/pxav/kelp/testing/sidebar/DefaultSidebarTest.java b/testing-module/src/main/java/de/pxav/kelp/testing/sidebar/DefaultSidebarTest.java deleted file mode 100644 index 6ab52b10..00000000 --- a/testing-module/src/main/java/de/pxav/kelp/testing/sidebar/DefaultSidebarTest.java +++ /dev/null @@ -1,56 +0,0 @@ -package de.pxav.kelp.testing.sidebar; - -import com.google.inject.Singleton; -import de.pxav.kelp.core.animation.TextAnimation; -import de.pxav.kelp.core.animation.TextAnimationFactory; -import de.pxav.kelp.core.sidebar.CreateSidebar; -import de.pxav.kelp.core.sidebar.SidebarRepository; -import de.pxav.kelp.core.sidebar.component.SidebarComponentFactory; -import de.pxav.kelp.core.sidebar.type.AnimatedSidebar; -import de.pxav.kelp.core.sidebar.type.SidebarFactory; -import org.bukkit.entity.Player; - -/** - * A class description goes here. - * - * @author pxav - */ -@Singleton -public class DefaultSidebarTest { - - private SidebarFactory sidebarFactory; - private SidebarComponentFactory sidebarComponentFactory; - private SidebarRepository sidebarRepository; - private TextAnimationFactory textAnimationFactory; - - public DefaultSidebarTest(SidebarFactory sidebarFactory, - SidebarComponentFactory sidebarComponentFactory, - SidebarRepository sidebarRepository, - TextAnimationFactory textAnimationFactory) { - this.sidebarFactory = sidebarFactory; - this.sidebarComponentFactory = sidebarComponentFactory; - this.sidebarRepository = sidebarRepository; - this.textAnimationFactory = textAnimationFactory; - } - - @CreateSidebar(identifier = "default", - setOnJoin = true, - titleAnimationInterval = 700) - public AnimatedSidebar defaultSidebar(Player player) { - return this.sidebarFactory.newAnimatedSidebar() - .withTitle(textAnimationFactory - .newBuildingTextAnimation() - .text("This is a test!") - .ignoreSpaces()) - .addComponent(sidebarComponentFactory.lineSeparatorComponent(10)) - .addComponent(sidebarComponentFactory.emptyLineComponent(9)) - .addComponent(sidebarComponentFactory - .simpleTextComponent() - .text("§aWelcome, ") - .line(8)) - .addComponent(sidebarComponentFactory - .simpleTextComponent() - .text(player.getName()) - .line(8)); - } -} diff --git a/testing-module/src/main/java/de/pxav/kelp/testing/sidebar/KSidebarCommand.java b/testing-module/src/main/java/de/pxav/kelp/testing/sidebar/KSidebarCommand.java new file mode 100644 index 00000000..24c2f5c5 --- /dev/null +++ b/testing-module/src/main/java/de/pxav/kelp/testing/sidebar/KSidebarCommand.java @@ -0,0 +1,40 @@ +package de.pxav.kelp.testing.sidebar; + +import de.pxav.kelp.core.command.CreateCommand; +import de.pxav.kelp.core.command.ExecutorType; +import de.pxav.kelp.core.command.KelpCommand; +import de.pxav.kelp.core.player.KelpPlayer; + +import javax.inject.Singleton; + +@Singleton +@CreateCommand( + name = "ksidebar", + executorType = ExecutorType.PLAYER_ONLY +) +public class KSidebarCommand extends KelpCommand { + + @Override + public void onCommandRegister() { + noPlayerMessage("§cYou have to be a player to execute this command"); + permission("kelp.test.sidebar"); + } + + @Override + public void onCommand(KelpPlayer player, String[] args) { + player.sendPrefixedMessages("§8[§2Kelp§8]", + " §8§m-------------------------", + " §7/ksidebar show ", + " §7/ksidebar update ", + " §7/ksidebar remove", + " §7", + " §7Example IDs§8:", + " §a1 §7Simple, static", + " §a2 §7Simple, updatable", + " §a3 §7Animated, updatable", + "", + " §8§m-------------------------" + ); + } + +} diff --git a/testing-module/src/main/java/de/pxav/kelp/testing/sidebar/RemoveCommand.java b/testing-module/src/main/java/de/pxav/kelp/testing/sidebar/RemoveCommand.java new file mode 100644 index 00000000..19705862 --- /dev/null +++ b/testing-module/src/main/java/de/pxav/kelp/testing/sidebar/RemoveCommand.java @@ -0,0 +1,25 @@ +package de.pxav.kelp.testing.sidebar; + +import de.pxav.kelp.core.command.CreateSubCommand; +import de.pxav.kelp.core.command.ExecutorType; +import de.pxav.kelp.core.command.KelpCommand; +import de.pxav.kelp.core.player.KelpPlayer; + +@CreateSubCommand(name = "remove", + executorType = ExecutorType.PLAYER_ONLY, + parentCommand = KSidebarCommand.class +) +public class RemoveCommand extends KelpCommand { + + @Override + public void onCommandRegister() { + allowCustomParameters(false); + } + + @Override + public void onCommand(KelpPlayer player, String[] args) { + player.removeSidebar(); + player.sendMessage("§8[§2Kelp§8] §7Your sidebar has been removed."); + } + +} diff --git a/testing-module/src/main/java/de/pxav/kelp/testing/sidebar/ShowCommand.java b/testing-module/src/main/java/de/pxav/kelp/testing/sidebar/ShowCommand.java new file mode 100644 index 00000000..f37bb018 --- /dev/null +++ b/testing-module/src/main/java/de/pxav/kelp/testing/sidebar/ShowCommand.java @@ -0,0 +1,126 @@ +package de.pxav.kelp.testing.sidebar; + +import de.pxav.kelp.core.animation.BuildingTextAnimation; +import de.pxav.kelp.core.command.CreateSubCommand; +import de.pxav.kelp.core.command.ExecutorType; +import de.pxav.kelp.core.command.KelpCommand; +import de.pxav.kelp.core.player.KelpPlayer; +import de.pxav.kelp.core.sidebar.component.EmptyLineComponent; +import de.pxav.kelp.core.sidebar.component.LineSeparatorComponent; +import de.pxav.kelp.core.sidebar.component.StatefulTextComponent; +import de.pxav.kelp.core.sidebar.component.StatelessTextComponent; +import de.pxav.kelp.core.sidebar.type.AnimatedSidebar; +import de.pxav.kelp.core.sidebar.type.SimpleSidebar; +import org.bukkit.Bukkit; + +import java.text.SimpleDateFormat; +import java.util.Date; +import java.util.concurrent.ThreadLocalRandom; + +@CreateSubCommand(name = "show", + executorType = ExecutorType.PLAYER_ONLY, + parentCommand = KSidebarCommand.class) +public class ShowCommand extends KelpCommand { + + @Override + public void onCommandRegister() { + allowCustomParameters(true); + argumentsStartFromZero(true); + } + + @Override + public void onCommand(KelpPlayer player, String[] args) { + if (args.length == 0) { + player.sendMessage("§cPlease provide an example id!"); + return; + } + + int id = 1; + + try { + id = Integer.parseInt(args[0]); + } catch (NumberFormatException e) { + player.sendMessage("§cExample id has to be a numeric value!"); + return; + } + + if (id < 1 || id > 3) { + player.sendMessage("§cExample id is out of range!"); + return; + } + + switch (id) { + case 1: + SimpleSidebar sidebar1 = SimpleSidebar.create(); + sidebar1.staticTitle("§2KELP EXAMPLE SIDEBAR 1"); + + sidebar1.addComponent(LineSeparatorComponent.create().line(20)); + sidebar1.addComponent(EmptyLineComponent.create().line(19)); + sidebar1.addComponent(StatelessTextComponent.create().line(18).text("§2§lServer")); + sidebar1.addComponent(StatelessTextComponent.create().line(17).text("§8» §7Lobby-1")); + sidebar1.addComponent(EmptyLineComponent.create().line(16)); + + sidebar1.addComponent(StatelessTextComponent.create().line(15).text("§2§lTeamSpeak")); + sidebar1.addComponent(StatelessTextComponent.create().line(14).text("§8» §7ts.serverdomain.com")); + sidebar1.addComponent(EmptyLineComponent.create().line(13)); + + sidebar1.addComponent(StatelessTextComponent.create().line(12).text("§2§lDate")); + SimpleDateFormat format = new SimpleDateFormat("dd.MM.yyyy"); + sidebar1.addComponent(StatelessTextComponent.create().line(11).text("§8» §7" + format.format(new Date()))); + sidebar1.addComponent(EmptyLineComponent.create().line(10)); + + sidebar1.render(player); + + break; + case 2: + SimpleSidebar sidebar2 = SimpleSidebar.create(); + sidebar2.title(() -> "§2KELP EXAMPLE " + ThreadLocalRandom.current().nextInt(100)); + + sidebar2.addComponent(LineSeparatorComponent.create().line(20)); + sidebar2.addComponent(EmptyLineComponent.create().line(19)); + sidebar2.addComponent(StatelessTextComponent.create().line(18).text("§2§lExperience")); + sidebar2.addComponent(StatelessTextComponent.create().line(17).text("§8» §7" + player.getLevel())); + sidebar2.addComponent(EmptyLineComponent.create().line(16)); + + sidebar2.addComponent(StatelessTextComponent.create().line(15).text("§2§lPlayers Online")); + sidebar2.addComponent(StatefulTextComponent.create().line(14).text(() -> "§8» §7" + Bukkit.getOnlinePlayers().size())); + sidebar2.addComponent(EmptyLineComponent.create().line(13)); + + sidebar2.addComponent(StatelessTextComponent.create().line(12).text("§2§lTime")); + SimpleDateFormat dateTime = new SimpleDateFormat("HH:mm:ss dd.MM.yyyy"); + sidebar2.addComponent(StatefulTextComponent.create().line(11).text(() -> "§8» §7" + dateTime.format(new Date()))); + sidebar2.addComponent(EmptyLineComponent.create().line(10)); + + sidebar2.render(player); + + break; + case 3: + AnimatedSidebar sidebar3 = AnimatedSidebar.create(); + sidebar3.title(BuildingTextAnimation.create().text("§2§lKELP EXAMPLE SIDEBAR")); + sidebar3.titleAnimationInterval(150); + sidebar3.clusterId("example_sidebar_3"); + + sidebar3.addComponent(LineSeparatorComponent.create().line(20)); + sidebar3.addComponent(EmptyLineComponent.create().line(19)); + sidebar3.addComponent(StatelessTextComponent.create().line(18).text("§2§lExperience")); + sidebar3.addComponent(StatefulTextComponent.create().line(17).text(() -> "§8» §7" + player.getLevel())); + sidebar3.addComponent(EmptyLineComponent.create().line(16)); + + sidebar3.addComponent(StatelessTextComponent.create().line(15).text("§2§lPlayers Online")); + sidebar3.addComponent(StatefulTextComponent.create().line(14).text(() -> "§8» §7" + Bukkit.getOnlinePlayers().size())); + sidebar3.addComponent(EmptyLineComponent.create().line(13)); + + sidebar3.addComponent(StatelessTextComponent.create().line(12).text("§2§lTime")); + SimpleDateFormat dateTime2 = new SimpleDateFormat("HH:mm:ss dd.MM.yyyy"); + sidebar3.addComponent(StatefulTextComponent.create().line(11).text(() -> "§8» §7" + dateTime2.format(new Date()))); + sidebar3.addComponent(EmptyLineComponent.create().line(10)); + + sidebar3.render(player); + + break; + } + + player.sendMessage("§8[§2Kelp§8] §7Successfully rendered sidebar §a" + id + " §7to you."); + + } +} diff --git a/testing-module/src/main/java/de/pxav/kelp/testing/sidebar/UpdateCommand.java b/testing-module/src/main/java/de/pxav/kelp/testing/sidebar/UpdateCommand.java new file mode 100644 index 00000000..6f463d7f --- /dev/null +++ b/testing-module/src/main/java/de/pxav/kelp/testing/sidebar/UpdateCommand.java @@ -0,0 +1,63 @@ +package de.pxav.kelp.testing.sidebar; + +import de.pxav.kelp.core.command.CreateSubCommand; +import de.pxav.kelp.core.command.ExecutorType; +import de.pxav.kelp.core.command.KelpCommand; +import de.pxav.kelp.core.player.KelpPlayer; +import de.pxav.kelp.core.sidebar.type.AnimatedSidebar; +import de.pxav.kelp.core.sidebar.type.KelpSidebar; +import de.pxav.kelp.core.sidebar.type.SimpleSidebar; + +@CreateSubCommand(name = "update", + executorType = ExecutorType.PLAYER_ONLY, + parentCommand = KSidebarCommand.class) +public class UpdateCommand extends KelpCommand { + + @Override + public void onCommandRegister() { + allowCustomParameters(true); + argumentsStartFromZero(true); + } + + @Override + public void onCommand(KelpPlayer player, String[] args) { + if (args.length == 0) { + player.sendMessage("§cPlease specify an update action."); + return; + } + + KelpSidebar sidebar = player.getCurrentSidebar(); + + if (sidebar == null) { + player.sendMessage("§cThere is no sidebar to update."); + return; + } + + if (args[0].equalsIgnoreCase("lazy")) { + if (sidebar instanceof AnimatedSidebar) { + ((AnimatedSidebar)sidebar).lazyUpdate(player); + } + if (sidebar instanceof SimpleSidebar) { + ((SimpleSidebar)sidebar).lazyUpdate(player); + } + player.sendMessage("§8[§2Kelp§8] §7A lazy update has been performed on your sidebar."); + } + + if (args[0].equalsIgnoreCase("normal")) { + sidebar.update(player); + player.sendMessage("§8[§2Kelp§8] §7A normal update has been performed on your sidebar."); + } + + if (args[0].equalsIgnoreCase("title")) { + + if (sidebar instanceof SimpleSidebar) { + ((SimpleSidebar)sidebar).updateTitleOnly(player); + } else { + player.sendMessage("§cCannot update AnimatedSidebar's title."); + } + + player.sendMessage("§8[§2Kelp§8] §7A title update has been performed on your sidebar."); + } + } + +} diff --git a/v1_8_implementation/pom.xml b/v1_8_implementation/pom.xml index f39f45c0..96997378 100644 --- a/v1_8_implementation/pom.xml +++ b/v1_8_implementation/pom.xml @@ -5,7 +5,7 @@ com.github.pxav.kelp parent - 0.0.5 + 0.1.0 4.0.0 @@ -90,7 +90,7 @@ com.github.pxav.kelp core - 0.0.5 + 0.1.0 provided 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 e9191804..2911ee83 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 @@ -28,6 +28,8 @@ import org.bukkit.inventory.ItemStack; import org.bukkit.permissions.PermissionAttachment; import org.bukkit.plugin.java.JavaPlugin; +import org.bukkit.scoreboard.DisplaySlot; +import org.bukkit.scoreboard.Scoreboard; import org.bukkit.util.Vector; import java.lang.reflect.Field; @@ -1335,6 +1337,24 @@ public void sendInteractiveMessage(Player player, InteractiveMessage interactive player.spigot().sendMessage(componentBuilder.create()); } + /** + * If the player currently sees a sidebar, it will be hidden for the given + * player. This mostly happens by replacing it with a new, empty scoreboard. + * + * @param player The player whose sidebar you want to hide/remove. + */ + @Override + public void removeSidebar(Player player) { + Scoreboard scoreboard = player.getScoreboard(); + if (scoreboard.getObjective(DisplaySlot.SIDEBAR) == null) { + return; + } + + scoreboard.getObjective(DisplaySlot.SIDEBAR).unregister(); + player.setScoreboard(scoreboard); + + } + /** * Appends a message to the given {@link ComponentBuilder}. Please note that every message you append * using this method may only have one chat color and the formatting code applies for the entire message. diff --git a/v1_8_implementation/src/main/java/de/pxav/kelp/implementation1_8/sidebar/SidebarVersion.java b/v1_8_implementation/src/main/java/de/pxav/kelp/implementation1_8/sidebar/SidebarVersion.java deleted file mode 100644 index 0a75a307..00000000 --- a/v1_8_implementation/src/main/java/de/pxav/kelp/implementation1_8/sidebar/SidebarVersion.java +++ /dev/null @@ -1,49 +0,0 @@ -package de.pxav.kelp.implementation1_8.sidebar; - -import com.google.common.base.Preconditions; -import de.pxav.kelp.core.sidebar.version.SidebarVersionTemplate; -import de.pxav.kelp.core.version.Versioned; -import org.bukkit.Bukkit; -import org.bukkit.entity.Player; -import org.bukkit.scoreboard.DisplaySlot; -import org.bukkit.scoreboard.Objective; -import org.bukkit.scoreboard.Scoreboard; -import org.bukkit.scoreboard.ScoreboardManager; - -/** - * A class description goes here. - * - * @author pxav - */ -@Versioned -public class SidebarVersion extends SidebarVersionTemplate { - - @Override - public Scoreboard createScoreboard() { - return Bukkit.getScoreboardManager().getNewScoreboard(); - } - - @Override - public Objective createObjective(Scoreboard parent, - String identifier, - String title) { - Objective objective = parent.registerNewObjective(identifier, "dummy"); - objective.setDisplayName(title); - objective.setDisplaySlot(DisplaySlot.SIDEBAR); - return objective; - } - - @Override - public void openScoreboard(Scoreboard scoreboard, Player player) { - player.setScoreboard(scoreboard); - } - - @Override - public void closeScoreboard(Player player) { - ScoreboardManager manager = Bukkit.getScoreboardManager(); - Preconditions.checkNotNull(manager); - Scoreboard emptyScoreboard = manager.getNewScoreboard(); - player.setScoreboard(emptyScoreboard); - } - -} diff --git a/v1_8_implementation/src/main/java/de/pxav/kelp/implementation1_8/sidebar/VersionedSidebar.java b/v1_8_implementation/src/main/java/de/pxav/kelp/implementation1_8/sidebar/VersionedSidebar.java new file mode 100755 index 00000000..fbcbdd50 --- /dev/null +++ b/v1_8_implementation/src/main/java/de/pxav/kelp/implementation1_8/sidebar/VersionedSidebar.java @@ -0,0 +1,186 @@ +package de.pxav.kelp.implementation1_8.sidebar; + +import com.google.common.base.Preconditions; +import de.pxav.kelp.core.event.kelpevent.sidebar.KelpSidebarRenderEvent; +import de.pxav.kelp.core.event.kelpevent.sidebar.KelpSidebarUpdateEvent; +import de.pxav.kelp.core.logger.KelpLogger; +import de.pxav.kelp.core.logger.LogLevel; +import de.pxav.kelp.core.player.KelpPlayer; +import de.pxav.kelp.core.sidebar.SidebarRepository; +import de.pxav.kelp.core.sidebar.SidebarUtils; +import de.pxav.kelp.core.sidebar.component.SidebarComponent; +import de.pxav.kelp.core.sidebar.type.AnimatedSidebar; +import de.pxav.kelp.core.sidebar.type.KelpSidebar; +import de.pxav.kelp.core.sidebar.type.SimpleSidebar; +import de.pxav.kelp.core.sidebar.version.SidebarVersionTemplate; +import de.pxav.kelp.core.version.Versioned; +import org.bukkit.Bukkit; +import org.bukkit.entity.Player; +import org.bukkit.scoreboard.*; + +import javax.inject.Inject; + +/** + * A class description goes here. + * + * @author pxav + */ +@Versioned +public class VersionedSidebar extends SidebarVersionTemplate { + + private SidebarUtils sidebarUtils; + private KelpLogger logger; + private SidebarRepository sidebarRepository; + + @Inject + public VersionedSidebar(SidebarUtils sidebarUtils, KelpLogger logger, SidebarRepository sidebarRepository) { + this.sidebarUtils = sidebarUtils; + this.logger = logger; + this.sidebarRepository = sidebarRepository; + } + + /** + * Renders the sidebar to a specific player. This means it displays + * it for the first time (so only use this method if the player does + * not already see this sidebar to avoid flicker effects or similar behaviour). + * + * @param sidebar The sidebar to render to the given player. + * @param kelpPlayer The player to render the sidebar to. + */ + @Override + public void renderSidebar(KelpSidebar sidebar, KelpPlayer kelpPlayer) { + Player player = kelpPlayer.getBukkitPlayer(); + Scoreboard scoreboard; + Objective objective; + + if (kelpPlayer.hasScoreboard()) { + scoreboard = player.getScoreboard(); + } else { + scoreboard = Bukkit.getScoreboardManager().getNewScoreboard(); + } + + if (scoreboard.getObjective(DisplaySlot.SIDEBAR) == null) { + objective = scoreboard.registerNewObjective("kelpObj", "dummy"); + } else { + objective = scoreboard.getObjective(DisplaySlot.SIDEBAR); + } + objective.setDisplaySlot(DisplaySlot.SIDEBAR); + + player.setScoreboard(scoreboard); + Bukkit.getPluginManager().callEvent(new KelpSidebarRenderEvent(kelpPlayer, sidebar)); + + // apply all contents to the sidebar. + this.updateSidebar(sidebar, kelpPlayer); + + if (sidebar.getClass().isAssignableFrom(AnimatedSidebar.class)) { + // if the sidebar is animated start the animation schedulers in + // the sidebar repository and set the first animation state as default. + AnimatedSidebar animatedSidebar = (AnimatedSidebar) sidebar; + objective.setDisplayName(animatedSidebar.getTitle().states().get(0)); + sidebarRepository.addAnimatedSidebar(animatedSidebar, kelpPlayer); + } else if (sidebar.getClass().isAssignableFrom(SimpleSidebar.class)) { + // if the sidebar is a simple sidebar, simply set the default title. + SimpleSidebar simpleSidebar = (SimpleSidebar) sidebar; + objective.setDisplayName(simpleSidebar.getTitle().get()); + } + + kelpPlayer.setSidebarInternally(sidebar); + + } + + /** + * Performs a lazy update on the sidebar. A lazy update does not remove all entries/lines + * from a scoreboard first, like it is done by {@link #updateSidebar(KelpSidebar, KelpPlayer)}. It only uses the + * existing entries in the sidebar, which means that you cannot use it if you know that the amount + * of lines in the sidebar might change with an update. + * + * However this update method is completely free from any flickering effects and it is + * not as performance heavy as a normal update. So if you can, you should prefer this update + * method over a normal update. + * + * @param sidebar The sidebar to update. + * @param kelpPlayer The player who should see the updated sidebar. + */ + @Override + public void lazyUpdate(KelpSidebar sidebar, KelpPlayer kelpPlayer) { + Scoreboard scoreboard = kelpPlayer.getBukkitPlayer().getScoreboard(); + + for (Object object : sidebar.getComponents()) { + if (!(object instanceof SidebarComponent)) { + continue; + } + + SidebarComponent component = (SidebarComponent) object; + + component.render().forEach((line, text) -> { + String teamName = "entry_" + line; + Team team = scoreboard.getTeam(teamName); + + if (team == null) { + logger.log(LogLevel.ERROR, "Cannot update component at score " + line + ", " + + "because there is no entry assigned to this score. Are you sure 'lazyUpdate' " + + "is the appropriate updating method for your case?"); + return; + } + + sidebarUtils.setTeamData(text, team); + }); + } + + Bukkit.getPluginManager().callEvent(new KelpSidebarUpdateEvent(kelpPlayer, sidebar, true)); + } + + /** + * Performs a full-update on the given sidebar, which means that all existing + * contents are removed and then new contents are applied. This method is safe + * against changing amounts of lines. + * + * @param sidebar The sidebar you want to update. + * @param kelpPlayer The player who should see the updates. + */ + @Override + public void updateSidebar(KelpSidebar sidebar, KelpPlayer kelpPlayer) { + Scoreboard scoreboard = kelpPlayer.getBukkitPlayer().getScoreboard(); + Objective objective = scoreboard.getObjective(DisplaySlot.SIDEBAR); + + if (objective == null) { + renderSidebar(sidebar, kelpPlayer); + return; + } + + // remove all old entries + for (String entry : scoreboard.getEntries()) { + Score score = objective.getScore(entry); + + if (score == null) { + continue; + } + + scoreboard.resetScores(entry); + } + + // unregister all teams + scoreboard.getTeams().forEach(Team::unregister); + + for (Object object : sidebar.getComponents()) { + if (!(object instanceof SidebarComponent)) { + continue; + } + + SidebarComponent component = (SidebarComponent) object; + + component.render().forEach((line, text) -> { + String entry = sidebarUtils.randomEmptyEntry(scoreboard); + objective.getScore(entry).setScore(line); + Team team = scoreboard.registerNewTeam("entry_" + line); + team.addEntry(entry); + + sidebarUtils.setTeamData(text, team); + }); + + } + + Bukkit.getPluginManager().callEvent(new KelpSidebarUpdateEvent(kelpPlayer, sidebar, false)); + } + +} diff --git a/v1_8_implementation/src/main/java/de/pxav/kelp/implementation1_8/sidebar/VersionedSidebarUpdater.java b/v1_8_implementation/src/main/java/de/pxav/kelp/implementation1_8/sidebar/VersionedSidebarUpdater.java new file mode 100644 index 00000000..8850edc9 --- /dev/null +++ b/v1_8_implementation/src/main/java/de/pxav/kelp/implementation1_8/sidebar/VersionedSidebarUpdater.java @@ -0,0 +1,26 @@ +package de.pxav.kelp.implementation1_8.sidebar; + +import de.pxav.kelp.core.player.KelpPlayer; +import de.pxav.kelp.core.sidebar.version.SidebarUpdaterVersionTemplate; +import de.pxav.kelp.core.version.Versioned; +import org.bukkit.scoreboard.DisplaySlot; +import org.bukkit.scoreboard.Objective; +import org.bukkit.scoreboard.Scoreboard; + +@Versioned +public class VersionedSidebarUpdater extends SidebarUpdaterVersionTemplate { + + /** + * Updates the title of sidebar of the given player to the given string. + * @param to The title to update to. + * @param kelpPlayer The player whose sidebar's title should be updated. + */ + @Override + public void updateTitleOnly(String to, KelpPlayer kelpPlayer) { + Scoreboard scoreboard = kelpPlayer.getBukkitPlayer().getScoreboard(); + Objective objective = scoreboard.getObjective(DisplaySlot.SIDEBAR); + + objective.setDisplayName(to); + } + +} diff --git a/v_1_14_implementation/pom.xml b/v_1_14_implementation/pom.xml index 6ed2265e..f6f9a39a 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.0.5 + 0.1.0 4.0.0 @@ -57,7 +57,7 @@ com.github.pxav.kelp core - 0.0.5 + 0.1.0 org.spigotmc