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));
+ }
+}