This commit is contained in:
2026-06-16 22:40:57 +02:00
commit 79a5a4bfa5
8 changed files with 805 additions and 0 deletions

6
.gitignore vendored Normal file
View File

@@ -0,0 +1,6 @@
target/
.idea/
*.iml
.classpath
.project
.settings/

58
README.md Normal file
View File

@@ -0,0 +1,58 @@
# jHolo
jHolo je simple Spigot/Paper hologram plugin za Minecraft 1.21.2.
Uporablja Triler repo dependency `net.triler:minecraft-holograms:1.0`.
## Build
```bash
mvn clean package
```
JAR bo v:
```text
target/jHolo-1.0.0.jar
```
## Namestitev
1. Buildaj plugin z `mvn clean package`.
2. Kopiraj `target/jHolo-1.0.0.jar` v server `plugins` mapo.
3. Zaženi server.
4. Uredi `plugins/jHolo/config.yml` ali uporabi ukaze.
## Ukazi
```text
/jholo help
/jholo create <id> <text>
/jholo delete <id>
/jholo list
/jholo movehere <id>
/jholo tp <id>
/jholo info <id>
/jholo addline <id> <text>
/jholo setline <id> <št.> <text>
/jholo removeline <id> <št.>
/jholo billboard <id> <CENTER|FIXED|HORIZONTAL|VERTICAL>
/jholo reload
```
Za več vrstic pri create uporabi `|`:
```text
/jholo create spawn &b&lSPAWN | &7Dobrodošel na serverju | &eUporabi /menu
```
## Permission
```text
jholo.admin
```
## Package
```text
net.triler.jernejtdo.jholo
```

85
pom.xml Normal file
View File

@@ -0,0 +1,85 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>net.triler.jernejtdo</groupId>
<artifactId>jholo</artifactId>
<version>1.0.0</version>
<packaging>jar</packaging>
<name>jHolo</name>
<description>Simple config based hologram plugin using Triler minecraft-holograms library.</description>
<properties>
<java.version>25</java.version>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
</properties>
<repositories>
<repository>
<id>spigot-repo</id>
<url>https://hub.spigotmc.org/nexus/content/repositories/snapshots/</url>
</repository>
<repository>
<id>trilergit</id>
<url>https://git.triler.eu/api/packages/TrilerCode/maven</url>
</repository>
</repositories>
<dependencies>
<dependency>
<groupId>org.spigotmc</groupId>
<artifactId>spigot-api</artifactId>
<version>26.1.2-R0.1-SNAPSHOT</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>net.triler</groupId>
<artifactId>minecraft-holograms</artifactId>
<version>1.0</version>
</dependency>
</dependencies>
<build>
<finalName>jHolo-${project.version}</finalName>
<resources>
<resource>
<directory>src/main/resources</directory>
<filtering>true</filtering>
</resource>
</resources>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.13.0</version>
<configuration>
<source>${java.version}</source>
<target>${java.version}</target>
<release>${java.version}</release>
</configuration>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-shade-plugin</artifactId>
<version>3.6.0</version>
<executions>
<execution>
<phase>package</phase>
<goals>
<goal>shade</goal>
</goals>
<configuration>
<createDependencyReducedPom>false</createDependencyReducedPom>
<minimizeJar>false</minimizeJar>
</configuration>
</execution>
</executions>
</plugin>
</plugins>
</build>
</project>

View File

@@ -0,0 +1,39 @@
package net.triler.jernejtdo.jholo;
import net.triler.HologramManager;
import net.triler.jernejtdo.jholo.commands.JHoloCommand;
import net.triler.jernejtdo.jholo.hologram.JHoloManager;
import org.bukkit.command.PluginCommand;
import org.bukkit.plugin.java.JavaPlugin;
public final class JHoloPlugin extends JavaPlugin {
private JHoloManager holoManager;
@Override
public void onEnable() {
saveDefaultConfig();
this.holoManager = new JHoloManager(this);
this.holoManager.loadFromConfig();
JHoloCommand command = new JHoloCommand(this, holoManager);
PluginCommand pluginCommand = getCommand("jholo");
if (pluginCommand != null) {
pluginCommand.setExecutor(command);
pluginCommand.setTabCompleter(command);
}
getLogger().info("jHolo enabled. Loaded " + holoManager.getHologramIds().size() + " holograms.");
}
@Override
public void onDisable() {
HologramManager.removeAll();
getLogger().info("jHolo disabled. All spawned holograms removed.");
}
public JHoloManager getHoloManager() {
return holoManager;
}
}

View File

@@ -0,0 +1,332 @@
package net.triler.jernejtdo.jholo.commands;
import net.triler.jernejtdo.jholo.JHoloPlugin;
import net.triler.jernejtdo.jholo.hologram.JHoloManager;
import org.bukkit.ChatColor;
import org.bukkit.Location;
import org.bukkit.command.Command;
import org.bukkit.command.CommandExecutor;
import org.bukkit.command.CommandSender;
import org.bukkit.command.TabCompleter;
import org.bukkit.entity.Display;
import org.bukkit.entity.Player;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import java.util.Locale;
import java.util.Set;
public final class JHoloCommand implements CommandExecutor, TabCompleter {
private final JHoloPlugin plugin;
private final JHoloManager manager;
private static final List<String> SUB_COMMANDS = List.of(
"help", "reload", "list", "create", "delete", "movehere", "tp", "info",
"addline", "setline", "removeline", "billboard"
);
public JHoloCommand(JHoloPlugin plugin, JHoloManager manager) {
this.plugin = plugin;
this.manager = manager;
}
@Override
public boolean onCommand(CommandSender sender, Command command, String label, String[] args) {
if (!sender.hasPermission("jholo.admin")) {
send(sender, "&cYou do not have permission: &fjholo.admin");
return true;
}
if (args.length == 0 || args[0].equalsIgnoreCase("help")) {
sendHelp(sender);
return true;
}
String sub = args[0].toLowerCase(Locale.ROOT);
switch (sub) {
case "reload" -> {
plugin.reloadConfig();
manager.loadFromConfig();
send(sender, "&aConfig reloaded. Holograms: &f" + manager.getHologramIds().size());
}
case "list" -> list(sender);
case "create" -> create(sender, args);
case "delete", "remove" -> delete(sender, args);
case "movehere" -> moveHere(sender, args);
case "tp", "teleport" -> teleport(sender, args);
case "info" -> info(sender, args);
case "addline" -> addLine(sender, args);
case "setline" -> setLine(sender, args);
case "removeline", "delline" -> removeLine(sender, args);
case "billboard" -> billboard(sender, args);
default -> send(sender, "&cUnknown command. Use &f/jholo help&c.");
}
return true;
}
private void sendHelp(CommandSender sender) {
send(sender, "&bjHolo commands:");
raw(sender, "&e/jholo create <id> <text> &7- create a hologram at your location");
raw(sender, "&e/jholo delete <id> &7- delete a hologram");
raw(sender, "&e/jholo list &7- list holograms");
raw(sender, "&e/jholo movehere <id> &7- move a hologram to your location");
raw(sender, "&e/jholo tp <id> &7- teleport to a hologram");
raw(sender, "&e/jholo addline <id> <text> &7- add a line");
raw(sender, "&e/jholo setline <id> <line> <text> &7- change a line");
raw(sender, "&e/jholo removeline <id> <line> &7- remove a line");
raw(sender, "&e/jholo billboard <id> <CENTER|FIXED|HORIZONTAL|VERTICAL> &7- display direction");
raw(sender, "&e/jholo reload &7- reload config");
raw(sender, "&7For multiple lines in create, use &f| &7or &f\\n&7. Example: &f/jholo create test &bTitle | &7description");
}
private void list(CommandSender sender) {
Set<String> ids = manager.getHologramIds();
if (ids.isEmpty()) {
send(sender, "&7No holograms loaded.");
return;
}
send(sender, "&7Holograms (&f" + ids.size() + "&7): &f" + String.join("&7, &f", ids));
}
private void create(CommandSender sender, String[] args) {
if (!(sender instanceof Player player)) {
send(sender, "&cOnly players can use this command.");
return;
}
if (args.length < 3) {
send(sender, "&cUsage: &f/jholo create <id> <text>");
return;
}
String id = args[1];
if (!manager.isValidId(id)) {
send(sender, "&cID may only contain A-Z, 0-9, _ and - and must be max 32 characters.");
return;
}
if (manager.exists(id)) {
send(sender, "&cA hologram with this ID already exists. First use &f/jholo delete " + id);
return;
}
List<String> lines = parseLines(join(args, 2));
manager.create(id, player.getLocation(), lines, Display.Billboard.CENTER);
send(sender, "&aHologram &f" + id + " &acreated.");
}
private void delete(CommandSender sender, String[] args) {
if (args.length < 2) {
send(sender, "&cUsage: &f/jholo delete <id>");
return;
}
if (manager.delete(args[1])) {
send(sender, "&aHologram &f" + args[1] + " &adeleted.");
} else {
send(sender, "&cHologram does not exist: &f" + args[1]);
}
}
private void moveHere(CommandSender sender, String[] args) {
if (!(sender instanceof Player player)) {
send(sender, "&cOnly players can use this command.");
return;
}
if (args.length < 2) {
send(sender, "&cUsage: &f/jholo movehere <id>");
return;
}
if (manager.moveTo(args[1], player.getLocation())) {
send(sender, "&aHologram &f" + args[1] + " &amoved to your location.");
} else {
send(sender, "&cHologram does not exist: &f" + args[1]);
}
}
private void teleport(CommandSender sender, String[] args) {
if (!(sender instanceof Player player)) {
send(sender, "&cOnly players can use this command.");
return;
}
if (args.length < 2) {
send(sender, "&cUsage: &f/jholo tp <id>");
return;
}
Location location = manager.getLocation(args[1]);
if (location == null) {
send(sender, "&cHologram does not exist or the world is not loaded: &f" + args[1]);
return;
}
player.teleport(location);
send(sender, "&aTeleported to hologram &f" + args[1] + "&a.");
}
private void info(CommandSender sender, String[] args) {
if (args.length < 2) {
send(sender, "&cUsage: &f/jholo info <id>");
return;
}
String id = args[1];
if (!manager.exists(id)) {
send(sender, "&cHologram does not exist: &f" + id);
return;
}
Location location = manager.getLocation(id);
send(sender, "&bInfo for &f" + id + "&b:");
if (location != null && location.getWorld() != null) {
raw(sender, "&7World: &f" + location.getWorld().getName());
raw(sender, "&7Location: &fx=" + round(location.getX()) + " y=" + round(location.getY()) + " z=" + round(location.getZ()));
}
raw(sender, "&7Billboard: &f" + manager.getBillboard(id).name());
List<String> lines = manager.getLines(id);
for (int i = 0; i < lines.size(); i++) {
raw(sender, "&7" + (i + 1) + ". &f" + lines.get(i));
}
}
private void addLine(CommandSender sender, String[] args) {
if (args.length < 3) {
send(sender, "&cUsage: &f/jholo addline <id> <text>");
return;
}
if (manager.addLine(args[1], join(args, 2))) {
send(sender, "&aLine added to &f" + args[1] + "&a.");
} else {
send(sender, "&cHologram does not exist: &f" + args[1]);
}
}
private void setLine(CommandSender sender, String[] args) {
if (args.length < 4) {
send(sender, "&cUsage: &f/jholo setline <id> <line> <text>");
return;
}
Integer line = parseInt(args[2]);
if (line == null) {
send(sender, "&cLine number is not valid.");
return;
}
if (manager.setLine(args[1], line, join(args, 3))) {
send(sender, "&aLine &f" + line + " &achanged in &f" + args[1] + "&a.");
} else {
send(sender, "&cHologram or line does not exist.");
}
}
private void removeLine(CommandSender sender, String[] args) {
if (args.length < 3) {
send(sender, "&cUsage: &f/jholo removeline <id> <line>");
return;
}
Integer line = parseInt(args[2]);
if (line == null) {
send(sender, "&cLine number is not valid.");
return;
}
if (manager.removeLine(args[1], line)) {
send(sender, "&aLine &f" + line + " &aremoved from &f" + args[1] + "&a.");
} else {
send(sender, "&cHologram or line does not exist.");
}
}
private void billboard(CommandSender sender, String[] args) {
if (args.length < 3) {
send(sender, "&cUsage: &f/jholo billboard <id> <CENTER|FIXED|HORIZONTAL|VERTICAL>");
return;
}
Display.Billboard billboard;
try {
billboard = Display.Billboard.valueOf(args[2].toUpperCase(Locale.ROOT));
} catch (IllegalArgumentException exception) {
send(sender, "&cInvalid billboard. Use: &fCENTER, FIXED, HORIZONTAL, VERTICAL");
return;
}
if (manager.setBillboard(args[1], billboard)) {
send(sender, "&aBillboard for &f" + args[1] + " &aset to &f" + billboard.name() + "&a.");
} else {
send(sender, "&cHologram does not exist: &f" + args[1]);
}
}
@Override
public List<String> onTabComplete(CommandSender sender, Command command, String alias, String[] args) {
if (!sender.hasPermission("jholo.admin")) {
return Collections.emptyList();
}
if (args.length == 1) {
return filter(SUB_COMMANDS, args[0]);
}
String sub = args[0].toLowerCase(Locale.ROOT);
if (args.length == 2 && List.of("delete", "remove", "movehere", "tp", "teleport", "info", "addline", "setline", "removeline", "delline", "billboard").contains(sub)) {
return filter(new ArrayList<>(manager.getHologramIds()), args[1]);
}
if (sub.equals("billboard") && args.length == 3) {
return filter(Arrays.stream(Display.Billboard.values()).map(Enum::name).toList(), args[2]);
}
return Collections.emptyList();
}
private List<String> filter(List<String> options, String input) {
String lower = input.toLowerCase(Locale.ROOT);
List<String> result = new ArrayList<>();
for (String option : options) {
if (option.toLowerCase(Locale.ROOT).startsWith(lower)) {
result.add(option);
}
}
return result;
}
private List<String> parseLines(String raw) {
String[] split = raw.split("\\s*\\|\\s*|\\\\n");
List<String> lines = new ArrayList<>();
for (String line : split) {
if (!line.isBlank()) {
lines.add(line);
}
}
if (lines.isEmpty()) {
lines.add("&cEmpty hologram");
}
return lines;
}
private String join(String[] args, int start) {
StringBuilder builder = new StringBuilder();
for (int i = start; i < args.length; i++) {
if (i > start) {
builder.append(' ');
}
builder.append(args[i]);
}
return builder.toString();
}
private Integer parseInt(String input) {
try {
return Integer.parseInt(input);
} catch (NumberFormatException exception) {
return null;
}
}
private String round(double value) {
return String.format(Locale.US, "%.2f", value);
}
private void send(CommandSender sender, String message) {
sender.sendMessage(manager.getPrefix() + ChatColor.translateAlternateColorCodes('&', message));
}
private void raw(CommandSender sender, String message) {
sender.sendMessage(ChatColor.translateAlternateColorCodes('&', message));
}
}

View File

@@ -0,0 +1,248 @@
package net.triler.jernejtdo.jholo.hologram;
import net.triler.HologramManager;
import net.triler.jernejtdo.jholo.JHoloPlugin;
import org.bukkit.Bukkit;
import org.bukkit.ChatColor;
import org.bukkit.Location;
import org.bukkit.World;
import org.bukkit.configuration.ConfigurationSection;
import org.bukkit.entity.Display;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Locale;
import java.util.Set;
public final class JHoloManager {
private final JHoloPlugin plugin;
private final Set<String> hologramIds = new LinkedHashSet<>();
public JHoloManager(JHoloPlugin plugin) {
this.plugin = plugin;
}
public void loadFromConfig() {
HologramManager.removeAll();
hologramIds.clear();
ConfigurationSection section = plugin.getConfig().getConfigurationSection("holograms");
if (section == null) {
return;
}
for (String id : section.getKeys(false)) {
spawnFromConfig(id);
}
}
public boolean spawnFromConfig(String id) {
String path = path(id);
World world = Bukkit.getWorld(plugin.getConfig().getString(path + ".world", "world"));
if (world == null) {
plugin.getLogger().warning("Cannot load hologram '" + id + "': world does not exist.");
return false;
}
double x = plugin.getConfig().getDouble(path + ".x");
double y = plugin.getConfig().getDouble(path + ".y");
double z = plugin.getConfig().getDouble(path + ".z");
float yaw = (float) plugin.getConfig().getDouble(path + ".yaw", 0.0);
float pitch = (float) plugin.getConfig().getDouble(path + ".pitch", 0.0);
Location location = new Location(world, x, y, z, yaw, pitch);
Display.Billboard billboard = parseBillboard(
plugin.getConfig().getString(path + ".billboard", plugin.getConfig().getString("settings.default-billboard", "CENTER"))
);
List<String> lines = getLines(id);
HologramManager.create(id, location, billboard, toDisplayText(lines));
hologramIds.add(id);
return true;
}
public void create(String id, Location location, List<String> lines, Display.Billboard billboard) {
writeConfig(id, location, lines, billboard);
HologramManager.create(id, location, billboard, toDisplayText(lines));
hologramIds.add(id);
}
public boolean delete(String id) {
if (!exists(id)) {
return false;
}
HologramManager.remove(id);
hologramIds.remove(id);
plugin.getConfig().set(path(id), null);
plugin.saveConfig();
return true;
}
public boolean moveTo(String id, Location location) {
if (!exists(id)) {
return false;
}
List<String> lines = getLines(id);
Display.Billboard billboard = getBillboard(id);
writeConfig(id, location, lines, billboard);
HologramManager.create(id, location, billboard, toDisplayText(lines));
hologramIds.add(id);
return true;
}
public boolean setLines(String id, List<String> lines) {
if (!exists(id)) {
return false;
}
plugin.getConfig().set(path(id) + ".lines", lines);
plugin.saveConfig();
HologramManager.update(id, toDisplayText(lines));
return true;
}
public boolean addLine(String id, String line) {
if (!exists(id)) {
return false;
}
List<String> lines = getLines(id);
lines.add(line);
return setLines(id, lines);
}
public boolean setLine(String id, int lineNumber, String text) {
if (!exists(id)) {
return false;
}
List<String> lines = getLines(id);
int index = lineNumber - 1;
if (index < 0 || index >= lines.size()) {
return false;
}
lines.set(index, text);
return setLines(id, lines);
}
public boolean removeLine(String id, int lineNumber) {
if (!exists(id)) {
return false;
}
List<String> lines = getLines(id);
int index = lineNumber - 1;
if (index < 0 || index >= lines.size()) {
return false;
}
lines.remove(index);
if (lines.isEmpty()) {
lines.add("&cEmpty hologram");
}
return setLines(id, lines);
}
public boolean setBillboard(String id, Display.Billboard billboard) {
if (!exists(id)) {
return false;
}
plugin.getConfig().set(path(id) + ".billboard", billboard.name());
plugin.saveConfig();
Location location = getLocation(id);
if (location == null) {
return false;
}
HologramManager.create(id, location, billboard, toDisplayText(getLines(id)));
hologramIds.add(id);
return true;
}
public Location getLocation(String id) {
if (!exists(id)) {
return null;
}
String path = path(id);
World world = Bukkit.getWorld(plugin.getConfig().getString(path + ".world", "world"));
if (world == null) {
return null;
}
return new Location(
world,
plugin.getConfig().getDouble(path + ".x"),
plugin.getConfig().getDouble(path + ".y"),
plugin.getConfig().getDouble(path + ".z"),
(float) plugin.getConfig().getDouble(path + ".yaw", 0.0),
(float) plugin.getConfig().getDouble(path + ".pitch", 0.0)
);
}
public List<String> getLines(String id) {
List<String> lines = plugin.getConfig().getStringList(path(id) + ".lines");
if (lines == null || lines.isEmpty()) {
return new ArrayList<>(Collections.singletonList("&cEmpty hologram"));
}
return new ArrayList<>(lines);
}
public Display.Billboard getBillboard(String id) {
return parseBillboard(plugin.getConfig().getString(path(id) + ".billboard", "CENTER"));
}
public boolean exists(String id) {
return plugin.getConfig().isConfigurationSection(path(id));
}
public Set<String> getHologramIds() {
List<String> sorted = new ArrayList<>(hologramIds);
sorted.sort(Comparator.naturalOrder());
return new LinkedHashSet<>(sorted);
}
public String getPrefix() {
return color(plugin.getConfig().getString("settings.prefix", "&8[&bjHolo&8] &7"));
}
public boolean isValidId(String id) {
return id != null && id.matches("[A-Za-z0-9_-]{1,32}");
}
public Display.Billboard parseBillboard(String input) {
if (input == null) {
return Display.Billboard.CENTER;
}
try {
return Display.Billboard.valueOf(input.toUpperCase(Locale.ROOT));
} catch (IllegalArgumentException exception) {
return Display.Billboard.CENTER;
}
}
public String color(String text) {
return ChatColor.translateAlternateColorCodes('&', text == null ? "" : text);
}
public String toDisplayText(List<String> lines) {
List<String> colored = new ArrayList<>();
for (String line : lines) {
colored.add(color(line));
}
return String.join("\n", colored);
}
private void writeConfig(String id, Location location, List<String> lines, Display.Billboard billboard) {
String path = path(id);
plugin.getConfig().set(path + ".world", location.getWorld() == null ? "world" : location.getWorld().getName());
plugin.getConfig().set(path + ".x", location.getX());
plugin.getConfig().set(path + ".y", location.getY());
plugin.getConfig().set(path + ".z", location.getZ());
plugin.getConfig().set(path + ".yaw", location.getYaw());
plugin.getConfig().set(path + ".pitch", location.getPitch());
plugin.getConfig().set(path + ".billboard", billboard.name());
plugin.getConfig().set(path + ".lines", lines);
plugin.saveConfig();
}
private String path(String id) {
return "holograms." + id;
}
}

View File

@@ -0,0 +1,20 @@
# jHolo config
# Holograms are loaded automatically on server start and /jholo reload.
settings:
prefix: "&8[&bjHolo&8] &7"
default-billboard: "CENTER"
holograms:
welcome:
world: "world"
x: 0.5
y: 100.0
z: 0.5
yaw: 0.0
pitch: 0.0
billboard: "CENTER"
lines:
- "&b&ljHolo"
- "&7Primer holograma iz config.yml"
- "&e/jholo help"

View File

@@ -0,0 +1,17 @@
name: jHolo
version: '${project.version}'
main: net.triler.jernejtdo.jholo.JHoloPlugin
api-version: '26.1.2'
author: JernejTDO
description: Config based hologram plugin using Triler minecraft-holograms library.
commands:
jholo:
description: Main jHolo command.
usage: /jholo help
aliases: [holo, hologram, holograms]
permissions:
jholo.admin:
description: Allows using all jHolo commands.
default: op