Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
30 commits
Select commit Hold shift + click to select a range
aee0fb2
refactor(commands): remove `endersee` and `invsee` commands and clean…
Trixmas Sep 4, 2026
7d05a81
feat: implement moderation system with punishment, command, and event…
Trixmas Sep 4, 2026
2222b37
feat(mod): standardize moderator chat and actionbar formatting
Trixmas Sep 4, 2026
c61a512
feat(mod): add book detail view for reports and testmod preview command
Trixmas Sep 4, 2026
7cc6f0e
fix(mod): resolve double-click tool interact and update report viewer…
Trixmas Sep 4, 2026
b9b0cf2
refactor(mod): replace manual getters with Lombok annotations and imp…
Trixmas Sep 4, 2026
3b0295c
refactor(menu): standardize paginated inner grid slots and prevent re…
Trixmas Sep 5, 2026
828284d
fix(menu): implement native line wrapping and page auto-splitting
Trixmas Sep 5, 2026
21c2fe2
feat(menu): add direct handled click callbacks to PaginatedMenu
Trixmas Sep 5, 2026
a28b0a7
fix(itembuilder): resolve skull owners asynchronously via PlayerProfile
Trixmas Sep 5, 2026
fa092a5
feat(mod): add formatShortDuration helper to DurationParser
Trixmas Sep 5, 2026
2a47cc9
refactor(mod): align moderation target notifications with standard fo…
Trixmas Sep 5, 2026
ffc8d4c
refactor(mod): consolidate teleport commands into single execution pi…
Trixmas Sep 5, 2026
8e0a620
refactor(mod): delegate packed ice tool action to freeze command
Trixmas Sep 5, 2026
27dca96
refactor(mod): clean report menus and deduplicate submission logic
Trixmas Sep 5, 2026
bca86c6
refactor: code cleanup
Trixmas Sep 5, 2026
6b84582
feat(hud): add dynamic health color indicator
Trixmas Sep 5, 2026
3abacbb
fix(menu): preserve text formatting and space tokens during book pagi…
Trixmas Sep 5, 2026
0b3bef0
refactor(mod): trim long report messages in menu lore with ellipsis
Trixmas Sep 5, 2026
275c4f4
refactor(mod): standardize userinfo, invsee, and gems command output
Trixmas Sep 5, 2026
2db100a
feat(mod): add target glow, freeze overlay, and moderation chat styling
Trixmas Sep 5, 2026
5b0dddf
fix(bedrock): resolve thread safety in BlightedPlayer and dynamic slo…
Trixmas Sep 5, 2026
9fade52
feat(mod): add console support for kick, ban, and mute commands
Trixmas Sep 5, 2026
c4786a6
refactor(mod): extract PunishmentArguments record for duration and re…
Trixmas Sep 5, 2026
438b7f2
chore: update README
Trixmas Sep 5, 2026
797f92f
fix: remove event cancellation
Trixmas Sep 5, 2026
c81bbe0
chore: update code peer code review
Trixmas Sep 5, 2026
a504458
refactor(bedrock): code improvements from code review
Trixmas Sep 5, 2026
ab0e0be
refactor(bedrock): code improvements from code review
Trixmas Sep 5, 2026
a70761d
refactor(bedrock): code improvements from code review
Trixmas Sep 5, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ A Spigot 26.2 plugin providing a high-difficulty survival experience inspired by

- **`bedrock`**: Core framework module handling UI menus, action bars, announcements, scheduling, and utility systems.
- **`blighted-smp`**: Primary SMP plugin module containing custom items, mobs, abilities, recipes, and gameplay mechanics.
- **`blighted-mod`**: Moderation plugin attaching to the SMP server.

## Requirements

Expand Down
10 changes: 10 additions & 0 deletions bedrock/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,16 @@
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
</dependency>
<dependency>
<groupId>org.junit.jupiter</groupId>
<artifactId>junit-jupiter-api</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.junit.jupiter</groupId>
<artifactId>junit-jupiter-engine</artifactId>
<scope>test</scope>
</dependency>
</dependencies>

<build>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -63,11 +63,11 @@ protected abstract boolean execute(
*
* <p>If no matching player is found, a warning is sent to the command sender.
*
* @param sender the player requesting the lookup
* @param sender the command sender requesting the lookup
* @param name the exact username to resolve
* @return the matching online player, or {@code null} if no player exists
*/
protected final @Nullable Player requireTarget(Player sender, String name) {
protected final @Nullable Player requireTarget(CommandSender sender, String name) {
Player target = Bukkit.getPlayerExact(name);

if (target == null) {
Expand All @@ -76,4 +76,17 @@ protected abstract boolean execute(

return target;
}

/**
* Resolves an online player by their exact username.
*
* <p>If no matching player is found, a warning is sent to the command sender.
*
* @param sender the player requesting the lookup
* @param name the exact username to resolve
* @return the matching online player, or {@code null} if no player exists
*/
protected final @Nullable Player requireTarget(Player sender, String name) {
return requireTarget((CommandSender) sender, name);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -5,12 +5,12 @@
import org.jspecify.annotations.NonNull;

import java.time.Duration;
import java.util.ArrayList;
import java.util.Comparator;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.PriorityQueue;
import java.util.concurrent.ConcurrentHashMap;
import java.util.stream.Collectors;

/**
* Assembles and formats per-player action bar text from active sections and temporary alerts.
Expand Down Expand Up @@ -91,8 +91,10 @@ public void clearAlerts() {
/**
* Compiles the formatted action bar message string for a given player.
*
* <p>If an active modal alert exists, its message is returned immediately. Otherwise,
* visible sections are sorted by priority and joined with the configured separator.</p>
* <p>If an active modal alert exists, its message is returned immediately. Otherwise, if any
* visible exclusive sections produce non-empty text, the exclusive section with the highest priority
* is returned. If no exclusive section produces content, visible normal sections are sorted by
* rendering priority (lowest first) and joined with the configured separator.</p>
*
* @param player player for whom to compile action bar content
* @return compiled action bar text, or an empty string if no content is visible
Expand All @@ -106,21 +108,47 @@ public String compile(@NonNull Player player) {
}
}

return sections.values().stream()
List<ActionbarSection> visibleSections = sections.values().stream()
.filter(section -> section.visibility().test(player))
.toList();

List<ActionbarSection> exclusiveSections = visibleSections.stream()
.filter(ActionbarSection::exclusive)
.sorted(Comparator.comparingInt(ActionbarSection::priority).reversed()
.thenComparing(ActionbarSection::id))
.toList();

for (ActionbarSection exclusiveSection : exclusiveSections) {
String text = evaluateSection(exclusiveSection, player);
if (text != null && !text.isEmpty()) {
return text;
}
}

List<ActionbarSection> normalSections = visibleSections.stream()
.filter(section -> !section.exclusive())
.sorted(Comparator.comparingInt(ActionbarSection::priority))
.map(section -> {
TimedAlert alert = slotAlerts.get(section.id());
if (alert != null) {
if (!alert.isExpired()) {
return alert.message();
}
slotAlerts.remove(section.id());
}
return section.textSupplier().apply(player);
})
.filter(Objects::nonNull)
.filter(sectionText -> !sectionText.isEmpty())
.collect(Collectors.joining(separator));
.toList();

List<String> evaluatedTexts = new ArrayList<>(normalSections.size());
for (ActionbarSection section : normalSections) {
String text = evaluateSection(section, player);
if (text != null && !text.isEmpty()) {
evaluatedTexts.add(text);
}
}

return String.join(separator, evaluatedTexts);
}

private String evaluateSection(ActionbarSection section, Player player) {
TimedAlert alert = slotAlerts.get(section.id());
if (alert != null) {
if (!alert.isExpired()) {
return alert.message();
}
slotAlerts.remove(section.id());
}
return section.textSupplier().apply(player);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -10,17 +10,31 @@
/**
* Represents a modular content section rendered within a player's action bar.
*
* <p>For normal (non-exclusive) sections, priority determines left-to-right rendering position
* (lower values render further to the left). For exclusive sections, priority defines override precedence
* (higher values take precedence over lower priority exclusive sections when multiple exclusive sections produce non-empty text).</p>
*
* @param id unique identifier for this section
* @param priority rendering priority; lower values render further to the left
* @param priority rendering priority for normal sections or precedence for exclusive sections
* @param textSupplier function producing the display text for a player, or {@code null} if empty
* @param visibility predicate determining whether this section should render for a player
* @param exclusive whether this section overrides non-exclusive sections when visible with non-empty text
*/
public record ActionbarSection(
@NonNull String id,
int priority,
@NonNull Function<Player, @Nullable String> textSupplier,
@NonNull Predicate<Player> visibility
@NonNull Predicate<Player> visibility,
boolean exclusive
Comment thread
Trixmas marked this conversation as resolved.
) {
public ActionbarSection(
@NonNull String id,
int priority,
@NonNull Function<Player, @Nullable String> textSupplier,
@NonNull Predicate<Player> visibility
) {
this(id, priority, textSupplier, visibility, false);
}
/**
* Creates an action bar section that is always visible for all players.
*
Expand All @@ -30,7 +44,7 @@ public record ActionbarSection(
* @return created action bar section
*/
public static ActionbarSection of(String id, int priority, Function<Player, @Nullable String> textSupplier) {
return new ActionbarSection(id, priority, textSupplier, player -> true);
return new ActionbarSection(id, priority, textSupplier, player -> true, false);
}

/**
Expand All @@ -43,6 +57,19 @@ public static ActionbarSection of(String id, int priority, Function<Player, @Nul
* @return created action bar section
*/
public static ActionbarSection of(String id, int priority, Function<Player, @Nullable String> textSupplier, Predicate<Player> visibility) {
return new ActionbarSection(id, priority, textSupplier, visibility);
return new ActionbarSection(id, priority, textSupplier, visibility, false);
}

/**
* Creates an exclusive action bar section that overrides non-exclusive sections when visible.
*
* @param id unique section identifier
* @param priority rendering priority
* @param textSupplier text supplier function
* @param visibility predicate controlling section display
* @return created exclusive action bar section
*/
public static ActionbarSection exclusiveOf(String id, int priority, Function<Player, @Nullable String> textSupplier, Predicate<Player> visibility) {
return new ActionbarSection(id, priority, textSupplier, visibility, true);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@
*/
public final class ActionbarService implements Listener {

// ponytail: kept — shared instance across plugins avoids actionbar packet collisions
@Getter
@Setter
private static ActionbarService instance;
Expand Down Expand Up @@ -52,10 +53,14 @@ public ActionbarService(@NonNull Plugin plugin) {
* @param periodTicks interval between renders in server ticks
*/
public void start(long periodTicks) {
if (running) return;
if (running && tickerTask != null && !tickerTask.isCancelled()) return;
running = true;

this.tickerTask = Bukkit.getScheduler().runTaskTimer(plugin, this::tickAll, periodTicks, periodTicks);
if (tickerTask != null) {
tickerTask.cancel();
}

this.tickerTask = Bukkit.getScheduler().runTaskTimer(plugin, this::tickAll, 0L, periodTicks);
}

/**
Expand Down Expand Up @@ -178,7 +183,7 @@ private void tickAll() {
try {
renderPlayer(player);
} catch (Exception exception) {
Log.warn("ActionbarService", "Failed to render for " + player.getName() + ": " + exception.getMessage());
Log.warn("ActionbarService", "Failed to render actionbar for " + player.getName() + ": " + exception.getMessage());
}
}
}
Expand All @@ -192,6 +197,7 @@ private ActionbarComposer getOrCreateComposer(UUID uuid) {
}

private void sendRawPacket(@NonNull Player player, @NonNull String text) {
if (text == null || text.isEmpty()) return;
player.spigot().sendMessage(ChatMessageType.ACTION_BAR, new TextComponent(text));
}
}
Loading
Loading