feat(team): add Team module for 0.3.0-Beta - #16
Open
j-gaertig-co-dev wants to merge 8 commits into
Open
j-gaertig-co-dev wants to merge 8 commits into
j-gaertig-co-dev wants to merge 8 commits into
Conversation
- Teams defined in modules/team.yml (name, MiniMessage color incl. gradients/rainbow) - Runtime membership/roles/invites/join-requests persisted by UUID under data/teams/ - Roles per team: MEMBER / ADMIN. OPs and plainbase.team.admin bypass to admin on any team - Commands: /team list, /team <team> info, /team <team> invite|add|kick|setrole|request, /team accept|deny|leave [team] - Every command has its own permission node, independent of team membership - Teams are mirrored to a real vanilla scoreboard team (pb_<id>) so any command with a target selector works, e.g. /gamemode creative @A[team=pb_team1] (documented limitation: a scoreboard entry can only be on one team at a time, matters only if max-teams-per-player > 1) - New PlaceholderAPI placeholders: team_names, team_count, team_primary, team_pending_invites, teams_count, team_role_<team>, team_members_<team> - config.yml version 1.6 -> 1.7 (new team module entry) - pom.xml version -> 0.3.0-Beta
- Folia currently considers ALL scoreboard API broken (confirmed via PaperMC/Folia repo). Registering/looking up scoreboard teams there throws UnsupportedOperationException and disabled the whole plugin. TeamManager now detects Folia and skips the vanilla-scoreboard mirror entirely there; membership/roles/invites/commands/placeholders are unaffected, only @A[team=pb_<id>] selectors are unavailable on Folia. - add() crashed with UnsupportedOperationException when a player had no prior invite/join-request for that team: Map.getOrDefault(k, Set.of()) returns the shared immutable empty set, and .remove() on it throws. Now only mutates a set that's actually present.
Old schema mixed two orders (verb-first for self actions, team-first for staff actions) and had no self-documenting help, no way to list pending join requests, and dead per-command config toggles (declared in team.yml but never actually read anywhere). - Every subcommand is now /team <action> [team] [player] [role] — table- driven via TeamCommand.ACTIONS, so adding a new one later is one map entry + one switch case instead of touching parsing logic in multiple places. - Bare /team prints a permission-aware help listing (only shows what the sender can actually run). - New /team requests <team> (admin: see who's waiting to join) and /team invites (self: see your own pending invites) — previously you only found out about these reactively via chat notifications. - /team info with no team now summarizes the sender's own memberships + roles instead of erroring. - Renamed deny-request -> reject with its own permission node (plainbase.team.reject) instead of silently sharing plainbase.team.deny. - team.yml's commands: block is now actually nested under team: (where the code reads it from) — it used to be a sibling top-level key, so every per-command enabled/disabled toggle was silently a no-op. Bumped to version 1.1. - PlaceholderAPI: %plainbase_team_pending_invites% now uses the new TeamManager#getPendingInvites() instead of an O(teams) scan loop. Tested end-to-end on Paper 26.2 + Folia 26.2 (RCON + a mineflayer bot as a real player): help output, every subcommand, the per-action config toggle actually taking effect, and the full invite/accept/request/add flow.
- getPendingRequests()/getPendingInvites()/listRequests()/listInvites()/ infoSelf() back the new /team requests, /team invites and no-arg /team info commands from the schema redesign. - Independent full-project code review (delegated, fresh context) flagged that TeamManager#resolveTarget() called the deprecated Bukkit#getOfflinePlayer(String) SYNCHRONOUSLY on the command-dispatch thread for invite/add/kick/reject/setrole — that call can block on a Mojang lookup (main thread on Paper, the sender's region thread on Folia). The moderation module already has the correct pattern for this (ModerationCommandBase#resolveTarget: online/cached lookups resolve instantly, only a true cache-miss falls back to the async scheduler with the callback dispatched back onto the main/region thread) — teams now uses the exact same pattern instead of its own naive synchronous version. Verified on Paper + Folia after the refactor: full invite/add/setrole/ reject/kick cycle including the async cache-miss fallback path (queried a never-seen name), no regressions.
…ault The team module commit bumped config.yml's shipped default to version: 1.7 (new modules.team key) but never bumped the corresponding entry in latestVersions, so upgraders on 1.6 never saw the outdated-config warning. Verified: forcing an on-disk config.yml back to 1.6 now logs '!!! OUTDATED CONFIG: config.yml !!!' on both Paper and Folia.
refreshCacheBlocking() cleared and repopulated the ban/kick caches without holding mutationLock, so a ban/unban committing to the DB between the refresh's read and its cache swap could have its update clobbered by a stale snapshot. The periodic refresh now swaps the caches under the same lock the mutation methods already use.
Each save*() scheduled an independent async task; two near-simultaneous mutations to the same team could produce two tasks racing to write the same YAML file concurrently, risking an interleaved/corrupted write -- the same bug class already fixed once for the moderation module's ban/kick files. Added one lock per file so concurrent saves to the same file serialize, while members/invites/requests still don't block each other.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Team module (new, 0.3.0-Beta)
Config-only team definitions (
modules/team.yml), UUID-backed runtime membership/roles/invites/join-requests, MiniMessage colors incl. gradients/rainbow, and a real vanilla-scoreboard mirror so any command with a target selector can hit@a[team=pb_<id>].Commands
/team list|info|accept|deny|leave|request|invite|add|kick|setrole|deny-request— full permission node per subcommand plusplainbase.team.adminbypass (OPs are always admin on every team).Roles
MEMBER / ADMIN, per team. Members can only leave; admins do everything else. Promote via
/team <team> setrole <player> admin(existing admin or console only).Placeholders
%plainbase_team_names%,_count%,_primary%,_pending_invites%,%plainbase_teams_count%,%plainbase_team_role_<team>%,%plainbase_team_members_<team>%.Bugs found & fixed during testing
TeamManagernow detects Folia and skips the scoreboard mirror there entirely — membership/roles/commands/placeholders are unaffected, only the@a[team=...]selector trick is unavailable on Folia./team <team> addcrash: threwUnsupportedOperationExceptionwhen the player had no prior invite/request for that team (Map.getOrDefault(k, Set.of())returns a shared immutable set,.remove()on it throws). Fixed to only mutate a set that actually exists.Testing
Built and live-tested on both a Paper 26.2 and Folia 26.2 test server (RCON + a mineflayer bot acting as a real player): module enable/disable,
/plainbase reload, full invite → join-reminder-on-login → accept/deny flow, add/kick/setrole, max-teams-per-player enforcement, permission checks (console vs. non-OP), placeholders, and clean shutdown/scoreboard cleanup on both platforms.Docs