diff --git a/eternalcore-core/src/main/java/com/eternalcode/core/feature/afk/AfkPlaceholderSetup.java b/eternalcore-core/src/main/java/com/eternalcode/core/feature/afk/placeholder/AfkPlaceholderSetup.java similarity index 50% rename from eternalcore-core/src/main/java/com/eternalcode/core/feature/afk/AfkPlaceholderSetup.java rename to eternalcore-core/src/main/java/com/eternalcode/core/feature/afk/placeholder/AfkPlaceholderSetup.java index d27c63541..719ea7612 100644 --- a/eternalcore-core/src/main/java/com/eternalcode/core/feature/afk/AfkPlaceholderSetup.java +++ b/eternalcore-core/src/main/java/com/eternalcode/core/feature/afk/placeholder/AfkPlaceholderSetup.java @@ -1,5 +1,8 @@ -package com.eternalcode.core.feature.afk; +package com.eternalcode.core.feature.afk.placeholder; +import com.eternalcode.annotations.scan.placeholder.PlaceholderDocs; +import com.eternalcode.core.feature.afk.Afk; +import com.eternalcode.core.feature.afk.AfkService; import com.eternalcode.core.injector.annotations.Inject; import com.eternalcode.core.injector.annotations.component.Controller; import com.eternalcode.core.placeholder.PlaceholderRegistry; @@ -28,18 +31,56 @@ class AfkPlaceholderSetup { @Subscribe(EternalInitializeEvent.class) void setUpPlaceholders(PlaceholderRegistry placeholderRegistry, AfkService afkService) { - placeholderRegistry.registerPlaceholder(PlaceholderReplacer.of( + placeholderRegistry.registerPlaceholder(this.createAfkPlaceholder(afkService)); + placeholderRegistry.registerPlaceholder(this.createAfkFormattedPlaceholder(afkService)); + placeholderRegistry.registerPlaceholder(this.createAfkTimePlaceholder(afkService)); + placeholderRegistry.registerPlaceholder(this.createAfkPlayerCountPlaceholder(afkService)); + } + + @PlaceholderDocs( + name = "afk", + description = "Returns true if the player is AFK, false otherwise", + example = "true", + returnType = "boolean", + category = "AFK", + requiresPlayer = true + ) + private PlaceholderReplacer createAfkPlaceholder(AfkService afkService) { + return PlaceholderReplacer.of( "afk", - player -> String.valueOf(afkService.isAfk(player.getUniqueId())))); - placeholderRegistry.registerPlaceholder(PlaceholderReplacer.of( + player -> String.valueOf(afkService.isAfk(player.getUniqueId())) + ); + } + + @PlaceholderDocs( + name = "afk_formatted", + description = "Returns a formatted AFK status message based on player's language settings", + example = "[AFK]", + returnType = "String", + category = "AFK", + requiresPlayer = true + ) + private PlaceholderReplacer createAfkFormattedPlaceholder(AfkService afkService) { + return PlaceholderReplacer.of( "afk_formatted", player -> { Translation messages = this.translationManager.getMessages(player.getUniqueId()); return afkService.isAfk(player.getUniqueId()) ? messages.afk().afkEnabledPlaceholder() : messages.afk().afkDisabledPlaceholder(); - })); + } + ); + } - placeholderRegistry.registerPlaceholder(PlaceholderReplacer.of( + @PlaceholderDocs( + name = "afk_time", + description = "Returns the duration the player has been AFK in a formatted string", + example = "5m 30s", + returnType = "String", + category = "AFK", + requiresPlayer = true + ) + private PlaceholderReplacer createAfkTimePlaceholder(AfkService afkService) { + return PlaceholderReplacer.of( "afk_time", player -> { Optional afkOptional = afkService.getAfk(player.getUniqueId()); @@ -52,16 +93,28 @@ void setUpPlaceholders(PlaceholderRegistry placeholderRegistry, AfkService afkSe Instant now = Instant.now(); Duration afkDuration = Duration.between(start, now); return DurationUtil.format(afkDuration, true); - })); + } + ); + } - placeholderRegistry.registerPlaceholder(PlaceholderReplacer.of( + @PlaceholderDocs( + name = "afk_playercount", + description = "Returns the total number of AFK players on the server", + example = "3", + returnType = "int", + category = "AFK", + requiresPlayer = false + ) + private PlaceholderReplacer createAfkPlayerCountPlaceholder(AfkService afkService) { + return PlaceholderReplacer.of( "afk_playercount", - player -> { + ignoredPlayer -> { long afkPlayerCount = this.server.getOnlinePlayers() .stream() .filter(onlinePlayer -> afkService.isAfk(onlinePlayer.getUniqueId())) .count(); return String.valueOf(afkPlayerCount); - })); + } + ); } } diff --git a/eternalcore-core/src/main/java/com/eternalcode/core/feature/home/HomePlaceholderSetup.java b/eternalcore-core/src/main/java/com/eternalcode/core/feature/home/HomePlaceholderSetup.java index f4d695763..1a40d4182 100644 --- a/eternalcore-core/src/main/java/com/eternalcode/core/feature/home/HomePlaceholderSetup.java +++ b/eternalcore-core/src/main/java/com/eternalcode/core/feature/home/HomePlaceholderSetup.java @@ -1,5 +1,6 @@ package com.eternalcode.core.feature.home; +import com.eternalcode.annotations.scan.placeholder.PlaceholderDocs; import com.eternalcode.core.injector.annotations.Inject; import com.eternalcode.core.injector.annotations.component.Controller; import com.eternalcode.core.placeholder.PlaceholderRegistry; @@ -28,46 +29,92 @@ class HomePlaceholderSetup { @Subscribe(EternalInitializeEvent.class) void setUp(PlaceholderRegistry placeholderRegistry) { Stream.of( - PlaceholderReplacer.of("homes_owned", (text, targetPlayer) -> this.ownedHomes(targetPlayer)), - PlaceholderReplacer.of("homes_count", (text, targetPlayer) -> this.homesCount(targetPlayer)), - PlaceholderReplacer.of("homes_limit", (text, targetPlayer) -> this.homesLimit(targetPlayer)), - PlaceholderReplacer.of("homes_left", (text, targetPlayer) -> this.homesLeft(targetPlayer)) - ).forEach(placeholder -> placeholderRegistry.registerPlaceholder(placeholder)); + this.createHomesOwnedPlaceholder(), + this.createHomesCountPlaceholder(), + this.createHomesLimitPlaceholder(), + this.createHomesLeftPlaceholder() + ).forEach(placeholderRegistry::registerPlaceholder); } - private String homesLeft(Player targetPlayer) { - int homesLimit = this.homeService.getHomeLimit(targetPlayer); - int amountOfHomes = this.homeService.getAmountOfHomes(targetPlayer.getUniqueId()); + @PlaceholderDocs( + name = "homes_owned", + description = "Returns a comma-separated list of all homes owned by the player. If the player has no homes, returns a localized message.", + example = "home1, home2, domek", + returnType = "String", + category = "Home", + requiresPlayer = true + ) + private PlaceholderReplacer createHomesOwnedPlaceholder() { + return PlaceholderReplacer.of( + "homes_owned", + (text, targetPlayer) -> { + Collection homes = this.homeService.getHomes(targetPlayer.getUniqueId()); + Translation translation = this.translationManager.getMessages(targetPlayer.getUniqueId()); - return homesLeft(homesLimit, amountOfHomes); - } - - static String homesLeft(int homesLimit, int amountOfHomes) { - if (homesLimit < -1 || amountOfHomes > homesLimit) { - return "0"; - } + if (homes.isEmpty()) { + return translation.home().noHomesOwnedPlaceholder(); + } - int result = homesLimit - amountOfHomes; - - return String.valueOf(result); + return homes.stream() + .map(Home::getName) + .collect(Collectors.joining(", ")); + } + ); } - private String ownedHomes(Player targetPlayer) { - Collection homes = this.homeService.getHomes(targetPlayer.getUniqueId()); - Translation translation = this.translationManager.getMessages(targetPlayer.getUniqueId()); - - if (homes.isEmpty()) { - return translation.home().noHomesOwnedPlaceholder(); - } - - return homes.stream().map(Home::getName).collect(Collectors.joining(", ")); + @PlaceholderDocs( + name = "homes_count", + description = "Returns the number of homes the player currently owns.", + example = "3", + returnType = "int", + category = "Home", + requiresPlayer = true + ) + private PlaceholderReplacer createHomesCountPlaceholder() { + return PlaceholderReplacer.of( + "homes_count", + (text, targetPlayer) -> + String.valueOf(this.homeService.getAmountOfHomes(targetPlayer.getUniqueId())) + ); } - private String homesCount(Player targetPlayer) { - return String.valueOf(this.homeService.getAmountOfHomes(targetPlayer.getUniqueId())); + @PlaceholderDocs( + name = "homes_limit", + description = "Returns the maximum number of homes the player can have.", + example = "5", + returnType = "int", + category = "Home", + requiresPlayer = true + ) + private PlaceholderReplacer createHomesLimitPlaceholder() { + return PlaceholderReplacer.of( + "homes_limit", + (text, targetPlayer) -> + String.valueOf(this.homeService.getHomeLimit(targetPlayer)) + ); } - private String homesLimit(Player targetPlayer) { - return String.valueOf(this.homeService.getHomeLimit(targetPlayer)); + @PlaceholderDocs( + name = "homes_left", + description = "Returns how many more homes the player can create before reaching the limit.", + example = "2", + returnType = "int", + category = "Home", + requiresPlayer = true + ) + private PlaceholderReplacer createHomesLeftPlaceholder() { + return PlaceholderReplacer.of( + "homes_left", + (text, targetPlayer) -> { + int homesLimit = this.homeService.getHomeLimit(targetPlayer); + int amountOfHomes = this.homeService.getAmountOfHomes(targetPlayer.getUniqueId()); + + if (homesLimit < -1 || amountOfHomes > homesLimit) { + return "0"; + } + + return String.valueOf(homesLimit - amountOfHomes); + } + ); } } diff --git a/eternalcore-docs-api/src/main/java/com/eternalcode/annotations/scan/EternalScanner.java b/eternalcore-docs-api/src/main/java/com/eternalcode/annotations/scan/EternalScanner.java index ed0458d36..43be81e01 100644 --- a/eternalcore-docs-api/src/main/java/com/eternalcode/annotations/scan/EternalScanner.java +++ b/eternalcore-docs-api/src/main/java/com/eternalcode/annotations/scan/EternalScanner.java @@ -4,6 +4,7 @@ import com.eternalcode.annotations.scan.reflect.PackageUtil; import java.util.ArrayList; +import java.util.Comparator; import java.util.List; public class EternalScanner { @@ -16,10 +17,13 @@ public EternalScanner(ClassLoader classLoader, Package packageToScan) { this.packageToScan = packageToScan; } - public > List scan(RESOLVER resolver) { + public > List scan(RESOLVER resolver, Comparator sort) { PackageStack packageStack = PackageUtil.createPackageStack(this.packageToScan, this.classLoader); - return this.scan(packageStack, resolver); + return this.scan(packageStack, resolver).stream() + .sorted(sort) + .distinct() + .toList(); } private > List scan(PackageStack packageStack, RESOLVER resolver) { diff --git a/eternalcore-docs-api/src/main/java/com/eternalcode/annotations/scan/placeholder/PlaceholderDocs.java b/eternalcore-docs-api/src/main/java/com/eternalcode/annotations/scan/placeholder/PlaceholderDocs.java new file mode 100644 index 000000000..fad6c73b7 --- /dev/null +++ b/eternalcore-docs-api/src/main/java/com/eternalcode/annotations/scan/placeholder/PlaceholderDocs.java @@ -0,0 +1,42 @@ +package com.eternalcode.annotations.scan.placeholder; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +@Retention(RetentionPolicy.RUNTIME) +@Target({ ElementType.METHOD, ElementType.FIELD }) +public @interface PlaceholderDocs { + + /** + * The name of the placeholder (without % symbols) + * Example: "afk" for %afk% + */ + String name(); + + /** + * Description of what the placeholder does + */ + String description(); + + /** + * Example output of the placeholder + */ + String example(); + + /** + * Return type of the placeholder + */ + String returnType(); + + /** + * Category for grouping placeholders + */ + String category() default "General"; + + /** + * Whether the placeholder requires a player context + */ + boolean requiresPlayer(); +} diff --git a/eternalcore-docs-api/src/main/java/com/eternalcode/annotations/scan/placeholder/PlaceholderResult.java b/eternalcore-docs-api/src/main/java/com/eternalcode/annotations/scan/placeholder/PlaceholderResult.java new file mode 100644 index 000000000..d8ca68807 --- /dev/null +++ b/eternalcore-docs-api/src/main/java/com/eternalcode/annotations/scan/placeholder/PlaceholderResult.java @@ -0,0 +1,11 @@ +package com.eternalcode.annotations.scan.placeholder; + +public record PlaceholderResult( + String name, + String description, + String example, + String returnType, + String category, + boolean requiresPlayer +) { +} diff --git a/eternalcore-docs-api/src/main/java/com/eternalcode/annotations/scan/placeholder/PlaceholderScanResolver.java b/eternalcore-docs-api/src/main/java/com/eternalcode/annotations/scan/placeholder/PlaceholderScanResolver.java new file mode 100644 index 000000000..c5b441aeb --- /dev/null +++ b/eternalcore-docs-api/src/main/java/com/eternalcode/annotations/scan/placeholder/PlaceholderScanResolver.java @@ -0,0 +1,24 @@ +package com.eternalcode.annotations.scan.placeholder; + +import com.eternalcode.annotations.scan.EternalScanRecord; +import com.eternalcode.annotations.scan.SingleAnnotationScanResolver; + +public class PlaceholderScanResolver extends SingleAnnotationScanResolver { + + public PlaceholderScanResolver() { + super(PlaceholderDocs.class); + } + + @Override + public PlaceholderResult resolve(EternalScanRecord record, PlaceholderDocs annotation) { + String prefixedName = "%eternalcore_" + annotation.name() + "%"; + return new PlaceholderResult( + prefixedName, + annotation.description(), + annotation.example(), + annotation.returnType(), + annotation.category(), + annotation.requiresPlayer() + ); + } +} diff --git a/eternalcore-docs-generate/build.gradle.kts b/eternalcore-docs-generate/build.gradle.kts index 12ae56845..b70478a09 100644 --- a/eternalcore-docs-generate/build.gradle.kts +++ b/eternalcore-docs-generate/build.gradle.kts @@ -24,9 +24,17 @@ dependencies { runtimeOnly("org.panda-lang:panda-utilities:${Versions.PANDA_UTILITIES}") runtimeOnly("commons-io:commons-io:${Versions.APACHE_COMMONS}") runtimeOnly("dev.triumphteam:triumph-gui:${Versions.TRIUMPH_GUI}") + runtimeOnly("eu.okaeri:okaeri-configs-yaml-snakeyaml:${Versions.OKAERI_CONFIGS}") + runtimeOnly("eu.okaeri:okaeri-configs-serdes-commons:${Versions.OKAERI_CONFIGS}") runtimeOnly("org.bstats:bstats-bukkit:${Versions.BSTATS}") runtimeOnly("com.github.ben-manes.caffeine:caffeine:${Versions.CAFFEINE}") runtimeOnly("com.eternalcode:multification-core:${Versions.MULTIFICATION}") runtimeOnly("com.eternalcode:eternalcode-commons-bukkit:${Versions.ETERNALCODE_COMMONS}") runtimeOnly("com.eternalcode:eternalcode-commons-adventure:${Versions.ETERNALCODE_COMMONS}") + runtimeOnly("com.eternalcode:eternalcode-commons-folia:${Versions.ETERNALCODE_COMMONS}") + runtimeOnly("com.eternalcode:eternalcode-commons-updater:${Versions.ETERNALCODE_COMMONS}") + runtimeOnly("com.github.cryptomorin:XSeries:${Versions.XSERIES}") + runtimeOnly("us.dynmap:dynmap-api:${Versions.DYNMAP_API}") + runtimeOnly("us.dynmap:DynmapCoreAPI:${Versions.DYNMAP_API}") + runtimeOnly("fr.skytasul:glowingentities:${Versions.GLOWING_ENTITIES}") } diff --git a/eternalcore-docs-generate/src/main/java/com/eternalcode/annotations/scan/GenerateDocs.java b/eternalcore-docs-generate/src/main/java/com/eternalcode/annotations/scan/GenerateDocs.java index e6ca1ea71..5b07bc8a1 100644 --- a/eternalcore-docs-generate/src/main/java/com/eternalcode/annotations/scan/GenerateDocs.java +++ b/eternalcore-docs-generate/src/main/java/com/eternalcode/annotations/scan/GenerateDocs.java @@ -4,46 +4,50 @@ import com.eternalcode.annotations.scan.command.CommandScanResolver; import com.eternalcode.annotations.scan.permission.PermissionResult; import com.eternalcode.annotations.scan.permission.PermissionScanResolver; +import com.eternalcode.annotations.scan.placeholder.PlaceholderResult; +import com.eternalcode.annotations.scan.placeholder.PlaceholderScanResolver; import com.google.gson.Gson; import com.google.gson.GsonBuilder; -import java.io.FileWriter; + import java.io.IOException; -import java.util.Comparator; +import java.nio.file.Files; +import java.nio.file.Path; import java.util.List; -public class GenerateDocs { +import static java.util.Comparator.comparing; - public static void main(String[] args) throws ClassNotFoundException { - Class aClass = Class.forName("com.eternalcode.core.EternalCore"); - EternalScanner scanner = new EternalScanner(aClass.getClassLoader(), aClass.getPackage()); +public class GenerateDocs { - List commandResults = scanner.scan(new CommandScanResolver()) - .stream() - .sorted(Comparator.comparing(CommandResult::name)) - .distinct() - .toList(); + private static final String TARGET_CLASS = "com.eternalcode.core.EternalCore"; + private static final String DOCS_FILE = "raw_eternalcore_documentation.json"; + private static final String PLACEHOLDERS_FILE = "raw_eternalcore_placeholders.json"; - List permissionResults = scanner.scan(new PermissionScanResolver()) - .stream() - .sorted(Comparator.comparing(PermissionResult::name)) - .distinct() - .toList(); + private static final Gson GSON = new GsonBuilder() + .setPrettyPrinting() + .disableHtmlEscaping() + .create(); - Gson gson = new GsonBuilder() - .setPrettyPrinting() - .disableHtmlEscaping() - .create(); + public static void main(String[] args) { + try { + Class targetClass = Class.forName(TARGET_CLASS); + EternalScanner scanner = new EternalScanner(targetClass.getClassLoader(), targetClass.getPackage()); - DocumentationResult combinedDocs = new DocumentationResult(commandResults, permissionResults); + List commands = scanner.scan(new CommandScanResolver(), comparing(CommandResult::name)); + List permissions = scanner.scan(new PermissionScanResolver(), comparing(PermissionResult::name)); + List placeholders = scanner.scan(new PlaceholderScanResolver(), comparing(PlaceholderResult::name)); - try (FileWriter fileWriter = new FileWriter("raw_eternalcore_documentation.json")) { - gson.toJson(combinedDocs, fileWriter); + writeJson(DOCS_FILE, new DocumentationResult(commands, permissions)); + writeJson(PLACEHOLDERS_FILE, placeholders); } - catch (IOException exception) { - exception.printStackTrace(); + catch (Exception exception) { + throw new RuntimeException("Failed to generate documentation", exception); } } + private static void writeJson(String fileName, Object data) throws IOException { + Files.writeString(Path.of(fileName), GSON.toJson(data)); + } + public record DocumentationResult( List commands, List permissions diff --git a/raw_eternalcore_placeholders.json b/raw_eternalcore_placeholders.json new file mode 100644 index 000000000..0fe107af9 --- /dev/null +++ b/raw_eternalcore_placeholders.json @@ -0,0 +1,66 @@ +[ + { + "name": "%eternalcore_afk%", + "description": "Returns true if the player is AFK, false otherwise", + "example": "true", + "returnType": "boolean", + "category": "AFK", + "requiresPlayer": true + }, + { + "name": "%eternalcore_afk_formatted%", + "description": "Returns a formatted AFK status message based on player's language settings", + "example": "[AFK]", + "returnType": "String", + "category": "AFK", + "requiresPlayer": true + }, + { + "name": "%eternalcore_afk_playercount%", + "description": "Returns the total number of AFK players on the server", + "example": "3", + "returnType": "int", + "category": "AFK", + "requiresPlayer": false + }, + { + "name": "%eternalcore_afk_time%", + "description": "Returns the duration the player has been AFK in a formatted string", + "example": "5m 30s", + "returnType": "String", + "category": "AFK", + "requiresPlayer": true + }, + { + "name": "%eternalcore_homes_count%", + "description": "Returns the number of homes the player currently owns.", + "example": "3", + "returnType": "int", + "category": "Home", + "requiresPlayer": true + }, + { + "name": "%eternalcore_homes_left%", + "description": "Returns how many more homes the player can create before reaching the limit.", + "example": "2", + "returnType": "int", + "category": "Home", + "requiresPlayer": true + }, + { + "name": "%eternalcore_homes_limit%", + "description": "Returns the maximum number of homes the player can have.", + "example": "5", + "returnType": "int", + "category": "Home", + "requiresPlayer": true + }, + { + "name": "%eternalcore_homes_owned%", + "description": "Returns a comma-separated list of all homes owned by the player. If the player has no homes, returns a localized message.", + "example": "home1, home2, domek", + "returnType": "String", + "category": "Home", + "requiresPlayer": true + } +] \ No newline at end of file