A small library that makes the use of customised maps in Spigot very easy.
- 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
<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>
repositories {
jcenter()
}
dependencies {
implementation("com.github.johnnyjayjay:spigot-maps:2.1.1")
}
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).
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.
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
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>)
.
GifRenderer
s 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.
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.
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 GifImage
s.
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
})
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);