diff --git a/Jenkinsfile b/Jenkinsfile index 25d8b26b..2610859e 100644 --- a/Jenkinsfile +++ b/Jenkinsfile @@ -15,7 +15,6 @@ pipeline { post { success { archiveArtifacts 'spigot/build/libs/CrossplatForms-Spigot.jar' - archiveArtifacts 'spigot-legacy/build/libs/CrossplatForms-SpigotLegacy.jar' archiveArtifacts 'bungeecord/build/libs/CrossplatForms-BungeeCord.jar' archiveArtifacts 'velocity/build/libs/CrossplatForms-Velocity.jar' } diff --git a/README.md b/README.md index 60df0302..ccffe5d5 100644 --- a/README.md +++ b/README.md @@ -1,7 +1,7 @@ CrossplatForms [![Build Status](https://ci.kejona.dev/job/CrossplatForms/job/main/badge/icon)](https://ci.kejona.dev/job/CrossplatForms/job/main/) -[![Version](https://img.shields.io/badge/version-1.4.0-blue)](https://github.com/kejonaMC/CrossplatForms/releases) +[![Version](https://img.shields.io/badge/version-1.5.0-blue)](https://github.com/kejonaMC/CrossplatForms/releases) [![License](https://img.shields.io/badge/License-GPL-orange)](https://github.com/kejonaMC/CrossplatForms/blob/master/LICENSE) [![Discord](https://img.shields.io/discord/853331530004299807?color=7289da&label=discord&logo=discord&logoColor=white)](https://discord.gg/M2SvqCu4e9) [![bStats](https://img.shields.io/badge/bStats-click%20me-yellow)](https://bstats.org/author/Konicai) @@ -38,8 +38,7 @@ BungeeCord and Velocity do not support Access Items. --- -* [`CrossplatForms-Spigot.jar`](https://ci.kejona.dev/job/CrossplatForms/job/main/) :        Spigot 1.14.4 - 1.19.1 -* [`CrossplatForms-SpigotLegacy.jar`](https://ci.kejona.dev/job/CrossplatForms/job/main/) :  Spigot 1.8.8 - 1.13.2 +* [`CrossplatForms-Spigot.jar`](https://ci.kejona.dev/job/CrossplatForms/job/main/) :        Spigot 1.8.8 - 1.19.4 * [`CrossplatForms-BungeeCord.jar`](https://ci.kejona.dev/job/CrossplatForms/job/main/) :    BungeeCord * [`CrossplatForms-Velocity.jar`](https://ci.kejona.dev/job/CrossplatForms/job/main/) :      Velocity 3.x diff --git a/access-item/src/main/java/dev/kejona/crossplatforms/accessitem/AccessItem.java b/access-item/src/main/java/dev/kejona/crossplatforms/accessitem/AccessItem.java index 1a4885eb..302bad99 100644 --- a/access-item/src/main/java/dev/kejona/crossplatforms/accessitem/AccessItem.java +++ b/access-item/src/main/java/dev/kejona/crossplatforms/accessitem/AccessItem.java @@ -8,6 +8,7 @@ import dev.kejona.crossplatforms.handler.BedrockHandler; import dev.kejona.crossplatforms.handler.FormPlayer; import dev.kejona.crossplatforms.handler.Placeholders; +import dev.kejona.crossplatforms.inventory.ConfiguredItem; import dev.kejona.crossplatforms.permission.Permission; import dev.kejona.crossplatforms.permission.PermissionDefault; import dev.kejona.crossplatforms.resolver.Resolver; @@ -27,7 +28,7 @@ @Getter @ConfigSerializable @SuppressWarnings("FieldMayBeFinal") -public class AccessItem { +public class AccessItem extends ConfiguredItem { public static final String STATIC_IDENTIFIER = "crossplatformsaccessitem"; // changing this will break existing setups private static final String PERMISSION_BASE = Constants.Id() + ".item"; @@ -49,20 +50,6 @@ public class AccessItem { private List> bedrockActions = Collections.emptyList(); private List> javaActions = Collections.emptyList(); - @Required - private String material = null; - - /** - * Display name of the itemstack - */ - @Required - private String displayName = null; - - /** - * Itemstack lore - */ - private List lore = Collections.emptyList(); - /** * The inventory slot to be placed in */ @@ -108,7 +95,7 @@ public void trigger(FormPlayer player) { public void generatePermissions(AccessItemRegistry registry) { if (permissions != null) { - Logger.get().severe("Permissions in Access Item '" + identifier + "' have already been generated!"); + Logger.get().warn("Permissions in Access Item '" + identifier + "' have already been generated!"); } String mainPermission = PERMISSION_BASE + "." + identifier; @@ -142,18 +129,6 @@ public enum Limit { PRESERVE(".preserve", "Stop the Access Item from being destroyed when it is dropped. This includes death, regardless of drop permission.", PermissionDefault.FALSE), MOVE(".move", "Ability to move the Access Item around and to inventories", PermissionDefault.FALSE); - /** - * Map of {@link PermissionDefault} to fallback to if the user does not define their own. - */ - public static final Map FALLBACK_DEFAULTS; - - static { - FALLBACK_DEFAULTS = new HashMap<>(); - for (Limit limit : Limit.values()) { - FALLBACK_DEFAULTS.put(limit, limit.fallbackDefault); - } - } - public final String permissionSuffix; public final String description; public final PermissionDefault fallbackDefault; diff --git a/access-item/src/main/java/dev/kejona/crossplatforms/accessitem/AccessItemRegistry.java b/access-item/src/main/java/dev/kejona/crossplatforms/accessitem/AccessItemRegistry.java index 404f7b22..5209fe87 100644 --- a/access-item/src/main/java/dev/kejona/crossplatforms/accessitem/AccessItemRegistry.java +++ b/access-item/src/main/java/dev/kejona/crossplatforms/accessitem/AccessItemRegistry.java @@ -3,9 +3,9 @@ import dev.kejona.crossplatforms.config.ConfigManager; import dev.kejona.crossplatforms.handler.FormPlayer; -import dev.kejona.crossplatforms.handler.ServerHandler; import dev.kejona.crossplatforms.permission.Permission; import dev.kejona.crossplatforms.permission.PermissionDefault; +import dev.kejona.crossplatforms.permission.Permissions; import dev.kejona.crossplatforms.reloadable.Reloadable; import dev.kejona.crossplatforms.reloadable.ReloadableRegistry; import lombok.Getter; @@ -15,12 +15,17 @@ import javax.annotation.Nullable; import java.util.Collections; import java.util.HashMap; +import java.util.HashSet; import java.util.Map; +import java.util.Set; public abstract class AccessItemRegistry implements Reloadable { private final ConfigManager configManager; - private final ServerHandler serverHandler; + private final Permissions permissions; + + @Getter + private final Map items = new HashMap<>(); @Getter private boolean enabled = false; @@ -35,12 +40,9 @@ public abstract class AccessItemRegistry implements Reloadable { @Getter private Map globalPermissionDefaults = Collections.emptyMap(); - @Getter - private final Map items = new HashMap<>(); - - public AccessItemRegistry(ConfigManager configManager, ServerHandler serverHandler) { + public AccessItemRegistry(ConfigManager configManager, Permissions permissions) { this.configManager = configManager; - this.serverHandler = serverHandler; + this.permissions = permissions; ReloadableRegistry.register(this); load(); } @@ -63,30 +65,23 @@ private void load() { setHeldSlot = config.isSetHeldSlot(); globalPermissionDefaults = config.getGlobalPermissionDefaults(); + Set permissions = new HashSet<>(); + for (String identifier : config.getItems().keySet()) { AccessItem item = config.getItems().get(identifier); items.put(identifier, item); // Register permissions with the server item.generatePermissions(this); - for (Permission entry : item.getPermissions().values()) { - serverHandler.registerPermission(entry); - } + permissions.addAll(item.getPermissions().values()); } + + this.permissions.registerPermissions(permissions); } } @Override public boolean reload() { - // Unregister permissions - if (enabled) { - for (AccessItem accessItem : items.values()) { - for (Permission permission : accessItem.getPermissions().values()) { - serverHandler.unregisterPermission(permission.key()); - } - } - } - load(); return true; } diff --git a/access-item/src/main/java/dev/kejona/crossplatforms/accessitem/GiveCommand.java b/access-item/src/main/java/dev/kejona/crossplatforms/accessitem/GiveCommand.java index f7c0d5c5..2aec6dcb 100644 --- a/access-item/src/main/java/dev/kejona/crossplatforms/accessitem/GiveCommand.java +++ b/access-item/src/main/java/dev/kejona/crossplatforms/accessitem/GiveCommand.java @@ -40,7 +40,7 @@ public void register(CommandManager manager, Command.BuildernewBuilder(ARGUMENT) + .argument(StringArgument.builder(ARGUMENT) .withSuggestionsProvider((context, s) -> itemSuggestions(context)) .build()) .permission(origin -> origin.hasPermission(PERMISSION) && origin.isPlayer()) @@ -70,10 +70,10 @@ public void register(CommandManager manager, Command.BuildernewBuilder(ARGUMENT) + .argument(StringArgument.builder(ARGUMENT) .withSuggestionsProvider((context, s) -> itemSuggestions(context)) .build()) - .argument(StringArgument.newBuilder("player") + .argument(StringArgument.builder("player") .withSuggestionsProvider(((context, s) -> playerSuggestions(context))) .build()) .permission(PERMISSION_OTHER) diff --git a/access-item/src/main/java/dev/kejona/crossplatforms/accessitem/InspectItemCommand.java b/access-item/src/main/java/dev/kejona/crossplatforms/accessitem/InspectItemCommand.java index f5ce45de..b83a27e4 100644 --- a/access-item/src/main/java/dev/kejona/crossplatforms/accessitem/InspectItemCommand.java +++ b/access-item/src/main/java/dev/kejona/crossplatforms/accessitem/InspectItemCommand.java @@ -26,7 +26,7 @@ public void register(CommandManager manager, Command.BuildernewBuilder("item") + .argument(StringArgument.builder("item") .withSuggestionsProvider(((context, s) -> itemRegistry.getItems().values() .stream() .map(AccessItem::getIdentifier) diff --git a/build.gradle.kts b/build.gradle.kts index 4385d351..48d875e1 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -13,7 +13,7 @@ allprojects{ apply(plugin = "net.kyori.indra.git") group = "dev.kejona" - version = "1.4.0" + version = "1.5.0" tasks.withType { options.encoding = "UTF-8" @@ -57,12 +57,12 @@ allprojects{ subprojects { dependencies { - testAnnotationProcessor("org.projectlombok:lombok:1.18.24") - testCompileOnly("org.projectlombok:lombok:1.18.22") - testImplementation("org.junit.jupiter:junit-jupiter:5.9.1") + testAnnotationProcessor("org.projectlombok:lombok:1.18.26") + testCompileOnly("org.projectlombok:lombok:1.18.26") + testImplementation("org.junit.jupiter:junit-jupiter:5.9.2") - annotationProcessor("org.projectlombok:lombok:1.18.22") - compileOnly("org.projectlombok:lombok:1.18.22") + annotationProcessor("org.projectlombok:lombok:1.18.26") + compileOnly("org.projectlombok:lombok:1.18.26") compileOnly("com.google.code.findbugs:jsr305:3.0.2") // nullability annotations } } diff --git a/bungeecord/build.gradle.kts b/bungeecord/build.gradle.kts index 50b73632..7274ca33 100644 --- a/bungeecord/build.gradle.kts +++ b/bungeecord/build.gradle.kts @@ -5,11 +5,11 @@ plugins { } dependencies { - compileOnly("net.md-5:bungeecord-api:1.18-R0.1-SNAPSHOT") - compileOnly("com.github.SpigotMC.BungeeCord:bungeecord-proxy:2d369e8") // through jitpack - api("cloud.commandframework:cloud-bungee:1.7.1") - api("net.kyori:adventure-platform-bungeecord:4.1.2") - implementation("org.bstats:bstats-bungeecord:3.0.0") + compileOnly("net.md-5:bungeecord-api:1.19-R0.1-SNAPSHOT") + compileOnly("com.github.SpigotMC.BungeeCord:bungeecord-proxy:9e5ed82") // For getting skins (dependency through jitpack) + api("cloud.commandframework:cloud-bungee:1.8.3") + api("net.kyori:adventure-platform-bungeecord:4.3.0") + implementation("org.bstats:bstats-bungeecord:3.0.2") api(projects.proxy) api(projects.core) } diff --git a/bungeecord/src/main/java/dev/kejona/crossplatforms/bungeecord/CrossplatFormsBungeeCord.java b/bungeecord/src/main/java/dev/kejona/crossplatforms/bungeecord/CrossplatFormsBungeeCord.java index b9c932b0..4a264677 100644 --- a/bungeecord/src/main/java/dev/kejona/crossplatforms/bungeecord/CrossplatFormsBungeeCord.java +++ b/bungeecord/src/main/java/dev/kejona/crossplatforms/bungeecord/CrossplatFormsBungeeCord.java @@ -2,6 +2,7 @@ import cloud.commandframework.bungee.BungeeCommandManager; import cloud.commandframework.execution.CommandExecutionCoordinator; +import com.google.inject.Module; import dev.kejona.crossplatforms.Constants; import dev.kejona.crossplatforms.CrossplatForms; import dev.kejona.crossplatforms.CrossplatFormsBootstrap; @@ -16,12 +17,10 @@ import dev.kejona.crossplatforms.config.ConfigManager; import dev.kejona.crossplatforms.handler.BasicPlaceholders; import dev.kejona.crossplatforms.handler.Placeholders; -import dev.kejona.crossplatforms.interfacing.Interfacer; -import dev.kejona.crossplatforms.interfacing.NoMenusInterfacer; +import dev.kejona.crossplatforms.permission.LuckPermsHook; +import dev.kejona.crossplatforms.permission.Permissions; import dev.kejona.crossplatforms.proxy.CloseMenuAction; -import dev.kejona.crossplatforms.proxy.LuckPermsHook; -import dev.kejona.crossplatforms.proxy.PermissionHook; -import dev.kejona.crossplatforms.proxy.ProtocolizeInterfacer; +import dev.kejona.crossplatforms.proxy.ProtocolizeModule; import net.kyori.adventure.platform.bungeecord.BungeeAudiences; import net.kyori.adventure.text.serializer.bungeecord.BungeeComponentSerializer; import net.md_5.bungee.api.CommandSender; @@ -29,6 +28,9 @@ import org.bstats.bungeecord.Metrics; import org.bstats.charts.CustomChart; +import java.util.ArrayList; +import java.util.List; + public class CrossplatFormsBungeeCord extends Plugin implements CrossplatFormsBootstrap { private static final int BSTATS_ID = 14706; @@ -54,11 +56,8 @@ public void onEnable() { metrics = new Metrics(this, BSTATS_ID); audiences = BungeeAudiences.create(this); - BungeeCordHandler serverHandler = new BungeeCordHandler( - this, - audiences, - pluginPresent("LuckPerms") ? new LuckPermsHook() : PermissionHook.empty() - ); + BungeeCordHandler serverHandler = new BungeeCordHandler(this, audiences); + Permissions permissions = pluginPresent("LuckPerms") ? new LuckPermsHook() : Permissions.empty(); BungeeCommandManager commandManager; try { @@ -83,17 +82,25 @@ public void onEnable() { logger, getDataFolder().toPath(), serverHandler, + permissions, "formsb", commandManager, placeholders, this ); - if (!crossplatForms.isSuccess()) { - return; + getProxy().getPluginManager().registerListener(this, serverHandler); // events for catching proxy commands + } + + @Override + public List configModules() { + List modules = new ArrayList<>(); + + if (protocolizePresent) { + modules.add(new ProtocolizeModule()); } - getProxy().getPluginManager().registerListener(this, serverHandler); // events for catching proxy commands + return modules; } @Override @@ -107,15 +114,6 @@ public void preConfigLoad(ConfigManager configManager) { } } - @Override - public Interfacer interfaceManager() { - if (protocolizePresent) { - return new ProtocolizeInterfacer(); - } else { - return new NoMenusInterfacer(); - } - } - @Override public void onDisable() { if (audiences != null) { diff --git a/bungeecord/src/main/java/dev/kejona/crossplatforms/bungeecord/handler/BungeeCordHandler.java b/bungeecord/src/main/java/dev/kejona/crossplatforms/bungeecord/handler/BungeeCordHandler.java index 5b8aa9ae..99a4640d 100644 --- a/bungeecord/src/main/java/dev/kejona/crossplatforms/bungeecord/handler/BungeeCordHandler.java +++ b/bungeecord/src/main/java/dev/kejona/crossplatforms/bungeecord/handler/BungeeCordHandler.java @@ -6,11 +6,10 @@ import dev.kejona.crossplatforms.command.CommandType; import dev.kejona.crossplatforms.command.DispatchableCommand; import dev.kejona.crossplatforms.command.custom.InterceptCommand; +import dev.kejona.crossplatforms.command.custom.InterceptCommandCache; import dev.kejona.crossplatforms.handler.BedrockHandler; import dev.kejona.crossplatforms.handler.FormPlayer; import dev.kejona.crossplatforms.handler.ServerHandler; -import dev.kejona.crossplatforms.proxy.PermissionHook; -import dev.kejona.crossplatforms.proxy.ProxyHandler; import net.kyori.adventure.audience.Audience; import net.kyori.adventure.platform.bungeecord.BungeeAudiences; import net.md_5.bungee.api.CommandSender; @@ -29,7 +28,7 @@ import java.util.UUID; import java.util.stream.Stream; -public class BungeeCordHandler extends ProxyHandler implements ServerHandler, Listener { +public class BungeeCordHandler extends InterceptCommandCache implements ServerHandler, Listener { private static final String OP_GROUP = "op"; @@ -38,8 +37,7 @@ public class BungeeCordHandler extends ProxyHandler implements ServerHandler, Li private final BungeeAudiences audiences; private final CommandSender console; - public BungeeCordHandler(Plugin plugin, BungeeAudiences audiences, PermissionHook permissionHook) { - super(permissionHook); + public BungeeCordHandler(Plugin plugin, BungeeAudiences audiences) { this.server = plugin.getProxy(); this.pluginManager = server.getPluginManager(); this.audiences = audiences; diff --git a/core/build.gradle.kts b/core/build.gradle.kts index d4c1e029..8ca2c2e0 100644 --- a/core/build.gradle.kts +++ b/core/build.gradle.kts @@ -1,7 +1,7 @@ plugins { id("net.kyori.indra") - id("java-test-fixtures") + id("java-test-fixtures") // used for the testFixtures source set, which is available for other test sources } sourceSets { @@ -23,32 +23,46 @@ dependencies { testImplementation("org.geysermc.cumulus:cumulus:1.1.1-SNAPSHOT") // needed for testing button components testImplementation("com.google.code.gson:gson:2.8.6") // needed for cumulus - compileOnly("org.geysermc.floodgate:api:2.2.0-SNAPSHOT") { + // Nullability annotations. todo: move to something else + testImplementation("com.google.code.findbugs:jsr305:3.0.2") + api("com.google.code.findbugs:jsr305:3.0.2") + + compileOnly("org.geysermc.floodgate:api:2.2.0-SNAPSHOT") { isTransitive = false } + compileOnly("net.luckperms:api:5.4") { isTransitive = false } + + api("org.bstats:bstats-base:3.0.2") + api("cloud.commandframework:cloud-core:1.8.3") + api("cloud.commandframework:cloud-minecraft-extras:1.8.3") + api("net.kyori:adventure-api:4.13.1") + api("net.kyori:adventure-text-serializer-legacy:4.13.1") + api("net.kyori:adventure-text-serializer-gson:4.13.1") { + // This is required or else it overrides the version we explicitly define below exclude(group = "com.google.code.gson", module = "gson") } - api("cloud.commandframework:cloud-core:1.7.1") - api("cloud.commandframework:cloud-minecraft-extras:1.7.1") - api("net.kyori:adventure-api:4.11.0") - api("net.kyori:adventure-text-serializer-legacy:4.11.0") - api("net.kyori:adventure-text-serializer-gson:4.11.0") api("org.spongepowered:configurate-yaml:4.2.0-SNAPSHOT") api("org.spongepowered:configurate-extra-guice:4.2.0-SNAPSHOT") - api("com.google.code.gson:gson:2.3.1") // version provided by spigot 1.8.8 - api("com.google.inject:guice:5.1.0") - api("org.bstats:bstats-base:3.0.0") - // Required because source and unshaded jars are all mixed up on the opencollab repo currently - val baseApi = "2.1.0-20221211.182143-60" - val geyserApi = "2.1.0-20221211.182145-60" - val geyserCore = "2.1.0-20221211.182157-60" + api("com.google.inject:guice:5.1.0") { + exclude(group = "com.google.guava", module = "guava") // Provides a newer version than provided by server platforms + } - // dependencies for java16 sources (optionally used at runtime) - java16Implementation("org.geysermc:api:$baseApi") - java16Implementation("org.geysermc.geyser:api:$geyserApi") - java16Implementation("org.geysermc.geyser:core:$geyserCore") { - isTransitive = false + // Provided by Velocity. A slightly higher version is provided by BungeeCord. + // Shaded on Spigot because on older Spigot versions, the Guava is too old and breaks Guice. + api("com.google.guava:guava") { + version { + prefer("30.1-jre") + } } + api("com.google.code.gson:gson") { + version { + prefer("2.3.1") // lowest version, provided by spigot 1.8.8 + } + } + + // dependencies for java16 sources (optionally used at runtime) + // if someone wants geyser to be directly used then they must be running java 16 or higher + java16Implementation("org.geysermc.api:geyser-api:1.0.1-SNAPSHOT") } description = "core" diff --git a/core/src/main/java/dev/kejona/crossplatforms/CrossplatForms.java b/core/src/main/java/dev/kejona/crossplatforms/CrossplatForms.java index 85faa9a1..bd453f24 100644 --- a/core/src/main/java/dev/kejona/crossplatforms/CrossplatForms.java +++ b/core/src/main/java/dev/kejona/crossplatforms/CrossplatForms.java @@ -6,6 +6,7 @@ import cloud.commandframework.minecraft.extras.MinecraftHelp; import com.google.inject.Guice; import com.google.inject.Injector; +import com.google.inject.Module; import dev.kejona.crossplatforms.action.BedrockTransferAction; import dev.kejona.crossplatforms.command.CommandOrigin; import dev.kejona.crossplatforms.command.FormsCommand; @@ -29,6 +30,7 @@ import dev.kejona.crossplatforms.interfacing.bedrock.custom.ComponentSerializer; import dev.kejona.crossplatforms.interfacing.bedrock.custom.CustomComponent; import dev.kejona.crossplatforms.interfacing.java.JavaMenuRegistry; +import dev.kejona.crossplatforms.permission.Permissions; import dev.kejona.crossplatforms.reloadable.ReloadableRegistry; import lombok.Getter; import net.kyori.adventure.text.serializer.legacy.LegacyComponentSerializer; @@ -36,8 +38,8 @@ import org.bstats.charts.SimplePie; import java.nio.file.Path; +import java.util.List; import java.util.Optional; -import java.util.concurrent.ExecutionException; @Getter public class CrossplatForms { @@ -48,6 +50,7 @@ public class CrossplatForms { private final ConfigManager configManager; private final ServerHandler serverHandler; + private final Permissions permissions; private final BedrockHandler bedrockHandler; private final boolean bedrockSupport; @@ -60,11 +63,10 @@ public class CrossplatForms { private final Placeholders placeholders; - private final boolean success = true; - public CrossplatForms(Logger logger, Path dataFolder, ServerHandler serverHandler, + Permissions permissions, String defaultCommand, CommandManager commandManager, Placeholders placeholders, @@ -75,6 +77,7 @@ public CrossplatForms(Logger logger, } INSTANCE = this; this.serverHandler = serverHandler; + this.permissions = permissions; this.commandManager = commandManager; this.placeholders = placeholders; ReloadableRegistry.clear(); @@ -104,20 +107,16 @@ public CrossplatForms(Logger logger, logger.warn("No Bedrock Handler being used! There may be issues."); } - interfacer = bootstrap.interfaceManager(); - Injector injector = Guice.createInjector( - new ConfigurationModule( - interfacer, - bedrockHandler, - serverHandler, - placeholders - ) - ); + interfacer = new Interfacer(); + + List modules = bootstrap.configModules(); + modules.add(new ConfigurationModule(interfacer, bedrockHandler, serverHandler, placeholders)); + Injector injector = Guice.createInjector(modules); - // Load all configs + // Register configs and serializers long configTime = System.currentTimeMillis(); configManager = new ConfigManager(dataFolder, logger, injector); - configManager.registerPriority(ConfigId.GENERAL); + configManager.registerPriority(ConfigId.GENERAL); // ensure this config is loaded first if (bedrockSupport) { // Only register bedrock form features and only references cumulus classes if cumulus is available configManager.register(ConfigId.BEDROCK_FORMS); @@ -128,20 +127,19 @@ public CrossplatForms(Logger logger, BedrockTransferAction.register(configManager.getActionSerializer()); } bootstrap.preConfigLoad(configManager); // allow implementation to add extra serializers, configs, actions, etc + if (!configManager.load()) { logger.severe("A severe configuration error occurred, which will lead to significant parts of this plugin not loading. Please repair the config and run /forms reload or restart the server."); } - Optional generalConfig = configManager.getConfig(GeneralConfig.class); logger.debug("Took " + (System.currentTimeMillis() - configTime) + "ms to load config files."); // Load forms and menus from the configs into registries - long registryTime = System.currentTimeMillis(); interfacer.load( - new BedrockFormRegistry(configManager, serverHandler), - new JavaMenuRegistry(configManager, serverHandler) + new BedrockFormRegistry(configManager, permissions), + new JavaMenuRegistry(configManager, permissions) ); - logger.debug("Took " + (System.currentTimeMillis() - registryTime) + "ms to setup registries."); + Optional generalConfig = configManager.getConfig(GeneralConfig.class); // Command defined in config or default provided by implementation rootCommand = generalConfig.map(GeneralConfig::getRootCommand).orElse(defaultCommand); @@ -169,17 +167,12 @@ public CrossplatForms(Logger logger, .permission(FormsCommand.PERMISSION_BASE + "base") .handler((context -> { CommandOrigin origin = context.getSender(); - try { - if (origin.hasPermission(ListCommand.PERMISSION)) { - logger.debug("Executing /forms list from /forms"); - commandManager.executeCommand(origin, rootCommand + " list").get(); - } else if (origin.hasPermission(HelpCommand.PERMISSION)) { - minecraftHelp.queryCommands("", context.getSender()); - } else { - origin.warn("Please specify a sub command"); - } - } catch (InterruptedException | ExecutionException e) { - e.printStackTrace(); + if (origin.hasPermission(ListCommand.PERMISSION)) { + commandManager.executeCommand(origin, rootCommand + " list"); + } else if (origin.hasPermission(HelpCommand.PERMISSION)) { + minecraftHelp.queryCommands("", context.getSender()); + } else { + origin.warn("Please specify a sub command"); } })) .build()); diff --git a/core/src/main/java/dev/kejona/crossplatforms/CrossplatFormsBootstrap.java b/core/src/main/java/dev/kejona/crossplatforms/CrossplatFormsBootstrap.java index bcccffdf..b9c7c826 100644 --- a/core/src/main/java/dev/kejona/crossplatforms/CrossplatFormsBootstrap.java +++ b/core/src/main/java/dev/kejona/crossplatforms/CrossplatFormsBootstrap.java @@ -1,23 +1,30 @@ package dev.kejona.crossplatforms; +import com.google.inject.Module; import dev.kejona.crossplatforms.config.ConfigId; import dev.kejona.crossplatforms.config.ConfigManager; -import dev.kejona.crossplatforms.interfacing.Interfacer; import org.bstats.charts.CustomChart; +import org.jetbrains.annotations.Contract; + +import java.util.List; public interface CrossplatFormsBootstrap { /** - * Perform any operations on the {@link ConfigManager} before {@link ConfigManager#load()} is called. For example, register - * additional {@link ConfigId}, or register additional type serializers. + * Returns A List of modules that should be used for injection of configuration instances. It is expected that this + * List can be added to without any consequences. It is not expected that these modules provide bindings for interfaces + * and classes already provided in the {@link CrossplatForms} constructor. + * + * @return a List of modules as described */ - void preConfigLoad(ConfigManager configManager); + @Contract(" -> new") + List configModules(); /** - * Construct an {@link Interfacer} implementation with the given parameters. This method will be called only once - * during the construction of {@link CrossplatForms}. This method exists + * Perform any operations on the {@link ConfigManager} before {@link ConfigManager#load()} is called. For example, register + * additional {@link ConfigId}, or register additional type serializers. */ - Interfacer interfaceManager(); + void preConfigLoad(ConfigManager configManager); void addCustomChart(CustomChart chart); } diff --git a/core/src/main/java/dev/kejona/crossplatforms/Entry.java b/core/src/main/java/dev/kejona/crossplatforms/Entry.java deleted file mode 100644 index 30b6f49d..00000000 --- a/core/src/main/java/dev/kejona/crossplatforms/Entry.java +++ /dev/null @@ -1,35 +0,0 @@ -package dev.kejona.crossplatforms; - -import java.util.Map; - -public class Entry implements Map.Entry { - - private final K key; - private V value; - - private Entry(K key, V value) { - this.key = key; - this.value = value; - } - - public static Entry of(K key, V value) { - return new Entry<>(key, value); - } - - @Override - public K getKey() { - return key; - } - - @Override - public V getValue() { - return value; - } - - @Override - public V setValue(V value) { - V old = this.value; - this.value = value; - return old; - } -} diff --git a/core/src/main/java/dev/kejona/crossplatforms/LegacyToMiniTranslator.java b/core/src/main/java/dev/kejona/crossplatforms/LegacyToMiniTranslator.java deleted file mode 100644 index 77a354d8..00000000 --- a/core/src/main/java/dev/kejona/crossplatforms/LegacyToMiniTranslator.java +++ /dev/null @@ -1,116 +0,0 @@ -package dev.kejona.crossplatforms; - -import com.google.common.collect.ImmutableList; - -import java.util.ArrayList; -import java.util.HashMap; -import java.util.List; -import java.util.Map; - -public class LegacyToMiniTranslator { - - private static final Map TRANSLATIONS = new HashMap<>(); - private static final List DECORATIONS = ImmutableList.of('k', 'l', 'm', 'n', 'o'); - private static final Map CLOSERS = new HashMap<>(); - private static final char RESET = 'r'; - - static { - // todo - TRANSLATIONS.put('0', ""); - CLOSERS.put('k', "<\\obf>"); - } - - private final char legacyChar; - - private LegacyToMiniTranslator(char legacyChar) { - this.legacyChar = legacyChar; - } - - public String translateToMini(String input) { - final StringBuilder result = new StringBuilder(); // intermediate - final char[] chars = input.toCharArray(); // original input as chars - final int maxIndex = input.length() - 1; // max index - - int skip = 0; // amount of following chars to skip - // this looks until the second last index, as formatting char would have to be at the last index - for (int i = 0; i < maxIndex; i++) { - if (skip > 0) { - skip--; - continue; - } - - char flag = chars[i]; - if (flag == legacyChar) { - // hit formatting marker - // look for formatting codes after the marker - // this looks until the last index if necessary/possible - for (int j = i + 1; j <= maxIndex; j++) { - char code = chars[j]; // colour or formatting code - String replacement = TRANSLATIONS.get(code); - if (replacement == null) { - // no more codes after the current marker - break; - } - skip++; // skip this char on the next iteration (parent loop) - result.append(replacement); - } - } else { - // something else - result.append(flag); - } - } - return result.toString(); - } - - public String translateToMini2(String input) { - final StringBuilder result = new StringBuilder(); // intermediate - final char[] chars = input.toCharArray(); // original input as chars - final int maxIndex = input.length() - 1; // max index - - boolean expectingCodes = false; - List open = new ArrayList<>(); // need to close everything after a reset - List openDecorations = new ArrayList<>(); // need to close all decorations after a new colour - // this looks until the second last index, as formatting char would have to be at the last index - for (int i = 0; i < maxIndex; i++) { - char c = chars[i]; - if (expectingCodes) { - // Next char can be a formatting code - String replacement = TRANSLATIONS.get(c); - if (replacement == null) { - // not a code, end expecting codes - expectingCodes = false; - result.append(c); - } else { - if (DECORATIONS.contains(c)) { - // decoration character - openDecorations.add(c); - } else if (c == RESET) { - // close all previous colours/decorations - for (char format : open) { - result.append(CLOSERS.get(format)); - } - open.clear(); // everything has been closed - openDecorations.clear(); - break; // No further action required - } else { - // color char, need to end all previous decorations - for (char decor : openDecorations) { - result.append(CLOSERS.get(decor)); - } - } - - open.add(c); - // char was a code, add replacement - result.append(replacement); - } - } else if (c == legacyChar) { - // char is the start of a formatting sequence. expect codes after this - expectingCodes = true; - } else { - // anything else, non formatting - result.append(c); - } - } - return result.toString(); - } -} diff --git a/core/src/main/java/dev/kejona/crossplatforms/SkinCache.java b/core/src/main/java/dev/kejona/crossplatforms/SkinCache.java index 13c404d2..ae948de1 100644 --- a/core/src/main/java/dev/kejona/crossplatforms/SkinCache.java +++ b/core/src/main/java/dev/kejona/crossplatforms/SkinCache.java @@ -2,30 +2,22 @@ import com.google.common.cache.Cache; import com.google.common.cache.CacheBuilder; -import com.google.gson.Gson; -import com.google.gson.JsonObject; import dev.kejona.crossplatforms.handler.FormPlayer; +import dev.kejona.crossplatforms.utils.SkinUtils; import org.checkerframework.checker.nullness.qual.Nullable; -import java.nio.charset.StandardCharsets; -import java.util.Base64; +import javax.annotation.Nonnull; import java.util.UUID; import java.util.concurrent.ExecutionException; import java.util.concurrent.TimeUnit; -import java.util.regex.Matcher; -import java.util.regex.Pattern; public class SkinCache { - private static final Gson GSON = new Gson(); - // See https://mc-heads.net/ private static final String AVATAR_ENDPOINT = "https://mc-heads.net/avatar/"; // See https://mc-heads.net/minecraft/mhf private static final String STEVE = "MHF_Steve"; - private static final String ALEX = "MHF_Alex"; - private static final Pattern URL_PATTERN = Pattern.compile("(http|https)://textures\\.minecraft\\.net/texture/([a-zA-Z0-9]+)"); private static final Logger LOGGER = Logger.get(); private final Cache avatars = CacheBuilder.newBuilder() @@ -36,7 +28,7 @@ public class SkinCache { public String getAvatarUrl(FormPlayer player) { UUID uuid = player.getUuid(); try { - return avatars.get(uuid, () -> readAvatarUrl(uuid, player.getEncodedSkinData())); + return avatars.get(uuid, () -> getAvatarUrl(uuid, player.getEncodedSkinData())); } catch (ExecutionException e) { LOGGER.warn("Exception while computing avatar url of " + player.getName()); e.printStackTrace(); @@ -44,44 +36,23 @@ public String getAvatarUrl(FormPlayer player) { } } - public static String readAvatarUrl(UUID uuid, @Nullable String encodedData) { - String url = AVATAR_ENDPOINT + readSkinId(uuid, encodedData); - LOGGER.debug("Avatar URL for " + uuid + ": " + url); - return url; - } - - public static String readSkinId(UUID uuid, @Nullable String encodedData) { + @Nonnull + public static String getAvatarUrl(UUID uuid, @Nullable String encodedData) { if (encodedData == null) { - LOGGER.debug("textures property (encoded) missing or empty for " + uuid + ", falling back to steve"); - return STEVE; - } - - // See https://wiki.vg/Mojang_API#UUID_to_Profile_and_Skin.2FCape - String decoded = new String(Base64.getDecoder().decode(encodedData), StandardCharsets.UTF_8); - JsonObject textures = GSON.fromJson(decoded, JsonObject.class).getAsJsonObject("textures"); - if (textures == null) { - LOGGER.debug("textures member missing for " + uuid + ", falling back to steve"); - return STEVE; + return AVATAR_ENDPOINT + STEVE; } + // todo: calculate default skin if no encoded data or failed to read - JsonObject skin = textures.getAsJsonObject("SKIN"); - if (skin == null) { - LOGGER.debug(uuid + " does not have custom skin, using steve or alex"); - // no custom skin - if ((uuid.hashCode() & 1) == 0) { - return STEVE; // even hashcode - } else { - return ALEX; // odd hashcode + try { + String avatarUrl = AVATAR_ENDPOINT + SkinUtils.idFromEncoding(encodedData); + LOGGER.debug("Avatar URL for " + uuid + ": " + avatarUrl); + return avatarUrl; + } catch (Exception e) { + if (LOGGER.isDebug()) { + LOGGER.debug("Failed to get avatar url for " + uuid); + e.printStackTrace(); } - } - - String url = skin.get("url").getAsString(); - Matcher matcher = URL_PATTERN.matcher(url); - if (matcher.matches()) { - return matcher.group(2); - } else { - LOGGER.debug("Skin url of " + uuid + " has unexpected format: " + url); - return STEVE; + return AVATAR_ENDPOINT + STEVE; } } } diff --git a/core/src/main/java/dev/kejona/crossplatforms/action/Action.java b/core/src/main/java/dev/kejona/crossplatforms/action/Action.java index 896cafd0..48f0a254 100644 --- a/core/src/main/java/dev/kejona/crossplatforms/action/Action.java +++ b/core/src/main/java/dev/kejona/crossplatforms/action/Action.java @@ -2,11 +2,11 @@ import dev.kejona.crossplatforms.handler.FormPlayer; import dev.kejona.crossplatforms.resolver.Resolver; -import dev.kejona.crossplatforms.serialize.ValuedType; +import dev.kejona.crossplatforms.serialize.KeyedType; import javax.annotation.Nonnull; -public interface Action extends ValuedType { +public interface Action extends KeyedType { /** * Affects a player diff --git a/core/src/main/java/dev/kejona/crossplatforms/action/ActionSerializer.java b/core/src/main/java/dev/kejona/crossplatforms/action/ActionSerializer.java index c0d33386..8c0a4202 100644 --- a/core/src/main/java/dev/kejona/crossplatforms/action/ActionSerializer.java +++ b/core/src/main/java/dev/kejona/crossplatforms/action/ActionSerializer.java @@ -1,7 +1,7 @@ package dev.kejona.crossplatforms.action; import dev.kejona.crossplatforms.serialize.TypeResolver; -import dev.kejona.crossplatforms.serialize.ValuedTypeSerializer; +import dev.kejona.crossplatforms.serialize.KeyedTypeSerializer; import io.leangen.geantyref.TypeToken; import org.spongepowered.configurate.serialize.TypeSerializerCollection; @@ -9,7 +9,7 @@ public class ActionSerializer { public static final TypeToken> TYPE = new TypeToken>() {}; - private final ValuedTypeSerializer> serializer = new ValuedTypeSerializer<>(); + private final KeyedTypeSerializer> serializer = new KeyedTypeSerializer<>(); public void register(String typeId, Class> type) { serializer.registerType(typeId, type); diff --git a/core/src/main/java/dev/kejona/crossplatforms/action/InterfaceAction.java b/core/src/main/java/dev/kejona/crossplatforms/action/InterfaceAction.java index 3ad3f7dc..c0b35ed5 100644 --- a/core/src/main/java/dev/kejona/crossplatforms/action/InterfaceAction.java +++ b/core/src/main/java/dev/kejona/crossplatforms/action/InterfaceAction.java @@ -4,7 +4,6 @@ import dev.kejona.crossplatforms.Logger; import dev.kejona.crossplatforms.handler.BedrockHandler; import dev.kejona.crossplatforms.handler.FormPlayer; -import dev.kejona.crossplatforms.handler.Placeholders; import dev.kejona.crossplatforms.interfacing.ArgumentException; import dev.kejona.crossplatforms.interfacing.Interface; import dev.kejona.crossplatforms.interfacing.Interfacer; @@ -32,13 +31,11 @@ public class InterfaceAction implements GenericAction { private final transient BedrockHandler bedrockHandler; private final transient Interfacer interfacer; - private final transient Placeholders placeholders; @Inject - public InterfaceAction(BedrockHandler bedrockHandler, Interfacer interfacer, Placeholders placeholders) { + public InterfaceAction(BedrockHandler bedrockHandler, Interfacer interfacer) { this.bedrockHandler = bedrockHandler; this.interfacer = interfacer; - this.placeholders = placeholders; } @Override @@ -66,7 +63,7 @@ public void affectPlayer(@Nonnull FormPlayer player, @Nonnull Resolver resolver) // The resolver given to this action is not passed to the form being opened. // If this action is triggered by a form that has arguments, and the user wants those arguments to be passed // to the form that this is opening, they must be passed explicitly as arguments (not through the resolver) - ui.send(player, placeholders.resolver(player), arguments); + ui.send(player, arguments); } catch (ArgumentException e) { player.warn("A configuration error resulted in you not opening a form or menu."); Logger.get().severe("Failed to open '" + form + "' because: " + e.getMessage()); diff --git a/core/src/main/java/dev/kejona/crossplatforms/command/custom/CustomCommand.java b/core/src/main/java/dev/kejona/crossplatforms/command/custom/CustomCommand.java index 9df9166e..9182b1b8 100644 --- a/core/src/main/java/dev/kejona/crossplatforms/command/custom/CustomCommand.java +++ b/core/src/main/java/dev/kejona/crossplatforms/command/custom/CustomCommand.java @@ -8,7 +8,7 @@ import dev.kejona.crossplatforms.handler.FormPlayer; import dev.kejona.crossplatforms.handler.Placeholders; import dev.kejona.crossplatforms.resolver.Resolver; -import dev.kejona.crossplatforms.serialize.ValuedType; +import dev.kejona.crossplatforms.serialize.KeyedType; import lombok.Getter; import lombok.Setter; import lombok.ToString; @@ -21,7 +21,7 @@ @ToString @Getter @SuppressWarnings("FieldMayBeFinal") -public abstract class CustomCommand implements ValuedType { +public abstract class CustomCommand implements KeyedType { @Inject private transient BedrockHandler bedrockHandler; diff --git a/core/src/main/java/dev/kejona/crossplatforms/command/custom/CustomCommandSerializer.java b/core/src/main/java/dev/kejona/crossplatforms/command/custom/CustomCommandSerializer.java index 2dd57696..b7ea5fbc 100644 --- a/core/src/main/java/dev/kejona/crossplatforms/command/custom/CustomCommandSerializer.java +++ b/core/src/main/java/dev/kejona/crossplatforms/command/custom/CustomCommandSerializer.java @@ -1,8 +1,8 @@ package dev.kejona.crossplatforms.command.custom; -import dev.kejona.crossplatforms.serialize.ValuedTypeSerializer; +import dev.kejona.crossplatforms.serialize.KeyedTypeSerializer; -public class CustomCommandSerializer extends ValuedTypeSerializer { +public class CustomCommandSerializer extends KeyedTypeSerializer { public CustomCommandSerializer() { super("method"); diff --git a/core/src/main/java/dev/kejona/crossplatforms/command/custom/RegisteredCommand.java b/core/src/main/java/dev/kejona/crossplatforms/command/custom/RegisteredCommand.java index 94cf4b1e..08bae11b 100644 --- a/core/src/main/java/dev/kejona/crossplatforms/command/custom/RegisteredCommand.java +++ b/core/src/main/java/dev/kejona/crossplatforms/command/custom/RegisteredCommand.java @@ -35,7 +35,7 @@ public String type() { } @PostProcess - protected void postProcess() { + private void postProcess() { if (command == null) { command = Literals.of(new String[]{getIdentifier()}); } diff --git a/core/src/main/java/dev/kejona/crossplatforms/command/defaults/HelpCommand.java b/core/src/main/java/dev/kejona/crossplatforms/command/defaults/HelpCommand.java index 54599f36..af8ef25d 100644 --- a/core/src/main/java/dev/kejona/crossplatforms/command/defaults/HelpCommand.java +++ b/core/src/main/java/dev/kejona/crossplatforms/command/defaults/HelpCommand.java @@ -20,7 +20,6 @@ public HelpCommand(CrossplatForms crossplatForms, MinecraftHelp m this.minecraftHelp = minecraftHelp; } - @SuppressWarnings("ConstantConditions") // CommandContext#getOrDefault will not return null if given nonnull default @Override public void register(CommandManager manager, Command.Builder defaultBuilder) { manager.command(defaultBuilder diff --git a/core/src/main/java/dev/kejona/crossplatforms/command/defaults/IdentifyCommand.java b/core/src/main/java/dev/kejona/crossplatforms/command/defaults/IdentifyCommand.java index 7fa5cea2..ffbe1d0a 100644 --- a/core/src/main/java/dev/kejona/crossplatforms/command/defaults/IdentifyCommand.java +++ b/core/src/main/java/dev/kejona/crossplatforms/command/defaults/IdentifyCommand.java @@ -43,7 +43,7 @@ public void register(CommandManager manager, Command.BuildernewBuilder("player") + .argument(StringArgument.builder("player") .withSuggestionsProvider((context, s) -> serverHandler.getPlayerNames().collect(Collectors.toList())) .build()) .permission(PERMISSION_OTHER) diff --git a/core/src/main/java/dev/kejona/crossplatforms/command/defaults/InspectCommand.java b/core/src/main/java/dev/kejona/crossplatforms/command/defaults/InspectCommand.java index e6dae568..43de8097 100644 --- a/core/src/main/java/dev/kejona/crossplatforms/command/defaults/InspectCommand.java +++ b/core/src/main/java/dev/kejona/crossplatforms/command/defaults/InspectCommand.java @@ -6,7 +6,6 @@ import dev.kejona.crossplatforms.CrossplatForms; import dev.kejona.crossplatforms.command.CommandOrigin; import dev.kejona.crossplatforms.command.FormsCommand; -import dev.kejona.crossplatforms.handler.ServerHandler; import dev.kejona.crossplatforms.interfacing.Interface; import dev.kejona.crossplatforms.interfacing.bedrock.BedrockForm; import dev.kejona.crossplatforms.interfacing.bedrock.BedrockFormRegistry; @@ -26,7 +25,6 @@ public InspectCommand(CrossplatForms crossplatForms) { @Override public void register(CommandManager manager, Command.Builder defaultBuilder) { - ServerHandler serverHandler = crossplatForms.getServerHandler(); BedrockFormRegistry bedrockRegistry = crossplatForms.getInterfacer().getBedrockRegistry(); JavaMenuRegistry javaRegistry = crossplatForms.getInterfacer().getJavaRegistry(); @@ -36,7 +34,7 @@ public void register(CommandManager manager, Command.BuildernewBuilder("form") + .argument(StringArgument.builder("form") .withSuggestionsProvider(((context, s) -> bedrockRegistry.getForms().values() .stream() .map(Interface::getIdentifier) @@ -56,7 +54,7 @@ public void register(CommandManager manager, Command.BuildernewBuilder("menu") + .argument(StringArgument.builder("menu") .withSuggestionsProvider(((context, s) -> javaRegistry.getMenus().values() .stream() .map(Interface::getIdentifier) diff --git a/core/src/main/java/dev/kejona/crossplatforms/command/defaults/OpenCommand.java b/core/src/main/java/dev/kejona/crossplatforms/command/defaults/OpenCommand.java index 45dca9ae..327a72fd 100644 --- a/core/src/main/java/dev/kejona/crossplatforms/command/defaults/OpenCommand.java +++ b/core/src/main/java/dev/kejona/crossplatforms/command/defaults/OpenCommand.java @@ -10,7 +10,6 @@ import dev.kejona.crossplatforms.command.FormsCommand; import dev.kejona.crossplatforms.handler.BedrockHandler; import dev.kejona.crossplatforms.handler.FormPlayer; -import dev.kejona.crossplatforms.handler.Placeholders; import dev.kejona.crossplatforms.handler.ServerHandler; import dev.kejona.crossplatforms.interfacing.Argument; import dev.kejona.crossplatforms.interfacing.ArgumentException; @@ -41,7 +40,6 @@ public class OpenCommand extends FormsCommand { private final BedrockHandler bedrockHandler; private final Interfacer interfacer; private final JavaMenuRegistry javaRegistry; - private final Placeholders placeholders; private final String openCommand; private final String sendCommand; @@ -53,7 +51,6 @@ public OpenCommand(CrossplatForms crossplatForms) { this.bedrockHandler = crossplatForms.getBedrockHandler(); this.interfacer = crossplatForms.getInterfacer(); this.javaRegistry = crossplatForms.getInterfacer().getJavaRegistry(); - this.placeholders = crossplatForms.getPlaceholders(); String root = crossplatForms.getRootCommand(); openCommand = join(root, OPEN_NAME); @@ -67,7 +64,7 @@ public void register(CommandManager manager, Command.Builder origin.hasPermission(PERMISSION) && origin.isPlayer()) - .argument(StringArgument.newBuilder(INTERFACE_ARG) + .argument(StringArgument.builder(INTERFACE_ARG) .withSuggestionsProvider((context, s) -> openSuggestions(context)) .build()) .argument(extrasArgument()) @@ -98,12 +95,12 @@ public void register(CommandManager manager, Command.BuildernewBuilder("player") + .argument(StringArgument.builder("player") .withSuggestionsProvider((context, s) -> serverHandler.getPlayers() .map(FormPlayer::getName) .collect(Collectors.toList())) .build()) - .argument(StringArgument.newBuilder(INTERFACE_ARG) + .argument(StringArgument.builder(INTERFACE_ARG) .withSuggestionsProvider((context, s) -> sendSuggestions(context)) .build()) .argument(extrasArgument()) @@ -164,7 +161,7 @@ private void send(String command, CommandContext ctx, Interface u private void send(Interface ui, CommandOrigin origin, FormPlayer recipient, @Nullable String... args) { try { - ui.send(recipient, placeholders.resolver(recipient), args); + ui.send(recipient, args); } catch (ArgumentException e) { origin.warn("Failed to open " + ui.getIdentifier() + ": " + e.getMessage()); badSyntax(origin, openCommand, ui); diff --git a/core/src/main/java/dev/kejona/crossplatforms/config/ConfigId.java b/core/src/main/java/dev/kejona/crossplatforms/config/ConfigId.java index 98f27ea8..236372d3 100644 --- a/core/src/main/java/dev/kejona/crossplatforms/config/ConfigId.java +++ b/core/src/main/java/dev/kejona/crossplatforms/config/ConfigId.java @@ -59,6 +59,7 @@ public class ConfigId { @Nullable public final Supplier updater; + // todo: can be removed and substituted for Configurate's new post processing feature @Nullable public final Consumer postProcessor; diff --git a/core/src/main/java/dev/kejona/crossplatforms/config/ConfigManager.java b/core/src/main/java/dev/kejona/crossplatforms/config/ConfigManager.java index 61c2f48c..ca10e041 100644 --- a/core/src/main/java/dev/kejona/crossplatforms/config/ConfigManager.java +++ b/core/src/main/java/dev/kejona/crossplatforms/config/ConfigManager.java @@ -18,6 +18,7 @@ import dev.kejona.crossplatforms.interfacing.Argument; import dev.kejona.crossplatforms.interfacing.bedrock.custom.Option; import dev.kejona.crossplatforms.interfacing.bedrock.custom.OptionSerializer; +import dev.kejona.crossplatforms.inventory.SkullProfile; import dev.kejona.crossplatforms.parser.Parser; import dev.kejona.crossplatforms.parser.ParserSerializer; import dev.kejona.crossplatforms.serialize.PathNodeResolver; @@ -91,6 +92,7 @@ public ConfigManager(Path directory, Logger logger, Injector injector) { builder.registerExact(Argument.class, new Argument.Serializer()); builder.registerExact(Option.class, new OptionSerializer()); builder.registerExact(Literals.class, new Literals.Serializer()); + builder.registerExact(SkullProfile.class, new SkullProfile.Serializer()); // serializers for abstract classes builder.registerExact(CustomCommand.class, new CustomCommandSerializer()); diff --git a/core/src/main/java/dev/kejona/crossplatforms/config/ConfigurationModule.java b/core/src/main/java/dev/kejona/crossplatforms/config/ConfigurationModule.java index 79854b1c..dbf0152c 100644 --- a/core/src/main/java/dev/kejona/crossplatforms/config/ConfigurationModule.java +++ b/core/src/main/java/dev/kejona/crossplatforms/config/ConfigurationModule.java @@ -19,9 +19,11 @@ public class ConfigurationModule extends AbstractModule { @Override protected void configure() { - // remember: explicit bindings cannot be required because this module is used for creating configs + // remember: explicit bindings cannot be set as required using binder().requireExplicitBindings() + // because this module is used for creating configs - bindings are created on the fly (just in time) bind(Interfacer.class).toInstance(interfacer); + // Hack to stop the instance from having its members being injected // which causes a ClassDefNotFound error if Cumulus is not present (EmptyBedrockHandler) bind(BedrockHandler.class).toProvider(Providers.of(bedrockHandler)); diff --git a/core/src/main/java/dev/kejona/crossplatforms/context/Context.java b/core/src/main/java/dev/kejona/crossplatforms/context/Context.java new file mode 100644 index 00000000..f429d407 --- /dev/null +++ b/core/src/main/java/dev/kejona/crossplatforms/context/Context.java @@ -0,0 +1,8 @@ +package dev.kejona.crossplatforms.context; + +import dev.kejona.crossplatforms.resolver.Resolver; + +public interface Context { + + Resolver resolver(); +} diff --git a/core/src/main/java/dev/kejona/crossplatforms/context/PlayerContext.java b/core/src/main/java/dev/kejona/crossplatforms/context/PlayerContext.java new file mode 100644 index 00000000..aae878e6 --- /dev/null +++ b/core/src/main/java/dev/kejona/crossplatforms/context/PlayerContext.java @@ -0,0 +1,16 @@ +package dev.kejona.crossplatforms.context; + +import dev.kejona.crossplatforms.handler.FormPlayer; +import dev.kejona.crossplatforms.resolver.Resolver; +import lombok.AllArgsConstructor; +import lombok.Getter; +import lombok.experimental.Accessors; + +@Getter +@Accessors(fluent = true) +@AllArgsConstructor +public class PlayerContext implements Context { + + private final FormPlayer player; + private final Resolver resolver; +} diff --git a/core/src/main/java/dev/kejona/crossplatforms/filler/FillerSerializer.java b/core/src/main/java/dev/kejona/crossplatforms/filler/FillerSerializer.java index d23e158a..a9ceea6a 100644 --- a/core/src/main/java/dev/kejona/crossplatforms/filler/FillerSerializer.java +++ b/core/src/main/java/dev/kejona/crossplatforms/filler/FillerSerializer.java @@ -1,7 +1,7 @@ package dev.kejona.crossplatforms.filler; import dev.kejona.crossplatforms.serialize.TypeResolver; -import dev.kejona.crossplatforms.serialize.ValuedTypeSerializer; +import dev.kejona.crossplatforms.serialize.KeyedTypeSerializer; import lombok.Getter; import lombok.experimental.Accessors; import org.spongepowered.configurate.serialize.TypeSerializerCollection; @@ -10,25 +10,21 @@ @Getter public class FillerSerializer { - private final ValuedTypeSerializer optionFillerSerializer = new ValuedTypeSerializer<>(); - private final ValuedTypeSerializer simpleFormFillerSerializer = new ValuedTypeSerializer<>(); - private final ValuedTypeSerializer inventoryFillerSerializer = new ValuedTypeSerializer<>(); + private final KeyedTypeSerializer optionFillerSerializer = new KeyedTypeSerializer<>(); + private final KeyedTypeSerializer simpleFormFillerSerializer = new KeyedTypeSerializer<>(); public void filler(String typeId, Class fillerType) { optionFillerSerializer.registerType(typeId, fillerType); simpleFormFillerSerializer.registerType(typeId, fillerType); - inventoryFillerSerializer.registerType(typeId, fillerType); } public void filler(String typeId, Class fillerType, TypeResolver resolver) { optionFillerSerializer.registerType(typeId, fillerType, resolver); simpleFormFillerSerializer.registerType(typeId, fillerType, resolver); - inventoryFillerSerializer.registerType(typeId, fillerType, resolver); } public void register(TypeSerializerCollection.Builder builder) { builder.registerExact(OptionFiller.class, optionFillerSerializer); builder.registerExact(SimpleFormFiller.class, simpleFormFillerSerializer); - builder.registerExact(InventoryFiller.class, inventoryFillerSerializer); } } diff --git a/core/src/main/java/dev/kejona/crossplatforms/filler/FillerUtils.java b/core/src/main/java/dev/kejona/crossplatforms/filler/FillerUtils.java index 65bdaa8b..95d4172a 100644 --- a/core/src/main/java/dev/kejona/crossplatforms/filler/FillerUtils.java +++ b/core/src/main/java/dev/kejona/crossplatforms/filler/FillerUtils.java @@ -4,19 +4,42 @@ import org.jetbrains.annotations.Contract; import javax.annotation.Nonnull; +import java.util.List; import java.util.regex.Matcher; import java.util.regex.Pattern; +import java.util.stream.Stream; public final class FillerUtils { + /** + * Replaces part(s) of a "template" string with a given value. This is primarily meant for formatting defaultValue + * with the optional template. + * + * @param template the template string to apply the key Pattern to + * @param key the Pattern used for doing replacement in the template String + * @param defaultValue The String that matches will be replaced with + * @return a new string with the replacements performed if both template and default value are not null. + * If defaultValue is null, template is returned. If template is null, it is considered that + * not formatting required, so defaultValue is returned. + */ @Nullable @Contract("_, _, null -> param1; null, _, !null -> param3; !null, _, !null -> !null") - public static String replace(@Nullable String base, @Nonnull Pattern key, @Nullable String replacement) { - if (replacement == null) { - return base; // value is not present. return either an override the user specified, or null. - } else if (base == null) { - return replacement; // user did not specify formatting, still use the raw value + public static String replace(@Nullable String template, @Nonnull Pattern key, @Nullable String defaultValue) { + if (defaultValue == null) { + return template; // value is not present. return either an override the user specified, or null. + } else if (template == null) { + return defaultValue; // user did not specify formatting, still use the raw value } - return key.matcher(base).replaceAll(Matcher.quoteReplacement(replacement)); - }} + return key.matcher(template).replaceAll(Matcher.quoteReplacement(defaultValue)); + } + + protected static void addAtIndex(Stream src, List dest, int index) { + if (index < 0) { + // index invalid or not set - silently ignore for config purposes + src.forEachOrdered(dest::add); + } else { + src.forEachOrdered(e -> dest.add(index, e)); + } + } +} diff --git a/core/src/main/java/dev/kejona/crossplatforms/filler/InventoryFiller.java b/core/src/main/java/dev/kejona/crossplatforms/filler/InventoryFiller.java deleted file mode 100644 index 88964d8b..00000000 --- a/core/src/main/java/dev/kejona/crossplatforms/filler/InventoryFiller.java +++ /dev/null @@ -1,30 +0,0 @@ -package dev.kejona.crossplatforms.filler; - -import dev.kejona.crossplatforms.resolver.Resolver; -import dev.kejona.crossplatforms.interfacing.java.ItemButton; -import dev.kejona.crossplatforms.serialize.ValuedType; - -import javax.annotation.Nonnull; -import javax.annotation.Nullable; -import java.util.List; -import java.util.stream.Collectors; -import java.util.stream.Stream; - -public interface InventoryFiller extends ValuedType { - - default List generateItems(Resolver resolver) { - ItemButton format = itemFormat(); - if (format == null) { - return rawItems(resolver).collect(Collectors.toList()); - } - - throw new IllegalStateException("Item generation not implemented yet"); - // return rawItems(resolver).map(item -> item.format(format)).collect(Collectors.toList()); - } - - @Nonnull - Stream rawItems(Resolver resolver); - - @Nullable - ItemButton itemFormat(); -} diff --git a/core/src/main/java/dev/kejona/crossplatforms/filler/OptionFiller.java b/core/src/main/java/dev/kejona/crossplatforms/filler/OptionFiller.java index 2aa25ad4..a8f47a50 100644 --- a/core/src/main/java/dev/kejona/crossplatforms/filler/OptionFiller.java +++ b/core/src/main/java/dev/kejona/crossplatforms/filler/OptionFiller.java @@ -1,32 +1,38 @@ package dev.kejona.crossplatforms.filler; -import dev.kejona.crossplatforms.resolver.Resolver; +import dev.kejona.crossplatforms.context.PlayerContext; import dev.kejona.crossplatforms.interfacing.bedrock.custom.Option; -import dev.kejona.crossplatforms.serialize.ValuedType; +import dev.kejona.crossplatforms.serialize.KeyedType; import javax.annotation.Nonnull; import javax.annotation.Nullable; +import java.util.List; import java.util.regex.Pattern; import java.util.stream.Stream; -public interface OptionFiller extends ValuedType { +public interface OptionFiller extends KeyedType { Pattern PLACEHOLDER = Pattern.compile("%raw_text%"); - default Stream