Skip to content

A library to simplify the use of customised maps in Spigot.

License

Notifications You must be signed in to change notification settings

JohnnyJayJay/spigot-maps

Repository files navigation

Spigot-Maps

A small library that makes the use of customised maps in Spigot very easy.

Features

  • Dynamic map rendering (based on render context)
  • (Animated) Text, image and animated gif rendering with convenient usage
  • Base class for own renderer implementations
  • API to store renderers persistently
  • Tools to resize / crop / divide images so that they fit the minecraft maps
  • Convenient builder classes and factory methods

YOU CAN FIND AN EXAMPLE PLUGIN HERE

To test this plugin, do

git clone https://github.com/johnnyjayjay/spigot-maps.git
cd spigot-maps/example-plugin
./gradlew shadowJar 

Then add example-plugin-1.0-TEST.jar (found in build/libs) to your plugins folder

Add as dependency

Maven

<repositories>
    <repository>
        <id>jcenter</id>
        <url>https://jcenter.bintray.com/</url>
    </repository>
 </repositories>

<dependencies>
    <dependency>
        <groupId>com.github.johnnyjayjay</groupId>
        <artifactId>spigot-maps</artifactId>
        <version>2.1.1</version>
    </dependency>
</dependencies>

Gradle

repositories {
    jcenter()
}

dependencies {
    implementation("com.github.johnnyjayjay:spigot-maps:2.1.1")
}

I don't use a build tool

An already built .jar file can be found here. Download it and add it to your project like any other jar file (Eclipse -> Build Path, IntelliJ -> Add as Library).

Quick Start

You've got various options when creating renderers and maps:

BufferedImage catImage = ImageIO.read(file); // read an image from a source, e.g. a file
catImage = ImageTools.resizeToMapSize(catImage); // resize the image to the minecraft map size
ImageRenderer catRenderer = ImageRenderer.builder()
        .addPlayers(player1, player2) // set the players this map should be rendered to (omitting this means it renders for everyone)
        .image(catImage) // set the image to render
        .build(); // build the instance

Dimension mapSize = ImageTools.MINECRAFT_MAP_SIZE; // the dimensions of a Minecraft map (in pixels)
SimpleTextRenderer messageRenderer = SimpleTextRenderer.builder()
        .addLines("Cats", "are", "so", "cute") // set the lines that will be drawn onto the map
        .addPlayers(player1, player2)
        .font(myFont) // set a text font
        .startingPoint(new Point(mapSize.width / 2, mapSize.height / 2)) // start in the middle
        .build(); // build the instance 

RenderedMap map = MapBuilder.create() // make a new builder
        .store(myStorage) // set a MapStorage for the map
        .addRenderers(catRenderer, messageRenderer) // add the renderers to this map
        .world(player1.getWorld()) // set the world this map is bound to, e.g. the world of the target player
        .build(); // build the map

ItemStack mapItem = map.createItemStack(); // get an ItemStack from this map to work with

This example would result in a map that has an image of a cat in the background and the text "Cats are so cute" in the foreground.

You can still mutate the renderers afterwards:

messageRenderer.setText("Cats\nare\nstill\ncute");

Note that this will only have effect if renderOnce is set to false while building. The reason for that decision is performance. Only rendering once saves resources, but disables later modification. If you want to mutate a renderer after building but then leave it that way, you may call

messageRenderer.stopRendering();

to save the resources.

Shortcuts

There are even quicker ways to accomplish some of these operations.

Create an ImageRenderer with just an image:

ImageRenderer renderer = ImageRenderer.create(image, player1, player2); // the player arguments are optional

Create an ImageRenderer that renders a single color (e.g. as a background for text):

ImageRenderer backgroundRenderer = ImageRenderer.createSingleColorRenderer(Color.BLUE, player1, player2) // the player arguments are optional

See ImageTools.createSingleColoredImage(Color) to get an instance of BufferedImage for that matter.

Create a TextRenderer with just some lines of text:

SimpleTextRenderer renderer = SimpleTextRenderer.create("This", "is", "noice");

Create a RenderedMap with just some MapRenderers:

RenderedMap map = RenderedMap.create(renderer1, renderer2); // not providing any renderers returns a map without renderers

Advanced

Gifs

This library can handle animated gifs using GifRenderer and GifImage. Instances of GifImage can be obtained via an instance of GifDecoder from this library's dependencies:

GifDecoder decoder = new GifDecoder();
decoder.read("./example.gif"); // this also works with URLs, InputStreams etc.
GifImage gif = GifImage.fromDecoder(decoder);

For more info about GifDecoder, look at this.

You can also implement an algorithm to decode gifs yourself and then make use of GifImage#create(List<Frame>).

GifRenderers are created like any other renderer:

GifRenderer renderer = GifRenderer.builder()
        .gif(gif) // set the GifImage we just created
        .repeat(5) // repeat the gif 5 times: to repeat it indefinitely, omit this setting or set it to GifRenderer.REPEAT_FOREVER
        .build();

This renderer stops rendering automatically after 5 repetitions and can now be added to a MapView / RenderedMap as shown above.

Animated Text

Text doesn't have to be static. This library provides a map renderer that renders text character by character.

AnimatedTextRenderer renderer = AnimatedTextRenderer.builder()
        .addText("This text will appear char by char.")
        .charsPerSecond(10) // 10 characters should appear each second
        .delay(20) // start the animation after 20 ticks (1 second)
        .build();

This renderer automatically stops rendering after having finished.

Splitting images

Images that take more than 1 map to display can be created using ImageTools.divideIntoMapSizedParts(BufferedImage, boolean). This uses an algorithm that makes a square version of the image first. How this is done can be determined via the second boolean parameter. If it is set to true, a square cropped out from the middle of the image will be used (if the image is big enough). If it is false, the whole image will be resized to 1:1.

List<BufferedImage> parts = ImageTools.divideIntoMapSizedParts(image, true);

The exact same methods are available to GifImages.

To turn these into map items:

for (BufferedImage part : parts) {
    ImageRenderer renderer = ImageRenderer.create(part);
    ItemStack mapItem = RenderedMap.create(renderer).createItemStack();
    // do something with it
}

Or, if you want to do it stream-like:

parts.stream()
        .map(ImageRenderer::create)
        .map(RenderedMap::create)
        .map(RenderedMap::createItemStack)
        .forEach((mapItem) -> {
    // do something with it
})

Using MapStorage

The MapStorage API makes it possible to save renderers persistently. To utilise it, you have to implement MapStorage:

public class FileStorage implements MapStorage {

    @Override
    public void store(int id, MapRenderer renderer) {
        // serialize the renderer associated with the given id
    }
    
    @Override
    public boolean remove(int id, MapRenderer renderer) {
        // remove the given renderer's association with the given id
    }
    
    @Override
    public List<MapRenderer> provide(int id) {
        // fetch / deserialize / read all renderers stored for the given id
    }
}

Then, do the following (e.g. on start up):

InitializationListener.register(new FileStorage(), plugin);