diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml new file mode 100644 index 0000000..550d89a --- /dev/null +++ b/.github/workflows/build.yml @@ -0,0 +1,34 @@ +name: Build and Test + +on: + push: + branches: + - develop + - master + pull_request: + branches: + - develop + - master + +jobs: + build-and-test: + runs-on: ubuntu-latest + permissions: + contents: read + + steps: + - name: Checkout repository + uses: actions/checkout@v4 + + - name: Set up Java 21 + uses: actions/setup-java@v4 + with: + java-version: '21' + distribution: 'temurin' + cache: 'maven' + + - name: Build with Maven + run: mvn -B clean compile + + - name: Run tests + run: mvn -B test diff --git a/CLAUDE.md b/CLAUDE.md new file mode 100644 index 0000000..f3063dd --- /dev/null +++ b/CLAUDE.md @@ -0,0 +1,128 @@ +# CLAUDE.md + +This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository. + +## Project Overview + +Chat is a BentoBox addon for Minecraft (Paper) that provides Team Chat and Island Chat for island-type game modes (BSkyBlock, AcidIsland, CaveBlock, SkyGrid). It requires Java 21 and targets Paper 1.21.11. + +## Build Commands + +```bash +mvn clean package # Build (default goal) +mvn test # Run all tests +mvn -Dtest=ChatListenerTest test # Run a single test class +mvn -Dtest=ChatListenerTest#onChat test # Run a single test method +``` + +The build produces `target/Chat-{version}-SNAPSHOT-LOCAL.jar`. + +## Architecture + +This is a **BentoBox Pladdon** (plugin-addon): + +- `ChatPladdon` — Entry point loaded by BentoBox's Pladdon system, creates the `Chat` addon instance +- `Chat` — Main addon class. On enable: loads `Settings` from config.yml, registers commands per game mode, creates `ChatListener`, and registers the `IsTeamChatHandler` request handler +- `ChatListener` — Core logic. Implements both `Listener` and `EventExecutor`. Intercepts `AsyncPlayerChatEvent` (registered manually with configurable priority from Settings). Maintains in-memory sets for team chat users, island chatters, team spies, and island spies. All toggle state is held in memory (not persisted) +- `Settings` — BentoBox `ConfigObject` stored at `addons/Chat/config.yml`. Controls which game modes have team/island chat, logging, event priority, and default chat game mode +- `IsTeamChatHandler` — BentoBox request handler that lets other addons query if a player has team chat enabled + +Commands follow BentoBox's `CompositeCommand` pattern and are registered dynamically onto each game mode's player/admin command trees: +- Player: `IslandChatCommand` (chat), `IslandTeamChatCommand` (teamchat), `IslandTeamMuteCommand` (muteteamchat) +- Admin: `AdminIslandChatSpyCommand` (chatspy), `AdminTeamChatSpyCommand` (teamchatspy) + +## Testing + +Tests use JUnit 5 + Mockito + MockBukkit. The `CommonTestSetup` base class provides standard mocks for BentoBox, Bukkit, Player, World, Island, etc. New test classes should extend `CommonTestSetup` and call `super.setUp()`/`super.tearDown()`. The `WhiteBox` utility sets private/static fields via reflection for test setup. + +Localization strings are defined in `src/main/resources/locales/` (en-US.yml is the primary locale). + + +## Dependency Source Lookup + +When you need to inspect source code for a dependency (e.g., BentoBox, addons): + +1. **Check local Maven repo first**: `~/.m2/repository/` — sources jars are named `*-sources.jar` +2. **Check the workspace**: Look for sibling directories or Git submodules that may contain the dependency as a local project (e.g., `../bentoBox`, `../addon-*`) +3. **Check Maven local cache for already-extracted sources** before downloading anything +4. Only download a jar or fetch from the internet if the above steps yield nothing useful + +Prefer reading `.java` source files directly from a local Git clone over decompiling or extracting a jar. + +In general, the latest version of BentoBox should be targeted. + +## Project Layout + +Related projects are checked out as siblings under `~/git/`: + +**Core:** +- `bentobox/` — core BentoBox framework + +**Game modes:** +- `addon-acidisland/` — AcidIsland game mode +- `addon-bskyblock/` — BSkyBlock game mode +- `Boxed/` — Boxed game mode (expandable box area) +- `CaveBlock/` — CaveBlock game mode +- `OneBlock/` — AOneBlock game mode +- `SkyGrid/` — SkyGrid game mode +- `RaftMode/` — Raft survival game mode +- `StrangerRealms/` — StrangerRealms game mode +- `Brix/` — plot game mode +- `parkour/` — Parkour game mode +- `poseidon/` — Poseidon game mode +- `gg/` — gg game mode + +**Addons:** +- `addon-level/` — island level calculation +- `addon-challenges/` — challenges system +- `addon-welcomewarpsigns/` — warp signs +- `addon-limits/` — block/entity limits +- `addon-invSwitcher/` / `invSwitcher/` — inventory switcher +- `addon-biomes/` / `Biomes/` — biomes management +- `Bank/` — island bank +- `Border/` — world border for islands +- `Chat/` — island chat +- `CheckMeOut/` — island submission/voting +- `ControlPanel/` — game mode control panel +- `Converter/` — ASkyBlock to BSkyBlock converter +- `DimensionalTrees/` — dimension-specific trees +- `discordwebhook/` — Discord integration +- `Downloads/` — BentoBox downloads site +- `DragonFights/` — per-island ender dragon fights +- `ExtraMobs/` — additional mob spawning rules +- `FarmersDance/` — twerking crop growth +- `GravityFlux/` — gravity addon +- `Greenhouses-addon/` — greenhouse biomes +- `IslandFly/` — island flight permission +- `IslandRankup/` — island rankup system +- `Likes/` — island likes/dislikes +- `Limits/` — block/entity limits +- `lost-sheep/` — lost sheep adventure +- `MagicCobblestoneGenerator/` — custom cobblestone generator +- `PortalStart/` — portal-based island start +- `pp/` — pp addon +- `Regionerator/` — region management +- `Residence/` — residence addon +- `TopBlock/` — top ten for OneBlock +- `TwerkingForTrees/` — twerking tree growth +- `Upgrades/` — island upgrades (Vault) +- `Visit/` — island visiting +- `weblink/` — web link addon +- `CrowdBound/` — CrowdBound addon + +**Data packs:** +- `BoxedDataPack/` — advancement datapack for Boxed + +**Documentation & tools:** +- `docs/` — main documentation site +- `docs-chinese/` — Chinese documentation +- `docs-french/` — French documentation +- `BentoBoxWorld.github.io/` — GitHub Pages site +- `website/` — website +- `translation-tool/` — translation tool + +Check these for source before any network fetch. + +## Key Dependencies (source locations) + +- `world.bentobox:bentobox` `3.14.0-SNAPSHOT` → `~/git/bentobox/src/` diff --git a/pom.xml b/pom.xml index 12209fa..cd69f81 100644 --- a/pom.xml +++ b/pom.xml @@ -60,16 +60,18 @@ UTF-8 21 - 2.0.9 + 5.10.2 + 5.11.0 + v1.21-SNAPSHOT - 1.21.3-R0.1-SNAPSHOT - 2.7.1-SNAPSHOT + 1.21.11-R0.1-SNAPSHOT + 3.14.0-SNAPSHOT ${build.version}-SNAPSHOT -LOCAL - 1.3.0 + 1.4.0 @@ -115,49 +117,81 @@ + - spigot-repo - https://hub.spigotmc.org/nexus/content/repositories/snapshots + jitpack.io + https://jitpack.io + true + true + bentoboxworld - https://repo.codemc.org/repository/bentoboxworld + https://repo.codemc.org/repository/bentoboxworld/ + true + true + codemc-repo https://repo.codemc.org/repository/maven-public/ + true + true - codemc-public - https://repo.codemc.org/repository/maven-public/ + codemc + https://repo.codemc.org/repository/maven-snapshots/ + false + true + + + + papermc + https://repo.papermc.io/repository/maven-public/ + true + true - + - org.spigotmc - spigot-api - ${spigot.version} + io.papermc.paper + paper-api + ${paper.version} provided - + - org.mockito - mockito-core - 3.11.2 + com.github.MockBukkit + MockBukkit + ${mock-bukkit.version} + test + + + + org.junit.jupiter + junit-jupiter-api + ${junit.version} test - org.powermock - powermock-module-junit4 - ${powermock.version} + org.junit.jupiter + junit-jupiter-engine + ${junit.version} test + - org.powermock - powermock-api-mockito2 - ${powermock.version} + org.mockito + mockito-core + ${mockito.version} + test + + + org.mockito + mockito-junit-jupiter + ${mockito.version} test @@ -195,70 +229,68 @@ org.apache.maven.plugins maven-clean-plugin - 3.1.0 + 3.5.0 org.apache.maven.plugins maven-resources-plugin - 3.1.0 + 3.5.0 org.apache.maven.plugins maven-compiler-plugin - 3.7.0 + 3.15.0 ${java.version} ${java.version} ${java.version} + true org.apache.maven.plugins maven-surefire-plugin - 3.0.0-M5 + 3.5.5 - - ${argLine} + + **/*Test.java + **/*Test?.java + **/*Test??.java + + @{argLine} --add-opens java.base/java.lang=ALL-UNNAMED --add-opens java.base/java.math=ALL-UNNAMED --add-opens java.base/java.io=ALL-UNNAMED --add-opens java.base/java.util=ALL-UNNAMED - --add-opens - java.base/java.util.stream=ALL-UNNAMED + --add-opens java.base/java.util.stream=ALL-UNNAMED --add-opens java.base/java.text=ALL-UNNAMED - --add-opens - java.base/java.util.regex=ALL-UNNAMED - --add-opens - java.base/java.nio.channels.spi=ALL-UNNAMED + --add-opens java.base/java.util.regex=ALL-UNNAMED + --add-opens java.base/java.nio.channels.spi=ALL-UNNAMED --add-opens java.base/sun.nio.ch=ALL-UNNAMED --add-opens java.base/java.net=ALL-UNNAMED - --add-opens - java.base/java.util.concurrent=ALL-UNNAMED + --add-opens java.base/java.util.concurrent=ALL-UNNAMED --add-opens java.base/sun.nio.fs=ALL-UNNAMED --add-opens java.base/sun.nio.cs=ALL-UNNAMED --add-opens java.base/java.nio.file=ALL-UNNAMED - --add-opens - java.base/java.nio.charset=ALL-UNNAMED - --add-opens - java.base/java.lang.reflect=ALL-UNNAMED - --add-opens - java.logging/java.util.logging=ALL-UNNAMED + --add-opens java.base/java.nio.charset=ALL-UNNAMED + --add-opens java.base/java.lang.reflect=ALL-UNNAMED + --add-opens java.logging/java.util.logging=ALL-UNNAMED + --add-opens jdk.unsupported/sun.misc=ALL-UNNAMED --add-opens java.base/java.lang.ref=ALL-UNNAMED --add-opens java.base/java.util.jar=ALL-UNNAMED --add-opens java.base/java.util.zip=ALL-UNNAMED - org.apache.maven.plugins maven-jar-plugin - 3.2.0 + 3.5.0 org.apache.maven.plugins maven-javadoc-plugin - 3.1.1 + 3.12.0 public false @@ -277,7 +309,7 @@ org.apache.maven.plugins maven-source-plugin - 3.0.1 + 3.4.0 attach-sources @@ -290,21 +322,21 @@ org.apache.maven.plugins maven-install-plugin - 2.5.2 + 3.1.4 org.apache.maven.plugins maven-deploy-plugin - 2.8.2 + 3.1.4 org.jacoco jacoco-maven-plugin - 0.8.10 + 0.8.14 true - **/*Names* diff --git a/src/main/java/world/bentobox/chat/Chat.java b/src/main/java/world/bentobox/chat/Chat.java index a03cd87..0326453 100644 --- a/src/main/java/world/bentobox/chat/Chat.java +++ b/src/main/java/world/bentobox/chat/Chat.java @@ -1,6 +1,9 @@ package world.bentobox.chat; +import java.util.ArrayList; import java.util.HashSet; +import java.util.List; +import java.util.Map; import java.util.Optional; import java.util.Set; @@ -15,6 +18,7 @@ import world.bentobox.chat.commands.admin.AdminTeamChatSpyCommand; import world.bentobox.chat.commands.island.IslandChatCommand; import world.bentobox.chat.commands.island.IslandTeamChatCommand; +import world.bentobox.chat.commands.island.IslandTeamMuteCommand; import world.bentobox.chat.listeners.ChatListener; import world.bentobox.chat.requesthandlers.IsTeamChatHandler; @@ -67,6 +71,7 @@ private void setupCommands() { if (settings.getTeamChatGamemodes().contains(gameModeAddon.getDescription().getName())) { log("Hooking team chat into " + gameModeAddon.getDescription().getName()); gameModeAddon.getPlayerCommand().ifPresent(c -> new IslandTeamChatCommand(this, c, "teamchat")); + gameModeAddon.getPlayerCommand().ifPresent(c -> new IslandTeamMuteCommand(this, c, "muteteamchat")); gameModeAddon.getAdminCommand().ifPresent(c -> new AdminTeamChatSpyCommand(this, c, "teamchatspy")); registeredGameModes.add(gameModeAddon); } @@ -99,6 +104,28 @@ public boolean isRegisteredGameWorld(World world) { return registeredGameModes.parallelStream().anyMatch(gmw -> gmw.inWorld(world)); } + /** + * Get the game mode overworlds for team chat based on extra chat worlds configuration. + * If a world is listed as an extra chat world for one or more game modes, this returns + * the overworlds of those game modes. + * @param worldName - the world name to check + * @return list of game mode overworlds that have this world listed as extra + */ + public List getWorldsFromExtra(String worldName) { + Map> extraWorlds = settings.getExtraChatWorlds(); + List result = new ArrayList<>(); + for (Map.Entry> entry : extraWorlds.entrySet()) { + if (entry.getValue().stream().anyMatch(worldName::equalsIgnoreCase)) { + getPlugin().getAddonsManager().getGameModeAddons().stream() + .filter(gm -> entry.getKey().equalsIgnoreCase(gm.getDescription().getName())) + .findFirst() + .map(GameModeAddon::getOverWorld) + .ifPresent(result::add); + } + } + return result; + } + /** * Loads the Settings from the config. diff --git a/src/main/java/world/bentobox/chat/Settings.java b/src/main/java/world/bentobox/chat/Settings.java index 7690af0..e5efa84 100644 --- a/src/main/java/world/bentobox/chat/Settings.java +++ b/src/main/java/world/bentobox/chat/Settings.java @@ -1,7 +1,9 @@ package world.bentobox.chat; import java.util.Arrays; +import java.util.HashMap; import java.util.List; +import java.util.Map; import org.bukkit.event.EventPriority; @@ -27,6 +29,12 @@ public class Settings implements ConfigObject { @ConfigEntry(path = "team-chat.default-teamchat-gamemode") private String defaultChatGamemode = ""; + @ConfigComment("Additional chat worlds. List the additional worlds for each active game mode") + @ConfigComment("where team chat should be captured. If more than one game mode covers a world,") + @ConfigComment("then chat may go to multiple teams.") + @ConfigEntry(path = "team-chat.extra-chat-worlds") + private Map> extraChatWorlds = new HashMap<>(); + @ConfigComment("Log team chats to console.") @ConfigEntry(path = "team-chat.log") private boolean logTeamChats; @@ -121,6 +129,20 @@ public void setDefaultChatGamemode(String universaleChatGamemode) { this.defaultChatGamemode = universaleChatGamemode; } + /** + * @return the extraChatWorlds + */ + public Map> getExtraChatWorlds() { + return extraChatWorlds; + } + + /** + * @param extraChatWorlds the extraChatWorlds to set + */ + public void setExtraChatWorlds(Map> extraChatWorlds) { + this.extraChatWorlds = extraChatWorlds; + } + /** * @param eventPriority the eventPriority to set */ diff --git a/src/main/java/world/bentobox/chat/commands/island/IslandTeamMuteCommand.java b/src/main/java/world/bentobox/chat/commands/island/IslandTeamMuteCommand.java new file mode 100644 index 0000000..039b02c --- /dev/null +++ b/src/main/java/world/bentobox/chat/commands/island/IslandTeamMuteCommand.java @@ -0,0 +1,49 @@ +package world.bentobox.chat.commands.island; + +import java.util.List; + +import world.bentobox.bentobox.api.addons.Addon; +import world.bentobox.bentobox.api.commands.CompositeCommand; +import world.bentobox.bentobox.api.user.User; +import world.bentobox.chat.Chat; + +/** + * Allows a player to mute/unmute team chat messages. + * When muted, the player will not receive team chat messages. + * @author tastybento + */ +public class IslandTeamMuteCommand extends CompositeCommand { + + public IslandTeamMuteCommand(Addon addon, CompositeCommand parent, String label) { + super(addon, parent, label, "mtc"); + } + + @Override + public void setup() { + this.setPermission("chat.team-chat.mute"); + this.setDescription("chat.team-chat.mute.description"); + this.setOnlyPlayer(true); + setConfigurableRankCommand(); + } + + @Override + public boolean canExecute(User user, String label, List args) { + boolean hasTeam = this.getIslands().inTeam(getWorld(), user.getUniqueId()); + if (!hasTeam) { + user.sendMessage("general.errors.no-team"); + } + return hasTeam; + } + + @Override + public boolean execute(User user, String label, List args) { + Chat addon = this.getAddon(); + + if (addon.getListener().toggleMuteTeamChat(user.getUniqueId())) { + user.sendMessage("chat.team-chat.mute.mute-on"); + } else { + user.sendMessage("chat.team-chat.mute.mute-off"); + } + return true; + } +} diff --git a/src/main/java/world/bentobox/chat/listeners/ChatListener.java b/src/main/java/world/bentobox/chat/listeners/ChatListener.java index 1ee29af..1547591 100644 --- a/src/main/java/world/bentobox/chat/listeners/ChatListener.java +++ b/src/main/java/world/bentobox/chat/listeners/ChatListener.java @@ -1,7 +1,9 @@ package world.bentobox.chat.listeners; +import java.util.ArrayList; import java.util.HashMap; import java.util.HashSet; +import java.util.List; import java.util.Map; import java.util.Set; import java.util.UUID; @@ -37,6 +39,8 @@ public class ChatListener implements Listener, EventExecutor { // List of which users are spying or not on team and island chat private final Set spies; private final Set islandSpies; + // List of which users have muted team chat + private final Set teamChatMuted; public ChatListener(Chat addon) { this.teamChatUsers = new HashSet<>(); @@ -45,6 +49,8 @@ public ChatListener(Chat addon) { // Initialize spies spies = new HashSet<>(); islandSpies = new HashSet<>(); + // Initialize muted + teamChatMuted = new HashSet<>(); } @Override @@ -59,53 +65,81 @@ public void execute(Listener listener, Event e) { onChat((AsyncPlayerChatEvent) e); } - public void onChat(final AsyncPlayerChatEvent e) { + private boolean handleChatSync(Player p, String message) { + boolean handled = false; + World playerWorld = p.getWorld(); - Player p = e.getPlayer(); - World ww = e.getPlayer().getWorld(); - // Check world - if (!addon.isRegisteredGameWorld(ww)) { - // Check to see if there is a default game mode for chat - if (addon.getChatWorld().isPresent()) { - ww = addon.getChatWorld().get(); - } else { - return; + // Determine the worlds to use for team chat + List teamChatWorlds = new ArrayList<>(); + if (addon.isRegisteredGameWorld(playerWorld)) { + teamChatWorlds.add(playerWorld); + } else { + // Check extra chat worlds config + teamChatWorlds.addAll(addon.getWorldsFromExtra(playerWorld.getName())); + // If no extra worlds matched, check default chat world + if (teamChatWorlds.isEmpty()) { + addon.getChatWorld().ifPresent(teamChatWorlds::add); } } - World w = ww; - if (teamChatUsers.contains(p.getUniqueId()) && addon.getIslands().inTeam(w, p.getUniqueId())) { - // Cancel the event - e.setCancelled(true); - if (e.isAsynchronous()) { - Bukkit.getScheduler().runTask(addon.getPlugin(), () -> teamChat(w, p, e.getMessage())); - } else { - teamChat(w, p, e.getMessage()); + + // Process team chat for all matching worlds. + // If multiple game modes cover the same extra world, chat goes to all matching teams. + if (!teamChatWorlds.isEmpty() && teamChatUsers.contains(p.getUniqueId())) { + for (World w : teamChatWorlds) { + if (addon.getIslands().inTeam(w, p.getUniqueId())) { + handled = true; + teamChat(w, p, message); + } } } - addon.getIslands().getIslandAt(p.getLocation()) - .filter(islandChatters.keySet()::contains) - .filter(i -> islandChatters.get(i).contains(p)) - .ifPresent(i -> { - // Cancel the event - e.setCancelled(true); - if (e.isAsynchronous()) { - Bukkit.getScheduler().runTask(addon.getPlugin(), () -> islandChat(i, p, e.getMessage())); - } else { - islandChat(i, p, e.getMessage()); + + // Island chat - uses physical location, only meaningful if player is on an island + Island island = addon.getIslands().getIslandAt(p.getLocation()) + .filter(islandChatters.keySet()::contains) + .filter(i -> islandChatters.get(i).contains(p)) + .orElse(null); + if (island != null) { + handled = true; + islandChat(island, p, message); + } + + return handled; + } + + public void onChat(final AsyncPlayerChatEvent e) { + + Player p = e.getPlayer(); + String message = e.getMessage(); + + if (e.isAsynchronous()) { + try { + Boolean handled = Bukkit.getScheduler().callSyncMethod(addon.getPlugin(), + () -> handleChatSync(p, message)).get(); + if (Boolean.TRUE.equals(handled)) { + e.setCancelled(true); + } + } catch (InterruptedException ex) { + Thread.currentThread().interrupt(); + } catch (java.util.concurrent.ExecutionException ex) { + addon.logError("Failed to process async chat for " + p.getName() + ": " + ex.getCause()); } - }); + } else if (handleChatSync(p, message)) { + e.setCancelled(true); + } } // Removes player from TeamChat set if he left the island @EventHandler(priority = EventPriority.NORMAL, ignoreCancelled = true) public void onLeave(TeamLeaveEvent e) { teamChatUsers.remove(e.getPlayerUUID()); + teamChatMuted.remove(e.getPlayerUUID()); } // Removes player from TeamChat set if he was kicked from the island @EventHandler(priority = EventPriority.NORMAL, ignoreCancelled = true) public void onKick(TeamKickEvent e) { teamChatUsers.remove(e.getPlayerUUID()); + teamChatMuted.remove(e.getPlayerUUID()); } public void islandChat(Island i, Player player, String message) { @@ -114,8 +148,8 @@ public void islandChat(Island i, Player player, String message) { // Send message to island .forEach(u -> u.sendMessage("chat.island-chat.syntax", TextVariables.NAME, player.getName(), MESSAGE, message)); // Log if required - if (addon.getSettings().isLogTeamChats()) { - addon.log("[Team Chat Log] " + player.getName() + ": " + message); + if (addon.getSettings().isLogIslandChats()) { + addon.log("[Island Chat Log] " + player.getName() + ": " + message); } // Spy if required Bukkit.getOnlinePlayers().stream() @@ -126,13 +160,25 @@ public void islandChat(Island i, Player player, String message) { public void teamChat(World w, final Player player, String message) { // Get island members of member or above - addon.getIslands().getIsland(w, player.getUniqueId()).getMemberSet().stream() + Island island = addon.getIslands().getIsland(w, player.getUniqueId()); + if (island == null) { + return; + } + island.getMemberSet().stream() // Map to users .map(User::getInstance) // Filter for online only .filter(User::isOnline) + // Filter out muted players + .filter(target -> !teamChatMuted.contains(target.getUniqueId())) // Send the message to them .forEach(target -> target.sendMessage("chat.team-chat.syntax", TextVariables.NAME, player.getName(), MESSAGE, message)); + // Always show the sender their own message if they have muted team chat, plus a reminder + if (teamChatMuted.contains(player.getUniqueId())) { + User sender = User.getInstance(player); + sender.sendMessage("chat.team-chat.syntax", TextVariables.NAME, player.getName(), MESSAGE, message); + sender.sendMessage("chat.team-chat.mute.reminder"); + } // Log if required if (addon.getSettings().isLogTeamChats()) { addon.log("[Team Chat Log] " + player.getName() + ": " + message); @@ -205,6 +251,7 @@ public boolean toggleIslandSpy(UUID playerUUID) { public boolean togglePlayerTeamChat(UUID playerUUID) { if (teamChatUsers.contains(playerUUID)) { teamChatUsers.remove(playerUUID); + teamChatMuted.remove(playerUUID); // clear mute when team chat is toggled off return false; } else { teamChatUsers.add(playerUUID); @@ -231,4 +278,28 @@ public boolean toggleIslandChat(Island island, Player player) { } } + /** + * Toggle player's team chat mute state + * @param playerUUID - player's uuid + * @return true if team chat is now muted, otherwise false + */ + public boolean toggleMuteTeamChat(UUID playerUUID) { + if (teamChatMuted.contains(playerUUID)) { + teamChatMuted.remove(playerUUID); + return false; + } else { + teamChatMuted.add(playerUUID); + return true; + } + } + + /** + * Whether the player has muted team chat or not + * @param playerUUID - the player's UUID + * @return true if team chat is muted + */ + public boolean isMutedTeamChat(UUID playerUUID) { + return this.teamChatMuted.contains(playerUUID); + } + } diff --git a/src/main/resources/config.yml b/src/main/resources/config.yml index aba90fc..6a52881 100644 --- a/src/main/resources/config.yml +++ b/src/main/resources/config.yml @@ -6,9 +6,26 @@ team-chat: - AcidIsland - CaveBlock - SkyGrid + - Brix + - Poseidon + - AOneBlock + - Parkour # If players are outside a game world, team chat can still exist for one game mode. List it # here if you want that, e.g., BSkyBlock default-teamchat-gamemode: '' + # Additional chat worlds. List the additional worlds for each active game mode + # where team chat should be captured. If more than one game mode covers a world, + # then chat may go to multiple teams. + # Example: + # extra-chat-worlds: + # BSkyBlock: + # - world + # - world_nether + # - world_the_end + # - spawn_world + # AcidIsland: + # - spawn_world + extra-chat-worlds: {} # Log team chats to console. log: false island-chat: @@ -18,6 +35,10 @@ island-chat: - AcidIsland - CaveBlock - SkyGrid + - Brix + - Poseidon + - AOneBlock + - Parkour # Log island chats to console. log: false chat-listener: diff --git a/src/main/resources/locales/cs.yml b/src/main/resources/locales/cs.yml index 60c8b39..2ba31e5 100644 --- a/src/main/resources/locales/cs.yml +++ b/src/main/resources/locales/cs.yml @@ -3,22 +3,26 @@ chat: team-chat: description: "Přepnout týmový chat zapnuto/vypnuto" - chat-on: "Týmový chat zapnut" - chat-off: "Týmový chat vypnut" - syntax: "&6[team]&a<[name]> &f[message]" - spy: + chat-on: Týmový chat zapnut + chat-off: Týmový chat vypnut + syntax: [team][name] [message] + mute: + description: "Přepnout ztlumení zpráv týmového chatu" + mute-on: Týmový chat ztlumen. + mute-off: Týmový chat odztlumen. + reminder: (Týmový chat je ztlumen - použij /teamchat mute k odtlumení) + spy: description: "Přepnout šmírování všech týmových chatů" - spy-on: "Šmírování týmových chatů zapnuto" - spy-off: "Šmírování týmových chatů vypnuto" - syntax: "&c[team-chat-spy] &4[name]: [message]" + spy-on: Šmírování týmových chatů zapnuto + spy-off: Šmírování týmových chatů vypnuto + syntax: "[team-chat-spy] [name]: [message]" island-chat: description: "Přepnout chat na ostrově zapnuto/vypnuto" - island-on: "Chat na ostrově zapnut" - island-off: "Chat na ostrově vypnut" - syntax: "&6[island]&a<[name]> &f[message]" + island-on: Chat na ostrově zapnut + island-off: Chat na ostrově vypnut + syntax: [island][name] [message] spy: description: "Přepnout šmírování chatů na všech ostrovech" - spy-on: "Šmírování chatů na ostrovech zapnuto" - spy-off: "Šmírování chatů na ostrovech vypnuto" - syntax: "&c[island-chat-spy] &4[name]: [message]" - \ No newline at end of file + spy-on: Šmírování chatů na ostrovech zapnuto + spy-off: Šmírování chatů na ostrovech vypnuto + syntax: [island-chat-spy] [name]: [message] diff --git a/src/main/resources/locales/de.yml b/src/main/resources/locales/de.yml index db3d140..ddc86b1 100644 --- a/src/main/resources/locales/de.yml +++ b/src/main/resources/locales/de.yml @@ -1,22 +1,27 @@ --- chat: team-chat: - description: Team Chat ein- oder ausschalten - chat-on: "&a Team Chat an." - chat-off: "&a Team Chat aus." - syntax: "&6[team] &a[name]&8: &f[message]" + description: "Team Chat ein- oder ausschalten" + chat-on: Team Chat an. + chat-off: Team Chat aus. + syntax: [team] [name]: [message] + mute: + description: "Team Chat Stummschalten ein- oder ausschalten" + mute-on: Team Chat stummgeschaltet. + mute-off: Team Chat Stummschaltung aufgehoben. + reminder: (Team Chat ist stummgeschaltet - verwende /teamchat mute zum Aufheben der Stummschaltung) spy: - description: Team Chat Socialspy ein- oder ausschalten - spy-on: "&a Team Chat Socialspy an." - spy-off: "&c Team Chat Socialspy aus." - syntax: "&c[team-chat-spy] &a[name]&8: [message]" + description: "Team Chat Socialspy ein- oder ausschalten" + spy-on: Team Chat Socialspy an. + spy-off: Team Chat Socialspy aus. + syntax: [team-chat-spy] [name]: [message] island-chat: - description: Insel Chat ein- oder ausschalten - island-on: "&a Insel Chat an." - island-off: "&c Insel Chat aus." - syntax: "&6[island] &a[name]&8: &f[message]" + description: "Insel Chat ein- oder ausschalten" + island-on: Insel Chat an. + island-off: Insel Chat aus. + syntax: [island] [name]: [message] spy: - description: Insel Chat Socialspy ein- oder ausschalten - spy-on: "&a Insel Chat Socialspy aus." - spy-off: "&c Insel-Chats Ausspionieren aus" - syntax: "&c [island-chat-spy] &a[name]&8: &f[message]" + description: "Insel Chat Socialspy ein- oder ausschalten" + spy-on: Insel Chat Socialspy an. + spy-off: Insel Chat Socialspy aus. + syntax: [island-chat-spy] [name]: [message] diff --git a/src/main/resources/locales/en-US.yml b/src/main/resources/locales/en-US.yml index 1a9a73b..9aae296 100644 --- a/src/main/resources/locales/en-US.yml +++ b/src/main/resources/locales/en-US.yml @@ -1,21 +1,26 @@ chat: team-chat: description: "toggles team chat on/off" - chat-on: "&a Team chat on." - chat-off: "&a Team chat off." - syntax: "&6[team] &a[name]&8: &f[message]" - spy: + chat-on: Team chat on. + chat-off: Team chat off. + syntax: [team] [name]: [message] + mute: + description: "toggles muting team chat messages" + mute-on: Team chat muted. + mute-off: Team chat unmuted. + reminder: (Team chat is muted - use /teamchat mute to unmute) + spy: description: "toggles spying on all team chats" - spy-on: "&a Team chat spy on." - spy-off: "&c Team chat spy off." - syntax: "&c[team-chat-spy] &a[name]&8: [message]" + spy-on: Team chat spy on. + spy-off: Team chat spy off. + syntax: [team-chat-spy] [name]: [message] island-chat: description: "toggles island chat on/off" - island-on: "&a Island chat on." - island-off: "&c Island chat off." - syntax: "&6[island] &a[name]&8: &f[message]" + island-on: Island chat on. + island-off: Island chat off. + syntax: [island] [name]: [message] spy: description: "toggles spying on all island chats" - spy-on: "&a Island chat spy on." - spy-off: "&c Island chat spy off." - syntax: "&c [island-chat-spy] &a[name]&8: &f[message]" + spy-on: Island chat spy on. + spy-off: Island chat spy off. + syntax: [island-chat-spy] [name]: [message] diff --git a/src/main/resources/locales/es.yml b/src/main/resources/locales/es.yml index 81c8193..d0a9222 100644 --- a/src/main/resources/locales/es.yml +++ b/src/main/resources/locales/es.yml @@ -1,21 +1,26 @@ chat: team-chat: description: "Alternar el chat de equipo" - chat-on: "&a Chat de equipo activado" - chat-off: "&c Chat de equipo desactivado" - syntax: "&6[team] &a[name]&8: &f[message]" - spy: + chat-on: Chat de equipo activado + chat-off: Chat de equipo desactivado + syntax: [team] [name]: [message] + mute: + description: "Alternar silenciar mensajes del chat de equipo" + mute-on: Chat de equipo silenciado. + mute-off: Chat de equipo sin silencio. + reminder: (El chat de equipo está silenciado - usa /teamchat mute para desmutear) + spy: description: "Alternar espiar en todos los chats de equipo" - spy-on: "&a Chat de equipo espía activado" - spy-off: "&c Chat de equipo espía desactivado" - syntax: "&c[team-chat-spy] &a[name]&8: &f[message]" + spy-on: Chat de equipo espía activado + spy-off: Chat de equipo espía desactivado + syntax: [team-chat-spy] [name]: [message] island-chat: description: "Alternar el chat de la isla" - island-on: "&a Chat de isla activado" - island-off: "&c Chat de isla desactivado" - syntax: "&6[island] &a[name]&8: &f[message]" + island-on: Chat de isla activado + island-off: Chat de isla desactivado + syntax: [island] [name]: [message] spy: description: "Alternar espiar en todos los chats de la isla" - spy-on: "&a Chat espia de isla activado" - spy-off: "&c Chat espia de isla desactivado" - syntax: "&c[island-chat-spy] &4[name]&8: &f[message]" + spy-on: Chat espia de isla activado + spy-off: Chat espia de isla desactivado + syntax: [island-chat-spy] [name]: [message] diff --git a/src/main/resources/locales/fr.yml b/src/main/resources/locales/fr.yml index ae80b42..8091de4 100644 --- a/src/main/resources/locales/fr.yml +++ b/src/main/resources/locales/fr.yml @@ -1,21 +1,26 @@ chat: team-chat: description: "activer/désactiver le chat d'équipe" - chat-on: "&a Chat d'équipe activé." - chat-off: "&a Chat d'équipe désactivé." - syntax: "&6[team] &a[name]&8: &f[message]" - spy: + chat-on: "Chat d'équipe activé." + chat-off: "Chat d'équipe désactivé." + syntax: "[team] [name]: [message]" + mute: + description: "activer/désactiver la mise en sourdine des messages du chat d'équipe" + mute-on: "Chat d'équipe mis en sourdine." + mute-off: "Chat d'équipe sorti de sourdine." + reminder: "(Chat d'équipe mis en sourdine - utilise /teamchat mute pour sortir de la sourdine)" + spy: description: "activer/désactiver la surveillance de tous les chats d'équipe" - spy-on: "&a Surveillance des chats d'équipe activée." - spy-off: "&c Surveillance des chats d'équipe désactivée." - syntax: "&c[team-chat-spy] &a[name]&8: [message]" + spy-on: "Surveillance des chats d'équipe activée." + spy-off: "Surveillance des chats d'équipe désactivée." + syntax: "[team-chat-spy] [name]: [message]" island-chat: description: "activer/désactiver le chat d'île" - island-on: "&a Chat d'île activé." - island-off: "&c Chat d'île désactivé." - syntax: "&6[island] &a[name]&8: &f[message]" + island-on: "Chat d'île activé." + island-off: "Chat d'île désactivé." + syntax: "[island] [name]: [message]" spy: description: "activer/désactiver la surveillance de tous les chats d'île" - spy-on: "&a Surveillance des chats d'île activée." - spy-off: "&c Surveillance des chats d'île désactivée." - syntax: "&c [island-chat-spy] &a[name]&8: &f[message]" + spy-on: "Surveillance des chats d'île activée." + spy-off: "Surveillance des chats d'île désactivée." + syntax: "[island-chat-spy] [name]: [message]" diff --git a/src/main/resources/locales/hr.yml b/src/main/resources/locales/hr.yml new file mode 100644 index 0000000..fd09c15 --- /dev/null +++ b/src/main/resources/locales/hr.yml @@ -0,0 +1,26 @@ +chat: + team-chat: + description: "uključuje/isključuje timski chat" + chat-on: Timski chat uključen. + chat-off: Timski chat isključen. + syntax: [team] [name]: [message] + mute: + description: "uključuje/isključuje nijeme poruke timskog chata" + mute-on: Timski chat je utišan. + mute-off: Timski chat više nije utišan. + reminder: (Timski chat je utišan - koristi /teamchat mute za aktiviranje) + spy: + description: "uključuje/isključuje praćenje svih timskih chatova" + spy-on: Praćenje timskog chata uključeno. + spy-off: Praćenje timskog chata isključeno. + syntax: [team-chat-spy] [name]: [message] + island-chat: + description: "uključuje/isključuje chat otoka" + island-on: Chat otoka uključen. + island-off: Chat otoka isključen. + syntax: [island] [name]: [message] + spy: + description: "uključuje/isključuje praćenje svih chatova otoka" + spy-on: Praćenje chata otoka uključeno. + spy-off: Praćenje chata otoka isključeno. + syntax: [island-chat-spy] [name]: [message] diff --git a/src/main/resources/locales/hu.yml b/src/main/resources/locales/hu.yml index 8f37f48..aa88e83 100644 --- a/src/main/resources/locales/hu.yml +++ b/src/main/resources/locales/hu.yml @@ -1,21 +1,26 @@ chat: team-chat: description: csapat chat állítása be/ki - chat-on: '&a Csapat chat be.' - chat-off: '&a Csapat chat ki.' - syntax: '&6[team] &a[name]&8: &f[message]' + chat-on: Csapat chat be. + chat-off: Csapat chat ki. + syntax: [team] [name]: [message] + mute: + description: csapat chat üzenetek elnémítása be/ki + mute-on: Csapat chat elnémítva. + mute-off: Csapat chat elnémítás feloldva. + reminder: (Csapat chat elnémítva - használd a /teamchat mute parancsot az elnémítás feloldásához) spy: description: kémkedés állítása az összes csapat chatben - spy-on: '&a Csapat chat kémkedés be.' - spy-off: '&c Csapat chat kémkedés ki.' - syntax: '&c[team-chat-spy] &a[name]&8: [message]' + spy-on: Csapat chat kémkedés be. + spy-off: Csapat chat kémkedés ki. + syntax: [team-chat-spy] [name]: [message] island-chat: description: sziget chat állítása be/ki - island-on: '&a Sziget chat be.' - island-off: '&c Sziget chat ki.' - syntax: '&6[island] &a[name]&8: &f[message]' + island-on: Sziget chat be. + island-off: Sziget chat ki. + syntax: [island] [name]: [message] spy: description: kémkedés állítása az összes sziget chatben - spy-on: '&a Sziget chat kémkedés be.' - spy-off: '&c Sziget chat kémkedés ki.' - syntax: '&c [island-chat-spy] &a[name]&8: &f[message]' + spy-on: Sziget chat kémkedés be. + spy-off: Sziget chat kémkedés ki. + syntax: [island-chat-spy] [name]: [message] diff --git a/src/main/resources/locales/id.yml b/src/main/resources/locales/id.yml index d129fc0..7c9544b 100644 --- a/src/main/resources/locales/id.yml +++ b/src/main/resources/locales/id.yml @@ -1,21 +1,26 @@ chat: team-chat: description: "nyala/matikan percakapan tim" - chat-on: "&a Percakapan tim dinyalakan." - chat-off: "&a Percakapan tim dimatikan." - syntax: "&6[team] &a[name]&8: &f[message]" - spy: + chat-on: Percakapan tim dinyalakan. + chat-off: Percakapan tim dimatikan. + syntax: [team] [name]: [message] + mute: + description: "nyala/matikan senyap pesan percakapan tim" + mute-on: Percakapan tim dibisukan. + mute-off: Percakapan tim tidak lagi dibisukan. + reminder: (Percakapan tim dibisukan - gunakan /teamchat mute untuk membatalkan bisuan) + spy: description: "nyala/matikan pengawasan pada semua percakapan tim" - spy-on: "&a Pengawasan percakapan tim dinyalakan." - spy-off: "&c Pengawasan percakapan tim dimatikan." - syntax: "&c[team-chat-spy] &a[name]&8: [message]" + spy-on: Pengawasan percakapan tim dinyalakan. + spy-off: Pengawasan percakapan tim dimatikan. + syntax: [team-chat-spy] [name]: [message] island-chat: description: "nyala/matikan percakapan pulau" - island-on: "&a Percakapan pulau dinyalakan." - island-off: "&c Percakapan pulau dimatikan." - syntax: "&6[island] &a[name]&8: &f[message]" + island-on: Percakapan pulau dinyalakan. + island-off: Percakapan pulau dimatikan. + syntax: [island] [name]: [message] spy: description: "nyala/matikan pengawasan pada semua percakapan pulau" - spy-on: "&a Pengawasan percakapan pulau dinyalakan." - spy-off: "&c Pengawasan percakapan pulau dimatikan." - syntax: "&c [island-chat-spy] &a[name]&8: &f[message]" + spy-on: Pengawasan percakapan pulau dinyalakan. + spy-off: Pengawasan percakapan pulau dimatikan. + syntax: [island-chat-spy] [name]: [message] diff --git a/src/main/resources/locales/it.yml b/src/main/resources/locales/it.yml index 4410d4a..b007b18 100644 --- a/src/main/resources/locales/it.yml +++ b/src/main/resources/locales/it.yml @@ -1,22 +1,26 @@ ---- chat: team-chat: - description: Attiva o disabilita la chat team - chat-on: "&a Team chat attivata." - chat-off: "&a Team chat disabilitata." - syntax: "&6[team] &a[name]&8: &f[message]" + description: "attiva/disattiva la chat del team" + chat-on: Chat del team attivata. + chat-off: Chat del team disattivata. + syntax: [team] [name]: [message] + mute: + description: "attiva/disattiva il silenziamento dei messaggi della chat del team" + mute-on: Chat del team silenziata. + mute-off: Chat del team non più silenziata. + reminder: (Chat del team silenziata - usa /teamchat mute per togliere il silenziamento) spy: - description: attiva o disattiva lo spionaggio di tutte le team chat - spy-on: "&a Spia sulle team chat attivata. " - spy-off: "&c Spia sulle team chat disattivata. " - syntax: "&c[team-chat-spy] &a[name]&8: [message]" + description: "attiva/disattiva la spia su tutte le chat del team" + spy-on: Spia chat del team attivata. + spy-off: Spia chat del team disattivata. + syntax: [team-chat-spy] [name]: [message] island-chat: - description: attiva o disabilita la chat isola - island-on: "&a chat isola attivata." - island-off: "&c chat isola disattivata." - syntax: "&6[island] &a[name]&8: &f[message]" + description: "attiva/disattiva la chat dell'isola" + island-on: Chat dell'isola attivata. + island-off: Chat dell'isola disattivata. + syntax: [island] [name]: [message] spy: - description: attiva o disattiva lo spionaggio di tutte le chat dell'isola - spy-on: "&a Spionaggio chat isole attivato." - spy-off: "&c Spionaggio chat isole disattivato." - syntax: "&c [island-chat-spy] &a[name]&8: &f[message]" + description: "attiva/disattiva la spia su tutte le chat dell'isola" + spy-on: Spia chat dell'isola attivata. + spy-off: Spia chat dell'isola disattivata. + syntax: [island-chat-spy] [name]: [message] diff --git a/src/main/resources/locales/ja.yml b/src/main/resources/locales/ja.yml index 0765de8..cb1434f 100644 --- a/src/main/resources/locales/ja.yml +++ b/src/main/resources/locales/ja.yml @@ -2,21 +2,26 @@ chat: team-chat: description: チームチャットのオン/オフを切り替えます - chat-on: "&aチームチャット。" - chat-off: "&aチームチャットオフ。" + chat-on: チームチャット オン. + chat-off: チームチャット オフ. + syntax: [team] [name]: [message] + mute: + description: チームチャットメッセージのミュートを切り替えます + mute-on: チームチャットをミュートしました. + mute-off: チームチャットのミュートを解除しました. + reminder: (チームチャットがミュートされています - /teamchat mute でミュートを解除してください) spy: description: すべてのチームチャットのスパイを切り替えます - spy-on: "&aチームチャットスパイ。" - spy-off: "&cチームチャットスパイオフ。" - syntax: "&c [team-chat-spy]&a [name]&8:[message]" - syntax: "&6 [team]&a [name&8:&f [message]" + spy-on: チームチャットスパイ オン. + spy-off: チームチャットスパイ オフ. + syntax: [team-chat-spy] [name]: [message] island-chat: description: アイランドチャットのオン/オフを切り替えます + island-on: アイランドチャット オン. + island-off: アイランドチャット オフ. + syntax: [island] [name]: [message] spy: description: すべての島のチャットのスパイを切り替えます - spy-on: "&a島のチャットスパイ。" - syntax: "&c [island-chat-spy]&a [name]&8:&f [message]" - spy-off: "&c島のチャットスパイオフ。" - island-on: "&a島のチャット。" - island-off: "&c島のチャットオフ。" - syntax: "&6 [island]&a [name]&8:&f [message]" + spy-on: アイランドチャットスパイ オン. + spy-off: アイランドチャットスパイ オフ. + syntax: [island-chat-spy] [name]: [message] diff --git a/src/main/resources/locales/ko.yml b/src/main/resources/locales/ko.yml new file mode 100644 index 0000000..796884c --- /dev/null +++ b/src/main/resources/locales/ko.yml @@ -0,0 +1,26 @@ +chat: + team-chat: + description: "팀 채팅 켜기/끄기 전환" + chat-on: 팀 채팅이 켜졌습니다. + chat-off: 팀 채팅이 꺼졌습니다. + syntax: [team] [name]: [message] + mute: + description: "팀 채팅 메시지 음소거 전환" + mute-on: 팀 채팅이 음소거되었습니다. + mute-off: 팀 채팅 음소거가 해제되었습니다. + reminder: (팀 채팅이 음소거되었습니다 - /teamchat mute 로 음소거를 해제하세요) + spy: + description: "모든 팀 채팅 감시 켜기/끄기 전환" + spy-on: 팀 채팅 감시가 켜졌습니다. + spy-off: 팀 채팅 감시가 꺼졌습니다. + syntax: [team-chat-spy] [name]: [message] + island-chat: + description: "섬 채팅 켜기/끄기 전환" + island-on: 섬 채팅이 켜졌습니다. + island-off: 섬 채팅이 꺼졌습니다. + syntax: [island] [name]: [message] + spy: + description: "모든 섬 채팅 감시 켜기/끄기 전환" + spy-on: 섬 채팅 감시가 켜졌습니다. + spy-off: 섬 채팅 감시가 꺼졌습니다. + syntax: [island-chat-spy] [name]: [message] diff --git a/src/main/resources/locales/lv.yml b/src/main/resources/locales/lv.yml index b33fa1b..1a205fa 100644 --- a/src/main/resources/locales/lv.yml +++ b/src/main/resources/locales/lv.yml @@ -1,22 +1,26 @@ ---- chat: team-chat: - description: pārslēdz komandas saraksti (ieslēdz/izslēdz) - chat-on: "&a Komandas sarakste ieslēgta." - chat-off: "&a Komandas sarakste izslēgta." - syntax: "&6[team] &a[name]&8: &f[message]" + description: "ieslēdz/izslēdz komandas tērzēšanu" + chat-on: Komandas tērzēšana ieslēgta. + chat-off: Komandas tērzēšana izslēgta. + syntax: [team] [name]: [message] + mute: + description: "ieslēdz/izslēdz komandas tērzēšanas ziņojumu apklusināšanu" + mute-on: Komandas tērzēšana apklusināta. + mute-off: Komandas tērzēšanas apklusināšana atcelta. + reminder: (Komandas tērzēšana apklusināta - izmanto /teamchat mute, lai atceltu apklusināšanu) spy: - description: pārslēdz iespēju spiegot visas komandu sarakstes - spy-on: "&a Komandu sarakstšu spiegošana ieslēgta." - spy-off: "&c Komandu sarakstšu spiegošana izslēgta." - syntax: "&c[team-chat-spy] &a[name]&8: [message]" + description: "ieslēdz/izslēdz visas komandas tērzēšanas uzraudzību" + spy-on: Komandas tērzēšanas uzraudzība ieslēgta. + spy-off: Komandas tērzēšanas uzraudzība izslēgta. + syntax: [team-chat-spy] [name]: [message] island-chat: - description: pārslēdz salas saraksti (ieslēgt/izslēgt) - island-on: "&a Salas sarakste ieslēgta." - island-off: "&c Salas sarakste izslēgta." - syntax: "&6[island] &a[name]&8: &f[message]" + description: "ieslēdz/izslēdz salas tērzēšanu" + island-on: Salas tērzēšana ieslēgta. + island-off: Salas tērzēšana izslēgta. + syntax: [island] [name]: [message] spy: - description: pārslēdz iespēju spiegot visas salu sarakstes - spy-on: "&a Salu sarakstšu spiegošana ieslēgta." - spy-off: "&c Salu sarakstšu spiegošana izslēgta." - syntax: "&c [island-chat-spy] &a[name]&8: &f[message]" + description: "ieslēdz/izslēdz visu salu tērzēšanas uzraudzību" + spy-on: Salas tērzēšanas uzraudzība ieslēgta. + spy-off: Salas tērzēšanas uzraudzība izslēgta. + syntax: [island-chat-spy] [name]: [message] diff --git a/src/main/resources/locales/nl.yml b/src/main/resources/locales/nl.yml new file mode 100644 index 0000000..5258600 --- /dev/null +++ b/src/main/resources/locales/nl.yml @@ -0,0 +1,26 @@ +chat: + team-chat: + description: "zet teamchat aan/uit" + chat-on: Teamchat aan. + chat-off: Teamchat uit. + syntax: [team] [name]: [message] + mute: + description: "zet het dempen van teamchat berichten aan/uit" + mute-on: Teamchat gedempt. + mute-off: Teamchat niet meer gedempt. + reminder: (Teamchat is gedempt - gebruik /teamchat mute om te ontdempen) + spy: + description: "zet het bespioneren van alle teamchats aan/uit" + spy-on: Teamchat spion aan. + spy-off: Teamchat spion uit. + syntax: [team-chat-spy] [name]: [message] + island-chat: + description: "zet eilandchat aan/uit" + island-on: Eilandchat aan. + island-off: Eilandchat uit. + syntax: [island] [name]: [message] + spy: + description: "zet het bespioneren van alle eilandchats aan/uit" + spy-on: Eilandchat spion aan. + spy-off: Eilandchat spion uit. + syntax: [island-chat-spy] [name]: [message] diff --git a/src/main/resources/locales/pl.yml b/src/main/resources/locales/pl.yml index f8d23dc..f888656 100644 --- a/src/main/resources/locales/pl.yml +++ b/src/main/resources/locales/pl.yml @@ -1,22 +1,26 @@ ---- chat: team-chat: - description: włącza/wyłącza czat zespołowy - chat-on: "&a Czat zespołu włączony." - chat-off: "&a Czat zespołu wyłączony." - syntax: "&6[team] &a[name]&8: &f[message]" + description: "włącza/wyłącza czat drużynowy" + chat-on: Czat drużynowy włączony. + chat-off: Czat drużynowy wyłączony. + syntax: [team] [name]: [message] + mute: + description: "włącza/wyłącza wyciszanie wiadomości czatu drużynowego" + mute-on: Czat drużynowy wyciszony. + mute-off: Czat drużynowy odciszony. + reminder: (Czat drużynowy jest wyciszony - użyj /teamchat mute, aby go odciszyć) spy: - description: włącza szpiegowanie na wszystkich czatach zespołowych - spy-on: "&a Włączono podglądanie czatu zespołu." - spy-off: "&c Wyłącz szpiegowanie czatu zespołowego." - syntax: "&c[team-chat-spy] &a[name]&8: [message]" + description: "włącza/wyłącza szpiegowanie wszystkich czatów drużynowych" + spy-on: Szpiegowanie czatu drużynowego włączone. + spy-off: Szpiegowanie czatu drużynowego wyłączone. + syntax: [team-chat-spy] [name]: [message] island-chat: - description: włącza/wyłącza czat na wyspie - island-on: "&a czat na wyspie." - island-off: "&c Wyłączenie czatu na wyspie." - syntax: "&6[island] &a[name]&8: &f[message]" + description: "włącza/wyłącza czat wyspy" + island-on: Czat wyspy włączony. + island-off: Czat wyspy wyłączony. + syntax: [island] [name]: [message] spy: - description: włącza szpiegowanie na wszystkich czatach na wyspie - spy-on: "&a Włączono podglądanie czatu wyspy." - spy-off: "&c Wyłączenie szpiegowania czatu na wyspie." - syntax: "&c[team-chat-spy] &a[name]&8: [message]" + description: "włącza/wyłącza szpiegowanie wszystkich czatów wyspy" + spy-on: Szpiegowanie czatu wyspy włączone. + spy-off: Szpiegowanie czatu wyspy wyłączone. + syntax: [island-chat-spy] [name]: [message] diff --git a/src/main/resources/locales/pt-BR.yml b/src/main/resources/locales/pt-BR.yml new file mode 100644 index 0000000..0d6792a --- /dev/null +++ b/src/main/resources/locales/pt-BR.yml @@ -0,0 +1,26 @@ +chat: + team-chat: + description: "ativa/desativa o chat da equipe" + chat-on: Chat da equipe ativado. + chat-off: Chat da equipe desativado. + syntax: [team] [name]: [message] + mute: + description: "ativa/desativa o silenciamento de mensagens do chat da equipe" + mute-on: Chat da equipe silenciado. + mute-off: Chat da equipe não está mais silenciado. + reminder: (Chat da equipe está silenciado - use /teamchat mute para dessilenciar) + spy: + description: "ativa/desativa a espionagem de todos os chats da equipe" + spy-on: Espião de chat da equipe ativado. + spy-off: Espião de chat da equipe desativado. + syntax: [team-chat-spy] [name]: [message] + island-chat: + description: "ativa/desativa o chat da ilha" + island-on: Chat da ilha ativado. + island-off: Chat da ilha desativado. + syntax: [island] [name]: [message] + spy: + description: "ativa/desativa a espionagem de todos os chats da ilha" + spy-on: Espião de chat da ilha ativado. + spy-off: Espião de chat da ilha desativado. + syntax: [island-chat-spy] [name]: [message] diff --git a/src/main/resources/locales/pt.yml b/src/main/resources/locales/pt.yml new file mode 100644 index 0000000..5e97560 --- /dev/null +++ b/src/main/resources/locales/pt.yml @@ -0,0 +1,26 @@ +chat: + team-chat: + description: "ativa/desativa o chat da equipa" + chat-on: Chat da equipa ativado. + chat-off: Chat da equipa desativado. + syntax: [team] [name]: [message] + mute: + description: "ativa/desativa o silenciamento de mensagens do chat da equipa" + mute-on: Chat da equipa silenciado. + mute-off: Chat da equipa não está mais silenciado. + reminder: (Chat da equipa está silenciado - use /teamchat mute para dessilenciar) + spy: + description: "ativa/desativa a espionagem de todos os chats da equipa" + spy-on: Espião de chat da equipa ativado. + spy-off: Espião de chat da equipa desativado. + syntax: [team-chat-spy] [name]: [message] + island-chat: + description: "ativa/desativa o chat da ilha" + island-on: Chat da ilha ativado. + island-off: Chat da ilha desativado. + syntax: [island] [name]: [message] + spy: + description: "ativa/desativa a espionagem de todos os chats da ilha" + spy-on: Espião de chat da ilha ativado. + spy-off: Espião de chat da ilha desativado. + syntax: [island-chat-spy] [name]: [message] diff --git a/src/main/resources/locales/ro.yml b/src/main/resources/locales/ro.yml new file mode 100644 index 0000000..842dce3 --- /dev/null +++ b/src/main/resources/locales/ro.yml @@ -0,0 +1,26 @@ +chat: + team-chat: + description: "activează/dezactivează chat-ul de echipă" + chat-on: Chat de echipă activat. + chat-off: Chat de echipă dezactivat. + syntax: [team] [name]: [message] + mute: + description: "activează/dezactivează dezactivarea sunetului chat-ului de echipă" + mute-on: Chat de echipă dezactivat sonor. + mute-off: Chat de echipă reactivat sonor. + reminder: (Chat-ul de echipă este dezactivat sonor - foloseste /teamchat mute pentru reactivare sonoră) + spy: + description: "activează/dezactivează spionarea tuturor chat-urilor de echipă" + spy-on: Spion chat de echipă activat. + spy-off: Spion chat de echipă dezactivat. + syntax: [team-chat-spy] [name]: [message] + island-chat: + description: "activează/dezactivează chat-ul de insulă" + island-on: Chat de insulă activat. + island-off: Chat de insulă dezactivat. + syntax: [island] [name]: [message] + spy: + description: "activează/dezactivează spionarea tuturor chat-urilor de insulă" + spy-on: Spion chat de insulă activat. + spy-off: Spion chat de insulă dezactivat. + syntax: [island-chat-spy] [name]: [message] diff --git a/src/main/resources/locales/ru.yml b/src/main/resources/locales/ru.yml index 0cfdb76..a1022d9 100644 --- a/src/main/resources/locales/ru.yml +++ b/src/main/resources/locales/ru.yml @@ -1,22 +1,31 @@ ---- +# ######################################################################################## # +# Это YML файл. Будьте осторожны при редактировании. Проверяйте свои правки # +# в YAML валидаторе, например, на http://yaml-online-parser.appspot.com # +# ######################################################################################## # + chat: team-chat: - description: включает/выключает командный чат - chat-on: "&a Командный чат вкл." - chat-off: "&a Командный чат выкл." - syntax: "&6[team] &a[name]&8: &f[message]" + description: включает/отключает командный чат + chat-on: Командный чат включен. + chat-off: Командный чат отключен. + syntax: [team] [name]: [message] + mute: + description: включает/отключает отключение звука командного чата + mute-on: Командный чат заглушен. + mute-off: Командный чат разглушен. + reminder: (Командный чат заглушен - используй /teamchat mute для разглушения) spy: description: переключает шпионаж на все командные чаты - spy-on: "&a Слежка за командным чатам вкл." - spy-off: "&c Слежка за командным чатам выкл." - syntax: "&c[team-chat-spy] &a[name]&8: [message]" + spy-on: Слежка за командным чатам включена. + spy-off: Слежка за командным чатам отключена. + syntax: [team-chat-spy] [name]: [message] island-chat: - description: включает/выключает чат на острове - island-on: "&a Островной чат вкл." - island-off: "&c Островной чат выкл." - syntax: "&6[island] &a[name]&8: &f[message]" + description: включает/отключает островной чат + island-on: Островной чат включен. + island-off: Островной чат отключен. + syntax: [island] [name]: [message] spy: - description: переключает шпионаж на всех островных чатах - spy-on: "&a Слежка за островным чатом вкл." - spy-off: "&c Слежка за островным чатом выкл." - syntax: "&c [island-chat-spy] &a[name]&8: &f[message]" + description: переключает шпионаж на все островные чаты + spy-on: Слежка за островным чатом включена. + spy-off: Слежка за островным чатом отключена. + syntax: [island-chat-spy] [name]: [message] diff --git a/src/main/resources/locales/tr.yml b/src/main/resources/locales/tr.yml index 16a76d8..56d2125 100644 --- a/src/main/resources/locales/tr.yml +++ b/src/main/resources/locales/tr.yml @@ -1,25 +1,27 @@ --- chat: team-chat: - description: ada takım sohbetini açar/kapatır - chat-on: "&aTakım sohbeti aktifleştirildi." - chat-off: "&cTakım sohbeti kapatıldı." - syntax: "&6[team] &a[name] &8: &f[message]" + description: "ada takım sohbetini açar/kapatır" + chat-on: Takım sohbeti aktifleştirildi. + chat-off: Takım sohbeti kapatıldı. + syntax: [team] [name] : [message] + mute: + description: "takım sohbeti mesajlarını susturmayı açar/kapatır" + mute-on: Takım sohbeti susturuldu. + mute-off: Takım sohbeti susturma kaldırıldı. + reminder: (Takım sohbeti susturuldu - susturma kaldırmak için /teamchat mute komutunu kullan) spy: - description: takım sohbetlerini görmeni sağlar - spy-on: "&aTakım sohbetlerini artık görebiliyorsun." - spy-off: "&cTakım sohbetlerini artık göremiyorsun." - syntax: "&c[team-chat-spy] &a[name]&8: &f[message]" + description: "takım sohbetlerini görmeni sağlar" + spy-on: Takım sohbetlerini artık görebiliyorsun. + spy-off: Takım sohbetlerini artık göremiyorsun. + syntax: [team-chat-spy] [name]: [message] island-chat: - description: ada sohbetini açar/kapatır - island-on: "&aAda sohbeti aktifleştirildi." - island-off: "&cAda sohbeti kapatıldı." - syntax: "&6[island] &a[name]&8: &f[message]" + description: "ada sohbetini açar/kapatır" + island-on: Ada sohbeti aktifleştirildi. + island-off: Ada sohbeti kapatıldı. + syntax: [island] [name]: [message] spy: - description: ada sohbetlerini görmeni sağlar - spy-on: "&aAda sohbetlerini artık görebiliyorsun." - spy-off: "&cAda sohbetlerini artık göremiyorsun." - syntax: "&c[island-chat-spy] &a[name]&8: &f[message]" -meta: - authors: - - Over_Brave + description: "ada sohbetlerini görmeni sağlar" + spy-on: Ada sohbetlerini artık görebiliyorsun. + spy-off: Ada sohbetlerini artık göremiyorsun. + syntax: [island-chat-spy] [name]: [message] diff --git a/src/main/resources/locales/uk.yml b/src/main/resources/locales/uk.yml new file mode 100644 index 0000000..1bc59ea --- /dev/null +++ b/src/main/resources/locales/uk.yml @@ -0,0 +1,26 @@ +chat: + team-chat: + description: "вмикає/вимикає командний чат" + chat-on: Командний чат увімкнено. + chat-off: Командний чат вимкнено. + syntax: [team] [name]: [message] + mute: + description: "вмикає/вимикає ігнорування повідомлень командного чату" + mute-on: Командний чат заглушено. + mute-off: Командний чат розглушено. + reminder: (Командний чат заглушено - використовуй /teamchat mute для розглушення) + spy: + description: "вмикає/вимикає стеження за всіма командними чатами" + spy-on: Стеження за командним чатом увімкнено. + spy-off: Стеження за командним чатом вимкнено. + syntax: [team-chat-spy] [name]: [message] + island-chat: + description: "вмикає/вимикає чат острова" + island-on: Чат острова увімкнено. + island-off: Чат острова вимкнено. + syntax: [island] [name]: [message] + spy: + description: "вмикає/вимикає стеження за всіма чатами острова" + spy-on: Стеження за чатом острова увімкнено. + spy-off: Стеження за чатом острова вимкнено. + syntax: [island-chat-spy] [name]: [message] diff --git a/src/main/resources/locales/vi.yml b/src/main/resources/locales/vi.yml index ea8773a..b97c137 100644 --- a/src/main/resources/locales/vi.yml +++ b/src/main/resources/locales/vi.yml @@ -1,21 +1,26 @@ chat: team-chat: description: bật/tắt chat đội - chat-on: '&aĐã bật chat đội.' - chat-off: '&aĐã tắt chat đội.' - syntax: '&6[team] &a[name]&8: &f[message]' + chat-on: Đã bật chat đội. + chat-off: Đã tắt chat đội. + syntax: [team] [name]: [message] + mute: + description: bật/tắt tắt tiếng tin nhắn chat đội + mute-on: Đã tắt tiếng chat đội. + mute-off: Đã bỏ tắt tiếng chat đội. + reminder: (Chat đội đã bị tắt tiếng - sử dụng /teamchat mute để bỏ tắt tiếng) spy: description: bật/tắt giám sát chat đội - spy-on: '&aĐã bật giám sát chat đội.' - spy-off: '&cĐã tắt giám sát chat đội.' - syntax: '&c[team-chat-spy] &a[name]&8: [message]' + spy-on: Đã bật giám sát chat đội. + spy-off: Đã tắt giám sát chat đội. + syntax: [team-chat-spy] [name]: [message] island-chat: description: bật/tắt chat đảo - island-on: '&aĐã bật chat đảo.' - island-off: '&cĐã tắt chat đảo.' - syntax: '&6[island] &a[name]&8: &f[message]' + island-on: Đã bật chat đảo. + island-off: Đã tắt chat đảo. + syntax: [island] [name]: [message] spy: description: bật/tắt giám sát chat đảo - spy-on: '&aĐã bật giám sát chat đảo.' - spy-off: '&cĐã tắt giám sát chat đảo.' - syntax: '&c [island-chat-spy] &a[name]&8: &f[message]' + spy-on: Đã bật giám sát chat đảo. + spy-off: Đã tắt giám sát chat đảo. + syntax: [island-chat-spy] [name]: [message] diff --git a/src/main/resources/locales/zh-CN.yml b/src/main/resources/locales/zh-CN.yml index 6e9c741..c9f016a 100644 --- a/src/main/resources/locales/zh-CN.yml +++ b/src/main/resources/locales/zh-CN.yml @@ -1,21 +1,26 @@ chat: team-chat: description: "切换团队聊天开闭" - chat-on: "&a 团队聊天已开启." - chat-off: "&a 团队聊天已关闭." - syntax: "&6[team] &a[name]&8: &f[message]" - spy: + chat-on: 团队聊天已开启. + chat-off: 团队聊天已关闭. + syntax: [team] [name]: [message] + mute: + description: "切换静音团队聊天消息" + mute-on: 团队聊天已静音. + mute-off: 团队聊天已取消静音. + reminder: (团队聊天已静音 - 使用 /teamchat mute 来取消静音) + spy: description: "切换偷听所有团队聊天" - spy-on: "&a 团队聊天偷听已开启." - spy-off: "&c 团队聊天偷听已关闭." - syntax: "&c[team-chat-spy] &a[name]&8: [message]" + spy-on: 团队聊天偷听已开启. + spy-off: 团队聊天偷听已关闭. + syntax: [team-chat-spy] [name]: [message] island-chat: description: "切换岛屿聊天开闭" - island-on: "&a 岛屿聊天已开启." - island-off: "&c 岛屿聊天已关闭." - syntax: "&6[island] &a[name]&8: &f[message]" + island-on: 岛屿聊天已开启. + island-off: 岛屿聊天已关闭. + syntax: [island] [name]: [message] spy: description: "切换偷听所有岛屿聊天" - spy-on: "&a 岛屿聊天偷听已开启." - spy-off: "&c 岛屿聊天偷听已关闭." - syntax: "&c [island-chat-spy] &a[name]&8: &f[message]" + spy-on: 岛屿聊天偷听已开启. + spy-off: 岛屿聊天偷听已关闭. + syntax: [island-chat-spy] [name]: [message] diff --git a/src/main/resources/locales/zh-HK.yml b/src/main/resources/locales/zh-HK.yml new file mode 100644 index 0000000..82d8522 --- /dev/null +++ b/src/main/resources/locales/zh-HK.yml @@ -0,0 +1,26 @@ +chat: + team-chat: + description: "切換團隊聊天開關" + chat-on: 團隊聊天已開啟. + chat-off: 團隊聊天已關閉. + syntax: [team] [name]: [message] + mute: + description: "切換靜音團隊聊天訊息" + mute-on: 團隊聊天已靜音. + mute-off: 團隊聊天已取消靜音. + reminder: (團隊聊天已靜音 - 使用 /teamchat mute 來取消靜音) + spy: + description: "切換監視所有團隊聊天" + spy-on: 團隊聊天監視已開啟. + spy-off: 團隊聊天監視已關閉. + syntax: [team-chat-spy] [name]: [message] + island-chat: + description: "切換島嶼聊天開關" + island-on: 島嶼聊天已開啟. + island-off: 島嶼聊天已關閉. + syntax: [island] [name]: [message] + spy: + description: "切換監視所有島嶼聊天" + spy-on: 島嶼聊天監視已開啟. + spy-off: 島嶼聊天監視已關閉. + syntax: [island-chat-spy] [name]: [message] diff --git a/src/main/resources/plugin.yml b/src/main/resources/plugin.yml index 42961f2..84e9f21 100644 --- a/src/main/resources/plugin.yml +++ b/src/main/resources/plugin.yml @@ -1,7 +1,7 @@ name: BentoBox-Chat main: world.bentobox.chat.ChatPladdon version: ${project.version}${build.number} -api-version: "1.16" +api-version: "1.21" authors: [tastybento] contributors: ["The BentoBoxWorld Community"] diff --git a/src/test/java/world/bentobox/chat/ChatPladdonTest.java b/src/test/java/world/bentobox/chat/ChatPladdonTest.java new file mode 100644 index 0000000..94ac59a --- /dev/null +++ b/src/test/java/world/bentobox/chat/ChatPladdonTest.java @@ -0,0 +1,45 @@ +package world.bentobox.chat; + +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertSame; + +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +/** + * Tests for the {@link ChatPladdon} class. + */ +public class ChatPladdonTest extends CommonTestSetup { + + private ChatPladdon pladdon; + + @Override + @BeforeEach + public void setUp() throws Exception { + super.setUp(); + pladdon = WhiteBox.newUninitializedInstance(ChatPladdon.class); + } + + @Override + @AfterEach + public void tearDown() throws Exception { + super.tearDown(); + } + + @Test + public void testGetAddonReturnsChat() { + // Set a Chat instance via reflection to avoid classloader issues + Chat chat = WhiteBox.newUninitializedInstance(Chat.class); + WhiteBox.setInternalState(pladdon, "addon", chat); + assertNotNull(pladdon.getAddon()); + } + + @Test + public void testGetAddonReturnsSameInstance() { + Chat chat = WhiteBox.newUninitializedInstance(Chat.class); + WhiteBox.setInternalState(pladdon, "addon", chat); + // Should return the same instance on repeated calls + assertSame(pladdon.getAddon(), pladdon.getAddon()); + } +} diff --git a/src/test/java/world/bentobox/chat/ChatTest.java b/src/test/java/world/bentobox/chat/ChatTest.java new file mode 100644 index 0000000..ed87b48 --- /dev/null +++ b/src/test/java/world/bentobox/chat/ChatTest.java @@ -0,0 +1,105 @@ +package world.bentobox.chat; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertTrue; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; + +import java.util.List; +import java.util.Map; + +import org.bukkit.World; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +import world.bentobox.bentobox.api.addons.AddonDescription; +import world.bentobox.bentobox.api.addons.GameModeAddon; + +class ChatTest extends CommonTestSetup { + + private Chat chat; + private GameModeAddon bskyblock; + private World bskyblockOverWorld; + + @BeforeEach + @Override + public void setUp() throws Exception { + super.setUp(); + chat = WhiteBox.newUninitializedInstance(Chat.class); + WhiteBox.setInternalState(chat, "settings", settings); + + bskyblockOverWorld = mock(World.class); + bskyblock = mock(GameModeAddon.class); + AddonDescription desc = new AddonDescription.Builder("world.bentobox.bskyblock.BSkyBlock", "BSkyBlock", "1.0.0").build(); + when(bskyblock.getDescription()).thenReturn(desc); + when(bskyblock.getOverWorld()).thenReturn(bskyblockOverWorld); + when(am.getGameModeAddons()).thenReturn(List.of(bskyblock)); + } + + @Test + void getWorldsFromExtra_emptyConfig_returnsEmpty() { + when(settings.getExtraChatWorlds()).thenReturn(Map.of()); + + List result = chat.getWorldsFromExtra("any_world"); + + assertTrue(result.isEmpty()); + } + + @Test + void getWorldsFromExtra_exactMatch_returnsOverworld() { + when(settings.getExtraChatWorlds()).thenReturn(Map.of("BSkyBlock", List.of("spawn_world"))); + + List result = chat.getWorldsFromExtra("spawn_world"); + + assertEquals(List.of(bskyblockOverWorld), result); + } + + @Test + void getWorldsFromExtra_caseInsensitiveWorldName_returnsOverworld() { + // Config lists "Spawn_World" but server names it "spawn_world" + when(settings.getExtraChatWorlds()).thenReturn(Map.of("BSkyBlock", List.of("Spawn_World"))); + + List result = chat.getWorldsFromExtra("spawn_world"); + + assertEquals(List.of(bskyblockOverWorld), result); + } + + @Test + void getWorldsFromExtra_noMatchingWorld_returnsEmpty() { + when(settings.getExtraChatWorlds()).thenReturn(Map.of("BSkyBlock", List.of("spawn_world"))); + + List result = chat.getWorldsFromExtra("some_other_world"); + + assertTrue(result.isEmpty()); + } + + @Test + void getWorldsFromExtra_unknownGameMode_returnsEmpty() { + // Config references a game mode that isn't registered + when(settings.getExtraChatWorlds()).thenReturn(Map.of("UnknownMode", List.of("spawn_world"))); + + List result = chat.getWorldsFromExtra("spawn_world"); + + assertTrue(result.isEmpty()); + } + + @Test + void getWorldsFromExtra_multipleGameModes_returnsAllMatchingOverworlds() { + GameModeAddon acidisland = mock(GameModeAddon.class); + World acidOverWorld = mock(World.class); + AddonDescription acidDesc = new AddonDescription.Builder("world.bentobox.acidisland.AcidIsland", "AcidIsland", "1.0.0").build(); + when(acidisland.getDescription()).thenReturn(acidDesc); + when(acidisland.getOverWorld()).thenReturn(acidOverWorld); + when(am.getGameModeAddons()).thenReturn(List.of(bskyblock, acidisland)); + + when(settings.getExtraChatWorlds()).thenReturn(Map.of( + "BSkyBlock", List.of("spawn_world"), + "AcidIsland", List.of("spawn_world"))); + + List result = chat.getWorldsFromExtra("spawn_world"); + + assertEquals(2, result.size()); + assertTrue(result.contains(bskyblockOverWorld)); + assertTrue(result.contains(acidOverWorld)); + } +} diff --git a/src/test/java/world/bentobox/chat/CommonTestSetup.java b/src/test/java/world/bentobox/chat/CommonTestSetup.java new file mode 100644 index 0000000..1b6e165 --- /dev/null +++ b/src/test/java/world/bentobox/chat/CommonTestSetup.java @@ -0,0 +1,208 @@ +package world.bentobox.chat; + +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.anyString; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; + +import java.io.File; +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Path; +import java.util.Collections; +import java.util.Comparator; +import java.util.Optional; +import java.util.UUID; + +import org.bukkit.Bukkit; +import org.bukkit.Location; +import org.bukkit.World; +import org.bukkit.entity.Player; +import org.bukkit.inventory.ItemFactory; +import org.bukkit.plugin.PluginManager; +import org.bukkit.scheduler.BukkitScheduler; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeEach; +import org.mockbukkit.mockbukkit.MockBukkit; +import org.mockbukkit.mockbukkit.ServerMock; +import org.mockito.Mock; +import org.mockito.MockedStatic; +import org.mockito.Mockito; +import org.mockito.MockitoAnnotations; +import org.mockito.stubbing.Answer; + +import com.google.common.collect.ImmutableSet; + +import world.bentobox.bentobox.BentoBox; +import world.bentobox.bentobox.api.addons.GameModeAddon; +import world.bentobox.bentobox.api.commands.CompositeCommand; +import world.bentobox.bentobox.api.user.Notifier; +import world.bentobox.bentobox.api.user.User; +import world.bentobox.bentobox.database.objects.Island; +import world.bentobox.bentobox.managers.AddonsManager; +import world.bentobox.bentobox.managers.IslandWorldManager; +import world.bentobox.bentobox.managers.IslandsManager; +import world.bentobox.bentobox.managers.LocalesManager; +import world.bentobox.bentobox.managers.PlaceholdersManager; + +/** + * Common test setup for Chat addon tests. + * Provides a BentoBox plugin mock, common mocks, and MockBukkit server. + * Don't forget to call {@code super.setUp()} and {@code super.tearDown()} in subclasses. + */ +public abstract class CommonTestSetup { + + protected UUID uuid = UUID.randomUUID(); + + @Mock + protected Chat addon; + @Mock + protected Player player; + @Mock + protected PluginManager pim; + @Mock + protected ItemFactory itemFactory; + @Mock + protected Location location; + @Mock + protected World world; + @Mock + protected IslandWorldManager iwm; + @Mock + protected IslandsManager im; + @Mock + protected Island island; + @Mock + protected BentoBox plugin; + @Mock + protected Notifier notifier; + @Mock + protected BukkitScheduler sch; + @Mock + protected LocalesManager lm; + @Mock + protected CompositeCommand ic; + @Mock + protected GameModeAddon gameModeAddon; + @Mock + protected AddonsManager am; + @Mock + protected Settings settings; + + protected ServerMock server; + protected MockedStatic mockedBukkit; + protected AutoCloseable closeable; + + @BeforeEach + public void setUp() throws Exception { + closeable = MockitoAnnotations.openMocks(this); + server = MockBukkit.mock(); + + // Set up BentoBox singleton + WhiteBox.setInternalState(BentoBox.class, "instance", plugin); + + // Register the static mock for Bukkit + mockedBukkit = Mockito.mockStatic(Bukkit.class, Mockito.RETURNS_DEEP_STUBS); + mockedBukkit.when(Bukkit::getMinecraftVersion).thenReturn("1.21.11"); + mockedBukkit.when(Bukkit::getBukkitVersion).thenReturn("1.21.11-R0.1-SNAPSHOT"); + mockedBukkit.when(Bukkit::getPluginManager).thenReturn(pim); + mockedBukkit.when(Bukkit::getItemFactory).thenReturn(itemFactory); + mockedBukkit.when(Bukkit::getServer).thenReturn(server); + mockedBukkit.when(() -> Bukkit.getScheduler()).thenReturn(sch); + mockedBukkit.when(Bukkit::getOnlinePlayers).thenAnswer(invocation -> Collections.emptyList()); + + // World + when(world.toString()).thenReturn("world"); + when(world.getName()).thenReturn("BSkyBlock_world"); + + // Location + when(location.getWorld()).thenReturn(world); + when(location.getBlockX()).thenReturn(0); + when(location.getBlockY()).thenReturn(0); + when(location.getBlockZ()).thenReturn(0); + when(location.clone()).thenReturn(location); + + // Player + when(player.getUniqueId()).thenReturn(uuid); + when(player.getLocation()).thenReturn(location); + when(player.getWorld()).thenReturn(world); + when(player.getName()).thenReturn("tastybento"); + + User.setPlugin(plugin); + User.clearUsers(); + User.getInstance(player); + + // IWM + when(plugin.getIWM()).thenReturn(iwm); + when(iwm.inWorld(any(Location.class))).thenReturn(true); + when(iwm.inWorld(any(World.class))).thenReturn(true); + when(iwm.getFriendlyName(any())).thenReturn("BSkyBlock"); + + // Island Manager + when(plugin.getIslands()).thenReturn(im); + when(im.getProtectedIslandAt(any())).thenReturn(Optional.of(island)); + when(island.getOwner()).thenReturn(uuid); + when(island.getMemberSet()).thenReturn(ImmutableSet.of(uuid)); + when(island.onIsland(any())).thenReturn(true); + + // Locales & Placeholders + when(lm.get(any(), any())).thenAnswer( + (Answer) invocation -> invocation.getArgument(1, String.class)); + PlaceholdersManager phm = mock(PlaceholdersManager.class); + when(plugin.getPlaceholdersManager()).thenReturn(phm); + when(phm.replacePlaceholders(any(), any())).thenAnswer( + (Answer) invocation -> invocation.getArgument(1, String.class)); + when(plugin.getLocalesManager()).thenReturn(lm); + + // Notifier + when(plugin.getNotifier()).thenReturn(notifier); + + // BentoBox settings + world.bentobox.bentobox.Settings bentoSettings = new world.bentobox.bentobox.Settings(); + when(plugin.getSettings()).thenReturn(bentoSettings); + + // AddonsManager + when(plugin.getAddonsManager()).thenReturn(am); + when(am.getGameModeAddons()).thenReturn(Collections.emptyList()); + + // Addon + when(addon.getPlugin()).thenReturn(plugin); + when(addon.getIslands()).thenReturn(im); + when(addon.getSettings()).thenReturn(settings); + + // Command + when(ic.getAddon()).thenReturn(addon); + when(ic.getPermissionPrefix()).thenReturn("bskyblock."); + when(ic.getLabel()).thenReturn("island"); + when(ic.getTopLabel()).thenReturn("island"); + when(ic.getWorld()).thenReturn(world); + + // Settings + when(settings.isLogTeamChats()).thenReturn(false); + when(settings.isLogIslandChats()).thenReturn(false); + when(settings.getEventPriority()).thenReturn(org.bukkit.event.EventPriority.NORMAL); + + // Addon commandsManager + world.bentobox.bentobox.managers.CommandsManager cm = mock( + world.bentobox.bentobox.managers.CommandsManager.class); + when(plugin.getCommandsManager()).thenReturn(cm); + } + + @AfterEach + public void tearDown() throws Exception { + mockedBukkit.closeOnDemand(); + closeable.close(); + MockBukkit.unmock(); + User.clearUsers(); + Mockito.framework().clearInlineMocks(); + deleteAll(new File("database")); + deleteAll(new File("database_backup")); + } + + protected static void deleteAll(File file) throws IOException { + if (file.exists()) { + Files.walk(file.toPath()).sorted(Comparator.reverseOrder()).map(Path::toFile) + .forEach(File::delete); + } + } +} diff --git a/src/test/java/world/bentobox/chat/SettingsTest.java b/src/test/java/world/bentobox/chat/SettingsTest.java new file mode 100644 index 0000000..40d3265 --- /dev/null +++ b/src/test/java/world/bentobox/chat/SettingsTest.java @@ -0,0 +1,162 @@ +package world.bentobox.chat; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertTrue; +import static org.mockito.Mockito.mock; + +import java.util.Arrays; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +import org.bukkit.event.EventPriority; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +/** + * Tests for the {@link Settings} class. + */ +public class SettingsTest { + + private Settings settings; + + @BeforeEach + public void setUp() { + // Set up static addon reference required by getEventPriority + Chat addonMock = mock(Chat.class); + Chat.addon = addonMock; + settings = new Settings(); + } + + @Test + public void testDefaultTeamChatGamemodes() { + List defaults = settings.getTeamChatGamemodes(); + assertNotNull(defaults); + assertTrue(defaults.contains("BSkyBlock")); + assertTrue(defaults.contains("AcidIsland")); + assertTrue(defaults.contains("CaveBlock")); + assertTrue(defaults.contains("SkyGrid")); + } + + @Test + public void testSetTeamChatGamemodes() { + List newList = Arrays.asList("BSkyBlock", "MyAddon"); + settings.setTeamChatGamemodes(newList); + assertEquals(newList, settings.getTeamChatGamemodes()); + } + + @Test + public void testDefaultIslandChatGamemodes() { + List defaults = settings.getIslandChatGamemodes(); + assertNotNull(defaults); + assertTrue(defaults.contains("BSkyBlock")); + assertTrue(defaults.contains("AcidIsland")); + } + + @Test + public void testSetIslandChatGamemodes() { + List newList = Arrays.asList("BSkyBlock"); + settings.setIslandChatGamemodes(newList); + assertEquals(newList, settings.getIslandChatGamemodes()); + } + + @Test + public void testDefaultLogTeamChats() { + assertFalse(settings.isLogTeamChats()); + } + + @Test + public void testSetLogTeamChats() { + settings.setLogTeamChats(true); + assertTrue(settings.isLogTeamChats()); + settings.setLogTeamChats(false); + assertFalse(settings.isLogTeamChats()); + } + + @Test + public void testDefaultLogIslandChats() { + assertFalse(settings.isLogIslandChats()); + } + + @Test + public void testSetLogIslandChats() { + settings.setLogIslandChats(true); + assertTrue(settings.isLogIslandChats()); + settings.setLogIslandChats(false); + assertFalse(settings.isLogIslandChats()); + } + + @Test + public void testDefaultEventPriority() { + // Default is "normal" which maps to NORMAL + assertEquals(EventPriority.NORMAL, settings.getEventPriority()); + } + + @Test + public void testSetEventPriorityEnum() { + settings.setEventPriority(EventPriority.HIGH); + assertEquals(EventPriority.HIGH, settings.getEventPriority()); + } + + @Test + public void testSetEventPriorityString() { + settings.setEventPriority("highest"); + assertEquals(EventPriority.HIGHEST, settings.getEventPriority()); + } + + @Test + public void testSetEventPriorityStringLow() { + settings.setEventPriority("low"); + assertEquals(EventPriority.LOW, settings.getEventPriority()); + } + + @Test + public void testSetEventPriorityStringLowest() { + settings.setEventPriority("lowest"); + assertEquals(EventPriority.LOWEST, settings.getEventPriority()); + } + + @Test + public void testSetEventPriorityStringMonitor() { + settings.setEventPriority("monitor"); + assertEquals(EventPriority.MONITOR, settings.getEventPriority()); + } + + @Test + public void testSetEventPriorityStringInvalid() { + // Invalid value should fall back to NORMAL and log an error + settings.setEventPriority("invalid_priority"); + assertEquals(EventPriority.NORMAL, settings.getEventPriority()); + } + + @Test + public void testDefaultChatGamemode() { + assertEquals("", settings.getDefaultChatGamemode()); + } + + @Test + public void testSetDefaultChatGamemode() { + settings.setDefaultChatGamemode("BSkyBlock"); + assertEquals("BSkyBlock", settings.getDefaultChatGamemode()); + } + + @Test + public void testDefaultExtraChatWorlds() { + Map> defaults = settings.getExtraChatWorlds(); + assertNotNull(defaults); + assertTrue(defaults.isEmpty()); + } + + @Test + public void testSetExtraChatWorlds() { + Map> extra = new HashMap<>(); + extra.put("BSkyBlock", Arrays.asList("world", "spawn_world")); + extra.put("AcidIsland", Arrays.asList("spawn_world")); + settings.setExtraChatWorlds(extra); + assertEquals(extra, settings.getExtraChatWorlds()); + assertEquals(2, settings.getExtraChatWorlds().get("BSkyBlock").size()); + assertEquals(1, settings.getExtraChatWorlds().get("AcidIsland").size()); + } +} diff --git a/src/test/java/world/bentobox/chat/WhiteBox.java b/src/test/java/world/bentobox/chat/WhiteBox.java new file mode 100644 index 0000000..8c22979 --- /dev/null +++ b/src/test/java/world/bentobox/chat/WhiteBox.java @@ -0,0 +1,67 @@ +package world.bentobox.chat; + +/** + * Utility class for setting private/static fields via reflection in tests. + */ +public class WhiteBox { + + /** + * Sets the value of a private static field using Java Reflection. + * @param targetClass The class containing the static field. + * @param fieldName The name of the private static field. + * @param value The value to set the field to. + */ + public static void setInternalState(Class targetClass, String fieldName, Object value) { + try { + java.lang.reflect.Field field = targetClass.getDeclaredField(fieldName); + field.setAccessible(true); + field.set(null, value); + } catch (NoSuchFieldException | IllegalAccessException e) { + throw new RuntimeException( + "Failed to set static field '" + fieldName + "' on class " + targetClass.getName(), e); + } + } + + /** + * Sets the value of a private instance field using Java Reflection. + * @param target The object instance containing the field. + * @param fieldName The name of the private field. + * @param value The value to set the field to. + */ + public static void setInternalState(Object target, String fieldName, Object value) { + try { + java.lang.reflect.Field field = target.getClass().getDeclaredField(fieldName); + field.setAccessible(true); + field.set(target, value); + } catch (NoSuchFieldException | IllegalAccessException e) { + throw new RuntimeException( + "Failed to set field '" + fieldName + "' on " + target.getClass().getName(), e); + } + } + + /** + * Creates an instance of a class without calling its constructor. + * Useful for classes that require special classloaders (e.g., JavaPlugin subclasses). + * @param clazz The class to instantiate. + * @param The type of the class. + * @return A new uninitialized instance. + */ + @SuppressWarnings("unchecked") + public static T newUninitializedInstance(Class clazz) { + try { + return (T) getUnsafe().allocateInstance(clazz); + } catch (Exception e) { + throw new RuntimeException("Failed to create uninitialized instance of " + clazz.getName(), e); + } + } + + private static sun.misc.Unsafe getUnsafe() { + try { + java.lang.reflect.Field f = sun.misc.Unsafe.class.getDeclaredField("theUnsafe"); + f.setAccessible(true); + return (sun.misc.Unsafe) f.get(null); + } catch (Exception e) { + throw new RuntimeException("Failed to get Unsafe instance", e); + } + } +} diff --git a/src/test/java/world/bentobox/chat/commands/admin/AdminIslandChatSpyCommandTest.java b/src/test/java/world/bentobox/chat/commands/admin/AdminIslandChatSpyCommandTest.java new file mode 100644 index 0000000..90a0b47 --- /dev/null +++ b/src/test/java/world/bentobox/chat/commands/admin/AdminIslandChatSpyCommandTest.java @@ -0,0 +1,67 @@ +package world.bentobox.chat.commands.admin; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertTrue; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +import java.util.Collections; + +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.mockito.Mock; + +import world.bentobox.bentobox.api.user.User; +import world.bentobox.chat.CommonTestSetup; +import world.bentobox.chat.listeners.ChatListener; + +/** + * Tests for the {@link AdminIslandChatSpyCommand} class. + */ +public class AdminIslandChatSpyCommandTest extends CommonTestSetup { + + @Mock + private ChatListener chatListener; + + @Mock + private User user; + + private AdminIslandChatSpyCommand cmd; + + @Override + @BeforeEach + public void setUp() throws Exception { + super.setUp(); + when(addon.getListener()).thenReturn(chatListener); + when(user.getUniqueId()).thenReturn(uuid); + cmd = new AdminIslandChatSpyCommand(addon, ic, "chatspy"); + } + + @Override + @AfterEach + public void tearDown() throws Exception { + super.tearDown(); + } + + @Test + public void testSetup() { + assertEquals("bskyblock.chat.spy", cmd.getPermission()); + assertTrue(cmd.isOnlyPlayer()); + assertEquals("chat.island-chat.spy.description", cmd.getDescription()); + } + + @Test + public void testExecuteToggleSpyOn() { + when(chatListener.toggleIslandSpy(uuid)).thenReturn(true); + assertTrue(cmd.execute(user, "chatspy", Collections.emptyList())); + verify(user).sendMessage("chat.island-chat.spy.spy-on"); + } + + @Test + public void testExecuteToggleSpyOff() { + when(chatListener.toggleIslandSpy(uuid)).thenReturn(false); + assertTrue(cmd.execute(user, "chatspy", Collections.emptyList())); + verify(user).sendMessage("chat.island-chat.spy.spy-off"); + } +} diff --git a/src/test/java/world/bentobox/chat/commands/admin/AdminTeamChatSpyCommandTest.java b/src/test/java/world/bentobox/chat/commands/admin/AdminTeamChatSpyCommandTest.java new file mode 100644 index 0000000..217b6ba --- /dev/null +++ b/src/test/java/world/bentobox/chat/commands/admin/AdminTeamChatSpyCommandTest.java @@ -0,0 +1,67 @@ +package world.bentobox.chat.commands.admin; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertTrue; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +import java.util.Collections; + +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.mockito.Mock; + +import world.bentobox.bentobox.api.user.User; +import world.bentobox.chat.CommonTestSetup; +import world.bentobox.chat.listeners.ChatListener; + +/** + * Tests for the {@link AdminTeamChatSpyCommand} class. + */ +public class AdminTeamChatSpyCommandTest extends CommonTestSetup { + + @Mock + private ChatListener chatListener; + + @Mock + private User user; + + private AdminTeamChatSpyCommand cmd; + + @Override + @BeforeEach + public void setUp() throws Exception { + super.setUp(); + when(addon.getListener()).thenReturn(chatListener); + when(user.getUniqueId()).thenReturn(uuid); + cmd = new AdminTeamChatSpyCommand(addon, ic, "teamchatspy"); + } + + @Override + @AfterEach + public void tearDown() throws Exception { + super.tearDown(); + } + + @Test + public void testSetup() { + assertEquals("bskyblock.chat.spy", cmd.getPermission()); + assertTrue(cmd.isOnlyPlayer()); + assertEquals("chat.team-chat.spy.description", cmd.getDescription()); + } + + @Test + public void testExecuteToggleSpyOn() { + when(chatListener.toggleSpy(uuid)).thenReturn(true); + assertTrue(cmd.execute(user, "teamchatspy", Collections.emptyList())); + verify(user).sendMessage("chat.team-chat.spy.spy-on"); + } + + @Test + public void testExecuteToggleSpyOff() { + when(chatListener.toggleSpy(uuid)).thenReturn(false); + assertTrue(cmd.execute(user, "teamchatspy", Collections.emptyList())); + verify(user).sendMessage("chat.team-chat.spy.spy-off"); + } +} diff --git a/src/test/java/world/bentobox/chat/commands/island/IslandChatCommandTest.java b/src/test/java/world/bentobox/chat/commands/island/IslandChatCommandTest.java new file mode 100644 index 0000000..4b9ddcb --- /dev/null +++ b/src/test/java/world/bentobox/chat/commands/island/IslandChatCommandTest.java @@ -0,0 +1,109 @@ +package world.bentobox.chat.commands.island; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertTrue; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.Mockito.never; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +import java.util.Collections; +import java.util.List; +import java.util.Optional; + +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.mockito.Mock; + +import world.bentobox.bentobox.api.user.User; +import world.bentobox.bentobox.database.objects.Island; +import world.bentobox.chat.CommonTestSetup; +import world.bentobox.chat.listeners.ChatListener; + +/** + * Tests for the {@link IslandChatCommand} class. + */ +public class IslandChatCommandTest extends CommonTestSetup { + + @Mock + private ChatListener chatListener; + + @Mock + private User user; + + private IslandChatCommand cmd; + + @Override + @BeforeEach + public void setUp() throws Exception { + super.setUp(); + when(addon.getListener()).thenReturn(chatListener); + when(user.getUniqueId()).thenReturn(uuid); + when(user.getPlayer()).thenReturn(player); + when(user.getLocation()).thenReturn(location); + cmd = new IslandChatCommand(addon, ic, "chat"); + } + + @Override + @AfterEach + public void tearDown() throws Exception { + super.tearDown(); + } + + @Test + public void testSetup() { + assertEquals("bskyblock.chat.island-chat", cmd.getPermission()); + assertTrue(cmd.isOnlyPlayer()); + assertEquals("chat.island-chat.description", cmd.getDescription()); + } + + @Test + public void testCanExecuteNoIsland() { + when(im.getIslandAt(any())).thenReturn(Optional.empty()); + assertFalse(cmd.canExecute(user, "chat", Collections.emptyList())); + } + + @Test + public void testCanExecuteWithIsland() { + when(im.getIslandAt(any())).thenReturn(Optional.of(island)); + assertTrue(cmd.canExecute(user, "chat", Collections.emptyList())); + } + + @Test + public void testExecuteToggleOn() { + when(im.getIslandAt(any())).thenReturn(Optional.of(island)); + cmd.canExecute(user, "chat", Collections.emptyList()); // sets island field + when(chatListener.toggleIslandChat(any(Island.class), eq(player))).thenReturn(true); + assertTrue(cmd.execute(user, "chat", Collections.emptyList())); + verify(user).sendMessage("chat.island-chat.island-on"); + } + + @Test + public void testExecuteToggleOff() { + when(im.getIslandAt(any())).thenReturn(Optional.of(island)); + cmd.canExecute(user, "chat", Collections.emptyList()); + when(chatListener.toggleIslandChat(any(Island.class), eq(player))).thenReturn(false); + assertTrue(cmd.execute(user, "chat", Collections.emptyList())); + verify(user).sendMessage("chat.island-chat.island-off"); + } + + @Test + public void testExecuteWithArgs() { + when(im.getIslandAt(any())).thenReturn(Optional.of(island)); + cmd.canExecute(user, "chat", Collections.emptyList()); + assertTrue(cmd.execute(user, "chat", List.of("hello", "island"))); + verify(chatListener).islandChat(any(Island.class), eq(player), eq("hello island")); + verify(chatListener, never()).toggleIslandChat(any(), any()); + } + + @Test + public void testExecuteWithSingleArg() { + when(im.getIslandAt(any())).thenReturn(Optional.of(island)); + cmd.canExecute(user, "chat", Collections.emptyList()); + assertTrue(cmd.execute(user, "chat", List.of("hi"))); + verify(chatListener).islandChat(any(Island.class), eq(player), eq("hi")); + } +} diff --git a/src/test/java/world/bentobox/chat/commands/island/IslandTeamChatCommandTest.java b/src/test/java/world/bentobox/chat/commands/island/IslandTeamChatCommandTest.java new file mode 100644 index 0000000..3749310 --- /dev/null +++ b/src/test/java/world/bentobox/chat/commands/island/IslandTeamChatCommandTest.java @@ -0,0 +1,105 @@ +package world.bentobox.chat.commands.island; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertTrue; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.anyString; +import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.never; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +import java.util.Collections; +import java.util.List; + +import org.bukkit.World; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.mockito.Mock; + +import world.bentobox.bentobox.api.user.User; +import world.bentobox.chat.CommonTestSetup; +import world.bentobox.chat.listeners.ChatListener; + +/** + * Tests for the {@link IslandTeamChatCommand} class. + */ +public class IslandTeamChatCommandTest extends CommonTestSetup { + + @Mock + private ChatListener chatListener; + + @Mock + private User user; + + private IslandTeamChatCommand cmd; + + @Override + @BeforeEach + public void setUp() throws Exception { + super.setUp(); + when(addon.getListener()).thenReturn(chatListener); + when(user.getUniqueId()).thenReturn(uuid); + when(user.getPlayer()).thenReturn(player); + when(user.getWorld()).thenReturn(world); + cmd = new IslandTeamChatCommand(addon, ic, "teamchat"); + } + + @Override + @AfterEach + public void tearDown() throws Exception { + super.tearDown(); + } + + @Test + public void testSetup() { + assertEquals("bskyblock.chat.team-chat", cmd.getPermission()); + assertTrue(cmd.isOnlyPlayer()); + assertEquals("chat.team-chat.description", cmd.getDescription()); + } + + @Test + public void testCanExecuteNoTeam() { + when(im.inTeam(any(World.class), eq(uuid))).thenReturn(false); + assertFalse(cmd.canExecute(user, "teamchat", Collections.emptyList())); + verify(user).sendMessage("general.errors.no-team"); + } + + @Test + public void testCanExecuteInTeam() { + when(im.inTeam(any(World.class), eq(uuid))).thenReturn(true); + assertTrue(cmd.canExecute(user, "teamchat", Collections.emptyList())); + verify(user, never()).sendMessage(anyString()); + } + + @Test + public void testExecuteToggleOn() { + when(chatListener.togglePlayerTeamChat(uuid)).thenReturn(true); + assertTrue(cmd.execute(user, "teamchat", Collections.emptyList())); + verify(user).sendMessage("chat.team-chat.chat-on"); + } + + @Test + public void testExecuteToggleOff() { + when(chatListener.togglePlayerTeamChat(uuid)).thenReturn(false); + assertTrue(cmd.execute(user, "teamchat", Collections.emptyList())); + verify(user).sendMessage("chat.team-chat.chat-off"); + } + + @Test + public void testExecuteWithArgs() { + // When args are provided, send directly to team chat + assertTrue(cmd.execute(user, "teamchat", List.of("hello", "world"))); + verify(chatListener).teamChat(any(World.class), eq(player), eq("hello world")); + verify(chatListener, never()).togglePlayerTeamChat(any()); + } + + @Test + public void testExecuteWithSingleArg() { + assertTrue(cmd.execute(user, "teamchat", List.of("hi"))); + verify(chatListener).teamChat(any(World.class), eq(player), eq("hi")); + } +} diff --git a/src/test/java/world/bentobox/chat/commands/island/IslandTeamMuteCommandTest.java b/src/test/java/world/bentobox/chat/commands/island/IslandTeamMuteCommandTest.java new file mode 100644 index 0000000..20a4b0c --- /dev/null +++ b/src/test/java/world/bentobox/chat/commands/island/IslandTeamMuteCommandTest.java @@ -0,0 +1,89 @@ +package world.bentobox.chat.commands.island; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertTrue; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.anyString; +import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.Mockito.never; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +import java.util.Collections; + +import org.bukkit.World; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.mockito.Mock; + +import world.bentobox.bentobox.api.user.User; +import world.bentobox.chat.CommonTestSetup; +import world.bentobox.chat.listeners.ChatListener; + +/** + * Tests for the {@link IslandTeamMuteCommand} class. + */ +public class IslandTeamMuteCommandTest extends CommonTestSetup { + + @Mock + private ChatListener chatListener; + + @Mock + private User user; + + private IslandTeamMuteCommand cmd; + + @Override + @BeforeEach + public void setUp() throws Exception { + super.setUp(); + when(addon.getListener()).thenReturn(chatListener); + when(user.getUniqueId()).thenReturn(uuid); + when(user.getPlayer()).thenReturn(player); + when(user.getWorld()).thenReturn(world); + cmd = new IslandTeamMuteCommand(addon, ic, "muteteamchat"); + } + + @Override + @AfterEach + public void tearDown() throws Exception { + super.tearDown(); + } + + @Test + public void testSetup() { + assertEquals("bskyblock.chat.team-chat.mute", cmd.getPermission()); + assertTrue(cmd.isOnlyPlayer()); + assertEquals("chat.team-chat.mute.description", cmd.getDescription()); + } + + @Test + public void testCanExecuteNoTeam() { + when(im.inTeam(any(World.class), eq(uuid))).thenReturn(false); + assertFalse(cmd.canExecute(user, "muteteamchat", Collections.emptyList())); + verify(user).sendMessage("general.errors.no-team"); + } + + @Test + public void testCanExecuteInTeam() { + when(im.inTeam(any(World.class), eq(uuid))).thenReturn(true); + assertTrue(cmd.canExecute(user, "muteteamchat", Collections.emptyList())); + verify(user, never()).sendMessage(anyString()); + } + + @Test + public void testExecuteMuteOn() { + when(chatListener.toggleMuteTeamChat(uuid)).thenReturn(true); + assertTrue(cmd.execute(user, "muteteamchat", Collections.emptyList())); + verify(user).sendMessage("chat.team-chat.mute.mute-on"); + } + + @Test + public void testExecuteMuteOff() { + when(chatListener.toggleMuteTeamChat(uuid)).thenReturn(false); + assertTrue(cmd.execute(user, "muteteamchat", Collections.emptyList())); + verify(user).sendMessage("chat.team-chat.mute.mute-off"); + } +} diff --git a/src/test/java/world/bentobox/chat/listeners/ChatListenerTest.java b/src/test/java/world/bentobox/chat/listeners/ChatListenerTest.java new file mode 100644 index 0000000..0501a88 --- /dev/null +++ b/src/test/java/world/bentobox/chat/listeners/ChatListenerTest.java @@ -0,0 +1,436 @@ +package world.bentobox.chat.listeners; + +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertTrue; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.never; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +import java.util.Collections; +import java.util.List; +import java.util.Optional; +import java.util.UUID; + +import org.bukkit.World; +import org.bukkit.entity.Player; +import org.bukkit.event.player.AsyncPlayerChatEvent; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.mockito.Mock; + +import com.google.common.collect.ImmutableSet; + +import world.bentobox.bentobox.database.objects.Island; +import world.bentobox.chat.CommonTestSetup; + +/** + * Tests for the {@link ChatListener} class. + */ +@SuppressWarnings("deprecation") +public class ChatListenerTest extends CommonTestSetup { + + private ChatListener listener; + + @Override + @BeforeEach + public void setUp() throws Exception { + super.setUp(); + when(addon.isRegisteredGameWorld(any(World.class))).thenReturn(true); + when(addon.getChatWorld()).thenReturn(Optional.empty()); + listener = new ChatListener(addon); + } + + @Override + @AfterEach + public void tearDown() throws Exception { + super.tearDown(); + } + + // ----------------------------------------------------------------------- + // togglePlayerTeamChat tests + // ----------------------------------------------------------------------- + + @Test + public void testTogglePlayerTeamChatOn() { + UUID uid = UUID.randomUUID(); + assertTrue(listener.togglePlayerTeamChat(uid), + "First toggle should turn team chat ON"); + } + + @Test + public void testTogglePlayerTeamChatOff() { + UUID uid = UUID.randomUUID(); + listener.togglePlayerTeamChat(uid); // ON + assertFalse(listener.togglePlayerTeamChat(uid), + "Second toggle should turn team chat OFF"); + } + + @Test + public void testIsTeamChatFalseByDefault() { + assertFalse(listener.isTeamChat(UUID.randomUUID())); + } + + @Test + public void testIsTeamChatTrueAfterToggle() { + UUID uid = UUID.randomUUID(); + listener.togglePlayerTeamChat(uid); + assertTrue(listener.isTeamChat(uid)); + } + + // ----------------------------------------------------------------------- + // toggleSpy tests + // ----------------------------------------------------------------------- + + @Test + public void testToggleSpyOn() { + assertTrue(listener.toggleSpy(uuid)); + } + + @Test + public void testToggleSpyOff() { + listener.toggleSpy(uuid); // ON + assertFalse(listener.toggleSpy(uuid)); + } + + // ----------------------------------------------------------------------- + // toggleIslandSpy tests + // ----------------------------------------------------------------------- + + @Test + public void testToggleIslandSpyOn() { + assertTrue(listener.toggleIslandSpy(uuid)); + } + + @Test + public void testToggleIslandSpyOff() { + listener.toggleIslandSpy(uuid); // ON + assertFalse(listener.toggleIslandSpy(uuid)); + } + + // ----------------------------------------------------------------------- + // toggleIslandChat tests + // ----------------------------------------------------------------------- + + @Test + public void testToggleIslandChatOn() { + assertTrue(listener.toggleIslandChat(island, player)); + } + + @Test + public void testToggleIslandChatOff() { + listener.toggleIslandChat(island, player); // ON + assertFalse(listener.toggleIslandChat(island, player)); + } + + // ----------------------------------------------------------------------- + // isChat tests + // ----------------------------------------------------------------------- + + @Test + public void testIsChatFalseByDefault() { + assertFalse(listener.isChat(uuid)); + } + + @Test + public void testIsChatFalseWhenPlayerOffline() { + // Player is not online in server mock by default for an unknown UUID + assertFalse(listener.isChat(UUID.randomUUID())); + } + + @Test + public void testIsChatTrueAfterToggle() { + mockedBukkit.when(() -> org.bukkit.Bukkit.getPlayer(uuid)).thenReturn(player); + listener.toggleIslandChat(island, player); + assertTrue(listener.isChat(uuid)); + } + + // ----------------------------------------------------------------------- + // onChat tests + // ----------------------------------------------------------------------- + + @Test + public void testOnChatNotInRegisteredWorld() { + // Not in a registered game world + when(addon.isRegisteredGameWorld(any(World.class))).thenReturn(false); + when(addon.getChatWorld()).thenReturn(Optional.empty()); + when(addon.getWorldsFromExtra(any(String.class))).thenReturn(Collections.emptyList()); + + AsyncPlayerChatEvent event = new AsyncPlayerChatEvent(false, player, "Hello", Collections.emptySet()); + listener.onChat(event); + assertFalse(event.isCancelled()); + } + + @Test + public void testOnChatExtraChatWorldTeamChat() { + // Player is NOT in a registered game world + when(addon.isRegisteredGameWorld(any(World.class))).thenReturn(false); + when(addon.getChatWorld()).thenReturn(Optional.empty()); + // But the player's world is an extra chat world for BSkyBlock + World gameWorld = mock(World.class); + when(addon.getWorldsFromExtra("BSkyBlock_world")).thenReturn(List.of(gameWorld)); + + // Player has team chat enabled and is in a team + listener.togglePlayerTeamChat(uuid); + when(im.inTeam(gameWorld, uuid)).thenReturn(true); + + AsyncPlayerChatEvent event = new AsyncPlayerChatEvent(false, player, "Hello from spawn!", Collections.emptySet()); + listener.onChat(event); + // Chat should be cancelled (intercepted for team chat via extra world) + assertTrue(event.isCancelled()); + } + + @Test + public void testOnChatExtraChatWorldNoTeamChat() { + // Player is NOT in a registered game world + when(addon.isRegisteredGameWorld(any(World.class))).thenReturn(false); + when(addon.getChatWorld()).thenReturn(Optional.empty()); + // The player's world is an extra chat world + World gameWorld = mock(World.class); + when(addon.getWorldsFromExtra("BSkyBlock_world")).thenReturn(List.of(gameWorld)); + + // Player does NOT have team chat enabled + AsyncPlayerChatEvent event = new AsyncPlayerChatEvent(false, player, "Hello!", Collections.emptySet()); + when(im.getIslandAt(any())).thenReturn(Optional.empty()); + listener.onChat(event); + // Not cancelled because team chat is not toggled on + assertFalse(event.isCancelled()); + } + + @Test + public void testOnChatExtraChatWorldFallsBackToDefault() { + // Player is NOT in a registered game world + when(addon.isRegisteredGameWorld(any(World.class))).thenReturn(false); + // No extra chat worlds match + when(addon.getWorldsFromExtra("BSkyBlock_world")).thenReturn(Collections.emptyList()); + // But there is a default chat world + World defaultWorld = mock(World.class); + when(addon.getChatWorld()).thenReturn(Optional.of(defaultWorld)); + + // Player has team chat enabled and is in a team + listener.togglePlayerTeamChat(uuid); + when(im.inTeam(defaultWorld, uuid)).thenReturn(true); + + AsyncPlayerChatEvent event = new AsyncPlayerChatEvent(false, player, "Hello default!", Collections.emptySet()); + listener.onChat(event); + assertTrue(event.isCancelled()); + } + + @Test + public void testOnChatTeamChatActive() { + // Player has team chat enabled and is in a team + listener.togglePlayerTeamChat(uuid); + when(im.inTeam(any(World.class), any(UUID.class))).thenReturn(true); + + AsyncPlayerChatEvent event = new AsyncPlayerChatEvent(false, player, "Hello team!", Collections.emptySet()); + listener.onChat(event); + // Chat should be cancelled (intercepted for team chat) + assertTrue(event.isCancelled()); + } + + @Test + public void testOnChatTeamChatNotInTeam() { + // Player has team chat enabled but is NOT in a team + listener.togglePlayerTeamChat(uuid); + when(im.inTeam(any(World.class), any(UUID.class))).thenReturn(false); + when(im.getIslandAt(any())).thenReturn(Optional.empty()); + + AsyncPlayerChatEvent event = new AsyncPlayerChatEvent(false, player, "Hello!", Collections.emptySet()); + listener.onChat(event); + // Not cancelled because not in team + assertFalse(event.isCancelled()); + } + + @Test + public void testOnChatIslandChatActive() { + // Player is in island chat + listener.toggleIslandChat(island, player); + when(im.getIslandAt(any())).thenReturn(Optional.of(island)); + + AsyncPlayerChatEvent event = new AsyncPlayerChatEvent(false, player, "Hello island!", Collections.emptySet()); + listener.onChat(event); + assertTrue(event.isCancelled()); + } + + @Test + public void testOnChatIslandChatPlayerNotRegistered() { + // Island is in the map but this player is not registered to island chat + Player otherPlayer = mock(Player.class); + when(otherPlayer.getUniqueId()).thenReturn(UUID.randomUUID()); + when(otherPlayer.getWorld()).thenReturn(world); + when(otherPlayer.getLocation()).thenReturn(location); + + listener.toggleIslandChat(island, otherPlayer); // Register different player + when(im.getIslandAt(any())).thenReturn(Optional.of(island)); + + AsyncPlayerChatEvent event = new AsyncPlayerChatEvent(false, player, "Hello!", Collections.emptySet()); + listener.onChat(event); + // This player (uuid) is not in the island chatters set, so not cancelled + assertFalse(event.isCancelled()); + } + + // ----------------------------------------------------------------------- + // teamChat tests + // ----------------------------------------------------------------------- + + @Test + public void testTeamChatNoIsland() { + when(im.getIsland(any(World.class), any(UUID.class))).thenReturn(null); + // Should not throw, just return silently + listener.teamChat(world, player, "test message"); + // No verification needed - just ensures no NPE + } + + @Test + public void testTeamChatWithIsland() { + when(im.getIsland(any(World.class), any(UUID.class))).thenReturn(island); + when(island.getMemberSet()).thenReturn(ImmutableSet.of(uuid)); + mockedBukkit.when(org.bukkit.Bukkit::getOnlinePlayers).thenReturn(Collections.emptyList()); + + // Should not throw + listener.teamChat(world, player, "team message"); + } + + @Test + public void testTeamChatLogsWhenEnabled() { + when(settings.isLogTeamChats()).thenReturn(true); + when(im.getIsland(any(World.class), any(UUID.class))).thenReturn(island); + when(island.getMemberSet()).thenReturn(ImmutableSet.of(uuid)); + mockedBukkit.when(org.bukkit.Bukkit::getOnlinePlayers).thenReturn(Collections.emptyList()); + + listener.teamChat(world, player, "logged message"); + verify(addon).log("[Team Chat Log] tastybento: logged message"); + } + + @Test + public void testTeamChatDoesNotLogWhenDisabled() { + when(settings.isLogTeamChats()).thenReturn(false); + when(im.getIsland(any(World.class), any(UUID.class))).thenReturn(island); + when(island.getMemberSet()).thenReturn(ImmutableSet.of(uuid)); + mockedBukkit.when(org.bukkit.Bukkit::getOnlinePlayers).thenReturn(Collections.emptyList()); + + listener.teamChat(world, player, "silent message"); + verify(addon, never()).log(any()); + } + + // ----------------------------------------------------------------------- + // islandChat tests + // ----------------------------------------------------------------------- + + @Test + public void testIslandChatLogsWhenEnabled() { + when(settings.isLogIslandChats()).thenReturn(true); + mockedBukkit.when(org.bukkit.Bukkit::getOnlinePlayers).thenReturn(Collections.emptyList()); + + listener.islandChat(island, player, "island log message"); + verify(addon).log("[Island Chat Log] tastybento: island log message"); + } + + @Test + public void testIslandChatDoesNotLogWhenDisabled() { + when(settings.isLogIslandChats()).thenReturn(false); + mockedBukkit.when(org.bukkit.Bukkit::getOnlinePlayers).thenReturn(Collections.emptyList()); + + listener.islandChat(island, player, "island silent message"); + verify(addon, never()).log(any()); + } + + @Test + public void testIslandChatSendsToIslandMembers() { + Player.Spigot spigot = mock(Player.Spigot.class); + when(player.spigot()).thenReturn(spigot); + mockedBukkit.when(org.bukkit.Bukkit::getOnlinePlayers).thenReturn(Collections.singletonList(player)); + + // Should not throw and should complete without errors + listener.islandChat(island, player, "hello island"); + } + + // ----------------------------------------------------------------------- + // Event handler tests + // ----------------------------------------------------------------------- + + @Test + public void testOnLeaveRemovesTeamChatUser() { + listener.togglePlayerTeamChat(uuid); + assertTrue(listener.isTeamChat(uuid)); + + world.bentobox.bentobox.api.events.team.TeamLeaveEvent leaveEvent = + mock(world.bentobox.bentobox.api.events.team.TeamLeaveEvent.class); + when(leaveEvent.getPlayerUUID()).thenReturn(uuid); + listener.onLeave(leaveEvent); + + assertFalse(listener.isTeamChat(uuid)); + } + + @Test + public void testOnKickRemovesTeamChatUser() { + listener.togglePlayerTeamChat(uuid); + assertTrue(listener.isTeamChat(uuid)); + + world.bentobox.bentobox.api.events.team.TeamKickEvent kickEvent = + mock(world.bentobox.bentobox.api.events.team.TeamKickEvent.class); + when(kickEvent.getPlayerUUID()).thenReturn(uuid); + listener.onKick(kickEvent); + + assertFalse(listener.isTeamChat(uuid)); + } + + // ----------------------------------------------------------------------- + // toggleMuteTeamChat tests + // ----------------------------------------------------------------------- + + @Test + public void testToggleMuteTeamChatOn() { + UUID uid = UUID.randomUUID(); + assertTrue(listener.toggleMuteTeamChat(uid), + "First toggle should mute team chat"); + } + + @Test + public void testToggleMuteTeamChatOff() { + UUID uid = UUID.randomUUID(); + listener.toggleMuteTeamChat(uid); // mute + assertFalse(listener.toggleMuteTeamChat(uid), + "Second toggle should unmute team chat"); + } + + @Test + public void testIsMutedTeamChatFalseByDefault() { + assertFalse(listener.isMutedTeamChat(UUID.randomUUID())); + } + + @Test + public void testIsMutedTeamChatTrueAfterToggle() { + UUID uid = UUID.randomUUID(); + listener.toggleMuteTeamChat(uid); + assertTrue(listener.isMutedTeamChat(uid)); + } + + @Test + public void testOnLeaveRemovesMuteStatus() { + listener.toggleMuteTeamChat(uuid); + assertTrue(listener.isMutedTeamChat(uuid)); + + world.bentobox.bentobox.api.events.team.TeamLeaveEvent leaveEvent = + mock(world.bentobox.bentobox.api.events.team.TeamLeaveEvent.class); + when(leaveEvent.getPlayerUUID()).thenReturn(uuid); + listener.onLeave(leaveEvent); + + assertFalse(listener.isMutedTeamChat(uuid)); + } + + @Test + public void testOnKickRemovesMuteStatus() { + listener.toggleMuteTeamChat(uuid); + assertTrue(listener.isMutedTeamChat(uuid)); + + world.bentobox.bentobox.api.events.team.TeamKickEvent kickEvent = + mock(world.bentobox.bentobox.api.events.team.TeamKickEvent.class); + when(kickEvent.getPlayerUUID()).thenReturn(uuid); + listener.onKick(kickEvent); + + assertFalse(listener.isMutedTeamChat(uuid)); + } +} diff --git a/src/test/java/world/bentobox/chat/requesthandlers/IsTeamChatHandlerTest.java b/src/test/java/world/bentobox/chat/requesthandlers/IsTeamChatHandlerTest.java new file mode 100644 index 0000000..1852bd7 --- /dev/null +++ b/src/test/java/world/bentobox/chat/requesthandlers/IsTeamChatHandlerTest.java @@ -0,0 +1,97 @@ +package world.bentobox.chat.requesthandlers; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertTrue; +import static org.mockito.Mockito.when; + +import java.util.HashMap; +import java.util.Map; +import java.util.UUID; + +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.mockito.Mock; + +import world.bentobox.chat.CommonTestSetup; +import world.bentobox.chat.listeners.ChatListener; + +/** + * Tests for the {@link IsTeamChatHandler} class. + */ +public class IsTeamChatHandlerTest extends CommonTestSetup { + + @Mock + private ChatListener chatListener; + + private IsTeamChatHandler handler; + + @Override + @BeforeEach + public void setUp() throws Exception { + super.setUp(); + when(addon.getListener()).thenReturn(chatListener); + handler = new IsTeamChatHandler(addon); + } + + @Override + @AfterEach + public void tearDown() throws Exception { + super.tearDown(); + } + + @Test + public void testHandleLabel() { + assertEquals("isteamchat", handler.getLabel()); + } + + @Test + public void testHandleNullMap() { + assertFalse((Boolean) handler.handle(null)); + } + + @Test + public void testHandleEmptyMap() { + assertFalse((Boolean) handler.handle(new HashMap<>())); + } + + @Test + public void testHandleMissingUuid() { + Map map = new HashMap<>(); + map.put("some_key", "some_value"); + assertFalse((Boolean) handler.handle(map)); + } + + @Test + public void testHandleNullUuid() { + Map map = new HashMap<>(); + map.put("uuid", null); + assertFalse((Boolean) handler.handle(map)); + } + + @Test + public void testHandleWrongTypeUuid() { + Map map = new HashMap<>(); + map.put("uuid", "not-a-uuid-object"); + assertFalse((Boolean) handler.handle(map)); + } + + @Test + public void testHandlePlayerNotInTeamChat() { + UUID testUuid = UUID.randomUUID(); + when(chatListener.isTeamChat(testUuid)).thenReturn(false); + Map map = new HashMap<>(); + map.put("uuid", testUuid); + assertFalse((Boolean) handler.handle(map)); + } + + @Test + public void testHandlePlayerInTeamChat() { + UUID testUuid = UUID.randomUUID(); + when(chatListener.isTeamChat(testUuid)).thenReturn(true); + Map map = new HashMap<>(); + map.put("uuid", testUuid); + assertTrue((Boolean) handler.handle(map)); + } +}