Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
4 changes: 2 additions & 2 deletions .github/ISSUE_TEMPLATE/bug-report.yml
Original file line number Diff line number Diff line change
Expand Up @@ -111,7 +111,7 @@ body:
id: server-version
attributes:
label: Server version
placeholder: Paper 1.21.11 build ...
placeholder: Paper 26.1.2 build ...
validations:
required: true

Expand All @@ -128,7 +128,7 @@ body:
id: client-version
attributes:
label: Minecraft client version
placeholder: 1.21.11
placeholder: 26.1.2
validations:
required: false

Expand Down
3 changes: 1 addition & 2 deletions .github/workflows/promote-release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -319,7 +319,6 @@ jobs:
paper
folia
game-versions: |-
1.21.x
26.1.x
26.2.x
files: ${{ steps.asset.outputs.jar }}
Expand All @@ -342,5 +341,5 @@ jobs:
]
platform_dependencies: |-
{
"PAPER": ["1.21.x", "26.1.x", "26.2.x"]
"PAPER": ["26.1.x", "26.2.x"]
}
4 changes: 2 additions & 2 deletions .github/workflows/publish-marketplaces.yml
Original file line number Diff line number Diff line change
Expand Up @@ -100,8 +100,8 @@ jobs:
paper
folia
game-versions: |-
1.21.x
26.1.x
26.2.x
files: ${{ steps.asset.outputs.jar }}

- name: Publish to Hangar
Expand All @@ -121,5 +121,5 @@ jobs:
]
platform_dependencies: |-
{
"PAPER": ["1.21.x", "26.1.x"]
"PAPER": ["26.1.x", "26.2.x"]
}
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@
import io.github.silentdevelopment.headdb.paper.message.Messages;
import io.github.silentdevelopment.headdb.paper.metrics.HeadDBMetrics;
import io.github.silentdevelopment.headdb.paper.prompt.PromptInputService;
import io.github.silentdevelopment.headdb.paper.runtime.PlatformRequirements;
import io.github.silentdevelopment.headdb.paper.runtime.PluginRuntime;
import io.github.silentdevelopment.headdb.paper.runtime.RuntimeDiagnostics;
import io.github.silentdevelopment.headdb.paper.runtime.StartupChecks;
Expand Down Expand Up @@ -69,6 +70,11 @@ public void onEnable() {
return;
}

if (!PlatformRequirements.supported(this)) {
getServer().getPluginManager().disablePlugin(this);
return;
}

try {
reload();

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@
import io.github.silentdevelopment.headdb.paper.command.subcommand.RandomCommand;
import io.github.silentdevelopment.headdb.paper.command.subcommand.RefreshCommand;
import io.github.silentdevelopment.headdb.paper.command.subcommand.ReloadCommand;
import io.github.silentdevelopment.headdb.paper.command.subcommand.ReportCommand;
import io.github.silentdevelopment.headdb.paper.command.subcommand.StatusCommand;
import io.github.silentdevelopment.headdb.paper.command.subcommand.TagsCommand;
import io.github.silentdevelopment.headdb.paper.command.subcommand.UpdateCommand;
Expand Down Expand Up @@ -47,6 +48,7 @@ public RootCommand(@NotNull HeadDBPlugin plugin) {
new VersionCommand(plugin),
new StatusCommand(plugin),
new DebugCommand(plugin),
new ReportCommand(plugin),
new VerifyCommand(plugin),
new RefreshCommand(plugin),
new ReloadCommand(plugin),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -36,14 +36,14 @@ private HelpFormatter() {
addSection(lines, sender, new HelpSection("General", List.of(
new HelpEntry(List.of("help"), "h", List.of(), "/hdb help", "Show this command reference.", Permissions.HELP),
new HelpEntry(List.of("version"), null, List.of(), "/hdb version", "Show version and build information.", Permissions.VERSION),
new HelpEntry(List.of("open"), "o", List.of(), "/hdb open", "Open the main HeadDB GUI.", Permissions.OPEN)
new HelpEntry(List.of("open"), "o", List.of(), "/hdb open", "Open the main GUI.", Permissions.OPEN)
)));

addSection(lines, sender, new HelpSection("Heads", List.of(
new HelpEntry(List.of("info"), "i", args(optional("id")), "/hdb info ", "Inspect a head by ID or held item.", Permissions.INFO),
new HelpEntry(List.of("give"), "g", args(required("id"), optional("player"), optional("amount")), "/hdb give ", "Give a HeadDB item.", Permissions.GIVE),
new HelpEntry(List.of("give"), "g", args(required("id"), optional("player"), optional("amount")), "/hdb give ", "Give a head.", Permissions.GIVE),
new HelpEntry(List.of("player"), "p", args(required("name|uuid"), optional("player"), optional("amount")), "/hdb player ", "Give a player head.", Permissions.PLAYER),
new HelpEntry(List.of("random"), "rnd", args(optional("amount"), optional("category"), optional("player")), "/hdb random ", "Give a random HeadDB item.", Permissions.GIVE)
new HelpEntry(List.of("random"), "rnd", args(optional("amount"), optional("category"), optional("player")), "/hdb random ", "Give a random head.", Permissions.GIVE)
)));

addSection(lines, sender, new HelpSection("Browse", List.of(
Expand Down Expand Up @@ -73,7 +73,8 @@ private HelpFormatter() {

addSection(lines, sender, new HelpSection("Database", List.of(
new HelpEntry(List.of("status"), "st", List.of(), "/hdb status", "Show database state and counts.", Permissions.STATUS),
new HelpEntry(List.of("debug"), "d", List.of(), "/hdb debug", "Show detailed runtime diagnostics.", Permissions.DEBUG),
new HelpEntry(List.of("debug"), "d", List.of(), "/hdb debug", "Show concise runtime diagnostics.", Permissions.DEBUG),
new HelpEntry(List.of("report"), "rpt", List.of(), "/hdb report", "Create a full support report.", Permissions.REPORT),
new HelpEntry(List.of("verify"), "v", List.of(), "/hdb verify", "Verify the public remote without replacing the active database.", Permissions.VERIFY),
new HelpEntry(List.of("refresh"), "ref", List.of(), "/hdb refresh", "Fetch the latest remote database.", Permissions.REFRESH),
new HelpEntry(List.of("reload"), "rl", List.of(), "/hdb reload", "Reload config, messages, and runtime.", Permissions.RELOAD)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,9 @@

import io.github.silentdevelopment.headdb.database.DatabaseStats;
import io.github.silentdevelopment.headdb.database.DatabaseStatus;
import io.github.silentdevelopment.headdb.paper.HeadDBPlugin;
import io.github.silentdevelopment.headdb.paper.permission.Permissions;
import io.github.silentdevelopment.headdb.paper.runtime.RefreshState;
import io.github.silentdevelopment.headdb.paper.runtime.PluginRuntime;
import net.kyori.adventure.text.Component;
import net.kyori.adventure.text.event.ClickEvent;
import net.kyori.adventure.text.event.HoverEvent;
Expand All @@ -16,6 +16,7 @@
import java.time.Instant;
import java.time.ZoneId;
import java.time.format.DateTimeFormatter;
import java.util.ArrayList;
import java.util.List;
import java.util.Objects;

Expand All @@ -26,77 +27,126 @@ public final class StatusFormatter {
private StatusFormatter() {
}

public static @NotNull List<Component> format(@NotNull PluginRuntime runtime, @NotNull CommandSender sender) {
Objects.requireNonNull(runtime, "runtime");
public static @NotNull List<Component> format(@NotNull HeadDBPlugin plugin, @NotNull CommandSender sender) {
Objects.requireNonNull(plugin, "plugin");
Objects.requireNonNull(sender, "sender");

DatabaseStatus status = runtime.database().status();
DatabaseStats stats = runtime.database().stats();
RefreshState refresh = runtime.refreshState();

return List.of(
Component.empty(),
Component.text("> ", NamedTextColor.GRAY).append(Component.text("HeadDB Status", NamedTextColor.RED)),
databaseStatusLine(status, sender),
line("Source", status.source()),
line("Manifest ID", value(status.manifestId())),
line("Catalog ID", value(status.artifactId())),
line("Loaded at", formatInstant(status.loadedAt())),
Component.empty(),
line("Heads", stats.heads()),
line("Categories", stats.categories()),
line("Tags", stats.tags()),
line("Collections", stats.collections()),
line("Revocations", stats.revocations()),
Component.empty(),
line("Refresh running", yesNo(refresh.running())),
line("Last failure", formatFailure(refresh.lastFailureMessage())),
Component.empty()
);
DatabaseStatus status = plugin.runtime().database().status();
DatabaseStats remoteStats = plugin.runtime().database().stats();
RefreshState refresh = plugin.runtime().refreshState();

int hiddenHeads = plugin.headRegistry().hiddenHeads().size();
int moreHeads = plugin.headRegistry().customHeads().list().size();
int overrides = plugin.headRegistry().overrides().list().size();
int playerHeads = plugin.headRegistry().playerHeads().knownPlayers().size();
int moreCategories = plugin.customCategories().list().size();

List<Component> lines = new ArrayList<>();
lines.add(Component.empty());
lines.add(Component.text("> ", NamedTextColor.DARK_GRAY).append(Component.text("Status", NamedTextColor.RED)));
lines.add(databaseLine(status));
lines.add(line("Heads", remoteStats.heads()));
lines.add(line("Hidden Heads", hiddenHeads));
lines.add(line("More Heads", moreHeads));
lines.add(line("Player Heads", playerHeads));
lines.add(line("Categories", remoteStats.categories()));
lines.add(line("More Categories", moreCategories));
lines.add(line("Tags", remoteStats.tags()));
lines.add(line("Collections", remoteStats.collections()));
lines.add(line("Revocations", remoteStats.revocations()));
lines.add(line("Overrides", overrides));
lines.add(refreshLine(refresh, sender));
lines.add(lastRefreshLine(refresh));

String failure = firstPresent(status.lastError(), refresh.lastFailureMessage());
if (failure != null) {
lines.add(line("Last error", failure));
}

addSupportLine(lines, sender);
lines.add(Component.empty());
return List.copyOf(lines);
}

private static @NotNull Component databaseStatusLine(@NotNull DatabaseStatus status, @NotNull CommandSender sender) {
Component line = Component.text("Status: ", NamedTextColor.GRAY).append(Component.text(String.valueOf(status.state()), statusColor(status)));
private static @NotNull Component databaseLine(@NotNull DatabaseStatus status) {
Component line = Component.text("Database: ", NamedTextColor.GRAY).append(Component.text(String.valueOf(status.state()), statusColor(status)));
String source = value(status.source());

if (isLoaded(status)) {
return line;
if (!source.equals("none")) {
line = line.append(Component.text(" from ", NamedTextColor.GRAY)).append(Component.text(source, NamedTextColor.GOLD));
}

if (!Permissions.has(sender, Permissions.REFRESH)) {
return line;
return line;
}

private static @NotNull Component refreshLine(@NotNull RefreshState refresh, @NotNull CommandSender sender) {
String text = refresh.running() ? "running " + refresh.currentOperation() : "idle";
Component line = line("Refresh", text);

if (!refresh.running() && Permissions.has(sender, Permissions.REFRESH)) {
line = line.append(Component.text(" ")).append(refreshButton());
}

return line.append(Component.text(" ")).append(refreshButton());
return line;
}

private static boolean isLoaded(@NotNull DatabaseStatus status) {
return "LOADED".equalsIgnoreCase(String.valueOf(status.state()));
}
private static @NotNull Component lastRefreshLine(@NotNull RefreshState refresh) {
if (refresh.lastOutcome() == RefreshState.RefreshOutcome.SUCCESS) {
return line("Last Refresh", refresh.lastOperation() + " completed at " + formatInstant(refresh.lastSuccessfulRefresh()));
}

private static @NotNull NamedTextColor statusColor(@NotNull DatabaseStatus status) {
if (isLoaded(status)) {
return NamedTextColor.GOLD;
if (refresh.lastOutcome() == RefreshState.RefreshOutcome.FAILURE) {
return line("Last Refresh", refresh.lastOperation() + " failed at " + formatInstant(refresh.lastFailedRefresh()));
}

return NamedTextColor.RED;
return line("Last Refresh", "never");
}

private static @NotNull Component refreshButton() {
return Component.text("[ ", NamedTextColor.DARK_GRAY)
.append(Component.text("REFRESH", NamedTextColor.GOLD).clickEvent(ClickEvent.runCommand("/hdb refresh")).hoverEvent(HoverEvent.showText(Component.text("Click to refresh the HeadDB database.", NamedTextColor.GRAY))))
.append(Component.text(" ]", NamedTextColor.DARK_GRAY));
return Component.text("[ ", NamedTextColor.DARK_GRAY).append(Component.text("REFRESH", NamedTextColor.GOLD).clickEvent(ClickEvent.runCommand("/hdb refresh")).hoverEvent(HoverEvent.showText(Component.text("Click to refresh the database.", NamedTextColor.GRAY)))).append(Component.text(" ]", NamedTextColor.DARK_GRAY));
}

private static @NotNull Component line(@NotNull String key, @Nullable Object value) {
return Component.text(key + ": ", NamedTextColor.GRAY).append(Component.text(String.valueOf(value), NamedTextColor.GOLD));
private static void addSupportLine(@NotNull List<Component> lines, @NotNull CommandSender sender) {
boolean canDebug = Permissions.has(sender, Permissions.DEBUG);
boolean canReport = Permissions.has(sender, Permissions.REPORT);

if (!canDebug && !canReport) {
return;
}

Component line = Component.text("Support: ", NamedTextColor.GRAY);

if (canDebug) {
line = line.append(Component.text("/hdb debug", NamedTextColor.GOLD));
}

if (canDebug && canReport) {
line = line.append(Component.text(" | ", NamedTextColor.DARK_GRAY));
}

if (canReport) {
line = line.append(Component.text("/hdb report", NamedTextColor.GOLD));
}

lines.add(line);
}

private static @NotNull String yesNo(boolean value) {
if (value) {
return "yes";
private static @NotNull NamedTextColor statusColor(@NotNull DatabaseStatus status) {
String state = String.valueOf(status.state());

if ("LOADED".equalsIgnoreCase(state)) {
return NamedTextColor.GOLD;
}

if ("LOADING".equalsIgnoreCase(state)) {
return NamedTextColor.YELLOW;
}

return "no";
return NamedTextColor.RED;
}

private static @NotNull Component line(@NotNull String key, @Nullable Object value) {
return Component.text(key + ": ", NamedTextColor.GRAY).append(Component.text(String.valueOf(value), NamedTextColor.GOLD));
}

private static @NotNull String formatInstant(@Nullable Instant instant) {
Expand All @@ -107,19 +157,35 @@ private static boolean isLoaded(@NotNull DatabaseStatus status) {
return TIME_FORMAT.format(instant);
}

private static @NotNull String formatFailure(@Nullable String message) {
if (message == null || message.isBlank()) {
private static @NotNull String value(@Nullable Object value) {
if (value == null) {
return "none";
}

return message;
String string = String.valueOf(value);
if (string.isBlank()) {
return "none";
}

return string;
}

private static @NotNull String value(@Nullable Object value) {
if (value == null) {
return "none";
private static @Nullable String firstPresent(@Nullable String first, @Nullable String second) {
String normalizedFirst = normalize(first);

if (normalizedFirst != null) {
return normalizedFirst;
}

return String.valueOf(value);
return normalize(second);
}
}

private static @Nullable String normalize(@Nullable String value) {
if (value == null || value.isBlank()) {
return null;
}

return value.trim();
}

}
Loading
Loading