diff --git a/platform/bukkit/build.gradle b/platform/bukkit/build.gradle new file mode 100644 index 0000000..a5423a0 --- /dev/null +++ b/platform/bukkit/build.gradle @@ -0,0 +1,63 @@ +apply plugin: 'kr.entree.spigradle' + +archivesBaseName = "${project.property("pluginName")}-bukkit" + +spigot { + name = project.property("pluginName") + authors = [project.property("author")] + apiVersion = project.property("apiVersion") + load = STARTUP + depends = [] + softDepends = ['PlaceholderAPI'] + permissions { + 'template.player' { + description 'Contains all basic player permissions.' + defaults 'true' + children = [ + 'template.player.one' : true, + 'template.player.two.example': true + ] + } + 'template.admin' { + description 'Contains all admin permissions.' + defaults 'op' + children = [ + 'template.admin.reload': true + ] + } + } +} + +dependencies { + implementation project(':platform') + + implementation spigot(mcVersion) + + implementation 'cloud.commandframework:cloud-paper:1.6.1' + implementation "net.kyori:adventure-platform-bukkit:4.0.1" + implementation "org.spongepowered:configurate-yaml:4.1.2" + + testImplementation 'com.github.seeseemelk:MockBukkit-v1.18:1.15.5' + testImplementation(testFixtures(project(":core"))) +} + +shadowJar { + archiveClassifier.set("") + dependencies { + include(project(':api')) + include(project(':core')) + include(project(':platform')) + include(dependency('cloud.commandframework::')) + include(dependency('io.leangen.geantyref::')) + include(dependency('org.spongepowered::')) + include(dependency('net.kyori::')) + } + relocate 'cloud.commandframework', "${packageName}.lib.commands" + relocate 'org.spongepowered.configurate', "${packageName}.lib.configurate" + relocate 'io.leangen.geantyref', "${packageName}.lib.typetoken" + relocate 'net.kyori', "${packageName}.lib.kyori" + relocate 'org.bstats', "${packageName}.lib.bstats" +} + +tasks.build.dependsOn(shadowJar) +tasks.publish.dependsOn(shadowJar) \ No newline at end of file diff --git a/platform/bukkit/src/main/java/net/silthus/template/bukkit/BukkitLoader.java b/platform/bukkit/src/main/java/net/silthus/template/bukkit/BukkitLoader.java new file mode 100644 index 0000000..ac1bc42 --- /dev/null +++ b/platform/bukkit/src/main/java/net/silthus/template/bukkit/BukkitLoader.java @@ -0,0 +1,61 @@ +/* + * sChat, a Supercharged Minecraft Chat Plugin + * Copyright (C) Silthus + * Copyright (C) sChat team and contributors + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +package net.silthus.template.bukkit; + +import java.io.File; +import kr.entree.spigradle.annotations.PluginMain; +import org.bukkit.plugin.PluginDescriptionFile; +import org.bukkit.plugin.java.JavaPlugin; +import org.bukkit.plugin.java.JavaPluginLoader; +import org.jetbrains.annotations.NotNull; + +@PluginMain +public final class BukkitLoader extends JavaPlugin { + + private final SChatBukkitBootstrap bootstrap; + + public BukkitLoader() { + this.bootstrap = new SChatBukkitBootstrap(this); + } + + // testing constructor + public BukkitLoader(@NotNull JavaPluginLoader loader, + @NotNull PluginDescriptionFile description, + @NotNull File dataFolder, + @NotNull File file) { + super(loader, description, dataFolder, file); + this.bootstrap = new SChatBukkitBootstrap(this); + } + + @Override + public void onLoad() { + bootstrap.onLoad(); + } + + @Override + public void onEnable() { + bootstrap.onEnable(); + } + + @Override + public void onDisable() { + bootstrap.onDisable(); + } +} diff --git a/platform/bukkit/src/main/java/net/silthus/template/bukkit/SChatBukkitBootstrap.java b/platform/bukkit/src/main/java/net/silthus/template/bukkit/SChatBukkitBootstrap.java new file mode 100644 index 0000000..568e67b --- /dev/null +++ b/platform/bukkit/src/main/java/net/silthus/template/bukkit/SChatBukkitBootstrap.java @@ -0,0 +1,67 @@ +/* + * sChat, a Supercharged Minecraft Chat Plugin + * Copyright (C) Silthus + * Copyright (C) sChat team and contributors + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +package net.silthus.template.bukkit; + +import java.nio.file.Path; +import lombok.Getter; +import net.silthus.template.bukkit.adapter.BukkitSchedulerAdapter; +import net.silthus.template.platform.plugin.bootstrap.Bootstrap; +import net.silthus.template.platform.plugin.bootstrap.LoaderBootstrap; +import net.silthus.template.platform.plugin.logging.JavaPluginLogger; +import net.silthus.template.platform.plugin.logging.PluginLogger; +import org.bukkit.plugin.java.JavaPlugin; + +@Getter +public class SChatBukkitBootstrap implements Bootstrap, LoaderBootstrap { + + private final JavaPlugin loader; + private final SChatBukkitPlugin plugin; + + private final PluginLogger pluginLogger; + private final BukkitSchedulerAdapter scheduler; + + public SChatBukkitBootstrap(JavaPlugin loader) { + this.loader = loader; + + this.pluginLogger = new JavaPluginLogger(loader.getLogger()); + this.scheduler = new BukkitSchedulerAdapter(loader); + this.plugin = new SChatBukkitPlugin(this); + } + + @Override + public void onLoad() { + plugin.load(); + } + + @Override + public void onEnable() { + plugin.enable(); + } + + @Override + public void onDisable() { + plugin.disable(); + } + + @Override + public Path getDataDirectory() { + return getLoader().getDataFolder().toPath().toAbsolutePath(); + } +} diff --git a/platform/bukkit/src/main/java/net/silthus/template/bukkit/SChatBukkitPlugin.java b/platform/bukkit/src/main/java/net/silthus/template/bukkit/SChatBukkitPlugin.java new file mode 100644 index 0000000..092308f --- /dev/null +++ b/platform/bukkit/src/main/java/net/silthus/template/bukkit/SChatBukkitPlugin.java @@ -0,0 +1,79 @@ +/* + * sChat, a Supercharged Minecraft Chat Plugin + * Copyright (C) Silthus + * Copyright (C) sChat team and contributors + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +package net.silthus.template.bukkit; + +import cloud.commandframework.CommandManager; +import cloud.commandframework.paper.PaperCommandManager; +import lombok.Getter; +import lombok.SneakyThrows; +import net.kyori.adventure.platform.bukkit.BukkitAudiences; +import net.silthus.template.bukkit.adapter.BukkitSchedulerAdapter; +import net.silthus.template.bukkit.adapter.BukkitSenderFactory; +import net.silthus.template.platform.config.adapter.ConfigurationAdapter; +import net.silthus.template.platform.config.adapter.ConfigurationAdapters; +import net.silthus.template.platform.plugin.AbstractTemplatePlugin; +import net.silthus.template.platform.sender.Sender; +import org.bukkit.Bukkit; +import org.jetbrains.annotations.NotNull; + +import static cloud.commandframework.execution.CommandExecutionCoordinator.simpleCoordinator; + +@Getter +public final class SChatBukkitPlugin extends AbstractTemplatePlugin { + + private final SChatBukkitBootstrap bootstrap; + private BukkitSenderFactory senderFactory; + + SChatBukkitPlugin(SChatBukkitBootstrap bootstrap) { + this.bootstrap = bootstrap; + } + + @Override + protected ConfigurationAdapter provideConfigurationAdapter() { + return ConfigurationAdapters.YAML.create(resolveConfig("config.yml").toFile()); + } + + @Override + protected void setupSenderFactory() { + senderFactory = new BukkitSenderFactory(getAudiences(), new BukkitSchedulerAdapter(bootstrap.getLoader())); + } + + @NotNull + private BukkitAudiences getAudiences() { + return BukkitAudiences.create(getBootstrap().getLoader()); + } + + @Override + @SneakyThrows + protected CommandManager provideCommandManager() { + try { + return new PaperCommandManager<>( + getBootstrap().getLoader(), + simpleCoordinator(), + commandSender -> getSenderFactory().wrap(commandSender), + sender -> getSenderFactory().unwrap(sender) + ); + } catch (Exception e) { + getLogger().severe("Failed to initialize the command manager."); + Bukkit.getPluginManager().disablePlugin(getBootstrap().getLoader()); + throw new RuntimeException(e); + } + } +} diff --git a/platform/bukkit/src/main/java/net/silthus/template/bukkit/adapter/BukkitIdentityAdapter.java b/platform/bukkit/src/main/java/net/silthus/template/bukkit/adapter/BukkitIdentityAdapter.java new file mode 100644 index 0000000..289a6ab --- /dev/null +++ b/platform/bukkit/src/main/java/net/silthus/template/bukkit/adapter/BukkitIdentityAdapter.java @@ -0,0 +1,58 @@ +package net.silthus.template.bukkit.adapter; + +import java.util.function.Supplier; +import lombok.NonNull; +import net.kyori.adventure.text.Component; +import net.kyori.adventure.text.TextComponent; +import net.kyori.adventure.text.serializer.legacy.LegacyComponentSerializer; +import net.silthus.template.identity.Identity; +import org.bukkit.OfflinePlayer; +import org.bukkit.entity.Player; +import org.jetbrains.annotations.NotNull; + +import static java.util.Objects.requireNonNullElse; +import static net.kyori.adventure.text.Component.text; +import static net.silthus.template.pointer.Pointer.weak; +import static org.bukkit.Bukkit.getPlayer; + +public final class BukkitIdentityAdapter { + + private static final LegacyComponentSerializer LEGACY_SERIALIZER = LegacyComponentSerializer.legacySection(); + + private BukkitIdentityAdapter() { + } + + public static @NotNull Identity identity(@NonNull OfflinePlayer player) { + return Identity.identity( + player.getUniqueId(), + player.getName(), + displayName(player) + ); + } + + @NotNull + private static Supplier displayName(@NonNull OfflinePlayer offlinePlayer) { + if (offlinePlayer.isOnline()) + if (offlinePlayer instanceof Player player) + return getOnlinePlayerDisplayName(player); + else + return getOnlinePlayerDisplayName(getPlayer(offlinePlayer.getUniqueId())); + else + return getDisplayNameFromName(offlinePlayer); + } + + @NotNull + private static Supplier getOnlinePlayerDisplayName(Player player) { + return weak(player, BukkitIdentityAdapter::deserializeDisplayName, text(player.getName())); + } + + @NotNull + private static Supplier getDisplayNameFromName(@NotNull OfflinePlayer offlinePlayer) { + return () -> text(requireNonNullElse(offlinePlayer.getName(), "")); + } + + @NotNull + private static TextComponent deserializeDisplayName(Player p) { + return LEGACY_SERIALIZER.deserialize(p.getDisplayName()); + } +} diff --git a/platform/bukkit/src/main/java/net/silthus/template/bukkit/adapter/BukkitSchedulerAdapter.java b/platform/bukkit/src/main/java/net/silthus/template/bukkit/adapter/BukkitSchedulerAdapter.java new file mode 100644 index 0000000..417ce51 --- /dev/null +++ b/platform/bukkit/src/main/java/net/silthus/template/bukkit/adapter/BukkitSchedulerAdapter.java @@ -0,0 +1,40 @@ +/* + * sChat, a Supercharged Minecraft Chat Plugin + * Copyright (C) Silthus + * Copyright (C) sChat team and contributors + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +package net.silthus.template.bukkit.adapter; + +import java.util.concurrent.Executor; +import net.silthus.template.platform.plugin.scheduler.AbstractJavaScheduler; +import net.silthus.template.platform.plugin.scheduler.SchedulerAdapter; +import org.bukkit.plugin.java.JavaPlugin; + +public final class BukkitSchedulerAdapter extends AbstractJavaScheduler implements SchedulerAdapter { + + private final Executor sync; + + public BukkitSchedulerAdapter(JavaPlugin loader) { + this.sync = r -> loader.getServer().getScheduler().scheduleSyncDelayedTask(loader, r); + } + + @Override + public Executor sync() { + return this.sync; + } + +} diff --git a/platform/bukkit/src/main/java/net/silthus/template/bukkit/adapter/BukkitSenderFactory.java b/platform/bukkit/src/main/java/net/silthus/template/bukkit/adapter/BukkitSenderFactory.java new file mode 100644 index 0000000..74e0a76 --- /dev/null +++ b/platform/bukkit/src/main/java/net/silthus/template/bukkit/adapter/BukkitSenderFactory.java @@ -0,0 +1,98 @@ +/* + * sChat, a Supercharged Minecraft Chat Plugin + * Copyright (C) Silthus + * Copyright (C) sChat team and contributors + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +package net.silthus.template.bukkit.adapter; + +import java.util.UUID; +import net.kyori.adventure.platform.bukkit.BukkitAudiences; +import net.kyori.adventure.text.Component; +import net.silthus.template.identity.Identity; +import net.silthus.template.platform.plugin.scheduler.SchedulerAdapter; +import net.silthus.template.platform.sender.SenderFactory; +import org.bukkit.Bukkit; +import org.bukkit.OfflinePlayer; +import org.bukkit.command.CommandSender; +import org.bukkit.command.ConsoleCommandSender; +import org.bukkit.command.RemoteConsoleCommandSender; +import org.bukkit.entity.Player; + +import static net.silthus.template.bukkit.adapter.BukkitIdentityAdapter.identity; +import static net.silthus.template.platform.sender.Sender.CONSOLE; + +public final class BukkitSenderFactory extends SenderFactory { + + private final BukkitAudiences audiences; + private final SchedulerAdapter scheduler; + + public BukkitSenderFactory(BukkitAudiences audiences, SchedulerAdapter scheduler) { + this.audiences = audiences; + this.scheduler = scheduler; + } + + @Override + protected Class getSenderType() { + return CommandSender.class; + } + + @Override + protected Identity getIdentity(CommandSender sender) { + if (sender instanceof OfflinePlayer player) + return identity(player); + return CONSOLE; + } + + @Override + protected void sendMessage(CommandSender sender, Component message) { + if (canSendAsync(sender)) + audiences.sender(sender).sendMessage(message); + else + scheduler.executeSync(() -> this.audiences.sender(sender).sendMessage(message)); + } + + private boolean canSendAsync(CommandSender sender) { + return sender instanceof Player || sender instanceof ConsoleCommandSender || sender instanceof RemoteConsoleCommandSender; + } + + @Override + protected boolean hasPermission(CommandSender sender, String node) { + return sender.hasPermission(node); + } + + @Override + protected void performCommand(CommandSender sender, String command) { + Bukkit.getServer().dispatchCommand(sender, command); + } + + @Override + protected boolean isConsole(CommandSender sender) { + return sender instanceof ConsoleCommandSender || sender instanceof RemoteConsoleCommandSender; + } + + @Override + public boolean isPlayerOnline(UUID playerId) { + final Player player = Bukkit.getPlayer(playerId); + return player != null && player.isOnline(); + } + + @Override + public void close() { + super.close(); + this.audiences.close(); + } +} diff --git a/platform/bukkit/src/main/resources/config.yml b/platform/bukkit/src/main/resources/config.yml new file mode 100644 index 0000000..1126889 --- /dev/null +++ b/platform/bukkit/src/main/resources/config.yml @@ -0,0 +1 @@ +test: "Hi from the bukkit config" \ No newline at end of file diff --git a/platform/bukkit/src/test/java/net/silthus/template/bukkit/BukkitPluginTests.java b/platform/bukkit/src/test/java/net/silthus/template/bukkit/BukkitPluginTests.java new file mode 100644 index 0000000..91c0175 --- /dev/null +++ b/platform/bukkit/src/test/java/net/silthus/template/bukkit/BukkitPluginTests.java @@ -0,0 +1,72 @@ +/* + * sChat, a Supercharged Minecraft Chat Plugin + * Copyright (C) Silthus + * Copyright (C) sChat team and contributors + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +package net.silthus.template.bukkit; + +import be.seeseemelk.mockbukkit.MockBukkit; +import be.seeseemelk.mockbukkit.entity.PlayerMock; +import org.bukkit.ChatColor; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Nested; +import org.junit.jupiter.api.Test; + +class BukkitPluginTests extends BukkitTests { + private BukkitLoader plugin; + + @BeforeEach + void setUp() { + plugin = MockBukkit.load(BukkitLoader.class); + } + + @Nested + class CommandTests { + private PlayerMock player; + + @BeforeEach + void setUp() { + player = server.addPlayer(); + } + + @Test + void given_player_has_no_permission_cannot_execute_command() { + player.performCommand("myplugin test"); + assertLastMessageContains(player, "you do not have permission to perform this command"); + } + + @Nested + class given_player_with_permission { + @BeforeEach + void setUp() { + player.setOp(true); + } + + @Test + void then_prints_config_message() { + player.performCommand("myplugin test"); + assertLastMessageIs(player, ChatColor.GREEN + "Message: Hi from the bukkit config."); + } + + @Test + void given_argument_prints_argument() { + player.performCommand("myplugin test it works ;)"); + assertLastMessageIs(player, ChatColor.GREEN + "Message: it works ;)."); + } + } + } +} diff --git a/platform/bukkit/src/test/java/net/silthus/template/bukkit/BukkitTests.java b/platform/bukkit/src/test/java/net/silthus/template/bukkit/BukkitTests.java new file mode 100644 index 0000000..48100f0 --- /dev/null +++ b/platform/bukkit/src/test/java/net/silthus/template/bukkit/BukkitTests.java @@ -0,0 +1,95 @@ +/* + * sChat, a Supercharged Minecraft Chat Plugin + * Copyright (C) Silthus + * Copyright (C) sChat team and contributors + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +package net.silthus.template.bukkit; + +import be.seeseemelk.mockbukkit.MockBukkit; +import be.seeseemelk.mockbukkit.MockPlugin; +import be.seeseemelk.mockbukkit.ServerMock; +import be.seeseemelk.mockbukkit.command.ConsoleCommandSenderMock; +import be.seeseemelk.mockbukkit.entity.PlayerMock; +import java.util.function.Supplier; +import net.kyori.adventure.platform.bukkit.BukkitAudiences; +import org.jetbrains.annotations.Nullable; +import org.junit.jupiter.api.AfterAll; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.BeforeEach; + +import static org.assertj.core.api.Assertions.assertThat; + +public abstract class BukkitTests { + protected static ServerMock server; + protected static MockPlugin mockPlugin; + protected static BukkitAudiences audiences; + + @BeforeAll + static void beforeAll() { + server = MockBukkit.mock(); + mockPlugin = MockBukkit.createMockPlugin(); + } + + @AfterAll + static void afterAll() { + MockBukkit.unmock(); + } + + @BeforeEach + void setup() { + audiences = BukkitAudiences.create(mockPlugin); + } + + @AfterEach + void teardown() { + audiences.close(); + } + + protected void assertLastMessageIs(ConsoleCommandSenderMock mock, String message) { + assertTrimmedEquals(getLastMessage(mock::nextMessage), message); + } + + protected void assertLastMessageIs(PlayerMock player, String message) { + assertTrimmedEquals(getLastMessage(player::nextMessage), message); + } + + @Nullable + private String getLastMessage(Supplier nextMessageSupplier) { + String nextMessage; + String lastMessage = null; + while ((nextMessage = nextMessageSupplier.get()) != null) { + lastMessage = nextMessage; + } + return lastMessage; + } + + protected void assertLastMessageContains(PlayerMock player, String message) { + assertThat(trim(getLastMessage(player::nextMessage))).contains(trim(message)); + } + + private void assertTrimmedEquals(String lastMessage, String message) { + assertThat(trim(lastMessage)).isEqualTo(trim(message)); + } + + @Nullable + private String trim(String text) { + if (text != null) + text = text.trim(); + return text; + } +} diff --git a/platform/bukkit/src/test/java/net/silthus/template/bukkit/adapter/BukkitSenderFactoryTests.java b/platform/bukkit/src/test/java/net/silthus/template/bukkit/adapter/BukkitSenderFactoryTests.java new file mode 100644 index 0000000..8cd028c --- /dev/null +++ b/platform/bukkit/src/test/java/net/silthus/template/bukkit/adapter/BukkitSenderFactoryTests.java @@ -0,0 +1,166 @@ +/* + * sChat, a Supercharged Minecraft Chat Plugin + * Copyright (C) Silthus + * Copyright (C) sChat team and contributors + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +package net.silthus.template.bukkit.adapter; + +import be.seeseemelk.mockbukkit.command.ConsoleCommandSenderMock; +import be.seeseemelk.mockbukkit.entity.PlayerMock; +import net.kyori.adventure.text.Component; +import net.silthus.template.bukkit.BukkitTests; +import net.silthus.template.platform.plugin.scheduler.SchedulerAdapter; +import net.silthus.template.platform.sender.Sender; +import org.bukkit.ChatColor; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Nested; +import org.junit.jupiter.api.Test; + +import static net.kyori.adventure.text.Component.text; +import static net.kyori.adventure.text.format.NamedTextColor.GREEN; +import static net.kyori.adventure.text.format.NamedTextColor.RED; +import static net.kyori.adventure.text.format.TextDecoration.BOLD; +import static org.assertj.core.api.Assertions.assertThat; +import static org.mockito.Mockito.mock; + +class BukkitSenderFactoryTests extends BukkitTests { + + private BukkitSenderFactory factory; + private PlayerMock player; + + @BeforeEach + void setUp() { + factory = new BukkitSenderFactory(audiences, mock(SchedulerAdapter.class)); + player = server.addPlayer(); + } + + private Sender chatter() { + return factory.wrap(player); + } + + private void assertDisplayName(Component name) { + assertThat(chatter().getDisplayName()).isEqualTo(name); + } + + @Nested + class wrap { + + @Test + void is_not_null() { + assertThat(chatter()).isNotNull(); + } + + @Test + void has_same_id_as_player() { + assertThat(chatter().getUniqueId()).isEqualTo(player.getUniqueId()); + } + + @Test + void has_same_name_as_player() { + assertThat(chatter().getName()).isEqualTo(player.getName()); + } + + @Test + void has_same_display_name_as_player() { + assertDisplayName(text(player.getDisplayName())); + } + + @Nested + class given_player_display_name_changes { + @BeforeEach + void setUp() { + player.setDisplayName("My Name"); + } + + @Test + void then_user_display_name_changes() { + final Component name = text("My Name"); + assertDisplayName(name); + } + } + + @Nested + class given_formatted_player_display_name { + @BeforeEach + void setUp() { + player.setDisplayName(ChatColor.RED + "" + ChatColor.BOLD + "Mr." + ChatColor.GREEN + " Bob"); + } + + @Test + void then_user_display_name_is_formatted() { + assertDisplayName(text().append(text("Mr.", RED, BOLD)).append(text(" Bob", GREEN)).build()); + } + } + + @Nested + class given_player_with_no_permissions { + @Test + void then_hasPermission_returns_false() { + assertThat(chatter().hasPermission("foobar")).isFalse(); + } + } + + @Nested + class given_player_with_permission { + @BeforeEach + void setUp() { + player.addAttachment(mockPlugin, "foobar", true); + } + + @Test + void then_user_hasPermission_returns_true() { + assertThat(chatter().hasPermission("foobar")).isTrue(); + } + } + + @Nested + class when_sendMessage_is_called { + @BeforeEach + void setUp() { + chatter().sendMessage(text("Hi")); + } + + @Test + void then_player_receives_message() { + assertThat(player.nextMessage()).isEqualTo("Hi"); + } + } + + @Nested + class given_console_sender { + private ConsoleCommandSenderMock consoleSender; + private Sender console; + + @BeforeEach + void setUp() { + consoleSender = (ConsoleCommandSenderMock) server.getConsoleSender(); + console = factory.wrap(consoleSender); + } + + @Test + void then_uses_console_properties() { + assertThat(console.getIdentity()).isEqualTo(Sender.CONSOLE); + } + + @Test + void then_sendMessage_sends_message_to_console() { + console.sendMessage(text("Hi")); + assertLastMessageIs(consoleSender, "Hi"); + } + } + } +} diff --git a/platform/bukkit/src/test/resources/junit-platform.properties b/platform/bukkit/src/test/resources/junit-platform.properties new file mode 100644 index 0000000..5d02964 --- /dev/null +++ b/platform/bukkit/src/test/resources/junit-platform.properties @@ -0,0 +1,19 @@ +# +# sChat, a Supercharged Minecraft Chat Plugin +# Copyright (C) Silthus +# Copyright (C) sChat team and contributors +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see . +# +junit.jupiter.displayname.generator.default=org.junit.jupiter.api.DisplayNameGenerator$ReplaceUnderscores \ No newline at end of file diff --git a/platform/bungeecord/build.gradle b/platform/bungeecord/build.gradle new file mode 100644 index 0000000..5474862 --- /dev/null +++ b/platform/bungeecord/build.gradle @@ -0,0 +1,19 @@ +plugins { + id 'java' +} + +group 'net.silthus' +version '4.5.2' + +repositories { + mavenCentral() +} + +dependencies { + testImplementation 'org.junit.jupiter:junit-jupiter-api:5.8.1' + testRuntimeOnly 'org.junit.jupiter:junit-jupiter-engine:5.8.1' +} + +test { + useJUnitPlatform() +} \ No newline at end of file diff --git a/platform/bungeecord/src/test/resources/junit-platform.properties b/platform/bungeecord/src/test/resources/junit-platform.properties new file mode 100644 index 0000000..5d02964 --- /dev/null +++ b/platform/bungeecord/src/test/resources/junit-platform.properties @@ -0,0 +1,19 @@ +# +# sChat, a Supercharged Minecraft Chat Plugin +# Copyright (C) Silthus +# Copyright (C) sChat team and contributors +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see . +# +junit.jupiter.displayname.generator.default=org.junit.jupiter.api.DisplayNameGenerator$ReplaceUnderscores \ No newline at end of file diff --git a/platform/minestom/build.gradle b/platform/minestom/build.gradle new file mode 100644 index 0000000..5474862 --- /dev/null +++ b/platform/minestom/build.gradle @@ -0,0 +1,19 @@ +plugins { + id 'java' +} + +group 'net.silthus' +version '4.5.2' + +repositories { + mavenCentral() +} + +dependencies { + testImplementation 'org.junit.jupiter:junit-jupiter-api:5.8.1' + testRuntimeOnly 'org.junit.jupiter:junit-jupiter-engine:5.8.1' +} + +test { + useJUnitPlatform() +} \ No newline at end of file diff --git a/platform/minestom/src/test/resources/junit-platform.properties b/platform/minestom/src/test/resources/junit-platform.properties new file mode 100644 index 0000000..5d02964 --- /dev/null +++ b/platform/minestom/src/test/resources/junit-platform.properties @@ -0,0 +1,19 @@ +# +# sChat, a Supercharged Minecraft Chat Plugin +# Copyright (C) Silthus +# Copyright (C) sChat team and contributors +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see . +# +junit.jupiter.displayname.generator.default=org.junit.jupiter.api.DisplayNameGenerator$ReplaceUnderscores \ No newline at end of file diff --git a/platform/nukkit/build.gradle b/platform/nukkit/build.gradle new file mode 100644 index 0000000..5474862 --- /dev/null +++ b/platform/nukkit/build.gradle @@ -0,0 +1,19 @@ +plugins { + id 'java' +} + +group 'net.silthus' +version '4.5.2' + +repositories { + mavenCentral() +} + +dependencies { + testImplementation 'org.junit.jupiter:junit-jupiter-api:5.8.1' + testRuntimeOnly 'org.junit.jupiter:junit-jupiter-engine:5.8.1' +} + +test { + useJUnitPlatform() +} \ No newline at end of file diff --git a/platform/nukkit/src/test/resources/junit-platform.properties b/platform/nukkit/src/test/resources/junit-platform.properties new file mode 100644 index 0000000..5d02964 --- /dev/null +++ b/platform/nukkit/src/test/resources/junit-platform.properties @@ -0,0 +1,19 @@ +# +# sChat, a Supercharged Minecraft Chat Plugin +# Copyright (C) Silthus +# Copyright (C) sChat team and contributors +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see . +# +junit.jupiter.displayname.generator.default=org.junit.jupiter.api.DisplayNameGenerator$ReplaceUnderscores \ No newline at end of file diff --git a/platform/paper/build.gradle b/platform/paper/build.gradle new file mode 100644 index 0000000..5474862 --- /dev/null +++ b/platform/paper/build.gradle @@ -0,0 +1,19 @@ +plugins { + id 'java' +} + +group 'net.silthus' +version '4.5.2' + +repositories { + mavenCentral() +} + +dependencies { + testImplementation 'org.junit.jupiter:junit-jupiter-api:5.8.1' + testRuntimeOnly 'org.junit.jupiter:junit-jupiter-engine:5.8.1' +} + +test { + useJUnitPlatform() +} \ No newline at end of file diff --git a/platform/paper/src/test/resources/junit-platform.properties b/platform/paper/src/test/resources/junit-platform.properties new file mode 100644 index 0000000..5d02964 --- /dev/null +++ b/platform/paper/src/test/resources/junit-platform.properties @@ -0,0 +1,19 @@ +# +# sChat, a Supercharged Minecraft Chat Plugin +# Copyright (C) Silthus +# Copyright (C) sChat team and contributors +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see . +# +junit.jupiter.displayname.generator.default=org.junit.jupiter.api.DisplayNameGenerator$ReplaceUnderscores \ No newline at end of file diff --git a/platform/sponge/build.gradle b/platform/sponge/build.gradle new file mode 100644 index 0000000..5474862 --- /dev/null +++ b/platform/sponge/build.gradle @@ -0,0 +1,19 @@ +plugins { + id 'java' +} + +group 'net.silthus' +version '4.5.2' + +repositories { + mavenCentral() +} + +dependencies { + testImplementation 'org.junit.jupiter:junit-jupiter-api:5.8.1' + testRuntimeOnly 'org.junit.jupiter:junit-jupiter-engine:5.8.1' +} + +test { + useJUnitPlatform() +} \ No newline at end of file diff --git a/platform/sponge/src/test/resources/junit-platform.properties b/platform/sponge/src/test/resources/junit-platform.properties new file mode 100644 index 0000000..5d02964 --- /dev/null +++ b/platform/sponge/src/test/resources/junit-platform.properties @@ -0,0 +1,19 @@ +# +# sChat, a Supercharged Minecraft Chat Plugin +# Copyright (C) Silthus +# Copyright (C) sChat team and contributors +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see . +# +junit.jupiter.displayname.generator.default=org.junit.jupiter.api.DisplayNameGenerator$ReplaceUnderscores \ No newline at end of file diff --git a/platform/src/main/java/net/silthus/template/platform/command/commands/ExampleCommand.java b/platform/src/main/java/net/silthus/template/platform/command/commands/ExampleCommand.java index 69ffb7c..f416647 100644 --- a/platform/src/main/java/net/silthus/template/platform/command/commands/ExampleCommand.java +++ b/platform/src/main/java/net/silthus/template/platform/command/commands/ExampleCommand.java @@ -4,12 +4,16 @@ import cloud.commandframework.annotations.AnnotationParser; import cloud.commandframework.annotations.Argument; import cloud.commandframework.annotations.CommandMethod; +import cloud.commandframework.annotations.CommandPermission; +import cloud.commandframework.annotations.specifier.Greedy; import net.silthus.template.platform.command.Command; import net.silthus.template.platform.config.Config; import net.silthus.template.platform.sender.Sender; import static net.silthus.template.platform.config.ConfigKeys.TEST; +import static net.silthus.template.platform.locale.Messages.CONFIG_RELOADED; import static net.silthus.template.platform.locale.Messages.TEST_MESSAGE; +import static net.silthus.template.platform.plugin.TemplatePlugin.NAMESPACE; public class ExampleCommand implements Command { private final Config config; @@ -21,8 +25,16 @@ public void register(CommandManager commandManager, AnnotationParser TEST_MESSAGE = text -> translatable() - .key("my-plugin.command.test") + // &aConfiguration successfully reloaded. + Args0 CONFIG_RELOADED = () -> prefixed(translatable("command.reload") + .color(GREEN) + .append(FULL_STOP) + .build()); + + // &aMessage: {0} + Args1 TEST_MESSAGE = text -> translatable("command.test") .color(GREEN) .args(text(text)) .append(FULL_STOP) @@ -73,7 +84,7 @@ static TextComponent prefixed(ComponentLike component) { static Component formatStringList(Collection strings) { Iterator it = strings.iterator(); if (!it.hasNext()) { - return translatable("schat.command.misc.none", AQUA); // "&bNone" + return translatable("command.misc.none").color(AQUA).build(); // "&bNone" } TextComponent.Builder builder = text().color(DARK_AQUA).content(it.next()); diff --git a/platform/src/main/java/net/silthus/template/platform/locale/TranslationManager.java b/platform/src/main/java/net/silthus/template/platform/locale/TranslationManager.java new file mode 100644 index 0000000..7dd248d --- /dev/null +++ b/platform/src/main/java/net/silthus/template/platform/locale/TranslationManager.java @@ -0,0 +1,231 @@ +package net.silthus.template.platform.locale; + +import com.google.common.collect.Maps; +import java.io.BufferedReader; +import java.io.IOException; +import java.nio.charset.StandardCharsets; +import java.nio.file.Files; +import java.nio.file.Path; +import java.util.Collections; +import java.util.HashMap; +import java.util.List; +import java.util.Locale; +import java.util.Map; +import java.util.PropertyResourceBundle; +import java.util.ResourceBundle; +import java.util.Set; +import java.util.concurrent.ConcurrentHashMap; +import java.util.logging.Level; +import java.util.stream.Collectors; +import java.util.stream.Stream; +import lombok.extern.java.Log; +import net.kyori.adventure.key.Key; +import net.kyori.adventure.text.Component; +import net.kyori.adventure.translation.GlobalTranslator; +import net.kyori.adventure.translation.TranslationRegistry; +import net.kyori.adventure.translation.Translator; +import net.kyori.adventure.util.UTF8ResourceBundleControl; +import org.jetbrains.annotations.Nullable; + +import static net.silthus.template.platform.plugin.TemplatePlugin.NAMESPACE; +import static net.silthus.template.platform.plugin.TemplatePlugin.PLUGIN_NAME; + +@Log(topic = PLUGIN_NAME) +public class TranslationManager { + public static final Locale DEFAULT_LOCALE = Locale.ENGLISH; + + private final Set installed = ConcurrentHashMap.newKeySet(); + private TranslationRegistry registry; + + private final Path translationsDirectory; + private final Path repositoryTranslationsDirectory; + private final Path customTranslationsDirectory; + + public TranslationManager(Path configDirectory) { + this.translationsDirectory = configDirectory.resolve("translations"); + this.repositoryTranslationsDirectory = this.translationsDirectory.resolve("repository"); + this.customTranslationsDirectory = this.translationsDirectory.resolve("custom"); + + try { + Files.createDirectories(repositoryTranslationsDirectory); + Files.createDirectories(customTranslationsDirectory); + } catch (IOException e) { + // ignore + } + } + + public Path getTranslationsDirectory() { + return this.translationsDirectory; + } + + public Path getRepositoryTranslationsDirectory() { + return this.repositoryTranslationsDirectory; + } + + public Path getRepositoryStatusFile() { + return this.repositoryTranslationsDirectory.resolve("status.json"); + } + + public Set getInstalledLocales() { + return Collections.unmodifiableSet(this.installed); + } + + public void reload() { + clearPreviousRegistry(); + createTranslationRegistry(); + loadTranslations(); + registerRegistryAsGlobalSource(); + } + + private void registerRegistryAsGlobalSource() { + GlobalTranslator.get().addSource(this.registry); + } + + private void loadTranslations() { + loadCustomTranslations(); + loadDefaultTranslations(); + loadFromResourceBundle(); + } + + private void loadDefaultTranslations() { + loadFromFileSystem(this.repositoryTranslationsDirectory, true); + } + + private void loadCustomTranslations() { + loadFromFileSystem(this.customTranslationsDirectory, false); + } + + private void createTranslationRegistry() { + this.registry = TranslationRegistry.create(Key.key(NAMESPACE, "main")); + this.registry.defaultLocale(DEFAULT_LOCALE); + } + + private void clearPreviousRegistry() { + if (this.registry != null) { + GlobalTranslator.get().removeSource(this.registry); + this.installed.clear(); + } + } + + /** + * Loads the base (English) translations from the jar file. + */ + private void loadFromResourceBundle() { + ResourceBundle bundle = ResourceBundle.getBundle(NAMESPACE, DEFAULT_LOCALE, UTF8ResourceBundleControl.get()); + try { + this.registry.registerAll(DEFAULT_LOCALE, bundle, false); + } catch (IllegalArgumentException e) { + if (!isAdventureDuplicatesException(e)) { + log.log(Level.WARNING, "Error loading default locale file", e); + } + } + } + + public static boolean isTranslationFile(Path path) { + return path.getFileName().toString().endsWith(".properties"); + } + + /** + * Loads custom translations (in any language) from the plugin configuration folder. + */ + public void loadFromFileSystem(Path directory, boolean suppressDuplicatesError) { + List translationFiles; + try (Stream stream = Files.list(directory)) { + translationFiles = stream.filter(TranslationManager::isTranslationFile).collect(Collectors.toList()); + } catch (IOException e) { + translationFiles = Collections.emptyList(); + } + + if (translationFiles.isEmpty()) { + return; + } + + Map loaded = new HashMap<>(); + for (Path translationFile : translationFiles) { + try { + Map.Entry result = loadTranslationFile(translationFile); + loaded.put(result.getKey(), result.getValue()); + } catch (Exception e) { + if (!suppressDuplicatesError || !isAdventureDuplicatesException(e)) { + log.log(Level.WARNING, "Error loading locale file: " + translationFile.getFileName(), e); + } + } + } + + // try registering the locale without a country code - if we don't already have a registration for that + loaded.forEach((locale, bundle) -> { + Locale localeWithoutCountry = new Locale(locale.getLanguage()); + if (!locale.equals(localeWithoutCountry) && !localeWithoutCountry.equals(DEFAULT_LOCALE) && this.installed.add(localeWithoutCountry)) { + try { + this.registry.registerAll(localeWithoutCountry, bundle, false); + } catch (IllegalArgumentException e) { + // ignore + } + } + }); + } + + private Map.Entry loadTranslationFile(Path translationFile) throws IOException { + String fileName = translationFile.getFileName().toString(); + String localeString = fileName.substring(0, fileName.length() - ".properties".length()); + Locale locale = parseLocale(localeString); + + if (locale == null) { + throw new IllegalStateException("Unknown locale '" + localeString + "' - unable to register."); + } + + PropertyResourceBundle bundle; + try (BufferedReader reader = Files.newBufferedReader(translationFile, StandardCharsets.UTF_8)) { + bundle = new PropertyResourceBundle(reader); + } + + this.registry.registerAll(locale, bundle, false); + this.installed.add(locale); + return Maps.immutableEntry(locale, bundle); + } + + @SuppressWarnings("BooleanMethodIsAlwaysInverted") + private static boolean isAdventureDuplicatesException(Exception e) { + return e instanceof IllegalArgumentException && (e.getMessage().startsWith("Invalid key") || e.getMessage().startsWith("Translation already exists")); + } + + public static Component render(Component component) { + return render(component, Locale.getDefault()); + } + + public static Component render(Component component, @Nullable String locale) { + return render(component, parseLocale(locale)); + } + + public static Component render(Component component, @Nullable Locale locale) { + if (locale == null) { + locale = Locale.getDefault(); + if (locale == null) { + locale = DEFAULT_LOCALE; + } + } + return GlobalTranslator.render(component, locale); + } + + public static @Nullable Locale parseLocale(@Nullable String locale) { + return locale == null ? null : Translator.parseLocale(locale); + } + + public static String localeDisplayName(Locale locale) { + if (locale.getLanguage().equals("zh")) { + if (locale.getCountry().equals("CN")) { + return "简体中文"; // Chinese (Simplified) + } else if (locale.getCountry().equals("TW")) { + return "繁體中文"; // Chinese (Traditional) + } + return locale.getDisplayCountry(locale) + locale.getDisplayLanguage(locale); + } + + if (locale.getLanguage().equals("en") && locale.getCountry().equals("PT")) { + return "Pirate"; + } + + return locale.getDisplayLanguage(locale); + } + +} diff --git a/platform/src/main/java/net/silthus/template/platform/plugin/AbstractTemplatePlugin.java b/platform/src/main/java/net/silthus/template/platform/plugin/AbstractTemplatePlugin.java index 616dee0..64ea21b 100644 --- a/platform/src/main/java/net/silthus/template/platform/plugin/AbstractTemplatePlugin.java +++ b/platform/src/main/java/net/silthus/template/platform/plugin/AbstractTemplatePlugin.java @@ -26,20 +26,24 @@ import java.nio.file.Path; import lombok.Getter; import net.silthus.template.platform.command.Commands; +import net.silthus.template.platform.command.commands.ExampleCommand; import net.silthus.template.platform.config.TemplateConfig; import net.silthus.template.platform.config.adapter.ConfigurationAdapter; +import net.silthus.template.platform.locale.TranslationManager; import net.silthus.template.platform.sender.Sender; import org.jetbrains.annotations.ApiStatus; @Getter public abstract class AbstractTemplatePlugin implements TemplatePlugin { + private TranslationManager translationManager; private TemplateConfig config; private Commands commands; @Override public final void load() { - + this.translationManager = new TranslationManager(getBootstrap().getConfigDirectory()); + this.translationManager.reload(); } @Override @@ -70,7 +74,7 @@ private void registerCommands() { } private void registerNativeCommands() { - commands.register(); + commands.register(new ExampleCommand(getConfig())); } @ApiStatus.OverrideOnly diff --git a/platform/src/main/java/net/silthus/template/platform/plugin/TemplatePlugin.java b/platform/src/main/java/net/silthus/template/platform/plugin/TemplatePlugin.java index 8cd048f..ebc95db 100644 --- a/platform/src/main/java/net/silthus/template/platform/plugin/TemplatePlugin.java +++ b/platform/src/main/java/net/silthus/template/platform/plugin/TemplatePlugin.java @@ -25,6 +25,9 @@ public interface TemplatePlugin { + String NAMESPACE = "myplugin"; // must be lowered key without special characters + String PLUGIN_NAME = "MyPlugin"; + void load(); void enable(); diff --git a/platform/src/main/resources/myplugin_en.properties b/platform/src/main/resources/myplugin_en.properties new file mode 100644 index 0000000..0d5fe96 --- /dev/null +++ b/platform/src/main/resources/myplugin_en.properties @@ -0,0 +1,3 @@ +myplugin.command.misc.none=None +myplugin.command.test=Message: {0} +myplugin.command.reload=Configuration successfully reloaded \ No newline at end of file diff --git a/platform/src/test/java/net/silthus/template/platform/command/commands/ExampleCommandTest.java b/platform/src/test/java/net/silthus/template/platform/command/commands/ExampleCommandTest.java index 6eb82c6..b3e70ad 100644 --- a/platform/src/test/java/net/silthus/template/platform/command/commands/ExampleCommandTest.java +++ b/platform/src/test/java/net/silthus/template/platform/command/commands/ExampleCommandTest.java @@ -21,19 +21,25 @@ void setUp() { register(new ExampleCommand(config)); } - @DisplayName("/template test ") + @DisplayName("/myplugin test ") @Nested class testCommand { @Test void sends_input_message() { - cmd("template test Hi!"); + cmd("myplugin test Hi!"); assertLastMessageIs(TEST_MESSAGE.build("Hi!")); } @Test void given_no_input_uses_config_value() { - cmd("template test"); + cmd("myplugin test"); assertLastMessageIs(TEST_MESSAGE.build(config.get(TEST))); } + + @Test + void given_multiple_words_prints_all() { + cmd("myplugin test hi there"); + assertLastMessageIs(TEST_MESSAGE.build("hi there")); + } } } \ No newline at end of file diff --git a/platform/velocity/build.gradle b/platform/velocity/build.gradle new file mode 100644 index 0000000..5474862 --- /dev/null +++ b/platform/velocity/build.gradle @@ -0,0 +1,19 @@ +plugins { + id 'java' +} + +group 'net.silthus' +version '4.5.2' + +repositories { + mavenCentral() +} + +dependencies { + testImplementation 'org.junit.jupiter:junit-jupiter-api:5.8.1' + testRuntimeOnly 'org.junit.jupiter:junit-jupiter-engine:5.8.1' +} + +test { + useJUnitPlatform() +} \ No newline at end of file