Stable Bot UUID Identity
- BotIdentityCache — each bot name is permanently tied to a stable UUID; LuckPerms data, inventory, and session history persist across restarts
- Storage: in-memory cache → fpp_bot_identities DB table → data/bot-identities.yml YAML fallback
In-Game Settings GUI
- /fpp settings opens a 3-row chest GUI; 5 categories (General, Body, Chat, Swap, Peak Hours)
- Toggle booleans instantly; numeric values via chat-input prompt; reset page to JAR defaults; all changes apply live
- Permission: fpp.settings
Peak Hours Scheduler
- PeakHoursManager scales the bot pool by time-of-day windows (peak-hours.schedule, day-overrides, stagger-seconds)
- Crash-safe: sleeping-bot state persisted in fpp_sleeping_bots DB table, restored at startup
- New command: /fpp peaks [on|off|status|next|force|list|wake <name>|sleep <name>] — requires swap.enabled: true
Per-Bot Chat Control
- Random activity tier per bot: quiet / passive / normal / active / chatty
- /fpp chat <bot> tier|mute|info per-bot controls; /fpp chat all <on|off|tier|mute> for bulk operations
- Event-triggered chat (event-triggers.) and keyword reactions (keyword-reactions.)
Bodyless Bot Mode & Bot Types
- bodyless flag — bots without a world location exist in tab-list/chat only, no world entity
- BotType: AFK (passive) and PVP (combat via BotPvpAI)
Config Migration v41 → v44
- v41→v42: Added peak-hours section · v42→v43: Added min-online, notify-transitions · v43→v44: Removed auto-enable-swap
/fpp swap Toggle Fix
- Running /fpp swap with no arguments now toggles swap on/off — exactly like /fpp chat
- swap-enabled and swap-disabled messages redesigned to match chat toggle style ("session rotation has been enabled/disabled")
- swap-status-on / swap-status-off now follow the same "is enabled / is disabled" pattern as chat status messages
Bot Chat Interval Fix
- Bot chat loops are now restarted on /fpp reload — changes to interval.min/max, chance, and stagger-interval take effect immediately instead of waiting for old scheduled tasks to expire
- /fpp reload output shows the new interval range as confirmation
Fake Chat Realism Enhancements
- typing-delay — simulates a 0–2.5 s typing pause before each message
- burst-chance / burst-delay — bots occasionally send a quick follow-up message
- reply-to-mentions / mention-reply-chance / reply-delay — bots can reply when a player says their name in chat
- activity-variation — random per-bot chat frequency tier (quiet/normal/active/very-active)
- history-size — bots avoid repeating their own recent messages
- remote-format — MiniMessage format for bodyless / proxy-remote bot broadcasts
Swim AI
- New swim-ai.enabled config key (default true) — bots automatically swim upward when submerged in water or lava
- Set to false to let bots sink instead
Language & Compatibility
- Biome.name() deprecated call replaced with Biome.getKey().getKey() — compatible with Paper 1.22+
- sync-usage and swap-now-usage messages now end with a period for consistency
- Startup banner now shows Bot swap status in the Features section
- Startup banner now shows actual Skin mode (auto/custom/off) instead of "disabled"
Ghost Player / "Anonymous User" Fix
- Replaced reflection-based Connection injection with a proper FakeConnection subclass — no-op send() overrides
- Eliminated the phantom "Anonymous User" entry with UUID 0 appearing in the tab list when bots connect
- Eliminated NullPointerException and ClassCastException log spam related to bot connections
%fpp_real% / %fpp_total% Accuracy Fix
- %fpp_real% now correctly subtracts bot count — bots appear in Bukkit.getOnlinePlayers() via placeNewPlayer()
- %fpp_real_<world>% similarly excludes bots from per-world real-player counts
- %fpp_total% fixed to avoid double-counting: real players + local bots (+ remote bots in NETWORK mode)
Proxy /fpp list Improvements (NETWORK mode)
- /fpp list shows [server-id] tags for local bots so admins know which server each bot belongs to
- Remote bots from other proxy servers shown in a dedicated "Remote bots" section with server, name, and skin info
- Total counts include both local and remote bots
New Proxy Placeholders
- %fpp_local_count% — bots on this server only
- %fpp_network_count% — bots on other proxy servers (NETWORK mode)
- %fpp_network_names% — comma-separated display names from remote servers
- %fpp_count% and %fpp_names% now include remote bots in NETWORK mode (29+ total placeholders)
LuckPerms ClassLoader Guard
- Fixed NoClassDefFoundError: net/luckperms/api/node/Node crash on servers without LuckPerms
- All LP-dependent code gated behind LuckPermsHelper.isAvailable() — no LP classes loaded unless LP is present
Technical
- Config version bumped to 37 (no structural key changes — version stamp only)
- Automatic migration on first startup from any previous version
- Fully backward compatible
Knockback fix (1.21.9 – 1.21.11)
- Bots now correctly receive knockback on 1.21.9+ servers
- In MC 1.21.9 Mojang replaced the individual getXa/Ya/Za() packet accessors with a single getMovement() → Vec3; the old code silently skipped knockback because all accessors returned null
- New tiered strategy system resolves the correct API once at startup and caches it — zero reflection overhead per hit:
- GET_MOVEMENT (1.21.9+): packet.getMovement() → Vec3 → player.lerpMotion(Vec3) (matches hello09x/fakeplayer reference plugin)
- GET_XA (≤ 1.21.8): packet.getXa/Ya/Za() → lerpMotion(double,double,double) or setDeltaMovement(Vec3) fallback
- NONE: no compatible API found — silently skipped, debug-logged
- Enable logging.debug.nms: true to see which strategy was selected on first use
Double-disconnect crash fix (Paper 1.21+)
- Fixed IllegalStateException: Already retired spam in server logs when a bot is slain
- Root cause: injectFakeListener was replacing ServerPlayer.connection but not Connection.packetListener — the field Connection.handleDisconnection() uses to call onDisconnect(); the vanilla SGPL (with no double-retirement guard) was being called instead of our override
- Fix: injectPacketListenerIntoConnection() now scans the Connection object's field hierarchy at spawn time and replaces the vanilla listener reference with our FakeServerGamePacketListenerImpl; the existing onDisconnect try-catch then correctly suppresses the second "Already retired" error
- Side-effect improvement: Connection.tick() now calls our fake listener's tick() instead of the vanilla SGPL, eliminating any lingering awaitingPositionFromClient processing
Bot Protection System
- Command blocking — bots can no longer execute commands from ANY source (4-layer protection system)
- Lobby spawn fix — 5-tick grace period prevents lobby plugins from teleporting bots during spawn
- New BotCommandBlocker listener blocks commands at LOWEST, HIGHEST, and MONITOR priorities
- New BotSpawnProtectionListener prevents lobby/spawn plugin teleports
- Command suggestion blocking prevents auto-command discovery
- Blocks all execution paths: typing, Player.performCommand(), Bukkit.dispatchCommand()
- Works with first-join command plugins, auto-command schedulers, and permission-based executors
Technical
- Enhanced event handling with ignoreCancelled checks
- Smart teleport cause filtering (blocks PLUGIN/UNKNOWN, allows COMMAND)
- Debug logging via logging.debug.nms: true
- Self-cleaning protection sets (no memory leaks)
- 100% backward compatible
PlaceholderAPI Expansion
- 26+ placeholders across 5 categories (up from 18+)
- Fixed %fpp_skin% incorrectly returning "disabled" instead of actual mode
- Added %fpp_persistence% placeholder (shows on/off for persistence.enabled)
- New Network/Proxy category: %fpp_network%, %fpp_server_id%, %fpp_spawn_cooldown%
Skin System Simplified
- Removed skin.fallback-pool and fallback-name (eliminates API rate-limiting)
- Changed guaranteed-skin default from true → false
- Bots with non-Mojang names now use Steve/Alex skins by default
- Config section reduced from ~60 lines to ~18 lines
Config Migration v35→v36
- Auto-cleanup of orphaned LuckPerms keys (weight-offset, use-prefix, etc.)
- Removes old skin.custom section and server: section
- Automatic backup created before migration runs
New Features
- /fpp info screen includes Discord support link
- Full support for Leaf server (Paper fork)
Technical
- Config version bumped to 36
- Automatic migration on first startup
- Fully backward compatible
- Skin diversity fix — guaranteed-skin fallback pool now uses on-demand random selection when bots spawn before async prewarm completes, ensuring every bot gets a unique skin even during rapid spawning at server startup (no more "Notch clone armies")
- Vanilla skin pool — default fallback-pool updated to use 27 official Minecraft system accounts (Mojang developers + MHF_* mob/block skins) instead of content creator accounts for a pure vanilla aesthetic
- Per-world placeholders — added dynamic world-specific bot counts:
%fpp_count_<world>%,%fpp_real_<world>%,%fpp_total_<world>%(case-insensitive world names)- PlaceholderAPI expansion — added
%fpp_online%as cleaner alias for%fpp_total%(real players + bots combined)- Fake chat prefix/suffix support —
fake-chat.chat-formatnow supports{prefix}and{suffix}placeholders for full LuckPerms integration (respectsluckperms.use-prefixtoggle)- Spawn race condition fixed —
/fpp despawn allwhile bots are spawning no longer leaves ghost entries in tab-list/scoreboard; all deferred spawn tasks now check if bot was removed before executing- Portal/teleport bug fixed — bots pushed through portals or cross-world teleports are now protected: portals cancelled, teleports cancelled, PDC-based entity recovery system added, orphaned nametags cleaned up automatically
- Body damageable toggle fixed —
body.damageable: falsenow correctly prevents all damage via event-level cancellation (previous entity-flag-only approach could be bypassed)- Body pushable/damageable live reload —
/fpp reloadnow immediately applies body config changes to all active Mannequins without requiring respawn- Enhanced documentation — added PLACEHOLDERAPI.md reference, updated Skin-System.md with guaranteed-skin details, created technical documentation for skin system fix
- Unified spawn syntax — in-game
/fpp spawnnow supports the same flexible positional syntax as console:[count] [world] [x y z] [--name <name>]. Count can be leading or trailing; coordinates can be space-separated (-609 71 -67) or comma-separated (-609,71,-67). Player without a world arg still spawns at their own location.- Improved
/fpp reloadoutput — box-drawing lines (┌ │ └), per-step detail (skin counts, LP prefix count, bot count, team size), yellow warning line for config issues, and a final timing line./fpp reloadcanUse fix — now correctly delegates toPerm.hasOrOpso operators without explicit nodes can also reload.
- Tab-list weight ordering completely overhauled — bots now perfectly respect LuckPerms group weights and always appear in correct rank order in tab list
- New rank command system —
/fpp rank set <bot> <luckperms-group>assigns a specific bot to a LuckPerms group;/fpp rank random <luckperms-group> [count]assigns random bots; both supportallparameter for bulk operations- Restoration bug fixed — bots restored after server restart now maintain correct weights and ranks (no longer appear with incorrect prefixes)
- BotTabTeam system enhanced — Minecraft client now correctly matches tab entries to scoreboard team members using packet profile names with weight prefixes
- Targeted cache invalidation — LuckPerms data cache now uses targeted group invalidation instead of full cache clearing, preserving rank ordering accuracy
- Auto-update on group change — when LuckPerms group data changes, all bot prefixes and tab ordering update in real-time (no reload/respawn needed)
- Performance improvements — tab-list synchronization optimized to prevent unnecessary packet flooding
