Skip to content

Commit 494fb32

Browse files
committed
fix: resolve IllegalPluginAccessException on disable, support custom hologram locations centered on blocks, and bump version to 1.0.8
1 parent b140386 commit 494fb32

7 files changed

Lines changed: 173 additions & 10 deletions

File tree

gradle.properties

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,3 @@
11
group=dev.velmax
2-
version=1.0.7
2+
version=1.0.8
33
description=A modern, high-performance King of the Hill plugin for PaperMC 1.21+

src/main/java/dev/velmax/velkoth/arena/Arena.java

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22

33
import dev.velmax.velkoth.arena.region.Region;
44
import dev.velmax.velkoth.reward.Reward;
5+
import org.bukkit.Location;
56

67
import java.util.ArrayList;
78
import java.util.List;
@@ -20,6 +21,7 @@ public final class Arena {
2021
private List<Reward> rewards;
2122
private int graceperiod;
2223
private int maxScore;
24+
private Location hologramLocation;
2325

2426
public Arena(String id, String displayName, Region region, int captureTime,
2527
CaptureMode captureMode, int gracePeriod, int maxScore) {
@@ -102,6 +104,14 @@ public void setMaxScore(int maxScore) {
102104
this.maxScore = maxScore;
103105
}
104106

107+
public Location hologramLocation() {
108+
return hologramLocation;
109+
}
110+
111+
public void setHologramLocation(Location hologramLocation) {
112+
this.hologramLocation = hologramLocation;
113+
}
114+
105115
/**
106116
* Mode of capture for this arena.
107117
*/

src/main/java/dev/velmax/velkoth/command/KothCommand.java

Lines changed: 83 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,8 @@
2222
import org.bukkit.persistence.PersistentDataType;
2323
import org.incendo.cloud.execution.ExecutionCoordinator;
2424
import org.incendo.cloud.paper.PaperCommandManager;
25+
import org.bukkit.Location;
26+
import org.incendo.cloud.parser.standard.DoubleParser;
2527
import org.incendo.cloud.parser.standard.EnumParser;
2628
import org.incendo.cloud.parser.standard.IntegerParser;
2729
import org.incendo.cloud.parser.standard.StringParser;
@@ -158,6 +160,23 @@ private void registerCommands() {
158160
.required("seconds", IntegerParser.integerParser())
159161
.handler(ctx -> handleSetGrace(ctx.sender().source(), ctx.get("arena"), ctx.get("seconds"))));
160162

163+
manager.command(setBuilder.literal("hologram")
164+
.required("arena", StringParser.stringParser(), arenaSuggestions)
165+
.senderType(PlayerSource.class)
166+
.handler(ctx -> handleSetHologramHere(ctx.sender().source(), ctx.get("arena"))));
167+
168+
manager.command(setBuilder.literal("hologram")
169+
.required("arena", StringParser.stringParser(), arenaSuggestions)
170+
.literal("reset")
171+
.handler(ctx -> handleSetHologramReset(ctx.sender().source(), ctx.get("arena"))));
172+
173+
manager.command(setBuilder.literal("hologram")
174+
.required("arena", StringParser.stringParser(), arenaSuggestions)
175+
.required("x", DoubleParser.doubleParser())
176+
.required("y", DoubleParser.doubleParser())
177+
.required("z", DoubleParser.doubleParser())
178+
.handler(ctx -> handleSetHologramCoords(ctx.sender().source(), ctx.get("arena"), ctx.get("x"), ctx.get("y"), ctx.get("z"))));
179+
161180
// ── Reward ──
162181
var rewardBuilder = base.literal("reward").permission("velkoth.admin");
163182

@@ -490,6 +509,67 @@ private void handleSetGrace(CommandSender sender, String arenaId, int seconds) {
490509
.replace("<value>", seconds + "s"));
491510
}
492511

512+
private void handleSetHologramHere(Player player, String arenaId) {
513+
Arena arena = plugin.getArenaManager().getArena(arenaId);
514+
if (arena == null) {
515+
sendPrefixed(player, plugin.getMessages().getArenaNotFound().replace("<arena>", arenaId));
516+
return;
517+
}
518+
519+
Location loc = player.getLocation();
520+
arena.setHologramLocation(loc);
521+
plugin.getArenaManager().saveArenas();
522+
523+
// Update the hologram location immediately if it is active
524+
plugin.getDisplayManager().getHologramManager().updateLocation(arena);
525+
526+
String coordsStr = String.format("%.1f, %.1f, %.1f", loc.getX(), loc.getY(), loc.getZ());
527+
sendPrefixed(player, plugin.getMessages().getArenaModified()
528+
.replace("<arena>", arena.id())
529+
.replace("<property>", "hologram-location")
530+
.replace("<value>", coordsStr));
531+
}
532+
533+
private void handleSetHologramReset(CommandSender sender, String arenaId) {
534+
Arena arena = plugin.getArenaManager().getArena(arenaId);
535+
if (arena == null) {
536+
sendPrefixed(sender, plugin.getMessages().getArenaNotFound().replace("<arena>", arenaId));
537+
return;
538+
}
539+
540+
arena.setHologramLocation(null);
541+
plugin.getArenaManager().saveArenas();
542+
543+
// Update the hologram location immediately if it is active
544+
plugin.getDisplayManager().getHologramManager().updateLocation(arena);
545+
546+
sendPrefixed(sender, plugin.getMessages().getArenaModified()
547+
.replace("<arena>", arena.id())
548+
.replace("<property>", "hologram-location")
549+
.replace("<value>", "default"));
550+
}
551+
552+
private void handleSetHologramCoords(CommandSender sender, String arenaId, double x, double y, double z) {
553+
Arena arena = plugin.getArenaManager().getArena(arenaId);
554+
if (arena == null) {
555+
sendPrefixed(sender, plugin.getMessages().getArenaNotFound().replace("<arena>", arenaId));
556+
return;
557+
}
558+
559+
Location loc = new Location(arena.region().getWorld(), x, y, z);
560+
arena.setHologramLocation(loc);
561+
plugin.getArenaManager().saveArenas();
562+
563+
// Update the hologram location immediately if it is active
564+
plugin.getDisplayManager().getHologramManager().updateLocation(arena);
565+
566+
String coordsStr = String.format("%.1f, %.1f, %.1f", x, y, z);
567+
sendPrefixed(sender, plugin.getMessages().getArenaModified()
568+
.replace("<arena>", arena.id())
569+
.replace("<property>", "hologram-location")
570+
.replace("<value>", coordsStr));
571+
}
572+
493573
// ── Reward ──
494574

495575
private void handleRewardAdd(CommandSender sender, String arenaId, String rewardStr) {
@@ -590,6 +670,9 @@ private void handleHelp(CommandSender sender) {
590670
sendPrefixed(sender, " <gold>/koth set time <arena> <seconds></gold>");
591671
sendPrefixed(sender, " <gold>/koth set score <arena> <maxScore></gold>");
592672
sendPrefixed(sender, " <gold>/koth set grace <arena> <seconds></gold>");
673+
sendPrefixed(sender, " <gold>/koth set hologram <arena></gold>");
674+
sendPrefixed(sender, " <gold>/koth set hologram <arena> <x> <y> <z></gold>");
675+
sendPrefixed(sender, " <gold>/koth set hologram <arena> reset</gold>");
593676
sendPrefixed(sender, "");
594677
sendPrefixed(sender, "<gold><bold>Reward Commands</bold></gold>");
595678
sendPrefixed(sender, " <gold>/koth reward add <arena> <reward></gold>");

src/main/java/dev/velmax/velkoth/config/ArenaConfig.java

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -72,6 +72,9 @@ public static class ArenaEntry extends OkaeriConfig {
7272
"give %player% diamond 3",
7373
"eco give %player% 500");
7474

75+
@Comment("Custom hologram location [x, y, z]")
76+
private List<Double> hologramLocation = null;
77+
7578
// Getters & Setters
7679
public String getDisplayName() {
7780
return displayName;
@@ -192,5 +195,13 @@ public List<String> getRewards() {
192195
public void setRewards(List<String> rewards) {
193196
this.rewards = rewards;
194197
}
198+
199+
public List<Double> getHologramLocation() {
200+
return hologramLocation;
201+
}
202+
203+
public void setHologramLocation(List<Double> hologramLocation) {
204+
this.hologramLocation = hologramLocation;
205+
}
195206
}
196207
}

src/main/java/dev/velmax/velkoth/display/HologramManager.java

Lines changed: 28 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -39,9 +39,18 @@ public void spawn(Arena arena) {
3939
if (!plugin.getPluginConfig().getDisplay().isHologramEnabled())
4040
return;
4141

42-
Location center = arena.region().getCenter();
43-
// Adjust Y offset as configured (e.g. +3 blocks above center)
44-
center.add(0, plugin.getPluginConfig().getDisplay().getHologramYOffset(), 0);
42+
Location center;
43+
if (arena.hologramLocation() != null) {
44+
center = arena.hologramLocation().clone();
45+
} else {
46+
center = arena.region().getCenter();
47+
// Adjust Y offset as configured (e.g. +3 blocks above center)
48+
center.add(0, plugin.getPluginConfig().getDisplay().getHologramYOffset(), 0);
49+
}
50+
51+
// Always center to the block (X.5, Z.5)
52+
center.setX(center.getBlockX() + 0.5);
53+
center.setZ(center.getBlockZ() + 0.5);
4554

4655
if (center.getWorld() == null)
4756
return;
@@ -68,15 +77,25 @@ public void spawn(Arena arena) {
6877
});
6978
}
7079

71-
/**
72-
* Safely removes the hologram for an arena.
73-
*
74-
* @param arena the arena
75-
*/
7680
public void remove(Arena arena) {
7781
TextDisplay display = holograms.remove(arena);
7882
if (display != null && display.isValid()) {
79-
display.getScheduler().run(plugin, scheduledTask -> display.remove(), null);
83+
if (plugin.isEnabled()) {
84+
display.getScheduler().run(plugin, scheduledTask -> display.remove(), null);
85+
} else {
86+
display.remove();
87+
}
88+
}
89+
}
90+
91+
92+
/**
93+
* Updates the location of the hologram dynamically if it is active.
94+
*/
95+
public void updateLocation(Arena arena) {
96+
if (holograms.containsKey(arena)) {
97+
remove(arena);
98+
spawn(arena);
8099
}
81100
}
82101

src/main/java/dev/velmax/velkoth/manager/ArenaManager.java

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -94,6 +94,12 @@ public void saveArenas() {
9494
}
9595
arena.setRewards(rewards);
9696

97+
// Parse custom hologram location
98+
if (ae.getHologramLocation() != null && ae.getHologramLocation().size() == 3) {
99+
List<Double> loc = ae.getHologramLocation();
100+
arena.setHologramLocation(new org.bukkit.Location(world, loc.get(0), loc.get(1), loc.get(2)));
101+
}
102+
97103
return arena;
98104
}
99105

@@ -163,6 +169,14 @@ private ArenaConfig.ArenaEntry serializeArena(Arena arena) {
163169
}
164170
ae.setRewards(rewardStrings);
165171

172+
// Serialize custom hologram location
173+
if (arena.hologramLocation() != null) {
174+
org.bukkit.Location loc = arena.hologramLocation();
175+
ae.setHologramLocation(List.of(loc.getX(), loc.getY(), loc.getZ()));
176+
} else {
177+
ae.setHologramLocation(null);
178+
}
179+
166180
return ae;
167181
}
168182

tasks/todo.md

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
# Tasks for Hologram Positioning and Commands
2+
3+
Implement centering holograms on blocks, adding commands to modify and change hologram positions, and verifying optimal performance using Display entities (TextDisplay).
4+
5+
## Todo List
6+
- [x] Add `hologramLocation` field (nullable Location) in `Arena.java` and its getter/setter
7+
- [x] Add `hologramLocation` field (List<Double>) in `ArenaConfig.ArenaEntry` and its getter/setter
8+
- [x] Update serialization/deserialization logic in `ArenaManager.java` to support custom hologram location
9+
- [x] Modify `HologramManager.java` to:
10+
- Spawn hologram at custom location if set, otherwise fallback to region center + Y offset
11+
- Always center X/Z coordinates of hologram location to block center (X.5, Z.5)
12+
- [x] Add commands in `KothCommand.java` under `/koth set hologram`:
13+
- `/koth set hologram <arena>` (sets to current player position)
14+
- `/koth set hologram <arena> <x> <y> <z>` (sets to custom coordinates)
15+
- `/koth set hologram <arena> reset` (clears the custom location)
16+
- [x] Add support to update active holograms immediately when their position changes
17+
18+
## Review Section
19+
- Custom `hologramLocation` has been added to `Arena` and `ArenaConfig.ArenaEntry`.
20+
- Deserialization and serialization logic have been fully integrated in `ArenaManager`.
21+
- Spawning logic in `HologramManager` now checks for a custom location first and falls back to the region center + Y offset if not set.
22+
- Both custom and default hologram positions are automatically centered to the block's horizontal middle (`X.5`, `Z.5`).
23+
- TextDisplay (Display Entity) is used for high-performance hologram rendering.
24+
- New commands registered: `/koth set hologram <arena>`, `/koth set hologram <arena> <x> <y> <z>`, and `/koth set hologram <arena> reset`.
25+
- If an arena is active, any modification to its hologram position immediately updates the active hologram in the game.
26+
- Verified build and compilation successfully with Java 21 toolchain.

0 commit comments

Comments
 (0)