Fixed bStats created_factions chart to reset the counter after each report, ensuring it shows the number of factions created in the reporting period rather than the cumulative total since startup.
TeamsService.getTeamIds() - returns all team UUIDs for iteration without loading full team objects. Consumers can iterate over team IDs for bulk operations without loading each full Team object.
Team.getOwner() - default method available via existing getOwnerUUID() and getMember() implementation. Returns the owner's TeamMember directly.
PvPIndex Factions is a long-term modernization of the classic Factions experience for modern Paper servers.
The mission is to revive Factions as an API-connectable, timeless, and production-ready plugin while preserving the gameplay identity players already love.
Supported platforms
Paper 1.21.x / 1.26.x - Recommended; full feature set
API/connectability direction PvPIndex Factions is designed as a modern service-oriented base so it can integrate cleanly with optional plugins and future ecosystem tooling.
This refactor direction focuses on long-term maintainability, adapter-based integration, and reliable standalone operation.
Operations docs available for rollout, troubleshooting, and scaling
Migration-oriented for existing communities If your network comes from legacy Factions servers, PvPIndex Factions is built to keep the familiar gameplay loop while giving you a cleaner and more future-proof backend.
/f chest opens the configured default chest (factions.team-chest.default-name) and auto-creates it on first use.
Per-faction chest count is capped by factions.max-team-chests (default: 1).
New permission nodes: factions.cmd.chest, factions.cmd.chest.create, and factions.cmd.chest.delete.
Changed
Bumped plugin version to 1.1.6.
Synced release metadata and public listing copy for this release.
Documentation
Updated TeamsAPI requirement references in public docs/listings to 2.3.0+ to match the current integration baseline.
Fixed
Fixed startup crash on servers using older TeamsAPI builds that do not provide TeamsChestService. TeamsAPI chest provider registration is now optional and loaded reflectively, so the plugin enables normally and keeps other available TeamsAPI adapters active.
All /f subcommands broken when TeamsAPI is absent: FactionCommandExecutor and FactionTabCompleter held direct import statements for TeamsAPI classes. On Paper's isolated plugin classloader this caused NoClassDefFoundError at class-load time when TeamsAPI was not installed, preventing the executor from being registered and making every /f <subcommand> (including /f info, /f map, etc.) fail with "Invalid command!".
Introduced TeamsCommandBridge (no TeamsAPI imports, loaded unconditionally) and TeamsCommandBridgeImpl (imports TeamsAPI, instantiated only via reflection when TeamsAPI is confirmed present). Mirrors the existing TeamsApiRegistrar isolation pattern. With TeamsAPI absent the bridge is null and all subcommands route through the local CommandRegistry as intended.
/f info detail pages (/f info page <n> [faction]): the summary view now shows a hint pointing players to /f info page <n>. The new page subcommand displays additional faction stats across paginated pages:
Online member count with a hover list of names
Rank distribution (members per rank)
Activity stats - inactive-over-7-days count and last-seen summary
Claim capacity with RAIDABLE / STABLE risk indicator
Full power breakdown (total, member contribution, power boost)
Top 3 power contributors
Newest member and most-recent activity timestamps
Sender context tracking for /f info page: the last faction viewed per sender is remembered so /f info page <n> works without repeating the faction name after an initial /f info [faction].
New messages in messages.yml: info.details-hint, info.page-title, info.page-next, info.page-prev, info.invalid-page, info.page-no-context.
TeamsAPI relation adapter now honors TeamRelation nature metadata (RelationNature) when persisting relation changes, so friendly/hostile custom relation natures map safely to internal declared relations.
Fixed
TeamsAPI relation adapter now honors RelationNature metadata (TeamsAPI 2.1.0+): custom relation natures (FRIENDLY / HOSTILE) on non-standard TeamRelation values are mapped to the correct internal ALLY / ENEMY relation instead of always falling back to NEUTRAL.
Faction MOTD (/f motd): officers and above can set a message of the day for their faction. The MOTD is displayed in chat when a member logs in. Use /f motd clear to remove it.
Configurable member-size cap: a new max-members setting in config.yml limits how many players can belong to a faction (0 = unlimited). The cap is enforced in /f join and in the join service. Default is 0 (no limit).
Warp passwords (/f warp password <name> [password | clear]): officers and above can protect a faction warp with a password. Players must supply the correct password as a second argument (/f warp <name> <password>) to teleport. Passwords are stored as plain text in the warps table — avoid using important credentials.
Warp per-use cost (/f warp cost <name> <amount>): officers and above can require a Vault economy payment each time a warp is used. Setting the cost to 0 makes the warp free. The charge is deducted via Vault before teleporting; if Vault is absent or the player has insufficient funds the teleport is blocked.
Database migration: the warps table gains two new nullable columns (password, use_cost) when the plugin starts on an existing installation. No data is lost during upgrade from 1.1.1.
Dynmap startup smoke test (.github/workflows/dynmap-smoke.yml): downloads the latest dynmap plugin from Modrinth, starts a Paper server with both plugins, and asserts dynmap hooked - faction territory layer enabled. is logged. Runs on Paper 1.21.4 and 1.21.11 on every PR to main/develop.
Changed
Dependency upgrades (maintenance): all outdated compile and test dependencies bumped to their latest compatible versions:
EssentialsX 2.21.0 → 2.21.2
worldedit-bukkit 7.3.9 → 7.4.3
worldguard-bukkit 7.0.12 → 7.0.16
placeholderapi 2.11.6 → 2.12.2 (repo URL updated to repo.helpch.at)
dynmap compatibility expanded to 3.x (3.4 – 3.8): the vendored compile-time dynmap-api dependency was upgraded from 3.4-beta-3 to 3.8 (latest stable, released 2026-01-14). In dynmap 3.8 the marker API was split into a separate DynmapCoreAPI JAR; both JARs are now vendored under libs/ with minimal POMs so CI never contacts repo.mikeprimm.com. The runtime hook code only uses stable org.dynmap marker APIs that are present in every 3.x release, so the plugin continues to work with any installed dynmap 3.x version.
Chat crash on Paper 26.x (ClassNotFoundException / AbstractMethodError in EngineChat$PaperChatListener): multiple layers of shade-related breakage were present and are fully resolved:
maven-shade rewrites net.kyori.adventure.* string literals in the bytecode constant pool to com.pvpindex.lib.adventure.*. At runtime Paper's classloader cannot find that relocated namespace, causing ClassNotFoundException. String literals are now constructed at runtime via String.valueOf concatenation so shade never sees the full class name in the constant pool.
The chat renderer was originally a raw 4-parameter lambda targeting ChatRenderer directly; LambdaMetafactory cannot reliably link it against Paper 26.x runtime classes. The renderer now uses ChatRenderer.viewerUnaware() and a Proxy-based InvocationHandler that implements only the 3-parameter ViewerUnaware.render method, eliminating cross-version descriptor mismatches.
EngineChat now contains zero net.kyori.adventure imports; all adventure interactions go through java.lang.reflect against classes loaded from Paper's own classloader, so shade has nothing to rewrite in type descriptors either.
Chat listener rebuilt as clean OOP dual-path design: Paper/Folia servers use a PaperChatListener that installs a ChatRenderer.ViewerUnaware proxy; Spigot/Bukkit servers use a LegacyChatListener with AsyncPlayerChatEvent.setFormat. Reflection handles and the cached MiniMessage instance are resolved once at listener construction time to avoid per-message overhead.
H2 database crash on first command (/f create, /f join, and any other command that saves a model): H2 2.x does not implement the VALUES(col) function reference inside ON DUPLICATE KEY UPDATE that Jaloquent generates, causing a JdbcSQLSyntaxErrorException (error code 42122) on every save against an H2 backend. Upsert SQL is now transparently rewritten to MERGE INTO … KEY(id) before execution, which H2 fully supports. MySQL/MariaDB backends are unaffected.
Changed
Chat formatting is now disabled by default (factions.chat.show-tag: false in config.yml). Operators must opt in by setting show-tag: true. Previously it was enabled for all players by default.
Officers and above can send a merge request proposing their faction be absorbed by another.
Officers and above of the target faction can accept the request to complete the merge.
On acceptance, all claims, warps, bank balance, and members are transferred to the target faction; members join at the target's default rank; the sender faction is disbanded.
Online members of the target faction are notified when a merge request arrives.
New permission factions.cmd.merge (default: true).
New MergeRequestModel / MergeRequestRepository data layer and MergeService / MergeServiceImpl service layer.
New audit actions merge-request and merge-accept for the audit log.
Internationalization support with locale bundles under messages/ for en, es, de, fr, pt-BR, ja, zh, and ru.
New player language command: /f language [code|reset] with aliases lang and locale.
New player profile locale persistence (players.locale) for per-player language overrides.
New config key: factions.language.default for server-wide default locale fallback.
New permission node: factions.cmd.language (default true).
New translator/contributor documentation: docs/i18n.md.
Changed
Upgraded TeamsAPI integration to 2.0.0:
getRelation(id, id) now returns TeamRelation.MEMBER when both team UUIDs are equal (new TeamsAPI 2.0.0 contract).
Message resolution now follows locale fallback order: player locale -> server default -> English -> inline fallback text.
/f help now includes the language command.
/fa reload now reloads locale bundles in messages/ in addition to config.
Faction flags (/f flag, /f flag list, /f flag set <flag> [on|off]):
Per-faction boolean toggles that officers can manage in-game. Five built-in flags:
pvp: allow PvP inside the faction's claimed territory (default: true)
friendly-fire: allow members to harm each other (default: false)
explosions: allow explosions to destroy terrain in territory (default: false)
fire-spread: allow fire to spread in territory (default: false)
open: allow anyone to join without a pending invite (default: false)
/f flag with no arguments shows the current flag list; /f flag list is an explicit alias.
/f flag set <flag> toggles; /f flag set <flag> on|off sets a specific value.
/fa flag <faction> <flag> [on|off] lets admins override any flag regardless of the player-editable config setting.
Flag defaults and player-editability are configurable per-flag under factions.flags.*.
New permissions: factions.cmd.flag (default true), factions.cmd.flag.set (default true, officer check enforced by command logic).
New messages.yml keys: flag.*.
WorldGuard region sync (integrations.worldguard-sync-regions: false, opt-in):
When enabled, every faction-claimed chunk is mirrored as a ProtectedCuboidRegion in WorldGuard on startup and kept in sync with claim, unclaim, join, leave, and disband events.
WG handles block-break and block-place denial at its native NORMAL event priority. Because the protection engine registers at HIGH with ignoreCancelled = true, enemy players are denied before our handler runs; no per-event database query is needed for them.
Faction members are added to their faction's regions as WG domain members so WG passes their interactions transparently.
Allies are handled by a dedicated HIGHEST ignoreCancelled = false pass that un-cancels their events after a single DB lookup.
Safezone and warzone chunks get WG regions with an empty member list, so WG denies all building there regardless of WG membership.
Requires WorldGuard to be installed and loaded. Toggling the option requires a restart.
New config key: integrations.worldguard-sync-regions (default false).
Startup update-check integration using ez-plugins/mc-plugin-update-notifier with chained sources: Modrinth public API as primary and GitHub public API as fallback. Modrinth queries loaders paper, folia, and spigot so the check works on all supported server software.
Operator join notification when an update is available, including clickable release URL output.
Faction create, disband, ally, truce, and enemy-declared events are broadcast to a Discord channel via DiscordSRV (pure reflection - no hard compile-time dependency).
Per-event toggles and Discord-markdown message templates are configurable in config.yml under integrations.discordsrv.events.*.
channel-id key routes messages to a specific text channel; leave empty to use DiscordSRV's main linked channel.
/f warp teleports now route through EssentialsX alongside /f home.
EssentialsX /back location is recorded before every teleport so players can return with /back.
Jailed players are blocked from /f home and /f warp with a message.
Detected EssentialsX version is logged at startup.
New messages.yml keys: home.teleported, home.teleport-failed, home.jailed, warp.teleported, warp.teleport-failed, warp.jailed.
Safe zones and war zones (factions.zones.safe-zone.enabled, factions.zones.war-zone.enabled):
Both zones are enabled by default. Disabling a zone causes its chunks to behave as Wilderness - protection, PvP rules, and power-loss suppression are all inactive.
New admin commands /fa safezone and /fa warzone let operators assign chunks to each zone in one-shot, square, or circle modes (with an optional remove sub-mode).
New permissions: factions.cmd.safezone, factions.cmd.warzone (default op).
When enabled, a faction can claim an enemy's chunk if the victim's land count exceeds their current maximum land (power-based). Border adjacency is waived for overclaims.
Optional guard factions.overclaiming.require-enemy-relation: true restricts overclaims to factions that have declared ENEMY relation.
Attacker receives a claim.overclaimed notification; all online victim members receive claim.overclaimed-victim showing remaining chunk count.
FactionChunkClaimEvent gains an optional overclaimedFromFaction field populated on overclaim so third-party plugins can observe the event.
New messages.yml keys: claim.overclaimed, claim.overclaimed-victim, claim.enemy-not-raidable.
Power and war improvements - six optional, independently toggleable features that improve PvP/raiding pacing. All are disabled by default:
F1 - Inactive member exclusion (factions.power.inactive-exclusion.enabled: false): members who have been offline longer than inactive-exclusion.days do not contribute to the faction's max-land calculation. Their stored power is unchanged; only the land-cap computation skips them, discouraging dead factions from holding territory indefinitely.
F2 - Death streak multiplier (factions.power.death-streak.enabled: false): consecutive deaths within a rolling window-seconds window each multiply the power loss by multiplier^streak. After the window expires the streak resets. A separate power.death-streak-penalty message is sent on streak deaths. Persisted in two new PlayerModel columns: last_death_at, death_streak.
F3 - Scaled kill rewards (factions.power.gain-on-kill.scale.enabled: false): kill power gain is multiplied by clamp(victimPower / killerPower, min-factor, max-factor). Rewarding high-risk kills and reducing farming incentive.
F4 - Raidable state broadcast (factions.raidable.broadcast.enabled: true): after each power tick, the engine detects factions that cross the raidable threshold and notifies members. Optional server-wide: true broadcasts to all online players. The raidable flag is persisted in a new FactionModel column: is_raidable.
F5 - Offline protection (factions.overclaiming.offline-protection.enabled: false): blocks overclaiming when all members of the defending faction are currently offline, preventing pure offline raiding.
F6 - War shield (factions.war.shield.enabled: false): a faction may be assigned a daily UTC protection window by an admin using the new /fa shield <faction> <start-hour> <duration-hours> command. During the window the faction's land cannot be overclaimed. Persisted in two new FactionModel columns: shield_start_hour, shield_duration_hours.
New admin command: /fa shield <faction> <clear|<start-hour (0-23)> <duration-hours>>.
New permission: factions.cmd.shield (default op, child of factions.admin).
New messages.yml keys: power.death-streak-penalty, raidable.*, shield.*, claim.enemy-offline-protected, claim.shield-active.
/f powerhistory [<player>] [<page>] (alias: phist): shows a paginated log of significant power changes (death, kill, and purchase events). Players can view their own history without any extra permission. Viewing another player's history requires factions.cmd.power.history.other (default op). All message templates are configurable in messages.yml under power.history-*.
TeamsAPI 1.8.0 power history provider: when TeamsAPI 1.8.0+ is present the plugin registers a TeamsPowerHistoryService implementation that exposes all recorded power-change events through the standard TeamsAPI surface. Consumer plugins can read player and team history, filter by time range, add external entries, and remove or clear records without touching the database directly. The provider is registered reflectively so servers running TeamsAPI 1.7.x or earlier start cleanly and the provider is silently skipped. New permissions: factions.cmd.power.history (default true), factions.cmd.power.history.other (default op).
Update notifier library is now explicitly relocated in shading: com.github.ezplugins.updater -> com.pvpindex.lib.updater, preventing runtime classpath conflicts.
Update check is disabled by default (factions.updates.enabled: false). Opt in by setting it to true.
factions.updates.modrinth-slug, factions.updates.github-owner, and factions.updates.github-repo are no longer exposed as config keys; the values are hardcoded in the plugin and the config entries are ignored if present.
EssentialsX interop now uses the compile-time EssentialsX API instead of reflection, making it resilient to future EssentialsX API changes and easier to diagnose when something breaks.
Plugin validates that the plugin named Essentials actually implements IEssentials before enabling the integration; logs a warning and falls back to noop if not.
TeamsAPI 1.6 relation provider: the plugin now implements the TeamsRelationService interface introduced in TeamsAPI 1.6. External plugins and scripts can read and write inter-faction relations (ALLY, TRUCE, NEUTRAL, ENEMY) through the standard TeamsAPI surface. TeamRelationChangeEvent is fired on every relation change, allowing third-party plugins to observe or cancel relation updates before they are persisted.
Changed
TeamsAPI dependency updated to 1.6.1 (was 1.5.0).
Fixed
Plugin crashed on startup when TeamsAPI 1.5.x was installed (NoClassDefFoundError: TeamsRelationService): the TeamsRelationService interface was introduced in TeamsAPI 1.6 and did not exist in older installations. A direct bytecode reference to it in the registrar caused the JVM bytecode verifier to fail when loading the class, crashing the plugin on startup. All references to TeamsRelationService and its concrete adapter are now loaded via reflection so that TeamsAPI 1.5.x servers start cleanly and the relation provider is silently skipped when TeamsAPI < 1.6 is detected.
Stale relation entries after faction disband: when a faction was disbanded, references to it stored in other factions' relation maps were not removed, leaving orphaned entries in the database. Disbanding a faction now clears all incoming relation references across every other faction.