Repository navigation
Changelog
Smarter hunting aim, safer bot persistence and the last of the old blue theme gone.
-
Persistence autosave - every bot's tasks (with aim point), inventory, XP and settings are saved every 2 minutes, so a crash loses at most that much. Config:
persistence.autosave-interval-seconds(default120,0= off, minimum30). -
Inventory / XP database backup - bot inventories and XP are also written to the plugin database (
fpp_bot_inventories) and recovered on restart if they are missing frombot-data.yml.
- Hitbox aiming (hunting / PVE) - bots aim at any exposed part of a mob's hitbox (feet, head, corners) instead of locking onto its center, so they hit mobs in farms that only show their feet. Target scanning uses the same check.
- No aiming at hidden mobs - a mob with no visible part of its hitbox is never aimed at or attacked. Chasing bots walk to a better angle; standing bots drop it and look for another target.
- Head look-back off while tasking - bots no longer turn their head to look at players while hunting, running a click task or walking.
- Purple theme - leftover blue GUI panes (inventory label, Pathfinding and Perf categories) are now purple.
Bots now act, fight, mine, walk and load chunks like a real client, with a new pathfinder, reworked GUI and automation commands.
-
/fpp clear- layer-by-layer excavation of a chunk, area, selection or radius. Permissionfpp.clear. -
Spawn actions -
/fpp spawn ... <action> [then|wait t <action> ...]starts work as soon as the bot is online. Config:spawn-actions.*. -
/fpp menuhub and a Chunks settings category. -
Rent GUI and shop integration -
/fpp rentshop, auto-renew, bulk discounts, placeholders, cancellableFppRentalPurchaseEvent. -
Find
--scanmode reports what is visible without moving. -
Build ID in every jar: banners, GUI credit,
%fpp_build%.
-
Left/right-click ported from the 26.3 client's click logic (
--once,--repeat,--hold). -
PVE /
/fpp attackrebuilt as client combat:--standguard,--chasehunter, real attack packets, no damage shortcut. Configattack-mob.*. - Find only reports blocks with an exposed face - no X-ray.
- Pathfinding rebuilt in-house (Pathetic removed): A* over real collision shapes (parkour, ladders, swimming, doors, bridging); Folia-safe.
- Chunk loading is client-authentic: no plugin chunk tickets, bots just report a render distance.
- GUI overhaul with card grids, paging, back buttons; creator head at bottom-left; new startup/shutdown banners.
- Economy has one price engine; failed spawns refund.
-
No bot chat events: no join/leave/death/advancement messages or relay.
messages.join-message/leave-messageremoved.
-
plugin.yml parse error (
fpp.rent.free/fpp.rent.auto) that stopped the plugin loading.
- Viewer snapshot built once per tick; faster pathfinder and Find/Clear.
- ~140 unused methods, four dead classes, unit tests, Pathetic and old
pathfinding.*options.
-
Network Protocol Error kick (
Cannot remove from team ...): a per-viewer packet guard keeps scoreboard teams consistent, so TAB/LuckPerms/AFK-display teams can no longer desync a client. - Hot reload (PlugManX): classes preloaded, shutdown steps isolated, leftover bots purged.
-
Honest bots: tab list off by default (
tab-list.enabled; when on, listed only as[Bot] - name, enforced for every viewer); no fabricated ping;%fpp_online%/%fpp_total%count real players only; bots can't take a real account's name. -
Name tag: three separate rows with vanilla-style plates (
nametag.second-line.background-opacity,row-spacing); name#A78BFA, indicator#BAFF4F. Vanilla name can't reappear, and the tag follows a mounted bot without lag.
-
Auto mount (
body.auto-mount, default on): boats and moving minecarts pick bots up like villagers; bots stay seated; a real player boarding a boat takes the first seat. - Mount memory: a bot that despawns in a vehicle returns in it (a shared vehicle is re-boarded), like a player logging back in.
-
Auto update (
update-checker.auto-update, default on): Modrinth release is downloaded toplugins/update/, SHA-512 verified, installed on restart.
-
CompatibilityChecker.isSupportedVersionnow accepts26.3.xalongside26.1.xand26.2.x. The unsupported-version warning and wiki version ranges now readup to 1.21.11, and 26.1.x–26.3.x. - Compile-time Paper dev bundle stays on
26.1.2.build.65-stable.
- The team packets that hide a bot's vanilla over-head name were gated on
PacketHelper's unrelated tab-list reflection init, so if that init failed the hide was silently skipped and the vanilla name drew on top of the custom text_display tag. Those packets no longer depend on it. - Failures while sending the hide-team packets are now written to the debug log, and a failure to build the hide team logs a warning at startup instead of failing silently.
-
/fpp spawncan now be run from the server console and from command blocks, not just in-game - admin-tier (fpp.spawn) only, and--location <x> <y> <z> <world>is now mandatory for them since neither has a location of its own to fall back on. User-tier spawns (fpp.spawn.user) still require a real player: personal bot ownership, the per-player bot limit, and the spawn cooldown all key off a player's UUID, which console/command-block senders don't have.
- Admins (
fpp.spawn) can now spawn a bot at an arbitrary spot instead of only their own location:/fpp spawn --location <x> <y> <z> <world>, combinable with--name. User-tier spawns (fpp.spawn.user) are unaffected and still always spawn at the sender's own location. Tab-completes coordinates and world names.
-
Auto tool-switch: before mining a block,
/fpp left-clicknow auto-equips the best available tool (highest tier of the right category - pickaxe/axe/shovel/hoe) already in the bot's inventory, the way a competent player would. Toggle vialeft-click.auto-tool-switchinconfig.yml(default on). Reuses the same tool-selection logic/fpp find's auto-mining already relied on, instead of a second copy of it. - Off-hand fallback for right-click: a bot's right-click now tries the main hand first and, only if that action had no effect, retries with the off-hand item - mirroring the vanilla client's own hand-loop exactly. Bots carrying a shield or food off-hand can now actually use it; previously the off-hand was never touched by right-click at all.
-
Fixed: a bare
/fpp right-click <bot>(no--once/--repeat/--holdflag) skipped the mode-specific permission check entirely - a player holding onlyfpp.right-click.stopcould still start a default click. Now always gated on the resolved mode's permission, matchingleft-click. -
Fixed:
/fpp right-click's task-resume path silently did nothing if its cached aim state was missing (e.g. after certain restart races) - the task stayed registered but never actually ran. It now rebuilds a fresh aim from the bot's own view and resumes normally, matchingleft-click. - Internal:
LeftClickCommand/RightClickCommandnow share one base class for aim geometry, stand-location search, and the stop/cleanup/save-restore lifecycle instead of two nearly-identical ~1000-line copies; the three parallelONCE/REPEAT/HOLD/STOPmode enums collapsed into one.
Bots can now play along with a login-wall plugin instead of sitting there unauthenticated. Off by
default (auth.enabled: false); needs database.enabled: true to remember passwords across joins.
-
Auto-register / auto-login: on a bot's first join it generates a strong random password,
encrypts and stores it (AES-256/GCM, key in
auth.keyin the plugin data folder, never in config.yml or logs), then sendsauth.register-commandas the bot after a randomized human-like delay. Every later join sendsauth.login-commandwith the same remembered password instead. -
Works against any login plugin built around a
/register <password> <password>//login <password>command shape - the defaults already match nLogin, AuthMe, LoginSecurity, CrazyLogin, and xAuth. Commands are replayed exactly like a real client's typed chat command (throughPlayerCommandPreprocessEvent), not a bare command dispatch, so plugins that hook that event directly (most login plugins, including nLogin) still see the attempt. - Dedicated nLogin integration: nLogin's command path throws for a bot's connection no matter what, so it's authenticated through nLogin's own public API instead - fully optional, inert if nLogin isn't installed.
-
A bot stands still and doesn't turn its head from the moment it joins until its register/login
outcome is known (read from the login plugin's own reply - chat, title, subtitle, or action bar),
exactly like a real not-yet-authenticated player;
auth.pending-timeout-ticksis the hard fallback so an unrecognized response never leaves a bot stuck frozen. -
New
/fpp authcommand:on/off,status [bot],reset <bot>,setpassword <bot> <password>. New permissionfpp.auth(defaultop, included infpp.op). -
New settings GUI category (
🔐 ᴀᴜᴛʜ): toggle, register/login command templates, delay/timeout ticks, and password length/complexity - see Configuration.
Every chat message, GUI, and console log now pulls from one consistent violet/lime palette instead of the scattered ad-hoc colors (blue accents, plain named colors, mismatched grays) used previously. Purely visual - no config or permission changes.
- Chat/GUI text: violet (
#A78BFA) primary accent, lime (#BAFF4F) success/highlight, red (#FF6A5C) errors, plus a consistent ink/mute/mute-2/line neutral scale for body text, captions, and dividers. - Console output (
FppLogger, the startup/shutdown banners) recolored to match with true-color ANSI. -
language/en.yml's header now documents the full palette for anyone customizing messages by hand.
- The settings GUI (
/fpp settings) gained a 🔐 ᴀᴜᴛʜ and a 📈 ᴘᴇʀꜰ category (general, body, auth, perf, debug) - the perf category exposes everyperformance.*config option, including the self-profiler, with changes restarting the monitor live instead of requiring/fpp reload. - The help GUI (
/fpp help) splitauthandperfout of the catch-all ᴄᴏʀᴇ category into their own tabs, each with a distinct accent color.
Bots can now run multiple tasks at once instead of being limited to one. Starting a new task no
longer cancels every other running task for that bot - move, find, left-click, right-click,
attack, and PVE auto-combat are now independent systems that can all be active on the same bot
simultaneously, the same way auto-eat already ran alongside a paused task.
- PVE always runs. Auto-combat no longer stands down while a manual task is active; it only ever chases a target when nothing else currently owns the bot's movement.
-
Movement is arbitrated by priority, not cancellation. Only one navigation can drive the bot
at a time: an explicit
/fpp movealways gets it, a manual hand-action's short walk-to-reach (left-click/right-click/find) outranks background movement (PVE's chase), and background movement never interrupts anything. A request that loses out declines gracefully instead of hijacking the bot mid-walk. -
/fpp findno longer collides with/fpp left-click- each now has its own navigation owner. - Auto-eat is fully parallel when eating from the off-hand - mining, moving, and combat all continue untouched. Only the main-hand fallback still pauses briefly.
-
Restart persistence now resumes both click tasks if a bot had
left-clickandright-clickboth active when the server stopped, instead of restoring only one. - Every command's cleanup is now scoped to what it actually owns, so stopping one task can no longer silently cancel an unrelated concurrent task's navigation.
What this doesn't change: a bot still has one head and one main hand - two hand-actions aimed at different targets will still visibly alternate the bot's aim between them each tick.
The pacing of held /fpp left-click//fpp right-click automation (--repeat/--hold) is no
longer a fixed constant - it's a server-wide default that every bot can individually override.
-
New config options:
left-click.interval-ticks(default4, matches vanilla's own blockdestroyDelay) andright-click.interval-ticks(default4, matches the vanilla client's ownrightClickDelay) inconfig.yml. Entity attacks are unaffected either way - those are still paced solely by the held weapon's real attack-speed cooldown, exactly like a real player. -
Per-bot override: each bot's
⚙ ɢᴇɴᴇʀᴀʟsettings category now has ʟᴇꜰᴛ-ᴄʟɪᴄᴋ ɪɴᴛᴇʀᴠᴀʟ and ʀɪɢʜᴛ-ᴄʟɪᴄᴋ ɪɴᴛᴇʀᴠᴀʟ tiles (1–40 ticks); leaving a bot on the global default requires no configuration. Overrides persist across restarts (YAML and database, schema bumped to v27). - No new permission node - gated by the existing
fpp.settingsbot-ownership model, same aschunk-load-radiusand the PVE range tile.
Removed fpp.mine, fpp.mine.start, fpp.mine.once, fpp.mine.stop, fpp.mine.area - leftovers
from the old /fpp mine command that was replaced by /fpp left-click//fpp right-click several
versions ago. They were still being silently granted through fpp.op's children list without ever
having their own permission declaration, and nothing in the codebase checked them. A full audit
confirmed every other permission in Perm.java now has exactly one plugin.yml declaration with no
dangling wildcard references.
Players can now pay real economy currency to rent a bot for a set number of hours; the bot
auto-despawns the moment its paid time runs out. Off by default (economy.enabled: false).
-
New
/fpp rentcommand family:buy <hours>,extend <bot> <hours>,info [bot],give <player> <bot|--new> <hours>(grant hours without charging - console-usable),clear <bot>. -
Provider-agnostic economy layer: reflective, soft-dependency support for Vault,
Vault2.0 (same
Economyinterface, transparently covered by the Vault provider), and ExcellentEconomy (own API, configurable currency id), with zero compile-time dependency on any of them. -
Shop-plugin friendly:
/fpp rent givenever touches an economy plugin itself - any shop plugin can run it as a purchase's console reward command with no FPP-side integration needed. -
Expiry sweep: warns the owner before time runs out, then despawns safely at zero.
fpp.rent.unlimitedexempts a player's bots from expiring. -
Settings GUI tile (
⚙ ɢᴇɴᴇʀᴀʟ→ ʀᴇɴᴛᴀʟ ᴛɪᴍᴇ) and new placeholders%fpp_rental_count%,%fpp_rental_remaining%. Persists across restarts (YAML + DB, schema bumped to v26). -
New permissions:
fpp.rent/fpp.rent.info(infpp.use),fpp.rent.give,fpp.rent.unlimited(both defaultop). New wiki page: Economy.
The bot's real name is hidden via a packet-only scoreboard team, but anything that puts a bot on a
different team (another plugin, an admin's /scoreboard teams join, …) silently evicts it,
bringing the real name back. The re-assertion now fires on every nametag-refresh sweep (~10 ticks)
for every active bot unconditionally, instead of only when the activity label changed - the hide
no longer lapses for more than one sweep interval, regardless of cause.
- While mining, a held left-click resolves its target with the same combined block+entity ray a real
client uses - but unlike a real client, nothing stopped it from picking a dropped item, XP orb, or
arrow floating near the block it was breaking and sending an attack packet at it. The vanilla server
disconnects any client that attacks one of those
(
multiplayer.disconnect.invalid_entity_attacked) - a real client's own UI never lets you target them with left-click, so this path only existed because a bot drives raw packets directly. The crosshair ray trace inLeftClickCommandnow excludesItem,ExperienceOrb, andAbstractArrowfrom valid attack targets, matching whatAttackCommand's ray trace (viaEntity#isAttackable()) already did.
- The bot's floating name tag is a real world entity (a
TextDisplay), so anything capable of removing an entity -/kill, WorldEdit, another plugin's cleanup pass - could remove it. The periodic refresh loop only checked whether its cached reference wasnull, not whether the entity was still valid, so an externally-removed tag never came back. It now checksisValid()and respawns the tag within half a second (10 ticks) of it disappearing, making the tag effectively unremovable instead of merely "usually present."
- A bot's connection never performs a real handshake/login, so ViaVersion (when installed) had no
record of it - any lookup of the bot's protocol version came back empty, which is the source of
console warnings about not being able to determine a bot's client version. Bots are now registered
with Via's connection manager as running the server's own native protocol version (i.e. "no
translation needed"), using the same
UserConnectionImpl+ProtocolPipelineImplconstruction Via's own channel initializer uses for a real connection, and unregistered again on despawn. Entirely reflective and a no-op if ViaVersion isn't installed or its internals ever change shape.
- Removed the hard-coded
player.isOp()shortcuts (Perm.hasOrOp,BotAccess.isAdmin,BotListGui.isAdmin,BotSettingGui.isOp) that granted admin access to server operators even when a permissions plugin like LuckPerms explicitly denied the relevantfpp.*node. Every permission check now goes throughhasPermission()only, so LuckPerms grants/denies are always respected, operators included. - Fixed
canUse()gates that were stricter than the granular permission nodes they were meant to allow - players granted onlyfpp.left-click.once/.stop,fpp.right-click.once/.stop,fpp.despawn.own,fpp.move.to/.coords, orfpp.inventory.ownwere previously blocked at the command dispatcher before those fine-grained nodes were ever checked. - Added the missing
fpp.inventory.ownpermission constant and wired it into the/fpp inventorycommand and the right-click-to-open-inventory listener - this node was documented inplugin.ymlbut had no effect at all.
- Fixed a
TickThreadcrash ("Accessing entity state off owning region's thread") when refreshing a bot's floating name-tag activity line. The nametag refresh loop now dispatches to each bot's owning region thread on Folia instead of running on the global region thread. - Fixed bots showing their vanilla over-head name doubled up with the custom name-tag display on Folia. Folia's
Scoreboard/Team API is a documented gap (global state the project hasn't implemented for regionised ticking), so
hiding the vanilla name no longer touches
Bukkit.getScoreboardManager()/Teamat all - aPlayerTeamis built directly from NMS and itsClientboundSetPlayerTeamPackets are sent straight down each viewer's connection. - Fixed a client-crashing bug in that same rework: sending a "remove from team" packet for a bot whose name had never actually been added (e.g. a second bot's very first spawn) made the vanilla client throw and disconnect ("Network Protocol Error"). The plugin now only sends a removal for a name it actually tracked as hidden.
- Fixed
onDisable()crashing on Folia withIllegalPluginAccessException("Plugin attempted to register task while disabled") when despawning bots during shutdown.FppSchedulernow detects a disabled plugin and runs the work directly instead of scheduling it - safe, since Folia halts all region ticking beforeonDisable()runs. - Fixed Head-AI being hard-disabled on Folia entirely. The restriction predated the current per-bot region-thread dispatch and was never removed after the rest of the tick loop became Folia-safe; it now runs on Folia the same as on Paper.
-
/fpp spawncan no longer be run from the server console (or dispatched as console) - it requires a real player sender, matching most other bot commands. - Removed world/coordinate targeting (
/fpp spawn <world> [x y z]) - a bot always spawns at the commanding player's own location now. Thefpp.spawn.coordspermission node is gone along with it.
- Removed reflective
Method.invoke()calls from every hot per-tick NMS path inNmsPlayerSpawner(doTick(),getHandle(), and everything built on it -setJumping,setHeadYaw,setMovementForward,setMovementStrafe,applyServerVelocity, and more) in favor of direct calls.ServerPlayer.tickPhysics()'sdoTick()invocation alone accounted for ~69% of the plugin's own profiled self-time in benchmark reports; this scales linearly with bot count, so the win grows with server size.
The bot left-click and right-click systems no longer re-implement what the server does on a click. Every action is now
dispatched as an authentic serverbound packet through the bot's real packet handler
(FakeServerGamePacketListenerImpl), so the server performs the click exactly as it does for a real player - full
reach/anti-cheat validation, native Bukkit events (BlockBreakEvent, PlayerInteractEvent,
PlayerInteractEntityEvent, …), item durability, hunger, use-timing and every item's own behaviour.
-
New
BotClickDispatcherbuilds and dispatchesServerboundPlayerActionPacket(dig),ServerboundAttackPacket,ServerboundInteractPacket,ServerboundUseItemOnPacket,ServerboundUseItemPacketandServerboundSwingPacketthrough the connection's livehandle*methods. It first sends aServerboundPlayerLoadedPacket(what a real client sends) so the server'shasClientLoaded()gate lets bot interactions through. -
Left-click mining now sends
START_DESTROY_BLOCKand lets the server drive destroy progress, sendingSTOP_DESTROY_BLOCKon a client-faithful predictor (creative/instant-break complete on the START tick). Removed the hand-rolled progress loop and directgameMode.destroyBlockcalls. -
Left-click attacks go through the real attack packet (respecting the attack-strength cooldown), replacing the
performAttack+ forced-attack-ticker hack. -
Right-click routes block use, item use and entity interaction through the real handlers, and deletes the
fake shortcuts: directly
setType-ing planted crops from a hardcoded seed→crop table,applyBoneMeal, and the manual place/plant/bonemeal branches. The server'suseItemOnnow handles placement, planting, bone meal, hoeing, bucket use, etc. natively for any item. - Protection plugins (WorldGuard, etc.) now block bot clicks the same way they block real players, since the real events fire.
-
1:1 vanilla click resolution - the bot's clicks now resolve targets exactly like a real client's pick: the
combined block+entity ray from the bot's own eyes, nearest hit wins. Left-click attacks the entity directly under the
crosshair (paced by the real attack-speed attribute) or digs the block; right-click interacts with the entity under
the crosshair (armor-stand equip, feeding, breeding, trading, mounting) and, when that PASSes, falls through to the
in-hand item use - the same order
Minecraft.startUseItemfollows. Removed all non-vanilla targeting: sender-aim entity picks (rayTraceHostileEntity,rayTraceEntity, the right-clickrayTraceTargetPlayerentity branch), the left-click forward-cone auto-attack (findBestMobTarget), and target lock-on. An entity behind the aimed block is never picked; the sender's crosshair only designates which block to work on. -
Full debug tracing - new
left-click/left-click-headdebug categories (indebug.yml, plusConfig.debugLeftClick()/debugLeftClickHead()) mirroringright-click. Both systems now log the whole pipeline: target resolution, navigation, every dispatched packet (dig START/STOP/ABORT, attack, useItemOn, useItem, entity interact), consume detection and stop/cleanup.
Two bugs that made --hold/--repeat clicks stop working after a few seconds:
-
Spam-limiter drop -
handleUseItem/handleUseItemOnfeed the packet's Spigot-addedtimestampfield into Paper's incoming-packet spam limiter (checkLimit). Directly-constructed packets leave that field0, which looks like "no time passed since the last packet", so after ~8 uses the server permanently dropped every use packet (eating, placing, doors - everything).BotClickDispatchernow stampsSystem.currentTimeMillis()ontoServerboundUseItemPacket/ServerboundUseItemOnPacket(reflection-probed, no-op if the field ever disappears), so the limiter sees real inter-packet timing exactly as for a networked client. -
Aim tracking - the bot's head was rotated once when the command started and could drift off the commanded target
(knockback, entity interactions), leaving it staring at the wrong block forever. Both click loops now re-aim at the
commanded target every tick (new
aimTarget/aimPointin the click state, never overwritten by transient per-tick picks): a passing entity still gets vanilla nearest-hit treatment, and the moment it's gone the crosshair is back on the commanded block.
Block re-resolution in the left-click loop was gated behind a "dynamic target" flag that was false when the click started without a block pick (entity in front of the crosshair, a resumed task after restart, or a plain self-view click) - so once the entity was gone, the bot stopped instead of mining the block now under its crosshair. The loop now re-resolves the block pick unconditionally every tick, matching a real held click: entity in front → attack it; entity gone → seamlessly dig whatever the crosshair sees. The dead flag was removed.
-
Attacks now use the held weapon's real damage - bots aren't driven by the normal player tick path, so two things
a real client gets for free never happened: the held weapon's attribute modifiers were never registered
(
ATTACK_DAMAGEstayed at fist level) and the attack-strength ticker never advanced (every hit was scaled to the ~20% spam-click minimum → a sword dealt ~1 damage).BotClickDispatcher.attacknow callsdetectEquipmentUpdates()and primes the attack-strength ticker before each attack; since the click loop already paces attacks at the real weapon cooldown, every hit lands at full strength - sword/axe damage, sweep, knockback and enchantments all apply. - Right-click now swings the arm - the server only broadcasts SERVER-sourced swings; for most interactions the vanilla client swings itself, which a bot has no client to do. The bot now sends its own swing on every consumed right-click (buttons, levers, bone meal, planting, placing, entity interactions) and on instant item uses (throwing pearls/eggs) - but not when a multi-tick use starts (eating, bow draw), matching the vanilla client exactly.
Two persistence defects made every server restart / plugin update wipe bot inventories and running tasks:
- Inventory/XP restore was a one-shot timer - applied a fixed 10–12 ticks after the restore chain spawned the bot, with a silent give-up if the body hadn't finished spawning yet (typical right after startup while chunks load). Both now use a retry loop (every 5 ticks, up to 30 s) that waits until the bot is online + valid, and log a warning instead of silently dropping if the bot never spawns.
-
Task persistence was hollow since the click-system migration - the task snapshot hardcoded every field to
null/0 ("legacy mine/use/place removed") and the restore hook was an empty stub, so the active task was lost by
design. The persistence layer now saves each bot's active left/right-click task (mode + world + exact aim point)
to both the unified YAML and the
fpp_bot_tasksDB table (LEFT_CLICK/RIGHT_CLICKrows), and resumes it after the restart: once the bot is fully spawned and its inventory (tools!) is restored, it re-aims at the saved point and restarts the same click mode via the normal task pipeline. NewSavedClickTasksnapshot +getSavedTask/resumeSavedTaskAPIs on both click commands; the dead 30-field legacy task model was replaced with a compact one.
handleUseItemOn refuses every block interaction while awaitingPositionFromClient is armed - and it gets armed by
any teleport/rotation done through the connection (bot spawn, /fpp tp, action locks, CraftPlayer#setRotation). A
real client confirms teleports instantly; the bot never did, so levers, buttons, bone meal, planting and placement
silently no-oped (the packet fell into the build-limit-message branch). Two-part fix:
-
BotClickDispatchernow confirms pending teleports like a real client before every interaction packet: if the connection is awaiting a position confirm, it dispatches aServerboundAcceptTeleportationPacketwith the pending id (clears the gate, snaps to the awaited position - the same thing a vanilla client does). -
refreshAimrotates via NMSabsSnapRotationToinstead ofCraftPlayer#setRotation, which internally performs a connection teleport and would re-arm the gate every tick.
-
Right-click at vanilla speed - held right-click now acts once per 4 ticks (the vanilla client's
rightClickDelay), instead of every 8: the extra post-action pulse skip was removed. Bone meal, levers, buttons, doors and planting now trigger at real-player pace. -
No more eating through armor stands - the vanilla client hardcodes
interactAton a non-marker armor stand as SUCCESS client-side, so a real player's click is always consumed there and never falls through to eating/using the held item. The bot now mirrors that: aiming at an armor stand consumes the right-click even when the server-side equipment swap does nothing. - Held left-click attacks at exact weapon speed - entity attacks are now paced solely by the bot's real attack-speed attribute (swing + attack packet each cycle); the loop's post-break pause no longer stacks on top (previously ≈16 ticks between sword hits instead of 12). Mining keeps its vanilla-like ~4-tick post-break pause.
Extended the runtime compatibility gate to cover the year-based 26.2.x releases (Minecraft 26.2 "Chaos Cubed").
-
CompatibilityChecker.isSupportedVersionnow accepts26.2.xalongside26.1.x(old1.x.yremains supported below1.21.12). - The unsupported-version warning banner and wiki version ranges updated to read
up to 1.21.11, and 26.1.x–26.2.x. - Compile-time Paper dev bundle stays on the stable
26.1.2.build.65-stable; 26.2 runs via the existing version-safe NMS reflection.
Major version bump to 2.0.0, opening the 2.0 release line. No behavioral changes from 1.6.6.12.8 beyond the items below.
-
Version -
1.6.6.12.8→2.0.0(build.gradle.kts,plugin.yml, Velocity companionvelocity-plugin.json). -
Personality API in core - the personality API now ships in core (
api/personality/); first-party extensions updated to consume it.
Right-click had a "helper" (checkForAttachedInteractiveBlock) that, whenever the bot's ray hit a block face, looked for a button/lever/switch mounted on that face and redirected the interaction to it. The result: the bot would flip a lever or press a button just by aiming anywhere on the block it's attached to (even a corner), without ever aiming at the switch itself.
- Removed that redirect entirely. The interaction ray trace already uses the block OUTLINE shape (
ignorePassableBlocks=false), so aiming at a button/lever's real hit box returns that block directly - the bot must now actually aim at the switch to trigger it, exactly like a real player. - Aiming at the mounting block's face (or corner) now interacts with that block, not the attached switch.
When the aimed target is out of reach, the bot no longer just searches for a stand spot hugging the block (which could land it somewhere the target isn't actually reachable/visible from). It now first tries to walk to a spot around the command sender's own standing location - a vantage the target is provably aim-able from, since the player just aimed at it from there - and only falls back to the old near-the-target search if that fails.
- New
findStandLocationNear(searches a walkable spot around an arbitrary centre, including that centre block) +resolveStandLocation(prefers the sender's location, falls back to near-target) in both click commands. - Console/bot-issued clicks keep the near-target search (no player vantage to prefer).
- If neither yields a reachable spot, the command still reports "no path" as before.
When a player runs /fpp left-click or /fpp right-click while looking at a block, the bot now aims its head at the exact point on the block the player was looking at, instead of snapping to the geometric center of the block face.
- Both commands capture the sender's precise ray-trace hit position (
RayTraceResult#getHitPosition()) and aim there from the bot's own eyes; they fall back to the block-face center only when no precise point is available (entity targets, or a ray with no hit position). - The aim point is a world coordinate, so it stays correct even when the bot has to walk to a stand location before acting.
- Right-click additionally seeds its first interaction with that exact hit point (the per-tick loop still refreshes it from the bot's own view afterward).
- Internally,
lockAndStartClickingnow takes a pre-computed aimVectorinstead of aBlockFace- the block-face-center math moved to the call sites.
Full pass over the README and wiki/legal docs to match the current plugin:
- README / Home / Getting-Started - added the precise-clicking, single-action, and auto-eat highlights; refreshed the nametag activity list (now includes eating) and the per-bot GUI category list (adds auto-eat + rename); corrected optional soft-depends (PlaceholderAPI / WorldEdit / Spark) and clarified there is no built-in LuckPerms integration; fixed the source-repo link.
- Commands / FAQ - documented single-action (no multitasking), auto-eat, precise-aim + vantage walking, and the button/lever hit-box fix.
-
Placeholders - removed placeholders that no longer exist (
%fpp_chat%,%fpp_skin%,%fpp_ping%,%fpp_swap%,%fpp_peak_hours%). -
Configuration - debug-topic list corrected (added
pathfinding/skin-pool, removed the staleswap/chatentries). - Extensions wiki + retired extension policy + ToS §8 - reflect that the extension API is removed; repo links updated to the new repository.
- AGENTS.md - added runtime invariants for single-action, pause/resume, and click-aim/button-hit-box so they aren't regressed.
A pass over every permission node so a permission manager (LuckPerms, etc.) sees a clean, complete set:
-
Removed the dead
fpp.farmnode - an orphaned constant with no command, no usage, and noplugin.ymldeclaration. -
Completed the
fpp.op/fpp.adminwildcard - added the previously-missingfpp.tphandfpp.xpso the admin wildcard explicitly grants every command node. -
Documented the per-bot GUI permission model in the Permissions wiki: all per-bot settings (general, PVE, pathfinding, skin, auto-eat, rename, danger) live behind
fpp.settings, and a bot's owner (or a shared controller) can always manage their own bot's settings without it. No individual per-bot toggle (including auto-eat) has its own node.
Bot tasks are now mutually exclusive and interruptible:
-
One action at a time - starting any user task (
move,find,left-click/mine,right-click/use,attack) now stops every other running task for that bot first, via a centralFakePlayerManager.beginExclusiveAction. No more a bot mining and attacking and walking at once. -
PVE yields to manual control - the per-bot PVE auto-combat now stands down whenever a user-issued task is active (
hasActiveManualAction) and re-engages automatically once that task finishes. -
Pause/resume - a transient
FakePlayer.actionsPausedflag is honoured by every action tick (pathfinding movement, find/mine, left-click, right-click, attack, PVE). While set, each loop no-ops without losing its state, so the task resumes seamlessly when the flag clears. This is the mechanism auto-eat uses to interrupt and then continue whatever the bot was doing.
The old auto-eat was a bare instant-consume with no controls. It is now a proper AutoEatController with a full per-bot 🍖 ᴀᴜᴛᴏ-ᴇᴀᴛ settings category:
- Toggle - enable/disable auto-eat per bot.
-
Hunger threshold - a chat-input tile to set the hunger level (0-19) at or below which the bot eats. Global default
automation.auto-eat-threshold(17) inconfig.yml. -
Allowed foods - a dedicated paged food-list GUI (mirrors the PVE mob selector). Toggle exactly which foods the bot may eat; none selected = eat any food. Food data lives in one source of truth (
BotFoods) that drives both the list and the eating math. - Priority - when hungry the bot reaches for food off-hand → hotbar → main inventory, so a food pinned in the off-hand is always eaten first.
- Realistic eat - the bot pauses its current action (via the new pause/resume system below), holds the food, eats over the vanilla ~1.6s window, applies nutrition + saturation (plus golden-apple / enchanted-apple / honey effects), then switches back to whatever it was holding and resumes the paused task.
-
Nametag - shows a live
ᴇᴀᴛɪɴɢactivity line while eating. - Persistence - the enabled flag, threshold and allowed-food list persist across restarts (YAML active-bot snapshot).
Bots can now be renamed. This changes only a bot's display name - the floating name-tag name row, its entity display name, the tab-list entry and command output. The login name and deterministic fb07 UUID (identity) are never touched, and the mandatory "ʙᴏᴛ ʙʏ {owner}" disclosure row on the name-tag is preserved.
-
/fpp rename <bot> <new name>- new command (fpp.rename, default op). The new name may contain colour codes and is capped at 32 visible characters. Admins can rename any bot; non-admins with the permission can rename bots they own or share (BotAccess.canAdminister). -
GUI: a new ✎ ʀᴇɴᴀᴍᴇ ʙᴏᴛ tile (name-tag icon) at the top of the ⚙ ɢᴇɴᴇʀᴀʟ category opens the existing chat-input prompt; type the new name (or
cancel) to apply. -
Persistence: the rename is written to the YAML active-bot snapshot (on its next save) and immediately to the
fpp_active_bots.bot_displaycolumn (newDatabaseManager.updateBotDisplay), so it survives restarts. - The dormant
FakePlayerManager.renameBot(...)helper was completed to also refresh the name-tag, entity display name and tab list, and to persist - it previously only updated the tab list.
FPP is now open source. The scaffolding for every removed system was deleted outright (not just neutered) so the capabilities can't be trivially re-enabled by dropping code back in, and the policy is documented so contributions reintroducing them are declined.
-
Extension/addon system - scaffolding deleted (~1,500+ lines). Beyond the earlier "neutered loader" step, the shells are now gone:
extension/ExtensionLoader.java,api/FppAddon.java,api/FppAddonCommand.java,api/FppExtension.java,api/FppCommandExtension.java,api/FppSettingsTab.java,api/FppBotSettingsTab.java,api/FppBotDisplayService.java,api/FppSpawnLocationProvider.java, andfakeplayer/BotSwapController.javaall deleted. Addon-registration machinery removed fromCommandManager,FppApi/FppApiImpl,SettingGui,BotSettingGui;FppCommandSourcereduced toCOREonly;CheckCommand's extension probe replaced with a soft-depend check;BotPersistence'sisExtensionLoaded()replaced withfalseconstants. External code can no longer be loaded - integrate via the firedFppBot*events and read-onlyFppApifrom a normal Bukkit plugin. -
Player spoofing/impersonation - kept out. Removed the impersonation-adjacent
FppApi.spawnBot(..., UUID)overload. Disclosure invariants are enforced in code and documented as non-negotiable: reservedfb07offline-derived UUIDs, mandatoryʙᴏᴛ ʙʏ {owner}nametag, tab-list + server-list-ping exclusion, and the advancement block. -
Swap / peak-hours - removed.
swap.*/peak-hoursconfig accessors,debugSwap, theConfigValidatorblock, the%fpp_swap%/%fpp_peak_hours%placeholders, thedebug.ymlswap topic, and the SettingGui swap toggle all removed. - Licensing - removed (no phone-home startup gate; the plugin runs unconditionally).
-
Dead language sections removed (
FAKE CHAT,RANK,MINE,PLACE,USE,SWAP,PEAK HOURS); dangling key references remapped. -
Docs: new
README.md(reframed as a bot/NPC plugin, not a spoofer) andCONTRIBUTING.mdwith a "Deliberately Removed Capabilities" policy;AGENTS.mdlicense/extension gotchas rewritten; stale "Spoofer"/extension wording scrubbed fromplugin.yml,velocity-plugin.json, andconfig.ymlcomments.
-
Core bot navigation is back, and it's a genuine engine now:
PathfindingServicepreviously had no implementation (Controllerwasnullunless an extension supplied one - and extensions no longer load), so all six ofleft-click's/right-click's/find's/place's/storage's "walk to target" fallbacks were silently no-ops.FakePlayerPluginnow wires up a realPathfindingService.Controlleron startup. -
Search engine: Pathetic (
de.bsommerfeld.pathetic:engine/api:5.5.2, MIT, bundled in the shaded jar) - a zero-allocation, fully async A* engine. One sharedPathfinderserves every bot; FPP supplies the Minecraft-specific pieces Pathetic deliberately doesn't know about:-
BukkitNavigationPointProvider- real block traversability (ground/headroom viaBotNavUtil, now-publiccanStandOn), plus per-request water/lava avoidance, block-breaking, and gap-bridging (block-placing), all sourced from each bot'spathfinding.*config / per-bot nav settings. -
NavNeighbors- 26-directional (DIAGONAL_3D) movement plus a small fixed set of 2-block "gap jump" offsets for parkour. -
ParkourGapValidator- only allows those gap-jump transitions when parkour is enabled for the search, and only over a headroom-clear gap. -
TerrainCostProcessor- soft preference for dry, unobstructed ground over water/breaking/bridging when a normal route exists. -
PatheticPathfindingController- theControlleritself: dispatches searches async, then drives movement every tick (rotation, sprint pastpathfinding.sprint-distance, jump-on-step/gap, waypoint advancement, stuck detection + recalculation, arrival, and real in-path block breaking/placing viaNavBlockOpson the existingpathfinding.break-ticks/place-ticks/place-materialconfig).
-
- Package:
me.bill.fakePlayerPlugin.fakeplayer.pathfinding. - No config migration needed - this fills in the pre-existing (previously unused)
pathfinding.*config block.
Reverses the earlier "core scope reduction" that restricted /fpp move to directional input only - the engine to back it now exists, so destination-based movement is back in core:
-
/fpp move <bot|all> --to <bot>- walk to another bot's current location, re-pathing if it moves more thanpathfinding.follow-recalc-distanceaway. Gated byfpp.move.to. -
/fpp move <bot|all> --coords <x> <y> <z> [world]- walk to a fixed point (defaults to the bot's current world). Gated byfpp.move.coords. - Both send a start message and, when a
CommandSenderis available, an arrival/no-path follow-up; both are cancellable via the existing/fpp move <bot|all> --stop, which now also cancels pathfinding movement (not just directional input) for that bot. -
--to/--coordsonallresolves the destination once up front (not per bot) so a--totarget that's itself part of the "all" batch is skipped, not treated as a batch-aborting error. -
Not added:
--pos/--roam(open-ended wandering) - out of scope for this change;--to/--coordscover explicit-destination movement.
-
Right-click on entities actually does something now. Previously
tryEntityUseonly fired an internal event and swung the bot's arm - no real interaction happened. It now routes through the real NMSPlayer#interactOn(Entity, InteractionHand, Vec3)(the exact entry point a real client's interact packet uses), trying main hand then off hand. This makes feeding/breeding/taming animals, shearing sheep, milking cows, boarding boats/minecarts/horses, leashing, name-tagging, filling bottles, and opening villager trades all work for real, not just visually.- Since fake players have no real client to drive a trade/container screen further, any menu opened by the interaction (villager trade, chest minecart, etc.) is closed immediately afterward so the entity isn't left "busy" and blocking real players.
- Right-click target resolution is now a single combined block+entity ray trace (matching how a real client picks its target), so whichever is genuinely closer along the look vector wins - an entity standing in front of a wall is no longer skipped in favor of the wall behind it. Entity interaction is tried first; on PASS it correctly falls through to block interaction, then generic item use (eat/drink/potion/shield/bow/throwables) - same order and fallthrough a real client uses.
-
Left-click now insta-breaks blocks in Creative mode (one click, no progress accumulation) instead of mining at survival speed, matching
ServerPlayerGameMode's real creative-destroy short-circuit. Survival/Adventure block-breaking (progress, tool speed, block hardness) and entity attacking (via the realPlayer#attack()- crits, sprint knockback, sweep, weapon cooldowns) were already accurate and are unchanged.
Bots can no longer talk in chat in any way. The entire chat system was removed: AI/fake-chat output, remote chat relay, and bot join / leave / death messages (bots are now completely silent in chat).
-
Deleted classes:
BotChatController,FppBotChatEvent,api.personality.ChatFrequency. -
BotBroadcastslimmed to a display-name resolver only (all chat/join/leave/kill broadcasting removed). -
Config: the entire
fake-chat.*surface, theChatMessageProviderSPI, andswap.farewell-chat/swap.greeting-chatgetters removed. -
GUI: the
💬 Chatsettings category and chat-tier/chat-enabled controls removed. -
Network: the
CHATplugin-message subchannel (sendChatToNetwork) removed.JOIN/LEAVEsubchannels remain but no longer render any message. -
Removed public API (
⚠️ breaks thefpp-spoofextension until it is updated):FppApi.sayAsBot,FppBot.isChatEnabled/setChatEnabled/getChatTier/setChatTier,FppBotChatEvent,BotChatController+FakePlayerPlugin.get/setBotChatAI,Config.ChatMessageProvider+setChatMessageProvider/clearChatMessageProvider/reloadChatMessages, allConfig.fakeChat*,Config.swapFarewellChat/swapGreetingChat,BotProfile.getChatFrequency+Builder.chatFrequency,Personality.chatFrequencyMultiplier,VelocityChannel.sendChatToNetwork. -
Retained (inert): per-bot
chat_enabled/chat_tierpersistence (DB columns + YAML) is kept as dormant data to avoid risky schema migration; it has no behavior and is no longer in the public API.
- Bots now show a two-row name tag - row 1 is the bot's name, row 2 an indicator (default
bot by <owner>), like aminecraft:mannequin. -
Both rows are rendered by a per-bot
text_display; the bot's vanilla over-head name is hidden (via aNAME_TAG_VISIBILITY=NEVERscoreboard team,COLLISION_RULE=ALWAYSso bot collision is unchanged) so it no longer overlays the display. - Follow: the display rides the bot as a passenger so the client keeps it perfectly glued (a free-standing display desynced from FPP's packet-driven movement and lagged). Only re-mounts if a teleport dismounts it.
-
Cleanup: the tag is removed on every despawn/death path via
FakePlayerManager.unregisterBotState(death removes the body directly and bypassedremoveAll), fixing tags that lingered after a bot died/despawned. - Implements the previously-stubbed
FakePlayerBody.spawnNametag/removeNametag/removeOrphanedNametags; refreshes on/fpp setowner. - Config block
nametag.second-line:enabled(defaulttrue),format(default<gray>bot by {owner}</gray>,{owner}placeholder),y-offset(default1.0, height above the mount point).
-
Tab list: bots are no longer listed in the in-game tab.
Config.tabListEnabled()now defaults to false and spawn setslisted=falseup front (no flicker). -
Server-list ping: new
ServerListPingListener(PaperPaperServerListPingEvent) subtracts online bots from the player count and removes bot profiles from the hover player sample, so the multiplayer menu never reveals bots. - Net effect: bots don't appear in the tab list, the server player count, or the server-list player sample.
-
External extensions no longer load.
ExtensionLoader.loadExtensions()/reload()are neutered (no JAR scanning/loading), the startup load is gone, and theplugins/FakePlayerPlugin/extensions/folder + README are no longer created. Anyfpp-spoof/third-party addon is inert. -
/fpp extensioncommand removed;/fpp reload extensionssubcommand removed. - Retained (inert): the internal API (
FppApi/FppApiImpl,api/event/*,FppBotImpl, the command-extension map) that core itself uses for events/tick hooks/GUI tabs - deleting it would destabilize core, same lesson as chat/skin/LP. TheExtensionLoadershell is kept for its read-only references (%fpp_extensions%→ 0,/fpp check, persistence checks).
-
/fpp listnow opens a GUI for players (newBotListGui) instead of a chat list. -
Permission-scoped visibility: a normal user sees only the bots they own; an admin/OP (
fpp.admin/fpp.opor op) sees every bot on the server. - Each bot is a head showing owner, location, uptime, and frozen/active status; click a bot to open its management GUI (
BotSettingGui). Paginated (45 per page). - Console/command-block senders still get the text list.
- Bot join/leave chat messages stay suppressed; the quit handler is now bot-only (real players' quit messages are untouched again).
-
NmsPlayerSpawner initialised (...)startup line dropped from INFO to debug. -
Vanilla
<bot>[<ip>] logged in with entity id ...login spam is now suppressed for bots via a Log4j filter (BotLoginLogFilter) installed on the root logger; real players' login lines are kept. -
JVM "Final field ... mutated reflectively by SkinProfileInjector" warning fixed:
createGameProfilenow inserts the texture into the backing multimap before wrapping it in thePropertyMap/GameProfile(a normalput, not a reflective final-field mutation). The oldapply()-on-fresh-profile path is gone.
-
LuckPerms integration removed: the LP user pre-load (
NmsPlayerSpawner.preLoadLuckPermsUser) and capability hook are gone, along with theFppBot.getLuckpermsGroup/setLuckpermsGroupAPI and thenet.luckperms:apibuild dependency. (Permissions still work via any permission plugin for real players; FPP just no longer hooks LP for prefix/suffix/groups.) -
UUID/identity system removed: bots no longer do Mojang name→UUID lookups -
resolveUuid/refreshIdentity/ restore now always use a deterministic offline UUID from the name. A bot named after a real player no longer inherits that account's identity, so it won't pick up that player's data in other plugins. -
Note: bots are still real
ServerPlayers, so other plugins can still see them online - they just no longer map to a real account or to LuckPerms. - Retained (inert): the
luckperms-groupper-bot persistence +BotIdentityCacheinstance (never queried) - kept to avoid destabilizing persistence, same as the chat/skin lessons.
- The dynamic skin system is gone: no Mojang fetching, no per-bot skins, no skin pool, and the
/fpp skincommand is removed (it was extension-owned; that extension breaks until updated). -
Every bot now uses one hard-coded, Mojang-signed slim skin (
BotSkin), stamped at spawn - not changeable. -
FakePlayer.getResolvedSkin()andSkinManager.resolveEffectiveSkin()always return the fixed skin; skin placeholder (%fpp_*_skin%), the stats "skin mode" row, and the help/tab entries were removed. - Retained (inert):
SkinProfile/SkinProfileInjector(the apply mechanism), and the now-unreachable fetch classes (SkinManager/SkinRepository/SkinFetchService) +skin_cachetable - kept to avoid destabilizing spawn/packets, same as the chat-field lesson.
-
Removed the random name generator (
RandomNameGeneratordeleted) and thebot-name.mode(random/pool) option. - Bots now spawn with sequential names
bot,bot2,bot3, … (lowest free name; despawned names are reused). This applies to both admin (/fpp spawn) and user-tier spawns. Explicit--name <name>still works. - The name tag equals the bot name (
bot-name.admin-formatnow defaults to{bot_name}). - Removed the
--random-namespawn flag and theFakePlayerManagerrandom/pool naming (generateUserBotName/UserBotName/botNameMode). -
bot-names.ymlis no longer used for bot naming (it still feeds skin-name selection in non-playerskin modes).
-
Removed commands:
/fpp migrate(backup/config/lang/db migration),/fpp stats(live statistics panel),/fpp badword(badword filter management). Their permissions (fpp.migrate,fpp.stats,fpp.badword) andplugin.yml/help-menu entries are gone; the underlying managers they used (BackupManager,DataMigrator,ConfigMigrator,BadwordFilter) are still used elsewhere and are untouched. -
Removed
PlaceCommand: already unregistered/unreachable in core (superseded byleft-click/right-click, per the existing "moved out of the registered core command set" note) - deleted the dead class along with its now-orphanedfpp.place*permissions and its dead area-fill sub-system (--pos1/--pos2/--block/--clear/start/status, which had been permanently disabled behind a hardcodedfalseflag with an empty tick method). -
Flag consistency: normalized every core command to accept only
--flag-style sub-command arguments (no bare-word duplicates).StorageCommandno longer acceptslist/remove/clear/enable/disable/depositalongside their--flagequivalents ---flagonly.PlaceCommand'sonce/--onceduplicate is gone with the command. -
Usage-string fixes:
left-click/right-clickusage now documents the standalone--stopform they already supported;findusage now documents the-r/-cshort aliases for--radius/--count;storageusage now lists--enable/--disable/--deposit, which were previously undocumented.
- New 🧭 Pathfinding category in the per-bot Settings GUI (
/fpp settings <bot>), restoring the previously-orphanednav_parkour/nav_break_blocks/nav_place_blockstoggles (their handlers already existed but had no GUI entry point since an earlier category removal) alongside a new Show Path (debug) toggle. -
Show Path renders a live particle trail along the bot's active
PatheticPathfindingControllerroute every 4 ticks while it's navigating - green dust for the general route, orange for the immediate next waypoint, and a red marker (with a short vertical particle stack) at the final destination, similar to Baritone's path render. -
Per-viewer only: this is a personal debug preference, not a bot property - toggling it only affects what you see (
PathfindingDebugManager, keyed by viewer UUID → bot UUID), it isn't persisted, and it costs nothing when nobody has it enabled (the render loop bails out immediately if there are no viewers for that bot). - Cleaned up automatically on player quit and bot despawn/delete.
- Both command classes were fully implemented (and had working permissions, tab-complete, and
CommandManagertask-target wiring) but were never instantiated or registered inFakePlayerPlugin.java-findCommandstayed a permanently-null field, andStorageCommandwas never constructed at all./fpp findand/fpp storagedid not exist in-game despite appearing fully built. Both are now registered on startup.
- Config-key validation checked the wrong paths (
max-bots/server-id/database.type) instead of the real ones (limits.max-bots/database.server-id/database.mode) - every install reported 3 "missing config key" warnings for keys that were actually present. - The "Lang directory" checks (both the database section and the dedicated language-file section) looked for a
lang/folder; the plugin actually useslanguage/- fixed to check the real path. - Removed the "Extensions directory - MISSING/FAILED" check: external extension loading is inert by design now, so that folder legitimately isn't created and the warning was permanent noise.
- Removed the LuckPerms soft-dependency check: FPP no longer integrates with LuckPerms at all, so checking for it made no sense.
- Removed the directional raw-input mode entirely (
--direction <forward|backward|left|right>,--seconds/--ticks) ---to/--coordscover the real use cases and were the source of user confusion about which flags did what. -
--to <bot|player>now also accepts a real online player's name, not just another bot. -
--tonow actually follows live: previously it snapshotted the target's location once at command time and never updated it, even though afollow-recalc-distanceconfig option implied live-following - the destination supplier was a constant, so recalculation never triggered. It now re-queries the target's current location on every check. - New usage:
/fpp move <bot|--all> --to <bot|player> | /fpp move <bot|--all> --coords <x> <y> <z> [world] | /fpp move <bot|--all> --stop.
-
frontend/wiki/Extensions.mdextensively described JAR-based extension loading as a live, working feature (installation steps, folder structure, bundle loading, troubleshooting) even though external extension loading was disabled entirely in an earlier change this session. Added a prominent notice at the top and annotated every section that describes non-functional install/load steps (Getting Started, Extension Bundles, Troubleshooting, First-Party Extension Bundle) so the page is no longer misleading about what currently works. The API reference itself (FppApi,FppBot, hooks, events) remains documented since core still uses the same interfaces internally. -
%fpp_extensions%/%fpp_extensions_names%placeholder docs (README.md,frontend/wiki/Placeholders.md) now note they always resolve to0/empty since external loading is disabled; removed the stale%fpp_extensions%line from the example scoreboard config. -
/fpp check's extensions step now explicitly states "0 loaded is expected, not an error" instead of presenting an inert subsystem as if it were a live one being validated. - Confirmed the settings-tab GUI injection API (
registerSettingsTab/registerBotSettingsTab) has no callers anywhere in core - it's dormant, unreachable scaffolding for a future extension loader, not something rendering an empty "Extensions" GUI tab today, so no GUI code changes were needed there.
Every mechanism that let a bot look, sound, or behave like a specific real player, or like a real connected client, has been removed. Bots remain fully functional automatable game entities (spawn, move, fight, mine, etc.) - only the ability to impersonate or be mistaken for a real player is gone.
-
Skin: the hard-coded skin (a real, signed Mojang skin belonging to a real account) is gone.
Every bot now renders the client's plain default Steve/Alex model (
BotSkin.fixed()returns an invalid profile, so the NMS injection path falls through to vanilla). No bot can wear a specific identifiable real player's actual skin - ever. -
Naming: removed every path to an arbitrary or real-player-matching bot name.
/fpp spawn's--nameflag is gone (bots are always sequentially auto-namedbot,bot2, …),/fpp renameis deleted entirely, and the Settings GUI's in-GUI rename button is gone along with the sharedBotRenameHelperit used.--notp(which only worked in combination with--name, to look up a specific named bot's last known location) is gone with it - persistence-based restore-on-restart is unaffected and still restores all bots automatically. -
Tab list:
Config.tabListEnabled()is now permanently hardcoded tofalsewith no override mechanism at all (the deadsetTabListEnabledProviderindirection, which already had zero callers anywhere, is removed outright). Bots can never appear in the tab list. -
Server-list ping:
ServerListPingListener(which used to subtract bots from the reported player count and clear the hover sample) is deleted. The server list now reports Bukkit's plain, true state with no special-casing in either direction - bots are neither hidden nor inflated. -
Ping/latency simulation: the entire fake-network-realism subsystem is removed - no more random
jitter, connection spikes, join ramp-up, or artificial action-latency throttling designed to make a
bot's connection seem human. An explicit, deliberate ping override (
FppApi's custom-ping method) still exists for legitimate debug/testing use, but nothing ever auto-generates a "realistic" fake value anymore. -
Nametag disclosure is now mandatory: the "bot by {owner}" second nametag line can no longer be
disabled (
nametag.second-line.enabledconfig key removed) - every bot must always visibly identify itself as a bot.
Hard constraint, unavoidable: bots are fundamentally NMS ServerPlayer entities and will always
render client-side as a humanoid Minecraft player model - removing that would require rewriting the
entire entity architecture (e.g. ArmorStand/NPC-based bots), breaking inventories, combat,
pathfinding, and nearly everything else this plugin does. That part is out of scope and isn't
changing. Everything else that could make a bot pass as or impersonate a specific real player is
gone.
- Despite bot chat being fully removed earlier this session, the vanilla
PlayerDeathEventdeath message (e.g. "bot1 was slain by Steve") was still showing in chat -Config.deathMessage()defaulted totrueand only got nulled out under specific conditions. Bots now unconditionally suppress their death message, matching join/leave. Removed the now-fully-deadConfig.deathMessage()/Config.killMessage()accessors and themessages.death-message/messages.kill-messageconfig.yml keys (neither had any other consumer).
- Every bot despawn/delete/death-removal path now plays a small poof-cloud particle burst and sound
at the bot's last location (
FakePlayerManager.playDespawnEffect, hooked into the sharedunregisterBotStatecleanup that all removal paths already funnel through) - a bot vanishing now has a visible/audible cue instead of just silently disappearing.
- The despawn poof/sound effect added earlier this session had a real ordering bug: on the
death-without-respawn path (and the respawn-failure edge cases),
fp.setPlayer(null)ran before the effect's entity lookup, so it silently no-op'd every time. Fixed by capturing the bot's location while the entity reference is still valid and threading it through explicitly (FakePlayerManager.removeByName(name, fallbackLoc),unregisterBotState(fp, reason, fallbackLoc)). The effect now fires correctly on/fpp despawn, death-without-respawn, and failed respawn attempts. - Separately:
/fpp despawn <name>already sent the sender a confirmation message (delete-success), but the death-without-respawn path sent no message to anyone - only a debug-level log line invisible unlessdebug.ymlis on. Addedbot-died-despawned, sent privately to just the bot's owner and whoever killed it (if online and different from the owner) - no one else sees it - when the bot dies and is removed instead of respawned.
- The nametag is now three rows instead of two: a new top row shows what the bot is currently doing (idle, moving, mining, attacking, using item, searching, frozen - joined if more than one applies), above the existing name and "bot by {owner}" rows.
- New
BotActivity.currentLabel(fp)(fakeplayer/BotActivity.java) computes this live by directly querying each task command's own state (PathfindingService.isNavigating/getOwner,LeftClickCommand/RightClickCommand.isClicking,AttackCommand.isAttacking,FindCommand.isFinding,FakePlayer.isFrozen) rather than mirroring a separately-tracked start/stop flag - avoids desync (e.g./fpp move --todoesn't fire a "stop" event on natural arrival, so a mirrored-state approach would get stuck showing "moving" forever after arrival). - Refreshed once a second per bot, and only re-sent to the nametag entity when the label actually
changed (
FakePlayer.lastRenderedActionLabel), to avoid needless packet churn. -
Fixed: the name row was picking up the action row's color (turning yellow) because Adventure
components inherit an unset color from whatever they're appended onto, and the name row was nested
as a child of the colored action row. Fixed by building all three rows under a colorless root and
giving each row an explicit color instead of relying on inheritance - the name is white again, and
the action row now matches whatever color the bottom "bot by {owner}" row is configured with
(read directly from
indicatorRow.color(), so it stays in sync if that format is customized).
/fpp find already had a solid search→path→mine loop and strong loop protection (blocks are marked
visited before navigation starts, plus a cross-bot reservation map so two bots never fight over the
same block). This pass fixes what was actually missing:
-
Real tool auto-equip -
equipBestTool()was a literal empty stub with a comment saying it was "handled by the addon auto-equipment tick handler," but external extensions can never load anymore, so bots mined with whatever was already held, tool or not. Replaced with real logic driven by Bukkit's ownTag.MINEABLE_PICKAXE/MINEABLE_AXE/MINEABLE_SHOVEL/MINEABLE_HOE(the same data vanilla itself uses) to pick the right tool category, then the best tier available in the bot's inventory (ITEMS_PICKAXES/etc., ranked wooden→netherite) - covers every tool type, not just pickaxes. - Anti-stuck, mining phase - tracks ticks-since-last-destroy-progress per block; if it stalls for 5s (wrong/no tool, block resisting breakage, etc.) the block is abandoned instead of grinding on it forever. It's already marked visited, so it's never re-targeted.
- Anti-stuck, navigation phase - the shared pathfinder auto-recalculates on physical stuck-detection but never abandons a target; added a 45s watchdog scoped to find-jobs only that gives up on a block and moves to the next one if navigation genuinely can't get there, without touching the shared pathfinding subsystem.
-
Inventory-aware auto-deposit - when free inventory space drops to 2 slots or fewer, the bot
detours to its nearest registered, enabled storage (reusing
StorageStore/StorageInteractionHelper, the same infrastructure/fpp storage's deposit already uses) and resumes the find job from wherever it ends up. Bots with no storage registered keep the previous passive drop-collection behavior - no regression. -
Fixed a real reservation leak:
FakePlayerManager.unregisterBotState(the shared despawn cleanup every despawn/death path funnels through) calledMoveCommand/LeftClickCommand/RightClickCommandcleanup on despawn, but neverFindCommand.cleanupBot- a bot despawned mid-find-job could leave its block reservations locked forever, permanently blocking those blocks for every other bot. Now cleaned up on every despawn path like the others.
- The existing per-bot "Show Path (debug)" particle-trail toggle (
🧭 Pathfindingcategory in each bot's own settings) now has a global counterpart:🐛 ᴅᴇʙᴜɢ→ Show All Paths in the main/fpp settingsGUI. One click enables (or disables) the pathfinding debug trail for every currently active bot at once, visible only to the player who toggled it. - Backed by two new methods on the existing
PathfindingDebugManager(setViewing- explicit set rather than flip,isViewingAny- true if the viewer has the trail on for any bot); no new state store, no persistence (same as the per-bot toggle, a pure viewer preference). - Doesn't retroactively apply to bots spawned after the toggle is flipped - re-click to pick them up.
The particle trail shows where a bot is trying to go, but not why it's not getting there. Whoever has the path-debug view enabled for a bot (per-bot toggle or the global "Show All Paths" toggle) now also gets chat messages explaining the specific failure when something goes wrong:
-
pathdebug-stuck- the bot hasn't moved far enough forpathfinding.stuck-ticksin a row and the path is being recalculated (e.g. wedged against terrain, a moving target, an obstacle the original path didn't account for). -
pathdebug-no-path- the search engine genuinely couldn't find a route to the target after exhausting its retry budget (destination unreachable, sealed off, out of range). -
pathdebug-watchdog-/fpp find's 45s navigation watchdog fired, meaning the bot never arrived/cancelled/failed in time and the target was abandoned outright (this is the case the recalculate-forever behavior above can't catch on its own). -
pathdebug-mine-stall-/fpp find's mining loop made no destroy progress for 5s straight and gave up on the block (almost always a missing/wrong tool, or a block too hard to break). - New
PatheticPathfindingController.sendDebugChat(botUuid, botName, langKey, args...)- reuses the existingPathfindingDebugManagerviewer set (the same subscription the particle trail uses), so there's exactly one toggle for both the visual and the chat debug output. - All four new lang keys added under
PATHFINDING DEBUG (chat)inlanguage/en.yml.
Bots could get permanently wedged (e.g. inside a tree canopy, tangled in vines/leaves) and spam "stuck - recalculating" forever without ever giving up, because recalculating against completely unchanged terrain just returns the same path and gets stuck again immediately - nothing about the old logic ever broke that cycle.
-
Hard give-up budget -
pathfinding.max-stuck-cycles(default4): each stuck→recalculate cycle that produces zero real forward progress counts against this budget; once exhausted, the navigation is abandoned outright (same as a genuine "no path found") instead of looping forever. Real movement resets the budget, so this only fires when the bot is truly wedged, not during ordinary momentary slowdowns. - Jump nudge before recalculating - halfway through the stuck window, the bot now attempts a jump before giving up and recomputing a whole new path. Covers the common case of being stuck on a lip/leaf/slab that just needs a hop, which a full recalculation wouldn't fix anyway (the route was already fine).
-
Less twitchy stuck detection -
pathfinding.stuck-ticksdefault raised5→10(0.25s was triggering on completely ordinary momentary slowdowns, e.g. foliage friction, not just genuine stuck states). -
Terrain cost now penalizes climbing and canopy routes -
TerrainCostProcessorpreviously had no opinion on vines/scaffolding/ladders or leaf-covered ground, so the search treated a vine climb through a tree canopy as equal-cost to a clear walking route. Both are still legal (nothing was removed fromBukkitNavigationPointProvider's hard traversability gate), but now cost more so a comparable ground route is preferred whenever one exists - the search only tunnels through foliage when it's genuinely the only way. -
BotNavUtil.isClimbablemade public so the cost processor can reuse the same climbable-block list the traversability check already uses (fakeplayer.pathfindingandfakeplayerare still separate packages, so this wasprivatebefore with no reuse path).
The chat debug messages added earlier said that something failed but not much about why. New
debug.yml → pathfinding toggle (also exposed in /fpp settings → 🐛 ᴅᴇʙᴜɢ → Pathfinding)
logs a full diagnostic line to the server console - not just in-game chat - for every stuck cycle,
give-up, no-path failure, /fpp find navigation watchdog, and mining stall:
-
Stuck / give-up / no-path (
PatheticPathfindingController): bot name, nav owner, stuck-cycle count vs. the give-up budget, failed-recalculation count, waypoint progress (index/total), current and target position (world + coords), remaining distance, the actual block at the bot's feet/below/ head (this is the part that would've explained the tree-canopy screenshot - you'd seeblocks{feet=VINE,below=OAK_LEAVES,head=OAK_LEAVES}directly in the log), and which path options were active (parkour/break/place/avoidWater/avoidLava). -
/fpp findnavigation watchdog: bot name, elapsed ticks, bot position, and the specific target block (type + coordinates) it gave up walking to. -
/fpp findmining stall: bot name, the block it was mining (type + coordinates), the tool currently held, and how much destroy-progress was actually made before giving up - makes "wrong tool" vs. "block genuinely too hard" immediately obvious from the log alone. - One line per event,
event=...prefixed and grep-friendly (e.g.grep "event=STUCK"), independent of whether anyone has the in-game path-debug view open - the whole point is being able to diagnose a bot that got stuck hours ago from the console/log file after the fact. - Same
debug-chatmaster switch as every other debug topic also mirrors these lines to online OP/ notify players if wanted, on top of the existing pathfinding-viewer-only chat messages.
Previously, once Pathetic's async search returned a route, the bot just started walking it - any "this route doesn't actually work" discovery only happened at runtime, via the stuck-detection budget added earlier. That's a wasted trip: the bot walks partway toward something, gets stuck, and only then gives up. Routes are now re-checked before any movement starts:
-
BukkitNavigationPointProvider.isTraversable(the exact rule the live search itself uses to decide "can a bot's feet legally occupy this block") is now a sharedstaticmethod instead of private, so it can be reused as a single source of truth instead of duplicating the logic. -
New
PatheticPathfindingController.verifyPathre-walks the entire finished waypoint list synchronously, on the main thread, against the real current block state (Pathetic's search runs async against a snapshot, so chunks can have changed by the time it finishes) and rejects the route outright if:- any waypoint is no longer traversable right now (
NOT_TRAVERSABLE), - a step rises more than one block without a climbable block (vine/ladder/scaffolding) actually
present there - an impossible move for the tick-driven walker to execute (
IMPASSABLE_RISE), - a gap-jump slipped through without parkour enabled, or is wider than the neighbor set could have
legitimately produced (
UNEXPECTED_GAP/GAP_TOO_WIDE) - defense-in-depth on top ofParkourGapValidator, which already gates this during the search itself.
- any waypoint is no longer traversable right now (
-
Waypoint 0 is skipped in the re-check - it's the bot's actual current position, not a
synthesized grid node, so it's valid by definition. The first version of this re-verification
re-ran the traversability heuristic against it too, and since that node is never re-validated by
the live search either (it's the given start, not something the neighbor strategy generated), any
disagreement between "the bot is physically standing here" and the block-model's opinion of that
same spot rejected the route outright - on every single navigation, since node 0 is present in
every path. That's what broke
/fpp find(and pathfinding generally) entirely right after this feature landed; fixed before it shipped any further. - A rejected route is treated exactly like a failed search (
onPathFailed) - it plugs into the existing recalculation/give-up budget rather than adding new state, and gets aevent=PATH_REJECTED reason=...line in the console when pathfinding debug logging is on. - Fixed a latent bug found while making this change: if the bot went offline in the moment between starting a search and it completing, the old code silently did nothing (never started the movement task, never reported failure) instead of calling the request's failure/cancel callback.
The wiki already documented bots being subtracted from the multiplayer menu's player count and hover
sample, but the listener that was supposed to do it (ServerListPingListener) never actually existed
in the codebase - bots were counting toward "X/20" and appearing in the hover list as "Anonymous
Player" (the client's fallback when it can't resolve a sample entry to a real profile) the entire
time. Implemented for real this time:
- New
ServerListPingListener(PaperServerListPingEvent,com.destroystokyo.paper.event.server) registered inFakePlayerPlugin.onEnable. - The hover sample is rebuilt from real online players rather than filtered by bot UUID. The
first attempt filtered
getListedPlayers()by each bot's logical UUID - that fixed the count but the "Anonymous Player" hover entry survived, because the profile a bot contributes to the sample isn't guaranteed to carry that UUID (skin injection can swap the profile identity, and an anonymized sample entry carries no identity at all). Rebuilding sidesteps the matching problem entirely: clear the sample, re-add only genuine players (excluded by both UUID - logical and live-entity - and name), and setnumPlayersto the count of what was re-added. - Runs at
EventPriority.LOWso other plugins (vanish, etc.) can still adjust the list after us. - Net effect: bots are invisible to the server-list ping entirely - not in the player count, not in the hover sample - same as they already were in the in-game tab list.
Bot UUIDs are no longer vanilla offline-mode hashes (MD5("OfflinePlayer:"+name)) - they now carry
a fixed, recognizable fb07 prefix with the bot's identity readable straight out of the UUID:
-
Sequential names embed their number:
bot→fb070000-0000-0000-0000-000000000001,bot2→…-000000000002,bot123→…-00000000007b(hex). The number comes from the name itself, so the scheme stays fully stateless - same name, same UUID, every restart, nothing stored. -
Custom names get the same prefix with a 64-bit hash of the lowercase name in the low bits
(UUIDs are hex-only, so a name like
Stevecan't be embedded literally), plus a marker bit in the high half so a bot custom-namedbot1orbot02can never collide with a canonical sequential bot's UUID. -
Why
fb07and not all-zero high bits:getMostSignificantBits() == 0is the standard idiom plugins use to detect Geyser/Floodgate Bedrock players - bots with00000000-…-0001UUIDs would be misclassified as Bedrock players. The fixed non-zero prefix avoids that and gives a trivial "is this a bot?" check: newBotIdentityCache.isBotUuid(uuid). - Zero collision risk with real accounts: premium UUIDs are random v4, offline-mode ones are MD5 v3 - neither can land in a fixed-prefix range.
-
Automatic migration: on restore, persisted bots still carrying their legacy offline-mode UUID
are remapped to the new scheme, and their saved inventory/XP/task state follows them to the new
key (via the existing
remapLoadedStatemachinery). Anything with a non-legacy stored UUID (e.g. an explicit-UUID API spawn) is trusted as-is and untouched. Oldworld/playerdata/<legacy-uuid>.datfiles become inert orphans. - New unit tests (
BotUuidTest) pin the exact mappings, case-insensitivity, thebot1/bot02no-collision guarantees, and the Floodgate/isBotUuidboundary behavior.
Bots previously spawned with no skin at all - SkinManager was never instantiated in core (it was
created by the removed FPP-Skin extension), so every bot fell back to default Steve/Alex. Core now
owns the skin system, built around rarity pools:
-
Skin pools live in
plugins/FakePlayerPlugin/skins/(seeded from bundled defaults on first run) as text files of NameMC skin URLs, one per line:-
main_skin.txt- the default skin every bot spawns with. -
1-<N>%.txt- a "1 in N" pool: each fresh spawn rolls a 1/N chance to draw a random skin from that file instead of the main skin. Ships with1-1000%,1-5000%,1-10000%, and1-100000%tiers; add or remove pool files freely. Rarest pools roll first, so a lucky spawn always claims the best tier it hit.
-
-
Signing pipeline (new
SkinPoolService): NameMC only hosts the PNG, and skins need a Mojang-signed texture to render on clients - so on first use the PNG is downloaded, its player model is detected, and it's run through the MineSkin API for signing (serialized, ≥6s apart, to respect MineSkin's anonymous rate limit). The signed result is cached forever indata/skin-cache.yml- each skin touches the network exactly once, ever. If a rare skin can't be resolved (network down), the bot degrades to the main skin, then to vanilla default. -
Slim/classic model detection (new
SkinModelDetector, unit-tested): two strategies - authoritativetextures.SKIN.metadata.modelfrom a signed texture's base64 JSON, and raw-pixel analysis for bare PNGs (the 4th arm-pixel column at x=54–55 is transparent on slim skins; legacy 64x32 skins are always classic; multiple sample rows so one stray pixel can't flip the result). The detected variant is passed to MineSkin so the signed skin renders with the correct arm model, and the model is stored in the cache alongside the texture. -
Roll-once semantics:
SkinManager.resolveEffectiveSkinshort-circuits when the bot already carries a resolved skin - restores from persistence, respawn snapshots, and manual skin applies all keep their existing skin. The rarity roll happens once per brand-new spawn only. -
Rare-roll announcement: the spawner gets a chat message when their bot hits a rare pool
(new
skin-rare-rolledlang key), plus a console log line. -
Config:
skin.rare-pools: trueinconfig.yml- setfalseto disable the entire system (bots then use the vanilla default skin as before). -
Fixed shortly after landing: the PNG download used NameMC's legacy
texture.namemc.commirror, which now fails behind Cloudflare (HTTP 526, invalid origin SSL) - switched to the directhttps://s.namemc.com/i/<id>.pngendpoint (verified live), with the legacy mirror kept as an automatic fallback. Also added a 10-minute per-skin failure cooldown so an unresolvable skin can't repeatedly burn MineSkin quota on every spawn attempt, and the main skin is now prewarmed asynchronously at startup (cache-first - a no-op on every start after the first), so the first spawn never waits on the signing API. -
Fixed: bundled pool files could never be extracted. Bukkit resolves plugin resources through
a URL, where
%is an escape character - so bundled names likeskins/1-1000%.txtfailed to extract (%.tis an invalid escape), the failure was only visible at debug level, and installs ended up with a permanently emptyskins/folder (seeding skipped because the directory existed). Bundled resources are now%-free (1-1000.txt), copied manually to their1-1000%.txtdata-folder names, seeding self-heals askins/directory that exists but holds no.txtfiles, extraction failures log at WARN, and a missing/emptymain_skin.txtnow warns loudly instead of silently spawning default-skin bots. -
Fixed the actual "skin never applies" root cause (found via the new debug tracing):
FakePlayer.getResolvedSkin()was a leftover stub from the old skin-system removal that unconditionally returned the invalidBotSkin.fixed()placeholder - the setter stored the resolved pool skin, but every read got the placeholder back, so the apply step always concluded "no valid resolved skin" and left the bot on the vanilla default. The getter now returns the stored field again. -
Full skin-pipeline debug tracing - new
debug.yml→skin-pooltoggle (also in/fpp settings→🐛 ᴅᴇʙᴜɢ→ Skin Pool) logs every step of the pipeline with a[DEBUG/SKIN-POOL]prefix: pool loading (per file, with skin counts), the roll each spawn makes (main vs rare tier), cache hit/miss, PNG download attempts per mirror with byte counts, the pixel-analysis model verdict, the MineSkin request (variant, URL, rate-limit waits) and its HTTP response code, response-shape parsing, cache writes, prewarm activity, and the finalresolveEffectiveSkindecision per bot (existing skin kept / pool skin applied / fell back to vanilla and why). MineSkin HTTP errors now log the response body at WARN unconditionally, so a quota rejection is visible without any debug flag.
-
New per-bot
🎨 ꜱᴋɪɴcategory in a bot's settings GUI (between Pathfinding and Danger):-
Current Skin - shows the skin's source (main pool / ✨ rare tier with odds / custom /
restored) and the player model (slim/classic), detected live from the signed texture via
SkinModelDetector. Clicking echoes the summary to the action bar. - Re-roll Skin - clears the bot's resolved skin and rolls the pools again with identical odds to a fresh spawn (rare tiers included); the new skin applies and persists immediately.
-
Current Skin - shows the skin's source (main pool / ✨ rare tier with odds / custom /
restored) and the player model (slim/classic), detected live from the signed texture via
-
/fpp settings→ Debug category reorganized - entries were in accretion order (each new toggle appended wherever); now grouped logically: core switches (master, debug-chat, general, startup) → bot systems (pathfinding, skin pool, commands, head AI, swap, chat, right-click ×2) → NMS internals (master, bot, connection, damage, physics, skin) → storage & network (database ×5, packets, network, config-sync). -
/fpp listbot entries enriched - each bot's lore now shows its live activity (idle / moving / mining / …, sharing the nametag activity engine, updated by the list's existing 1 Hz refresh) and a ✨ ʀᴀʀᴇ 1/N skin line when the bot wears a rare pool skin (no line for main/custom skins, keeping common bots uncluttered). -
HelpGuireviewed - already fully on the sharedGuiKitutility (colors, fillers, click sounds, text wrapping); no changes needed.
Every player-facing string now follows one style: small-caps lettering, {prefix} branding, gray
body text, #0079FF accent for names/commands/values, white for emphasis, red for errors.
-
/fpp perfsection rewritten (~25 keys inlanguage/en.yml) - the entire performance command output was plain English ("Provider:", "Benchmark started for 5 minutes…") while the rest of the plugin used small-caps; now fully matched, including theperf topdashboard rows. - Version-unsupported messages converted to the same style (brand names, MC versions, and links stay readable plain text).
-
Nine hardcoded English messages moved into the lang system -
/fpp save(3),/fpp setowner(3),/fpp storagedeposit (2), and the bulk-task dispatch confirmation were built inline in Java with rawNamedTextColors and no prefix, so they were unthemeable and looked foreign next to everything else. All now use new lang keys (save-*,setowner-*,storage-none-enabled,storage-walking,task-dispatched) - server owners can retheme them like any other message. -
/fpp tpcleaned up - it had its own homemade[ꜰᴘᴘ]prefix and three inline English messages; converted to lang keys (tp-no-body,tp-success,tp-active-bots). -
inv-busywas the one lang key still in plain English - converted. -
Glyph consistency: 26+ occurrences of the wrong "g" codepoint (
ɡLATIN SMALL LETTER SCRIPT G instead ofɢLATIN LETTER SMALL CAPITAL G) fixed acrossen.yml,SettingGui, andBotSettingGui- they render visibly differently in-game. -
Typos fixed in GUI text:
ꜰᴏʟʟᴡ→follow,ꜰɪʜᴇᴅ→fixed,ɪɴᴠᴇɴᴛᴏʏ→inventory, a strayɘin "the", and syncronization→synchronization. -
Default nametag format (
config.ymlnametag.second-line.format) now small-caps (ʙᴏᴛ ʙʏ {owner}) to match the activity row above it - existing configs keep their current value.
Full scan for code with zero remaining callers, mostly orphans of the removed extension/skin/identity systems. Everything below was verified unreachable before deletion; the shaded jar shrinks accordingly.
-
SkinManagerrewritten from ~1,700 to ~180 lines - the entire player-name skin resolution system was unreachable: the 1,000-name fallback account pool, Mojang name→skin fetching,applySkinByPlayerName/Username/Url,applySkinFromPlayer/OfflinePlayer,preloadSkin, the profile cache, and the guaranteed-skin fallback chain. What remains is exactly the live surface: pool-basedresolveEffectiveSkin,applySkinFromProfile/applySkinFromTextures,resetToDefaultSkin, andreload. -
SkinRepositorydeleted (~420 lines) - only reachable from the deleted SkinManager paths. -
SkinFetchServiceinterface deleted (+ the plugin's field/getter/setter) - its only real implementation lived in the removed FPP-Skin extension; core only ever saw the NOOP. -
BotIdentityCachestripped to its static UUID helpers (~460 → ~120 lines) - the stateful name→UUID cache (YAML/DB loading, Mojang premium-UUID resolution, rate limiting, migrations) had zero callers since the identity-system removal; onlydeterministicBotUuid/isBotUuid/offlineModeUuidare live. The oldidentities.by-nameYAML data andbot_identitiesDB rows stay on disk untouched but are no longer read or written - one less startup I/O pass. -
Eight dead
Configskin accessors deleted (skinMode,skinGuaranteed,skinCustomPool,skinCustomByName,skinUseSkinFolder,skinClearCacheOnReload,skinMineSkinUrlUploadEnabled,skinMineSkinVisibility) - all read config keys that no shipped config contains, for systems that no longer exist. -
TpCommandunused color constants and stale@SuppressWarningsremoved.
-
MineSkin API key support - the one dead accessor worth keeping,
skinMineSkinApiKey, is now actually wired in: setskin.mineskin-api-keyinconfig.yml(get one at mineskin.org/apikey) and the skin-pool signer sends it as a Bearer token for a much larger quota than anonymous use. -
/fpp reloadnow reloads the skin system - previously neitherreload confignorreload allre-read the skin pool files or cache; editingskins/*.txtrequired a full restart. -
Fixed: skin resolution failed entirely when NameMC's CDN blocks the server - NameMC sits
behind Cloudflare bot protection, which 403s plain Java downloads from many hosting/datacenter
IPs even when the same URL works from a browser. The local PNG download only feeds slim/classic
pre-detection, and MineSkin fetches the texture URL itself from its own (unblocked) servers - so
a blocked local download is no longer fatal: the signer proceeds with
variant=auto(MineSkin runs the same pixel detection server-side) and the authoritative model is still read from the signed texture afterward. The local download also now sends browser-like headers so it succeeds on more networks in the first place.
-
The bulk
[amount]argument is gone -/fpp spawn 50no longer exists; every invocation spawns exactly one bot. Thefpp.spawn.multiple/fpp.spawn.masspermissions and thelimits.spawn-presetsconfig accessor died with it and were removed. -
The bot-type tag is gone - the leading
afktoken is no longer accepted; bots always spawn as the default type internally. -
New
--name <name>flag -/fpp spawn --name Minerspawns one custom-named bot. Names are validated (1-16 chars, letters/numbers/underscores, badword filter), rejected when a bot already uses them (spawn-name-taken), invalid (spawn-invalid-name), or when a real player with that name is online (spawn-name-online) - three new lang keys. Works for both admin (fpp.spawn) and user (fpp.spawn.user) tiers; the mandatory "ʙᴏᴛ ʙʏ {owner}" nametag row still marks every bot as a bot regardless of name. -
World/coordinate arguments unchanged (
/fpp spawn world_nether 100 64 -200,~ ~ ~relatives) - butfpp.spawn.coordsis now actually enforced: the permission was declared in plugin.yml and never checked in code, so user-tier spawners could place bots at arbitrary coordinates despite the docs saying they can't. - Tab completion reworked: suggests
--name, then worlds/coords; no more count presets. -
Commands.mdwiki page updated to match.
The entire license verification system is gone: the license package (LicenseManager,
LicenseCredentialsApi), the startup credential fetch + verify + license heartbeat, the
"verification failed → disable plugin" kill switch, the shutdown hook, and the debugLicense
config/debug plumbing. The plugin now enables unconditionally with no license checks and no
license-related network calls. (The separate opt-in stats heartbeat, heartbeat.enabled in
config.yml, is unrelated and remains.) Old logging.debug.license config-migration steps are kept
so historical config upgrades still replay cleanly; the key is simply unused.
The GUIs were internally consistent but isolated - moving between them meant closing and retyping
commands. /fpp list is now the hub:
-
/fpp listgained two control-row buttons (permission-gated, hidden without access): + ꜱᴘᴀᴡɴ ʙᴏᴛ (slot 47) spawns one auto-named bot at your location without leaving the GUI - the list's live refresh picks the new bot up within a second; ⚙ ɢʟᴏʙᴀʟ ꜱᴇᴛᴛɪɴɢꜱ (slot 51) jumps straight to the plugin-wide settings GUI. - Shift-click on ✕ ᴄʟᴏꜱᴇ returns to the bot list in both the per-bot settings GUI and the global settings GUI (plain click still closes; global settings still save either way since switching inventories fires the same close-and-save path). Both close buttons' lore now spells out the two actions.
- All shortcuts route through
player.performCommand(...), so every permission check still applies.
The per-bot "🗡 ᴘᴠᴇ" settings (smart attack, mob selector, range, priority) stored values and fired
a pve_restart event that only the removed extension system used to consume - nothing in core
ever scanned for mobs or attacked. New core PveController:
- Supervisor + per-bot combat tasks - a 1 s supervisor attaches a per-entity, every-tick combat task to each bot whose smart-attack mode is on (Folia-safe: combat runs at the bot's region), and detaches it on disable/despawn/shutdown. GUI toggles apply instantly via a direct refresh hook; bots restored from persistence with PVE enabled resume automatically within a second.
-
Targeting - honors every existing setting: the mob-type selector (empty selection = all
hostiles via Bukkit's
Enemyinterface, so Slimes/Phantoms/Ghasts count), detect range, and target priority (nearest / lowest-health). Sticky targeting with a grace margin prevents target flapping at the range boundary. Never targets players or other bots. -
Combat - faces the target (body + head), swings, and attacks through the same
NmsPlayerSpawner.performAttackpath as/fpp attack(real crits/knockback), paced by the same per-weapon cooldown table (now shared fromAttackCommand). The cancellableFppBotAttackEventstill fires per swing. -
Pathfinding link -
ON_MOVEmode chases the target through the shared pathfinding engine (Owner.ATTACK): live destination supplier re-paths as the mob moves, navigation cancels on arrival-in-reach or target loss, and PVE never hijacks a navigation owned by another system (mining/moving/deposit) - it keeps attacking whatever wanders into reach instead. The nametag activity line already labels this state "ᴄʜᴀꜱɪɴɢ ᴛᴀʀɢᴇᴛ", andpathfindingdebug logsevent=PVE_STARTlines. - Wired into the shared despawn cleanup (
unregisterBotState) like Move/Click/Find, so a bot despawned mid-fight leaves no orphaned task or navigation.
The live activity row above bots said "ɪᴅʟᴇ" during several real actions because those systems were
never wired into BotActivity:
-
PVE combat now reports - new
PveController.isEngaged()feeds a "ꜰɪɢʜᴛɪɴɢ" label whenever the bot has an acquired target. In-range combat previously showed idle (only the chase phase surfaced, via the navigation owner); a hunting bot now shows "ᴄʜᴀꜱɪɴɢ ᴛᴀʀɢᴇᴛ · ꜰɪɢʜᴛɪɴɢ" while closing in and "ꜰɪɢʜᴛɪɴɢ" while swinging. -
Sneaking now reports -
/fpp sneakpreviously had no activity representation at all. -
Refresh loop hardened + faster - each bot's refresh is individually guarded so one failure
can't kill the repeating task (a dead loop silently froze every nametag on its last label
forever - the likely cause of persistent "ɪᴅʟᴇ"), and the cadence doubled from 1 s to 0.5 s so
labels react noticeably quicker. Failures log under the
generaldebug topic. -
/fpp list's activity lore shares the same engine, so it picks all of this up automatically.
First tile of the 🗡 ᴘᴠᴇ category now shows the engine's live state - ✘ ᴏꜰꜰ (gray dye) /
◌ ꜱᴄᴀɴɴɪɴɢ ꜰᴏʀ ᴛᴀʀɢᴇᴛꜱ (spyglass) / ⚔ ꜰɪɢʜᴛɪɴɢ (diamond sword) - so it's verifiable at a
glance that the PVE controller picked the bot up after toggling smart attack. Click to re-read.
The dead command-extension "ᴀᴅᴅᴏɴꜱ" lore section and its plumbing (HelpEntry.modifiers,
the per-command extension query) were stripped - extensions can't load, so the block could never
render.
-
The "ᴀᴅᴅᴏɴꜱ" category tab is gone - extensions can no longer load, so the tab could never
contain anything; it also carried a dead legacy-name mapping (chat, personality, rank, swap,
waypoints, …) for commands that no longer exist. The addon-command feed into the help list and
the
addonflag on help entries were removed with it. -
Category mapping refreshed for the real command set -
sneaknow files under ᴀᴄᴛɪᴏɴꜱ,check/perf/deleteunder ᴄᴏʀᴇ; removed mappings for long-deleted commands (follow,sleep,mine,place,use). The ᴀᴄᴛɪᴏɴꜱ tab tooltip now describes what's actually there (nav, mining, find, combat, storage). - Four category tabs (ᴀʟʟ · ᴄᴏʀᴇ · ʙᴏᴛꜱ · ᴀᴄᴛɪᴏɴꜱ) now sit in the bottom bar with the freed slot falling back to filler glass.
Wiki brought in line with the current plugin:
-
Deleted
frontend/wiki/fpp-spoof/(19 pages) - documented the removed spoof extension and its sub-extensions (chat, skins, LuckPerms, personality, swap, peaks, ping, AI chat). -
Extensions.mdrewritten from an 890-line developer guide for the removed loader into a short page explaining what was removed and where each former extension feature lives now (pathfinding/PVE/skins/tool-equip → core; chat/LuckPerms/spoofing → gone). -
Home.mdrewritten - v2.0.0 highlights now describe the actual feature set (pathfinding engine, PVE, find automation, skin pools, fb07 UUIDs, live nametags, GUI suite, player invisibility, perf tooling); marketplace/license/extension marketing removed. -
Permissions.mdrewritten against the realPermnode set - removedfpp.spawn.multiple/mass, addedfpp.find/fpp.storage/fpp.perf/fpp.move.to/fpp.move.coords, corrected the spawn/movement descriptions. -
FAQ.mdrewritten - dropped all license/extension answers; added current answers for PVE, pathfinding, skins, UUIDs, activity nametags, and/fpp find. -
Configuration.md- oldskin.mode/pool/overrides/mineskin.*section replaced with the realskin.rare-pools/skin.mineskin-api-keykeys + pool-folder docs; the "Extension-Only Settings" section (13 nonexistent extensions) deleted. -
Getting-Started.md- data-folder listing corrected (skins/added,extensions/removed), first-steps move example updated to--to/--coords. -
Commands.md/Database.md/Config-Sync.md/Placeholders.md- stale extension references removed;fpp_skin_cachedocumented as dormant; placeholder docs match the code.
Dead code removed alongside:
-
DatabaseManagerskin-cache accessors (getCachedSkin,cacheSkin,cleanExpiredSkinCache,getSkinCacheSize,SkinCacheEntry) and the startup cache-cleanup call - unreachable since the SkinManager rewrite; the table itself stays (schema/migration chain untouched, now dormant). - Placeholders for removed systems:
%fpp_extensions%,%fpp_extensions_names%(always 0/empty),%fpp_chat_<bot>%(read the deleted chat system).
New BotAdvancementBlocker listener cancels Paper's PlayerAdvancementCriterionGrantEvent
(at LOWEST priority) for every bot, blocking advancement progress at the source - no completion,
no toast, no chat announcement, and nothing accumulating in the bot's
world/advancements/<uuid>.json. Real players are unaffected.
-
Rotation broadcast cache -
lastSentVisualRotationmap caches last broadcast yaw/pitch per bot; rotation packets dropped when delta < 0.5°, eliminating redundant head-rotation broadcasts under heavy load. -
Direct NMS fast-paths -
sendRotationDirect()andsendPositionSync()callPacketSendListenerdirectly on the NMSServerGamePacketListenerImplinstead of Bukkit'ssendPacket(Player)wrapper. -
Tab-list batching -
broadcastTabListRemove()sends a singleClientboundPlayerInfoRemovePacketper bot instead of N individual packet sends. Applied to all despawn paths. - Frozen bot early return - Skips all per-bot work (AI, physics, handlers, fall damage, position sync) for frozen bots at the top of the tick lambda.
-
Location reuse -
beforeLocation captured once per tick and reused across head-AI target distance, mining-lock check, gaze vector, fall-damage delta, and position-sync threshold - eliminates redundantbot.getLocation()calls. -
Throttled subsystems - Auto-eat runs every ~4 ticks per bot. Fall damage runs every other tick (accumulated fall distance is still tracked via NMS
getFallDistance()). -
Active-bot UUID snapshot -
activeBotUuidsset built once per tick for O(1)contains()in head-AI filtering and tab-list remove. -
Mining-lock optimization - Reuses
beforeLocation fordistanceSquaredcheck instead of callingbot.getLocation()again.
-
Position sync dependency on Head AI -
onlineSnapshotand player-position arrays now always populated regardless ofdoHeadAi. Previously, when Head AI was disabled, position sync packets were never sent - bots appeared frozen on other clients. -
Swim AI jumping-field reset - Removed incorrect
&& (isNavigating || isInWaterOrBubbleColumn(bot))guard that skippedtickSwimAi(). Thejumpingfield stuck attrueafter a bot exited water becausesetJumping(bot, false)was never called. -
Ground detection for partial blocks -
isBotOnGround()restored toloc.clone().subtract(0, 0.08, 0).getBlock().isPassable(). ThegetBlockAt(getBlockX(), getBlockY()-1, ...)replacement misdetected slabs and stairs. -
isInBubbleColumn()deprecation - Replaced deprecatedPlayer.isInBubbleColumn()with block-type check.-Xlint:deprecationadded tocompileJava.
-
/fpp perfcommand -check/top/report/history/sparksubcommands. Background monitor samples TPS, MSPT, CPU, GC, memory everysample-interval-ticks, keeps rollinghistory-minutes, warns on consecutive threshold breaches. -
Built-in self-profiler -
BuiltinFppProfilerwith lock-freeLongAddersampling, thread-local call stack, adaptive detail reduction. Enabled viaperformance.self-profiler.enabled. -
Benchmark sessions -
/fpp perf reportstarts a 10-minute method-level benchmark, reminds every 2 minutes, exports Spark-style call tree toplugins/FakePlayerPlugin/performance-report/. -
Perf providers -
SparkPerfProvider(preferred, reads Spark-API snapshots) andBuiltinPerfProvider(fallback via CraftServer + JMX). -
Auto-export -
PerformanceReportExporterwrites.txtreports on: benchmark finish, threshold warning (export-on-warning: true), plugin disable, and fatal exceptions. -
Perf placeholders -
%fpp_perf_tps%,%fpp_perf_mspt%,%fpp_perf_cpu_process%,%fpp_perf_cpu_system%,%fpp_perf_gc_avg_time%,%fpp_perf_gc_avg_frequency%,%fpp_perf_health%. -
Profiling instrumentation - Hot paths in
FakePlayerManager.tick(),NmsPlayerSpawner.tickPhysics(),PacketHelper,tickSwimAi(),tickAutoEat(),fireTickHandlers(),tickFallDamage()profiled when self-profiler is active. -
Language keys - Full
perf-*set inen.ymlfor all/fpp perfoutput.
-
New
performance:block -enabled,spark-enabled,placeholders,sample-interval-ticks,history-minutes,warn-mspt,warn-tps,warn-consecutive-samples,warn-cooldown-minutes,auto-profiler-timeout-seconds,self-profiler.enabled,self-profiler.method-level,self-profiler.export-on-warning. -
Swap player-aware settings - New
swap.player-aware.*keys for nearby-player detection radius, idle threshold, idle bonus percent, active penalty percent. -
ConfigMigrator- Updated for config-version 76 to handle new keys. -
FppMetricsintegration - FastStats metrics startup with graceful fallback when FastStats is unavailable.
-
FppApi- AddedgetOnlineCount(),removePlayerBody(UUID)(shutdown-safe),disableAllAddons(). -
FppScheduler-runAtEntityRepeatingWithId()returnsint taskIdfor per-entity repeating tasks. -
CommandManager-/fpp perfregistered withfpp.perfpermission (child offpp.opinplugin.yml). -
NmsPlayerSpawner-tickPhysics()reverted to originaldoTickMethod.invoke()via reflection; guard restored to|| doTickMethod == null. -
FakePlayerEntityListener- Removed exact damage-canceller detector/tracer in favor of simplerbody.damageableswitch. -
FakeChannelPipeline- Handler insertion refactored for channel-active vs connection-set path. -
BotPersistence-saveForShutdown()saves snapshot without clearingactive-botsto prevent destructive overwrite on restart. -
Shutdown lifecycle - Profiler stopped before monitor; extension class loaders closed;
disableAllAddons()called;saveForShutdown()runs before body removal.
-
nLogin compatibility -
NmsPlayerSpawnernow suppresses nLogin (com.nickuc.*)PlayerJoinEventlisteners for fake players alongside the existing SimpleVoiceChat suppression. Auth plugins that expect normal client login pipelines no longer kick/despawn FPP bots during spawn.
-
Synthetic quit kick handling -
FakePlayerKickListenernow marks kicked bots as synthetic quits viaFakePlayerManager.addSyntheticQuit(UUID)before despawning. This ensures the manager treats server kicks as synthetic quits and despawns the bot with consistent quit-event semantics instead of treating it as a raw deletion. -
addSyntheticQuit(UUID)helper - added null-checkedaddSyntheticQuit(UUID)toFakePlayerManagerso callers can safely record synthetic quit UUIDs without duplicating null-guard logic.
-
Version bump - plugin metadata and Gradle version are now
1.6.6.12.5. - Core scope reduction - advanced pathfinding movement, follow, sleep, and rich combat behavior moved out of core and into extension-owned systems.
-
Directional move only - core
/fpp movenow accepts--direction forward|backward|left|rightwith optional--seconds/--ticksand--stop. -
Basic attack only - core
/fpp attackreduced to--once/--stop. Rich hunting/mob targeting is extension-owned. -
Sneak command - added core
/fpp sneak <bot> [on|off|toggle]withfpp.sneakpermission. -
Click API - added public
FppClickModeandFppApi.leftClick/rightClickoverloads so extensions can trigger core click actions without dispatching commands. -
Tab completion hardening -
CommandManagernow guards tab completion against exceptions fromcanUse()andtabComplete()in both core and addon commands. -
Spawn location correctness - normal
/fpp spawnreasserts requested spawn location after join/spawn redirects; early join handling consumes pending locations at LOWEST priority. -
Shutdown persistence - non-destructive shutdown save: empty snapshots do not clear
persistence.active-bots; addon shutdown runs before final bot persistence save. -
Inventory persistence fix - empty inventories saved with
__emptymarker; restore clears slots before applying saved data so old items do not survive. - Damage/knockback - preserved Paper/Bukkit damage event semantics; explicit FPP knockback restored for allowed damage; suppressed for cancelled events; cross-world teleports reset transient damage state.
- Removed broad core protection gates - WorldGuard helper removed from core; external protection plugins own cancellation decisions.
-
Removed core commands -
FollowCommandandSleepCommandremoved from core; follow/sleep are extension-owned. -
Build output -
shadowJarcopies the shaded runnable jar to workspace root asfake-player-plugin-1.6.6.12.5.jar; plainjarrefreshesbuild/fpp.jarwithout overwriting the deployable root jar. -
FastStats packaging -
shadowJarverifies FastStats classes are present; metrics initialization is fail-safe so a thin jar cannot break startup. -
Config -
config-versionis74; reorganized and heavily documented; debug settings moved todebug.yml.
-
Gradle extension build -
fpp-extensionsis a Gradle multi-project that builds individual module jars into workspacebuilds/. -
Bundle rename - aggregate first-party bundle is now
fpp-spoof.jarinstead offpp-extensions-bundle.jar. -
Current modules -
fpp-aichat,fpp-chat,fpp-command,fpp-groups,fpp-list,fpp-luckperms,fpp-nametag,fpp-pathfinder,fpp-peaks,fpp-ping,fpp-skin,fpp-swap, andfpp-waypoints.
- Fix bot despawn after spawn bug - bots no longer instantly despawn due to stale spawn-protection checks or missing WorldGuard session state after teleport/respawn
-
PacketEvents fail injection - suppressed kicks caused by
"packetevents"+"inject"errors that triggered an infinite despawn loop on every bot join -
LuckPerms patch - pre-caches LuckPerms user data before
placeNewPlayer()to preventServerThreadLookupExceptionon Folia and ensure Vault/WG hooks resolve correctly at spawn time
-
Debug Settings GUI -
/fpp settingsnow has a 🐛 ᴅᴇʙᴜɢ category with 23 clickable toggles for everydebug.ymlcategory (master, general, startup, NMS, database, packets, network, config-sync, chat, swap, commands, head-ai, right-click, etc.) -
Debug Chat Broadcast - new
debug-chat: falsekey indebug.yml. When enabled, allFppLogger.debug()output is sent to online players withfpp.oporfpp.notifyas in-game chat messages (gray prefix:[ꜰᴘᴘ DEBUG/<topic>] <message>) -
Runtime debug toggling - debug categories can be flipped on/off without restarting via the GUI; changes are saved to
debug.ymlimmediately
- Auto-target hostile mobs - bots now automatically detect and attack hostile mobs (Monsters, Slimes, Ghasts, Phantoms, Hoglins, Shulkers, EnderDragon) in their forward cone when no block is targeted
- Auto-aiming - bot head and body smoothly rotate to face the targeted mob
-
Multi-flag parsing - fixed
--once,--repeat,--hold, and--stopflag handling so multiple flags can be specified correctly in a single command
-
LuckPerms cache warmup -
NmsPlayerSpawnerpre-loads LuckPerms user data beforeplaceNewPlayer()to preventServerThreadLookupExceptionon Folia -
WorldGuard session refresh - complete rewrite using cold re-initialization via reflection (
tryRemoveSession+Session.initialize()) to prevent stale region data after bot teleports/world changes -
Teleport/respawn WG refresh -
FakePlayerEntityListeneraddsPlayerTeleportEvent.MONITORandPlayerRespawnEventhandlers with delayed (1-2 tick) WG session refresh -
Spawn protection teleport fix -
BotSpawnProtectionListenernow allowsPLUGINandCOMMANDteleports during the grace window so/fpp tphand cross-world moves work correctly; portals are still blocked -
Despawn reason tracking - all
removeBot()calls now pass descriptive reasons (spawn_body_failed,command_despawn,gui_delete,badword_cleanup,packetevents_kick,kicked_by_server,api_despawn,rename_swap,body_remove, etc.) instead of"unspecified" -
PacketEvents kick suppression -
FakePlayerKickListenersilently cancels kicks containing"packetevents"+"inject"instead of despawning the bot, preventing instant-despawn loops -
Attribution/logging cleanup - silenced license heartbeat, JSON response, and integrity check logs unless explicitly enabled via
debug.yml -
Placeholder formatting - cleaned up
formatUptimeone-liner inFppPlaceholderExpansion - Help GUI formatting - fixed indentation in lore builder
-
Folia config issue patched - formatting normalization across
build.gradle.kts,Config.java, andplugin.ymlto resolve Folia-related configuration loading problems
- Silent License Verification - No more startup spam (Team ID, challenge, JSON response removed)
-
Debug Logging Fixed - All NMS-BOT messages now respect
debug.yml(17 calls fixed) - Cleaner Startup Logs - Removed backups count, name pool size, debug section from banner
- Minimal Shutdown Log - Reduced from 7 lines to 4 lines
-
Left-Click Command - Replaced MineCommand (
/fpp left-click) -
Right-Click Command - Replaced UseCommand (
/fpp right-click) - Legacy Removed - 2162 lines of mine/use/place code deleted
- Net Reduction - ~500 lines of code removed overall
- debug.yml - All debug settings moved to separate file
-
Config v75 - Auto-migrates and removes
logging.debug.*keys from config.yml - License Category Removed - No longer needed (silent verification)
- Folia Support - Full compatibility with region-threaded spawning
-
Permission Checks - Bot ownership validation for
/fpp attack --all,/fpp follow --all -
New Flags -
/fpp despawn --own,/fpp delete --own - PlaceholderAPI - Updated to 2.12.2
- Updated: Changelog, Configuration, FAQ, Getting-Started, Home
- AGENTS.md added for development reference
-
License server migration - Switched license verification from
license.fpp.wtftoapp.lukittu.com -
Frontend credential fetch - Credentials now fetched from
fpp.wtf/api/license/freewith HMAC signature verification - Improved license logging - Better error messages and debug logging for license verification failures
- API key authentication - Added Bearer token authentication for frontend API requests
- License credentials fetch - Fixed API key encoding for frontend authentication
- Folia support restored - FPP now fully supports Folia with region-threaded bot spawning
-
Body disable system removed -
body.enabledconfig option removed. Bots always spawn with physical bodies (tab-list only mode no longer available). - SpigotMC distribution removed - Plugin no longer distributed on SpigotMC. Download from Modrinth, PaperMC Hangar, or BuiltByBit.
-
%fpp_body%placeholder - Removed along with body disable system. - Body toggle in GUI - Removed from Settings GUI (body category).
- Skin system toggle - Removed from Settings GUI.
-
Pathfinding overhaul - Major improvements to
BotPathfinder.javaandPathfindingService.javawith better A* navigation, gap walking, block break/place support, and stuck detection. -
Mine command improvements - Added actual block breaking via
nms.gameMode.destroyBlock(), improved progress tracking, and pickup flow. -
Use command enhancements - Combined Use+Place functionality with
UseModeenum, flexible targeting from bot look direction, and better ray-tracing. -
Head AI action locking - Added
actingBotsconcurrent set to fully disable head AI while bots perform actions (mining, using, placing).
- PacketEvents injection error - Added try-catch wrapper around PacketEvents registration to prevent GrimAC/ViaVersion compatibility issues from breaking bot spawns.
- UseCommand NPE - Fixed null pointer when storing ray-trace targets; only stores non-null targets.
- Head AI during actions - Bots now properly disable head rotation while performing mine/use/place actions.
- Mining not breaking blocks - MineCommand now actually breaks blocks via NMS game mode.
- Removed
spawnBody()config method and all references to body disable logic - Cleaned up
FakePlayerManager.javaspawn logic (no more bodyless mode) - Updated startup banner, metrics, and placeholders to remove body enable references
- Removed unused custom metrics from
FppMetrics.java - Removed outdated
AGENTS.mdfile - Added
note.mddevelopment tracking document
- Updated all wiki pages to reflect Paper/Purpur/Folia support
- Updated FAQ to explicitly state Folia is supported
- Updated legal documents (copyright, privacy-policy, extensions, terms-of-service)
- Updated README.md with platform changes
-
Online player count - bots now correctly subtracted from real-player count in
/fpp statsand network totals (commit6afca8a) -
Database flush - runs outside the main thread to prevent server lag spikes (
f671781) -
Batching logic - added proper batching for DB writes and network heartbeats (
528cf0e) - Removed dead writer/health-check logic that caused unnecessary DB overhead (
fcbe072) - Removed pointless bot record update before clearing the list on shutdown (
8c1eb56)
- Removed unnecessarily fully qualified class names across codebase (
001416d) - General cleanup of dead code, unused fields, and redundant calls (
14d1803)
- Updated command reference with
extension --list,spawn --notp, andattack --onceflags - Synced config docs with
pathfinding.*,skin.*,help.*,ping.*,metrics.debug, andheartbeat.enabled
- Hardcoded original author updated from
el_pepestoF_PPacross codebase
- ErrorTracker - context-aware error tracking via FastStats API
-
Debug toggle -
metrics.debugoption inconfig.yml(defaultfalse) - onFlush callback - logs at debug level when metrics are flushed to FastStats
-
New metrics added:
active_features(string array), feature flags, installed plugins (LuckPerms, PlaceholderAPI, WorldGuard, WorldEdit, NameTag), server info, PvE settings, automation toggles -
trackError() helpers - two public overloads (
ThrowableandString) for external error reporting - Added
getFppMetrics()public getter onFakePlayerPlugin.java
-
FakeChannelPipeline deprecation warning - added
@SuppressWarnings("deprecation")to suppress unavoidable NettyChannelPipelineAPI deprecation warnings forEventExecutorGroupoverloads -
PluginRemapper duplicate entries -
pom.xmlnow properly excludes Mojang-mappedpaper-serverNMS classes from shaded JAR, fixing Paper 1.21.11 runtime remapping crash -
SQLite AUTO_INCREMENT syntax - split
fpp_network_taskstable creation into SQLite (INTEGER PRIMARY KEY AUTOINCREMENT) and MySQL (BIGINT AUTO_INCREMENT) variants, fixingSQLITE_ERROR near "AUTO_INCREMENT": syntax error
- Full wiki sync: added missing
pathfinding.*,skin.*,help.*,ping.*,metrics.debug,heartbeat.enabled, andbody.drop-items-on-despawnconfig keys - Added missing commands (
extension,extension --list) and flags (spawn --notp,spawn <bottype>,attack --once,find --prefer-visible, short flags-r/-c) - Added missing permissions (
fpp.mine.wesel,fpp.place.wesel) - Added extension-dependency notes for placeholders (
peak_hours,swap, etc.) and config keys (fake-chat,swap,peak-hours)
- None
Requires MySQL for cross-server features.
Proxy-merged database - all backends share live bot registry and player counts via MySQL.
- Schema v25:
fpp_network_bots,fpp_server_heartbeat,fpp_network_tasks - NetworkHeartbeatManager - publishes local bots / reads remote bots every 5s, stale pruning every 60s
- Proxy companions (Velocity + Bungee) push
NETWORK_STATSto all backends independently of players -
RemoteBotCachenow survives restarts via DB (no longer messaging-only)
New cross-server placeholders: %fpp_network_total%, %fpp_network_real%, %fpp_network_bots%
Also added: server performance, extensions, 30+ config toggles, player-relative per-world, per-bot dynamic lookups.
-
/fpp extensionbare command → marketplace link -
/fpp extension --list→ loaded extensions detail table - Extension data folders fixed (
getName()instead of JAR filename)
-
getServers()→getServersCopy(),FixedMetadataValue→PersistentDataContainer, unchecked warnings cleaned - Startup banner shows extension count
- Authors updated to
F_PP
Added frontend/legal/ pages (copyright, extension policy, privacy, ToS)
- Fall damage tracking + config
- Skin injector fixes
- Config migrator v71→v72
- Extension bundles, API additions
- Wiki marketplace links
- Spoofing/chat-related features moved out of core into first-party extensions (chat, AI, swap, peak-hours, ping, groups, stored cmds)
- PvE Smart Attack Mode (OFF / ON_NO_MOVE / ON_MOVE)
-
/fpp save,/fpp setowner - Per-bot overrides: respawn-on-death, auto-eat, auto-place-bed
- BotSettingGui PvE + Pathfinding tabs, share control
- DB schema v22: PvE, automation, ping, LuckPerms
- Folia scheduling guards
- Water-path stability fixes
- Spawn grace-period protection
- BungeeCord companion plugin support
-
AttributeCompatfix
/fpp follow- Skin persistence
- Server-list config additions
- DB schema v17
/fpp ping/fpp attack- Permission restructure
- Skin mode rename
-
FlagParserutility
https://github.com/Pepe-tf/Fake-Player-plugin-2.0/commits/master
Note: The built-in ConfigMigrator handles upgrades transparently. Current default config version: 74. Always back up
plugins/FakePlayerPlugin/before major updates.
If you are upgrading from an older version, debug.yml will be recreated from the template. The new debug-chat: false key controls whether debug output is broadcast to OP/notify players in-game. You can also toggle it via /fpp settings → 🐛 ᴅᴇʙᴜɢ.
Prior to v1.6.6.12.4, debug.yml could only be edited by hand. The Settings GUI now lists every debug category as a clickable toggle. Changes are saved to disk immediately.
FPP now supports both Paper/Purpur and Folia. If you were running FPP on Folia:
- FPP will work out of the box on both platforms
- Bot spawning automatically detects Folia and uses region scheduler
- No migration needed - FPP handles both seamlessly
If you were using body.enabled: false for tab-list only mode:
- This option has been removed
- All bots now spawn with physical bodies
- Consider using
body.damageable: falseandbody.pushable: falsefor invulnerable/immobile bots