Skip to content

Commit

Permalink
Merge pull request #19 from josemmo/develop
Browse files Browse the repository at this point in the history
v1.2.1
  • Loading branch information
josemmo authored Jan 5, 2022
2 parents 981ee0c + 0c03386 commit 9048795
Show file tree
Hide file tree
Showing 18 changed files with 841 additions and 163 deletions.
35 changes: 29 additions & 6 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,10 +11,10 @@ without having to install any local client mod.
It is designed with performance and compatibility in mind, so even the most low-specs servers should be able to run it.

<p align="center">
<a href="https://i.imgur.com/9rzeKFS.mp4"><img alt="Placing and removing image" src="screenshots/demo.gif" width="300"></a>
<a href="https://imgur.com/a/J22z9Dq.mp4"><img alt="Works with animated images too!" src="screenshots/demo-animated.gif" width="300"></a>
<a href="screenshots/sample-1.jpg"><img alt="Sample Screenshot" src="screenshots/sample-1.jpg" width="300"></a>
<a href="screenshots/sample-2.jpg"><img alt="Sample Screenshot" src="screenshots/sample-2.jpg" width="300"></a>
<a href="https://imgur.com/9rzeKFS"><img alt="Placing and removing image" src="screenshots/demo.gif" width="300"></a>
<a href="https://imgur.com/a/J22z9Dq"><img alt="Works with animated images too!" src="screenshots/demo-animated.gif" width="300"></a>
<a href="https://imgur.com/a/laPF1eN"><img alt="Placing and removing image item" src="screenshots/demo-item.gif" width="300"></a>
<a href="screenshots/sample.jpg"><img alt="Optimized to work in low-spec servers" src="screenshots/sample.jpg" width="300"></a>
</p>

## Installation
Expand All @@ -32,7 +32,7 @@ Here are the Minecraft distributions where Yamipa should be able to run:
|------------------:|:-----------:|:------:|:-------:|
| 1.16.x ||||
| 1.17 & 1.17.1 ||||
| 1.18 ||||
| 1.18 & 1.18.1 ||||

## Configuration
Yamipa is ready-to-go right out of the box. By default, it creates the following files and directories under the
Expand Down Expand Up @@ -62,8 +62,9 @@ This plugin adds the following commands:
- `/image clear <x z world> <r> [<placed-by>]`: Remove all placed images in a radius of `r` blocks around an origin.
- `/image describe`: Show detailed information about a placed image.
- `/image download <url> <filename>`: Download an image from a URL and place it in the images directory.
- `/image give <player> <filename> <amount> <w> [<h>] [<flags>]`: Give image items that can be placed later to a player.
- `/image list [<page>]`: List all available files in the images directory.
- `/image place <filename> <width> [<height>]`: Place an image of size `width`x`height` blocks.
- `/image place <filename> <w> [<h>] [<flags>]`: Place an image of size `w`x`h` blocks.
- `/image remove`: Remove a placed image from the world without deleting the image file.
- `/image top`: List players with the most placed images.

Expand All @@ -73,10 +74,16 @@ This plugin adds the following commands:
`/image`
- Download an image from a URL and save it with another name\
`/image download "https://www.example.com/a/b/c/1234.jpg" imagename.jpg`
- Give 10 image items to "TestPlayer" for the "test.jpg" image (3x5 blocks)\
`/image give TestPlayer test.jpg 10 3 5`
- Give 10 image items to "TestPlayer" that will not drop an image item when removed\
`/image give TestPlayer test.jpg 10 3 5 -DROP`
- Start the dialog to place an image with a width of 3 blocks and auto height\
`/image place imagename.jpg 3`
- Start the dialog to place a 3-blocks wide and 2-blocks high image\
`/image place imagename.jpg 3 2`
- Start the dialog to place an image that glows in the dark\
`/image place imagename.jpg 3 2 +GLOW`
- Start the dialog to remove a placed image while keeping the original file\
`/image remove`
- Remove all placed images in a radius of 5 blocks around the spawn\
Expand All @@ -94,6 +101,7 @@ Yamipa defines the following permissions, each one corresponding to the command
- `yamipa.clear`
- `yamipa.describe`
- `yamipa.download`
- `yamipa.give`
- `yamipa.list`
- `yamipa.place`
- `yamipa.remove`
Expand All @@ -104,6 +112,21 @@ such as [LuckPerms](https://luckperms.net/) or [GroupManager](https://elgarl.git

Both these plugins have been tested to work with Yamipa, although any similar one should work just fine.

## Flags
Images from this plugin have a set of boolean attributes called "flags" that modify its behavior. Possible values are:

- `ANIM` (animatable): Whether an image should be animated or not, useful when you don't want a GIF image to play.
- `REMO` (removable): Whether an image can be removed by any player by left-clicking it.
- `DROP` (droppable): Whether an image drops an image item when is removed by any player.
- `GLOW` (glowing): Whether an image glows in the dark (only works on Minecraft 1.17 and above).

By default, images placed with the "/image place" command only have the `ANIM` flag.
Similarly, image items issued with the "/image give" command have `ANIM`, `REMO` and `DROP` flags.

Default flags can be modified through the "<flag>" argument.
To add a flag to the default ones use "+{FLAG_NAME}" (e.g. `+GLOW`), and to remove it use "-{FLAG_NAME}" (e.g. `-ANIM`).
You can modify multiple flags separating them with commas (e.g. `+GLOW,-ANIM`).

## How does it work?
As you may have already guessed, Minecraft does not support the placing of image files.
Yamipa bypasses this limitation by using two built-in features (**item frames and maps**) to render custom images.
Expand Down
4 changes: 2 additions & 2 deletions pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@

<groupId>io.josemmo.bukkit.plugin</groupId>
<artifactId>YamipaPlugin</artifactId>
<version>1.2.0</version>
<version>1.2.1</version>

<properties>
<maven.compiler.source>8</maven.compiler.source>
Expand All @@ -33,7 +33,7 @@
<dependency>
<groupId>org.spigotmc</groupId>
<artifactId>spigot-api</artifactId>
<version>1.18-R0.1-SNAPSHOT</version>
<version>1.18.1-R0.1-SNAPSHOT</version>
<scope>provided</scope>
</dependency>
<dependency>
Expand Down
Binary file added screenshots/demo-item.gif
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file removed screenshots/sample-1.jpg
Binary file not shown.
File renamed without changes
8 changes: 8 additions & 0 deletions src/main/java/io/josemmo/bukkit/plugin/YamipaPlugin.java
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
import io.josemmo.bukkit.plugin.commands.ImageCommandBridge;
import io.josemmo.bukkit.plugin.renderer.FakeImage;
import io.josemmo.bukkit.plugin.renderer.ImageRenderer;
import io.josemmo.bukkit.plugin.renderer.ItemService;
import io.josemmo.bukkit.plugin.storage.ImageStorage;
import org.bstats.bukkit.Metrics;
import org.bstats.charts.SimplePie;
Expand All @@ -23,6 +24,7 @@ public class YamipaPlugin extends JavaPlugin {
private boolean verbose;
private ImageStorage storage;
private ImageRenderer renderer;
private ItemService itemService;
private ScheduledExecutorService scheduler;

/**
Expand Down Expand Up @@ -110,6 +112,10 @@ public void onEnable() {
renderer = new ImageRenderer(basePath.resolve(dataPath).toString());
renderer.start();

// Create image item service
itemService = new ItemService();
itemService.start();

// Create thread pool
scheduler = Executors.newScheduledThreadPool(6);

Expand All @@ -133,8 +139,10 @@ public void onDisable() {
// Stop plugin components
storage.stop();
renderer.stop();
itemService.stop();
storage = null;
renderer = null;
itemService = null;

// Stop internal scheduler
scheduler.shutdownNow();
Expand Down
159 changes: 115 additions & 44 deletions src/main/java/io/josemmo/bukkit/plugin/commands/ImageCommand.java
Original file line number Diff line number Diff line change
Expand Up @@ -3,12 +3,15 @@
import io.josemmo.bukkit.plugin.YamipaPlugin;
import io.josemmo.bukkit.plugin.renderer.FakeImage;
import io.josemmo.bukkit.plugin.renderer.ImageRenderer;
import io.josemmo.bukkit.plugin.renderer.ItemService;
import io.josemmo.bukkit.plugin.storage.ImageFile;
import io.josemmo.bukkit.plugin.utils.SelectBlockTask;
import io.josemmo.bukkit.plugin.utils.ActionBar;
import org.bukkit.*;
import org.bukkit.block.BlockFace;
import org.bukkit.command.CommandSender;
import org.bukkit.entity.Player;
import org.bukkit.inventory.ItemStack;
import org.bukkit.plugin.PluginDescriptionFile;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
Expand All @@ -21,10 +24,7 @@
import java.nio.file.Path;
import java.nio.file.Paths;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.Map;
import java.util.Set;
import java.util.UUID;
import java.util.*;

public class ImageCommand {
public static final int ITEMS_PER_PAGE = 9;
Expand All @@ -42,11 +42,14 @@ public static void showHelp(@NotNull CommandSender s, @NotNull String commandNam
if (s.hasPermission("yamipa.download")) {
s.sendMessage(ChatColor.AQUA + cmd + " download <url> <filename>" + ChatColor.RESET + " - Download image");
}
if (s.hasPermission("yamipa.give")) {
s.sendMessage(ChatColor.AQUA + cmd + " give <p> <filename> <#> <w> [<h>] [<f>]" + ChatColor.RESET + " - Give image items");
}
if (s.hasPermission("yamipa.list")) {
s.sendMessage(ChatColor.AQUA + cmd + " list [<page>]" + ChatColor.RESET + " - List all images");
}
if (s.hasPermission("yamipa.place")) {
s.sendMessage(ChatColor.AQUA + cmd + " place <filename> <w> [<h>]" + ChatColor.RESET + " - Place image");
s.sendMessage(ChatColor.AQUA + cmd + " place <filename> <w> [<h>] [<f>]" + ChatColor.RESET + " - Place image");
}
if (s.hasPermission("yamipa.remove")) {
s.sendMessage(ChatColor.AQUA + cmd + " remove" + ChatColor.RESET + " - Remove a single placed image");
Expand Down Expand Up @@ -120,48 +123,58 @@ public static void placeImage(
@NotNull Player player,
@NotNull ImageFile image,
int width,
int height
int height,
int flags
) {
YamipaPlugin plugin = YamipaPlugin.getInstance();

// Get image size in blocks
Dimension sizeInPixels = image.getSize();
if (sizeInPixels == null) {
player.sendMessage(ChatColor.RED + "The requested file is not a valid image");
return;
}
if (height == 0) {
float imageRatio = (float) sizeInPixels.height / sizeInPixels.width;
height = Math.round(width * imageRatio);
height = Math.min(height, FakeImage.MAX_DIMENSION);
}
final int finalHeight = height;
final int finalHeight = (height == 0) ? FakeImage.getProportionalHeight(sizeInPixels, width) : height;

// Ask player where to place image
SelectBlockTask task = new SelectBlockTask(player);
task.onSuccess((location, face) -> {
FakeImage existingImage = plugin.getRenderer().getImage(location, face);
if (existingImage != null) {
ActionBar.send(player, ChatColor.RED + "There's already an image there!");
return;
}

// Create new fake image instance
Rotation rotation = FakeImage.getRotationFromPlayerEyesight(face, player.getEyeLocation());
FakeImage fakeImage = new FakeImage(image.getName(), location, face, rotation,
width, finalHeight, new Date(), player);

// Show loading status to player
ActionBar loadingActionBar = ActionBar.repeat(player, ChatColor.AQUA + "Loading image...");
fakeImage.setOnLoadedListener(loadingActionBar::clear);

// Add fake image to renderer
plugin.getRenderer().addImage(fakeImage);
placeImage(player, image, width, finalHeight, flags, location, face);
});
task.onFailure(() -> ActionBar.send(player, ChatColor.RED + "Image placing canceled"));
task.run("Right click a block to continue");
}

public static boolean placeImage(
@NotNull Player player,
@NotNull ImageFile image,
int width,
int height,
int flags,
@NotNull Location location,
@NotNull BlockFace face
) {
YamipaPlugin plugin = YamipaPlugin.getInstance();

// Prevent two images occupying the same space
FakeImage existingImage = plugin.getRenderer().getImage(location, face);
if (existingImage != null) {
ActionBar.send(player, ChatColor.RED + "There's already an image there!");
return false;
}

// Create new fake image instance
Rotation rotation = FakeImage.getRotationFromPlayerEyesight(face, player.getEyeLocation());
FakeImage fakeImage = new FakeImage(image.getName(), location, face, rotation,
width, height, new Date(), player, flags);

// Show loading status to player
ActionBar loadingActionBar = ActionBar.repeat(player, ChatColor.AQUA + "Loading image...");
fakeImage.setOnLoadedListener(loadingActionBar::clear);

// Add fake image to renderer
plugin.getRenderer().addImage(fakeImage);
return true;
}

public static void removeImage(@NotNull Player player) {
ImageRenderer renderer = YamipaPlugin.getInstance().getRenderer();

Expand Down Expand Up @@ -221,19 +234,10 @@ public static void describeImage(@NotNull Player player) {
return;
}

// Send placed image information to player
String dateStr = (image.getPlacedAt() == null) ?
ChatColor.GRAY + "Some point in time" :
new SimpleDateFormat("yyyy-MM-dd HH:mm:ss Z").format(image.getPlacedAt());
String playerStr;
if (image.getPlacedBy().getUniqueId().equals(FakeImage.UNKNOWN_PLAYER_ID)) {
playerStr = ChatColor.GRAY + "Someone";
} else if (image.getPlacedBy().getName() == null) {
playerStr = ChatColor.DARK_AQUA + image.getPlacedBy().getUniqueId().toString();
} else {
playerStr = image.getPlacedBy().getName();
}
// Separate previous messages
player.sendMessage("");

// Basic information
player.sendMessage(ChatColor.GOLD + "Filename: " + ChatColor.RESET + image.getFilename());
player.sendMessage(ChatColor.GOLD + "World: " + ChatColor.RESET +
image.getLocation().getChunk().getWorld().getName());
Expand All @@ -245,11 +249,47 @@ public static void describeImage(@NotNull Player player) {
player.sendMessage(ChatColor.GOLD + "Rotation: " + ChatColor.RESET + image.getRotation());
player.sendMessage(ChatColor.GOLD + "Dimensions: " + ChatColor.RESET +
image.getWidth() + "x" + image.getHeight() + " blocks");

// Speed
int delay = image.getDelay() * 50;
String delayStr = (delay > 0) ? delay + " ms per step" : ChatColor.GRAY + "Not animatable";
String delayStr = (delay > 0) ? delay + " ms per step" : ChatColor.GRAY + "N/A";
player.sendMessage(ChatColor.GOLD + "Speed: " + ChatColor.RESET + delayStr);

// Placed At
String dateStr = (image.getPlacedAt() == null) ?
ChatColor.GRAY + "Some point in time" :
new SimpleDateFormat("yyyy-MM-dd HH:mm:ss Z").format(image.getPlacedAt());
player.sendMessage(ChatColor.GOLD + "Placed At: " + ChatColor.RESET + dateStr);

// Placed By
String playerStr;
if (image.getPlacedBy().getUniqueId().equals(FakeImage.UNKNOWN_PLAYER_ID)) {
playerStr = ChatColor.GRAY + "Someone";
} else if (image.getPlacedBy().getName() == null) {
playerStr = ChatColor.DARK_AQUA + image.getPlacedBy().getUniqueId().toString();
} else {
playerStr = image.getPlacedBy().getName();
}
player.sendMessage(ChatColor.GOLD + "Placed By: " + ChatColor.RESET + playerStr);

// Flags
String flagsStr = "";
if (image.hasFlag(FakeImage.FLAG_ANIMATABLE)) {
flagsStr += ChatColor.AQUA + "ANIM ";
}
if (image.hasFlag(FakeImage.FLAG_REMOVABLE)) {
flagsStr += ChatColor.RED + "REMO ";
}
if (image.hasFlag(FakeImage.FLAG_DROPPABLE)) {
flagsStr += ChatColor.LIGHT_PURPLE + "DROP ";
}
if (image.hasFlag(FakeImage.FLAG_GLOWING)) {
flagsStr += ChatColor.GREEN + "GLOW ";
}
if (flagsStr.isEmpty()) {
flagsStr = ChatColor.GRAY + "N/A";
}
player.sendMessage(ChatColor.GOLD + "Flags: " + ChatColor.RESET + flagsStr);
});
task.onFailure(() -> ActionBar.send(player, ChatColor.RED + "Image describing canceled"));
task.run("Right click the image to describe");
Expand Down Expand Up @@ -297,4 +337,35 @@ public static void showTopPlayers(@NotNull CommandSender sender) {
++printedLines;
}
}

public static void giveImageItems(
@NotNull CommandSender sender,
@NotNull Player player,
@NotNull ImageFile image,
int amount,
int width,
int height,
int flags
) {
// Get image size in blocks
Dimension sizeInPixels = image.getSize();
if (sizeInPixels == null) {
sender.sendMessage(ChatColor.RED + "The requested file is not a valid image");
return;
}
if (height == 0) {
height = FakeImage.getProportionalHeight(sizeInPixels, width);
}

// Create item stack
ItemStack itemStack = ItemService.getImageItem(image, amount, width, height, flags);

// Add item stack to player's inventory
player.getInventory().addItem(itemStack);
sender.sendMessage(
ChatColor.ITALIC + "Added " + amount + " " +
(amount == 1 ? "image item" : "image items") +
" to " + player.getName() + "'s inventory"
);
}
}
Loading

0 comments on commit 9048795

Please sign in to comment.