Skip to content

Creating custom sidebars

PXAV edited this page Jan 28, 2021 · 3 revisions

This tutorial demonstrates how sidebars are used in kelp-v0.1.0 and above. The old annotation-based sidebar system (v.0.0.5 and below) is not supported anymore.

Creating the Sidebar

There are different types of sidebars offered by Kelp.

Type Description
SimpleSidebar Used to display plain sidebars. Can hold all components, but has a static title.
AnimatedSidebar Can holld all components, but the title can be animated.

We will use the SimpleSidebar first and then look at the differences to the AnimatedSidebar.

To create a new instance of a sidebar, you can call the static factory method of one of the sidebar types.

SimpleSidebar sidebar = SimpleSidebar.create();

sidebar.staticTitle("§b§lWELCOME ON THE SERVER!");
// title won't change if you update it

sidebar.title(() -> "§b§l" + player.getLocation());
// title will change if you call the update method
// and display the latest location of the player

As you can see there are different ways of setting the title of a SimpleSidebar. The major difference between the two methods is that staticTitle(String) sets a title that won't update if you call updateTitleOnly(), but remain the same for the entire sidebar's lifetime. title(Supplier<String>) instead takes a supplier instead of a static string and the title can change if you call the corresponding method.

Populating a sidebar

After you've created your fresh sidebar instance and set the title, you can start becoming creative about its content. Unlike normal bukkit scoreboards, Kelp does not use simple scores but components. Components make it easier to work with modifiable and updatable content. By default there are some components which allow you to display basic text for example:

sidebar.addComponent(
  StatelessTextComponent.create()
    .line(3) // line in the final sidebar
    .text("§aIP: exampleserver.com")
);

A StatelessTextComponent is a bit like the static title we have discussed earlier. The text inside it is constant and cannot be changed during the sidebar's lifetime anymore. This is useful if you just want to display simple things such as your server id. But what if you want to update the content when displaying a player's coins for example? Well StatefulTextComponent got you covered.

Stateless vs. Stateful components

You can use stateful components to display updateable content. An example would be:

sidebar.addComponent(
  StatelessTextComponent.create()
   .line(3)
   .text("§aYour kills:")
);
sidebar.addComponent(
  StatefulTextComponent.create()
    .line(3)
    .text(() -> "§8> §7" + player.getKills()) // supplier
);

Note that instead of a normal string we wrote () -> "text". This is a lambda expression for a supplier that returns the string we want to display. A supplier can return another text each time we query it (on an update for example).

At the end of this article, you will find a detailed overview of all components available in Kelp by default.

Sending our sidebar

A sidebar has three different stages during its lifetime:

  • render
  • update
  • remove

As you might have guessed, we need render to display the sidebar to a player. But we can only render a sidebar once per player, after that we have to either update or remove it. The code to render would look like this:

sidebar.render(player);

player is a KelpPlayer in this case

Updating a sidebar

There are two different ways of updating a sidebar - each comes with its own advantages and drawbacks:

  • "normal" update(KelpPlayer): This method completely clears the content of the scoreboard and reloads it. This is important if you know that the number of lines in the scoreboard will change with the update, but for a tiny bit of a second our sidebar scales down (because it loses all its contents) and then up again. This effect is also known as flicker and can be annoying if you need to update often (if you have a timer for example). So there is another solution:
  • lazyUpdate: This method takes the current scores and manipulates their content without removing or adding anything. This is flicker-free, not as performance-intensive, and looks much better. But it is not safe against removing or adding new lines.

So as you can see it depends on your situation when to use which update method. But you can use both methods whenever you need them. If you have a jump league scoreboard for example, where you display a list of different players, you can only call the update method if a player leaves the game and for everything else lazyUpdate should be enough.

Some examples:

sidebar.update(player);

// or
sidebar.lazyUpdate(player);

Those methods do both not update the sidebar's title

Something that mostly only becomes relevant for you when using SimpleSidebars is updating the title only:

sidebar.updateTitleOnly(player);

In AnimatedSidebars the title animation is handled automatically by Kelp, so you won't need it there that often.

Removing a sidebar

A sidebar can be removed using the KelpPlayer class. On a player simply call the removeSidebar method and the sidebar will be hidden and all animation schedulers will be stopped. This example removes the sidebar each time the player performs an interaction:

@EventHandler
public void onInteract(PlayerInteractEvent event) {
  KelpPlayer kelpPlayer = playerRepository.getKelpPlayer(event.getPlayer());
  kelpPlayer.removeSidebar();
}

Animated Sidebars

Animated sidebars allow easy animation handling as Kelp manages all schedulers and animation states for you. It can be created as follows:

AnimatedSidebar sidebar = AnimatedSidebar.create();

sidebar.title(BuildingTextAnimation.create()
  .text("§6§lWELCOME ON THE SERVER!")
  .ignoreSpaces());
sidebar.titleAnimationInterval(500); // in ms

As you can see, we don't pass a string anymore, but a TextAnimation. A more detailed guide on text animations can be found here.

Basically, a text animation is a predefined algorithm that takes a string and calculates different texts, that - if played one by one - look like an animation. Just like in a movie. If you want to know exactly which predefined algorithms there are and how you create your own animations, read the article I have linked above.

If you render the scoreboard to a player, the animation will automatically be played in the given interval in milliseconds.

Animation clusters

However, as you might have guessed, running a scheduler for each animation/each player can become quite performance heavy, which is why there are clusters. Clusters allow you to group certain sidebars with the same animation interval and purpose together so that they use one single scheduler. In a lobby, for example, you could reduce the number of schedulers to 1 as there mostly is only one sidebar.

To cluster a sidebar, you have to give it a clusterId. This id has to be the same for all clustered sidebars in a group. Example:

sidebar.clusterId("lobby_sidebar");

Kelp automatically creates a new cluster if there has been no instance of it and it will equally remove it once there are no players online with it anymore. We strongly encourage you to use clusters if you can as they can save lots of performance on bigger servers.

An overview of all components

For a tutorial on how you can create your own components head over to this article.

  • StatelessTextComponent: Simple component displaying a static text.
sidebar.addComponent(
  StatelessTextComponent.create()
    .line(3)
    .text("§aIP§8: §7exampleserver.com")
);
  • StatefulTextComponent: A text component displaying updateable content
sidebar.addComponent(
  StatefulTextComponent.create()
    .line(2)
    .text(() -> ThreadLocalRandom.current().nextInt(10_000))
);
  • EmptyLineComponent: Generates an empty/transparent line at the given position
sidebar.addComponent(EmptyLineComponent.create().line(4));
  • LineSeperatorComponent: Generates a line separator based on a given char/symbol that is repeated n times.
// using the default settings
sidebar.addComponent(LineSeparatorComponent.create().line(40));

// or using custom settings
sidebar.addComponent(LineSeparatorComponent.create()
  .line(40)
  .symbol('=')
  .color(ChatColor.RED, ChatColor.BOLD)
  .length(15));
  • StatefulListComponent: Used to display dynamic lists (online players and their progression in JumpLeague for example)
sidebar.addComponent(
  StatefulListComponent.create()
    .startFrom(15) // line of the first list item
    .descendingOrder() // the items are ordered from a high number 15 to 10. ascendingOrder() would be the opposite
    .limitTo(10) // optional limit. There won't be any list items below that limit
    .enableAutoFill() // makes it compatible with lazy loading, limiter must be enabled
    .list(() -> {
      List<String> list = Lists.newArrayList();
      for (int i = 0; i < ThreadLocalRandom.current().nextInt(5); i++) {
        list.add("§a" + UUID.randomUUID().toString().substring(0, 6) + " §7(53%)");
      }
      return list;
    })
);

Normally you would think that you cannot use this component safely with lazyUpdate, because when the amount of list items changes, the amount of lines in the scoreboard changes as well. But autoFill is the solution here: The limiter (limitTo) sets the maximum/default amount of lines to 10 which means that if there are 12 items in the list, only 10 will be displayed. If there are fewer items, enableAutoFill becomes important. It fills up the space with empty lines until the component covers 10 lines in total. This way the amount of lines is always the same and it is safe to update.

Clone this wiki locally