Skip to content
Open
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@
| **Vanish** | Hide players from other players, the tab list and mobs. |
| **Menu** | Config-driven, locked GUI menus. |
| **Moderation** | Ban / tempban / unban / kick / IP-ban with database storage (SQLite/MySQL, cross-server). |
| **Team** | Config-defined player groups with MiniMessage colors (gradients/rainbow included), invite/accept/deny/request flow, per-team Member/Admin roles, and a real vanilla scoreboard mirror for `@a[team=pb_<id>]` selectors. |

Also supports **PlaceholderAPI** (optional, not a toggleable module) — auto-detected if installed, adds the `%plainbase_*%` expansion, zero effect if it's missing.

Expand Down
2 changes: 1 addition & 1 deletion pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@

<groupId>de.jgaertig</groupId>
<artifactId>PlainBase</artifactId>
<version>0.2.0-Beta</version>
<version>0.3.0-Beta</version>
<packaging>jar</packaging>

<name>PlainBase</name>
Expand Down
84 changes: 83 additions & 1 deletion src/main/java/de/jgaertig/plainBase/PlainBase.java
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,9 @@
import de.jgaertig.plainBase.moderation.commands.*;
import de.jgaertig.plainBase.placeholder.PlaceholderBridge;
import de.jgaertig.plainBase.placeholder.PlainBaseExpansion;
import de.jgaertig.plainBase.team.TeamListener;
import de.jgaertig.plainBase.team.TeamManager;
import de.jgaertig.plainBase.team.commands.TeamCommand;
import de.jgaertig.plainBase.vanish.VanishListener;
import de.jgaertig.plainBase.vanish.VanishManager;
import de.jgaertig.plainBase.vanish.commands.VanishCommand;
Expand Down Expand Up @@ -49,6 +52,7 @@ public final class PlainBase extends JavaPlugin {
private VanishManager vanishManager;
private MenuManager menuManager;
private BanManager banManager;
private TeamManager teamManager;
private boolean placeholdersRegistered = false;

private boolean commandsRegistered = false;
Expand All @@ -59,14 +63,15 @@ public void onEnable() {

saveDefaultConfig();

latestVersions.put("config.yml", 1.6);
latestVersions.put("config.yml", 1.7);
latestVersions.put("spawn.yml", 1.2);
latestVersions.put("joinitems.yml", 1.2);
latestVersions.put("messages.yml", 1.1);
latestVersions.put("teleport.yml", 1.0);
latestVersions.put("vanish.yml", 1.1);
latestVersions.put("menu.yml", 1.1);
latestVersions.put("moderation.yml", 2.0);
latestVersions.put("team.yml", 1.1);

registerPlaceholderExpansion();

Expand All @@ -80,6 +85,7 @@ public void onEnable() {
r.register("plainbase", new PlainBaseCommand(this));
r.register("vanish", new VanishCommand(this));
r.register("menu", new MenuCommand(this));
r.register("team", new TeamCommand(this));

r.register("ban", new BanCommand(this));
r.register("tempban", new TempBanCommand(this));
Expand Down Expand Up @@ -244,6 +250,50 @@ private void setupPermissions() {
getServer().getPluginManager().addPermission(
new Permission("plainbase.moderation.unbanip", "PlainBase: Allows access to /unbanip", PermissionDefault.OP)
);

// team module
getServer().getPluginManager().addPermission(
new Permission("plainbase.team.admin", "PlainBase: Bypass — acts as team-admin on any team regardless of membership", PermissionDefault.OP)
);
getServer().getPluginManager().addPermission(
new Permission("plainbase.team.invite", "PlainBase: Allows access to /team <team> invite", PermissionDefault.OP)
);
getServer().getPluginManager().addPermission(
new Permission("plainbase.team.add", "PlainBase: Allows access to /team <team> add", PermissionDefault.OP)
);
getServer().getPluginManager().addPermission(
new Permission("plainbase.team.kick", "PlainBase: Allows access to /team <team> kick", PermissionDefault.OP)
);
getServer().getPluginManager().addPermission(
new Permission("plainbase.team.setrole", "PlainBase: Allows access to /team <team> setrole", PermissionDefault.OP)
);
getServer().getPluginManager().addPermission(
new Permission("plainbase.team.request", "PlainBase: Allows access to /team <team> request", PermissionDefault.OP)
);
getServer().getPluginManager().addPermission(
new Permission("plainbase.team.accept", "PlainBase: Allows access to /team accept", PermissionDefault.OP)
);
getServer().getPluginManager().addPermission(
new Permission("plainbase.team.deny", "PlainBase: Allows access to /team deny", PermissionDefault.OP)
);
getServer().getPluginManager().addPermission(
new Permission("plainbase.team.reject", "PlainBase: Allows access to /team reject (reject a pending join request)", PermissionDefault.OP)
);
getServer().getPluginManager().addPermission(
new Permission("plainbase.team.leave", "PlainBase: Allows access to /team leave", PermissionDefault.OP)
);
getServer().getPluginManager().addPermission(
new Permission("plainbase.team.list", "PlainBase: Allows access to /team list", PermissionDefault.OP)
);
getServer().getPluginManager().addPermission(
new Permission("plainbase.team.info", "PlainBase: Allows access to /team info", PermissionDefault.OP)
);
getServer().getPluginManager().addPermission(
new Permission("plainbase.team.invites", "PlainBase: Allows access to /team invites (list your own pending invites)", PermissionDefault.OP)
);
getServer().getPluginManager().addPermission(
new Permission("plainbase.team.requests", "PlainBase: Allows access to /team requests (list a team's pending join requests)", PermissionDefault.OP)
);
}

public void reloadModules() {
Expand All @@ -257,6 +307,7 @@ public void reloadModules() {
if (getConfig().getBoolean("modules.vanish", true)) setupVanish();
if (getConfig().getBoolean("modules.menu", true)) setupMenu();
if (getConfig().getBoolean("modules.moderation", true)) setupModeration();
if (getConfig().getBoolean("modules.team", true)) setupTeam();
}

public void stopModules() {
Expand Down Expand Up @@ -287,6 +338,14 @@ public void stopModules() {
banManager.shutdown();
}
banManager = null;

// Unregisters the mirrored vanilla scoreboard teams so a disabled/reloaded
// team module doesn't leave stale "pb_<id>" teams around.
if (teamManager != null) {
teamManager.shutdown();
}
teamManager = null;

org.bukkit.event.HandlerList.unregisterAll(this);
}

Expand Down Expand Up @@ -454,6 +513,21 @@ public void setupModeration() {
getServer().getPluginManager().registerEvents(new ModerationListener(this), this);
}

public void setupTeam() {
loadModuleConfig("team.yml");

teamManager = new TeamManager(this);

getServer().getPluginManager().registerEvents(new TeamListener(this), this);

// Re-sync scoreboard entries for already-online players after a reload
// (their teams were just re-loaded from disk into a fresh TeamManager).
// No invite reminders here — they don't need to be re-notified on reload.
for (Player player : Bukkit.getOnlinePlayers()) {
teamManager.resyncScoreboard(player);
}
}

/**
* Registers the %plainbase_*% PlaceholderAPI expansion when PlaceholderAPI
* is present. Safe no-op otherwise (soft dependency).
Expand Down Expand Up @@ -500,6 +574,14 @@ public FileConfiguration getModerationConfig() {
return configs.get("moderation.yml");
}

public TeamManager getTeamManager() {
return teamManager;
}

public FileConfiguration getTeamConfig() {
return configs.get("team.yml");
}

public void saveMenuConfig() {
try {
FileConfiguration config = getMenuConfig();
Expand Down
26 changes: 16 additions & 10 deletions src/main/java/de/jgaertig/plainBase/moderation/BanManager.java
Original file line number Diff line number Diff line change
Expand Up @@ -104,16 +104,22 @@ private void refreshCacheBlocking() {
newKicksByUuid.computeIfAbsent(record.uuid(), k -> new CopyOnWriteArrayList<>()).add(record);
}

bansCache.clear();
bansCache.addAll(bans);
kicksCache.clear();
kicksCache.addAll(kicks);
ipBansCache.clear();
ipBansCache.addAll(ipBans);
bansByUuid.clear();
bansByUuid.putAll(newBansByUuid);
kicksByUuid.clear();
kicksByUuid.putAll(newKicksByUuid);
// Swap the cache under the same lock the mutation methods use, so a
// ban/unban that commits to the DB between this method's reads and
// its cache swap can't have its cache update clobbered by a stale
// snapshot read just before it.
synchronized (mutationLock) {
bansCache.clear();
bansCache.addAll(bans);
kicksCache.clear();
kicksCache.addAll(kicks);
ipBansCache.clear();
ipBansCache.addAll(ipBans);
bansByUuid.clear();
bansByUuid.putAll(newBansByUuid);
kicksByUuid.clear();
kicksByUuid.putAll(newKicksByUuid);
}
} catch (SQLException e) {
plugin.getLogger().severe("Could not refresh moderation cache: " + e.getMessage());
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -89,7 +89,38 @@ public String onPlaceholderRequest(Player player, @NotNull String params) {
case "menu_count" -> plugin.getMenuManager() != null
? String.valueOf(plugin.getMenuManager().getMenuNames().size()) : "0";

default -> null;
// Team module (static placeholders)
case "team_names" -> player != null && plugin.getTeamManager() != null
? String.join(", ", plugin.getTeamManager().getPlayerTeams(player.getUniqueId())) : "";
case "team_count" -> player != null && plugin.getTeamManager() != null
? String.valueOf(plugin.getTeamManager().getPlayerTeams(player.getUniqueId()).size()) : "0";
case "team_primary" -> player != null && plugin.getTeamManager() != null
? plugin.getTeamManager().getPlayerTeams(player.getUniqueId()).stream().findFirst().orElse("") : "";
case "team_pending_invites" -> player != null && plugin.getTeamManager() != null
? String.valueOf(plugin.getTeamManager().getPendingInvites(player.getUniqueId()).size()) : "0";
case "teams_count" -> plugin.getTeamManager() != null
? String.valueOf(plugin.getTeamManager().getTeams().size()) : "0";

// Team module (parameterized: %plainbase_team_role_<team>% / %plainbase_team_members_<team>%)
default -> handleTeamParameterized(player, params.toLowerCase());
};
}

private String handleTeamParameterized(Player player, String params) {
if (plugin.getTeamManager() == null) return null;

if (params.startsWith("team_role_")) {
String teamId = params.substring("team_role_".length());
if (player == null) return "none";
var role = plugin.getTeamManager().getRole(player.getUniqueId(), teamId);
return role != null ? role.name().toLowerCase() : "none";
}

if (params.startsWith("team_members_")) {
String teamId = params.substring("team_members_".length());
return String.valueOf(plugin.getTeamManager().getMembers(teamId).size());
}

return null;
}
}
21 changes: 21 additions & 0 deletions src/main/java/de/jgaertig/plainBase/team/TeamListener.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
package de.jgaertig.plainBase.team;

import de.jgaertig.plainBase.PlainBase;
import org.bukkit.event.EventHandler;
import org.bukkit.event.Listener;
import org.bukkit.event.player.PlayerJoinEvent;

public class TeamListener implements Listener {

private final PlainBase plugin;

public TeamListener(PlainBase plugin) {
this.plugin = plugin;
}

@EventHandler
public void onJoin(PlayerJoinEvent event) {
if (plugin.getTeamManager() == null) return;
plugin.getTeamManager().handleJoin(event.getPlayer());
}
}
Loading