Skip to content

Developers

Flick Server API

Recognise Flick players on your Minecraft server and work with their client: waypoints, notifications, Discord status and module policies.

Summary

The Flick Server API lets Minecraft servers (the FlickServerAPI Paper plugin, server-api/) recognise players who use Flick Client and cooperate with it: add waypoints, switch off selected Flick modules (they are greyed out in the menu with "Disabled by the server" and cannot be re-enabled until the player leaves), send notifications, set the Discord Rich Presence text and ask for the client's mod list. Communication runs over the flick:api plugin message channel (JSON). The client only introduces itself (hello) to servers that registered that channel and acts on server commands only after the server's hello reply. Everything the server sets lives in memory only and disappears when the player leaves or switches server. A server cannot run code, open URLs, read files or touch the account or cosmetics. Players can turn the whole API off ("Allow the Flick server API" in the client settings) or single categories. The mod list and module state are reported by the client and can be forged, so this is not an anti-cheat. No-code setup: plugins/FlickServerAPI/config.yml (globally or per-world disabled modules, welcome message, kick-if-mods). Commands: /flick list, /flick info <player>, /flick reload.


#Overview

Capability Server → client Player can refuse
Detect Flick players, client & MC version, enabled modules hello, state (client → server) only by turning the whole API off
Waypoints (beam, icon, colour, auto-expiry, remove-on-arrival) waypoint_add / waypoint_remove / waypoint_clear yes (Server waypoints)
Force Flick modules off while on the server modules / modules_reset only by turning the whole API off
Glass notifications (toasts) notify yes (Server notifications)
Discord Rich Presence text rpc / rpc_reset yes (Server Discord status), and only shown when the player shows the server in their status
Loaded mod list (client-reported) mods_request → mods yes (Share mod list) — the client then answers denied:true
Clear everything the server set reset —

Client side: mod/src/main/java/net/flick/client/api/ (Flick Client, all MC versions 1.21.4 → 26.2). Server side: server-api/ — Paper plugin FlickServerAPI (Paper 1.21.x, Java 21).

#Installing the plugin

  1. Build: cd server-api && ./gradlew build → server-api/build/libs/FlickServerAPI-1.0.0.jar.
  2. Copy the jar into plugins/, start the server once, edit plugins/FlickServerAPI/config.yml, /flick reload.

Works behind Velocity/BungeeCord: install the plugin on each backend server; plugin messages on flick:api pass through the proxy. Every backend switch is a new session (the client sends a new hello; state from the previous backend is discarded by the client).

#Using it from another plugin

plugin.yml of your plugin:

depend: [FlickServerAPI]      # or softdepend, and check Bukkit.getPluginManager().isPluginEnabled("FlickServerAPI")

Gradle (until the API is published to a Maven repository, compile against the jar):

dependencies {
    compileOnly files("libs/FlickServerAPI-1.0.0.jar")
}

#Java API

Package net.flick.serverapi. Obtain the instance with FlickAPI.get() (also registered in Bukkit's ServicesManager as FlickAPI). Call it on the main thread (calls from other threads are re-scheduled onto it). Every method that targets a player returns false (or a failed future) when the player is not a Flick player, so you can call them for everyone.

import net.flick.serverapi.*;
import net.flick.serverapi.event.*;

public final class Arena implements Listener {
    private final FlickAPI flick = FlickAPI.get();

    @EventHandler
    public void onFlickJoin(FlickPlayerJoinEvent e) {
        FlickPlayer c = e.getClient();
        getLogger().info(e.getPlayer().getName() + " uses Flick " + c.getVersion() + " on MC " + c.getMinecraftVersion()
                + ", modules: " + c.getEnabledModules());
    }

    public void startRankedMatch(Player p, Location redFlag) {
        // greyed out in the mod menu ("Disabled by the server"), cannot be enabled until the player leaves
        flick.disableModules(p, FlickModules.COMPETITIVE, "Ranked match");

        flick.addWaypoint(p, FlickWaypoint.builder("ctf_red_flag")
                .name("Red flag")
                .location(redFlag)                 // sets the dimension from the world
                .color(0xFF5555)
                .icon(WaypointIcon.FLAG)
                .build());

        flick.addWaypoint(p, FlickWaypoint.builder("spawn_hint")
                .name("Go here")
                .position(10.5, 64, -20.5)
                .world(p.getWorld())
                .temporary(true)                   // disappears when the player gets within 4 blocks
                .duration(Duration.ofMinutes(2))   // or after 2 minutes
                .build());

        flick.sendNotification(p, "Match found", "Good luck!", NotificationKind.SUCCESS, Duration.ofSeconds(6));
        flick.setRichPresence(p, "Ranked CTF", "Red team");
    }

    public void endMatch(Player p) {
        flick.reset(p);          // waypoints, API-disabled modules and presence; config policy is re-applied
    }

    public void checkMods(Player p) {
        flick.requestMods(p).thenAccept(ids -> {   // completes on the main thread
            if (ids.contains("freecam")) p.kick(Component.text("Freecam is not allowed"));
        }).exceptionally(err -> null);            // TimeoutException after 10 s, IllegalStateException if not Flick
    }
}

#Reference

Method Notes
boolean isFlick(Player) handshake completed
Optional<FlickPlayer> getClient(Player), Collection<FlickPlayer> getClients()
boolean addWaypoint(Player, FlickWaypoint) same id replaces; max 200 per player (returns false beyond)
boolean removeWaypoint(Player, String id), boolean clearWaypoints(Player)
boolean disableModules(Player, Collection<String> ids, String reason) adds to the API-disabled set; ids from FlickModules
boolean enableModules(Player, Collection<String> ids), boolean resetModules(Player) config-disabled modules stay disabled
boolean sendNotification(Player, title, body[, NotificationKind, Duration]) title ≤ 64, body ≤ 160 chars, 1–15 s
boolean setRichPresence(Player, details, state), boolean clearRichPresence(Player) ≤ 64 chars each
CompletableFuture<List<String>> requestMods(Player) client-reported, spoofable; empty list if the player refused
boolean reset(Player) clears everything this server set (config module policy is re-sent)

FlickPlayer: getVersion(), getMinecraftVersion(), getProtocolVersion(), getEnabledModules() (live, via state), getCapabilities(), allows(FlickFeature), getWaypointIds(), getDisabledModules(), getLastReportedMods(), getHandshakeTime().

Events (main thread): FlickPlayerJoinEvent (after the handshake; config modules and welcome already sent), FlickPlayerQuitEvent.

Validation: FlickWaypoint.builder throws IllegalArgumentException for an invalid id ([A-Za-z0-9_.:-]{1,64}), non-finite / out-of-world coordinates (|x|,|z| ≤ 30 000 000, −2048 ≤ y ≤ 4096), invalid dimension keys or a duration over 24 h; module ids must match [a-z0-9_]{1,32}. Strings are stripped of § formatting codes and control characters and cut to their limits.

#config.yml (no-code setup)

server-name: "My Server"            # shown under notifications (≤ 32 chars)

disabled-modules:
  global: [freelook]                 # forced off for every Flick player
  worlds:                           # additionally, per world folder name
    pvp_arena: [fullbright, hitboxes, zoom]
  reason: "Disabled on this server" # shown in the mod menu / toast

welcome:
  enabled: true
  title: "Welcome, {player}!"
  body: "This server supports Flick Client."
  kind: info                        # info | success | warning | error
  duration-ms: 6000

kick-if-mods:                       # asks for the mod list right after the handshake
  enabled: false
  mods: [freecam, xaerominimap]     # Fabric mod ids (lower case)
  message: "Disallowed client mods: {mods}"

rate-limit:                         # inbound messages per player
  messages-per-second: 20
  burst: 40

debug: false

The module set a player receives is global ∪ worlds[current world] ∪ modules disabled through the API; it is re-sent on world change and on /flick reload.

#Commands

Command Permission
/flick list flick.admin (op) Flick players online, with client and MC versions
/flick info <player> flick.admin version, protocol, enabled modules, server-disabled modules, waypoint count, refused categories, reported mods
/flick reload flick.admin reload config.yml and re-apply module policy

#Disableable module ids

Generated from the client's module registry at runtime (Flick Client 0.1.0; DevShots [flick-api-test] MODULES). Only toggleable modules can be disabled; client (settings), theme and friends are always on. Unknown ids are ignored by the client. Constants: net.flick.serverapi.FlickModules (ALL, COMPETITIVE).

id Name
fps FPS
cps CPS
keystrokes Keystrokes
armor_status Armor Status
potion_effects Potion Effects
coordinates Coordinates
ping Ping
clock Clock
server_address Server Address
memory Memory
saturation Saturation
toggle_sprint Toggle Sprint
hit_color Hit Color
old_animations 1.7 Animations
reach_display Reach Display
combo_counter Combo Counter
target_hud Target HUD
low_fire Low Fire
hitboxes Hitboxes
zoom Zoom
fullbright Fullbright
crosshair Custom Crosshair
block_outline Block Outline
time_changer Time Changer
weather_changer Weather Changer
particles Hit Particles
freelook Freelook
auto_gg Auto GG
nick_hider Nick Hider
address_hider Server Address Hider (replaces the server address on screen; a server may want it off so the address stays visible in screenshots)
screenshots Screenshots
waypoints Waypoints (player's own)
chat_tweaks Chat Tweaks
inventory_blur Inventory Blur
item_physics Item Physics
emotes Emotes
perf_panel Performance panel
aspect_ratio Aspect Ratio
viewmodel ViewModel
perspective Perspective (hold F4 for a free orbit camera; the scroll wheel then changes the camera distance up to 64 blocks, and an optional no-collision mode sees through walls, so competitive servers should disable it)
hurt_cam Hurt Cam
clear_water Clear Water
fast_chest Fast Chest
hit_sounds Hit Sounds
swing_animations Swing Animations
totem_pop Totem Pop (custom totem animation on screen)
totem_counter Totem Counter (totem pops per player next to the nametag, icon for a held totem, own totem count on the HUD, optional pop message; purely client-side bookkeeping of events the server already sends)
cooldown_hud Cooldown HUD (active item cooldowns, incl. event items, with the real remaining time; only shows data the client already has)
combat_fps Combat FPS (fewer explosion/totem particles, smaller totem pop and blocking shield; client-side performance only)
hit_bubbles Hit Bubbles
hit_marker Hit Marker
damage_numbers Damage Numbers (shows health changes of nearby entities)
health_indicators Health Indicators (hearts above other players' heads showing their synced health; an information advantage, part of COMPETITIVE)
color_saturation Color Saturation (post effect: saturation, vibrance, contrast, brightness, tint, presets; skipped under an Iris shader pack)
motion_blur Motion Blur (post effect)
kill_effects Kill Effects (client-side effects on deaths)
target_esp Target ESP (marker and card on your current opponent; optional through-walls mode)
jump_circles Jump Circles (glowing rings on jump/landing; optionally under other players)
trails Trails (glowing trail behind you; optionally other players, arrows, ender pearls)
china_hat China Hat (translucent glowing hat / halo above the head)
aura Aura (ambient particles around the player)
trajectories Trajectories (predicted path of bows, crossbows, tridents and thrown items; an aiming aid)
ambience Ambience (client-side sky and fog tint)
glint_color Glint Color (enchantment glint tint)
shulker_preview Shulker Preview (tooltip grid with the contents of shulker boxes and other containers; client-side, from item data)
title_tweaks Title Customization (scale, position, shadow, hiding and timing of /title titles and the action bar)
bossbar_tweaks Boss Bar (scale, position, hiding and compact spacing of boss bars)
scoreboard_tweaks Scoreboard (sidebar scale, position, background, hidden red score numbers, title and line limit; purely cosmetic)
nametags Nametags (scale, vertical offset, background, shadow, distance fade, own nametag in third person; never changes which nametags vanilla shows)
friends_ingame Friends in game (Flick friends highlighted in the tab list and on nametags; party HUD of friends on the same server, with health only when the friend is already a visible entity)

Removed module camera_distance. The scroll-wheel camera distance (formerly a separate F5 module) is now part of perspective; F5 is vanilla again. Servers that disabled camera_distance should disable perspective instead - the old id is ignored by the client. FlickModules.COMPETITIVE now contains PERSPECTIVE instead of CAMERA_DISTANCE.

Removed module minimap. The minimap and the world map (M) were removed from Flick Client. The id is ignored by the client; FlickModules.MINIMAP is kept as @Deprecated (no effect) so existing plugins still compile, and it is no longer in FlickModules.ALL or FlickModules.COMPETITIVE.

Disabling waypoints hides the player's own waypoints; server waypoints are controlled separately by the player's Server waypoints setting.

#Protocol specification

Protocol version 1. Implement this to support Flick from a non-Paper server (Fabric, Minestom, a proxy...).

#Transport

  • Minecraft custom payload (plugin message) on channel flick:api, play phase only.
  • Body = one UTF-8 JSON object, no length prefix (exactly the byte[] of Bukkit's sendPluginMessage / onPluginMessageReceived). Every object has a string type field "t" (≤ 32 chars).
  • Max 32 767 bytes per message in both directions. Larger messages are ignored (never a disconnect).
  • Unknown t values and unknown fields are ignored → forward compatible. Bump proto only for breaking changes.
  • The client registers flick:api with minecraft:register (Fabric does this automatically). Bukkit only delivers messages to clients that registered the channel.

#Handshake and session

  1. When joining, the client checks whether the server registered flick:api (Paper/Bukkit announce every registered incoming channel in minecraft:register on join). Only then does it send hello. A server without the plugin therefore never receives anything.
  2. The server answers hello. Until that answer, the client ignores every server message.
  3. The session ends on disconnect, on a new play session (server switch behind a proxy), or when the player turns the API off. The client then discards everything the server set: waypoints, disabled modules (restored to the player's own choice), Rich Presence. Nothing server-set is ever written to disk.

#Client → server

| t | Fields | |---|---|---| | hello | proto (int, 1), client ("flick"), version (Flick version, e.g. 0.1.0+1.21.11), mc (1.21.11), modules (enabled module ids), caps (supported families: waypoints, modules, notify, rpc, mods, reset), allow ({waypoints, notify, rpc, mods} booleans — player's per-category choices) | | state | modules (effectively enabled module ids) | | mods | nonce (echo of the request), ids (loaded mod ids, ≤ 512), denied (true if the player refused; ids empty) |

Example (real hello captured from the e2e test):

{"t":"hello","proto":1,"client":"flick","version":"0.1.0+1.21.11","mc":"1.21.11",
 "modules":["fps","cps","keystrokes","zoom","crosshair","waypoints"],
 "caps":["waypoints","modules","notify","rpc","mods","reset"],
 "allow":{"waypoints":true,"notify":true,"rpc":true,"mods":true}}

#Server → client

| t | Fields (limits) | |---|---|---| | hello | proto (int ≥ 1), server (name, ≤ 32) | | waypoint_add | id ([A-Za-z0-9_.:-]{1,64}), name (≤ 48), x y z (finite numbers; |x|,|z| ≤ 3·10⁷, −2048 ≤ y ≤ 4096), dimension (minecraft:overworld default; ns:path), color ("#RRGGBB" or int 0xRRGGBB), icon? (pin flag sword star cube globe skull heart users lock arrow chest), beam? (default true), temporary? (remove within 4 blocks), durationMs? (0–86 400 000, 0 = no expiry) | add / replace by id; max 200 (extra ids ignored); invalid → ignored | | waypoint_remove | id | | waypoint_clear | — | | modules | disable (module ids, ≤ 128), reason? (≤ 96) | | modules_reset | — | | notify | title (≤ 64, required), body (≤ 160), kind (info/success/warning/error), durationMs (1000–15 000, default 5000) | | rpc | details (≤ 64), state (≤ 64); both empty = clear | | rpc_reset | — | | mods_request | nonce? ([A-Za-z0-9_-]{1,32}) | | reset | — |

All strings are sanitised on the client: § formatting codes, control, bidi/format and private-use characters are removed, whitespace collapsed, length cut to the limit.

#Rate limits

  • Client accepts server messages with a token bucket of 40 msg/s, burst 100; excess is dropped (logged once). Send bulk waypoint updates in batches (e.g. ≤ 30 per tick-second).
  • The plugin accepts client messages at 20 msg/s, burst 40 (configurable); it accepts nothing but hello before the handshake.

#Security model

  • No code, no URLs, no files, no accounts. The protocol has no message that runs code, opens a link or a screen, executes commands/chat, reads or writes files, or touches the Mojang/Microsoft account, Flick account, friends or cosmetics. Text is only ever displayed.
  • Opt-in per server, gated by the handshake. The client never talks first to servers that do not register flick:api, and obeys nothing until the server answers hello.
  • Session-only. Waypoints, module blocks and presence live in memory and are cleared on disconnect / server switch / when the player disables the API. The player's saved configuration is never modified (a module blocked by the server keeps the player's own on/off choice and returns to it).
  • Bounded. Size limit 32 KiB, string limits, 200 waypoints, 128 module ids, token-bucket rate limits on both sides, notification and mod-request throttles. Malformed input is ignored, never fatal.
  • Player control. Allow the Flick server API (master switch, default on) and per-category switches: server waypoints, server notifications, server Discord status, share mod list. With the master switch off the client does not identify itself at all.
  • Client-reported data is untrusted. hello, state and mods come from the client; a modified client can lie or omit anything. Module disabling is cooperative (honest Flick clients obey it) — use it for fair-play UX, not as an anti-cheat. The same applies to kick-if-mods.
  • Privacy. The mod list is only sent on request, only to a server that completed the handshake, and only if the player allows it.

#Testing

  • Plugin unit tests (MockBukkit 4.116.3, Paper API 1.21.11): cd server-api && ./gradlew test — handshake, gating, waypoint/module/notification/presence messages, config policy (global/per world), welcome, mod requests (reply, denial, timeout, quit), kick-if-mods, malformed input, rate limiting, commands, golden wire format.

  • Client end-to-end without Paper: a dev-only shim inside the integrated server speaks the same protocol (net.flick.client.api.dev.ApiDevShim, enabled only with -Dflick.apishim=true or the DevShots suite):

    cd mod
    ./gradlew :1.21.11:runClient -Pdevshots -Pdevsuite=api -Pdevres=1920x1080
    

    The suite creates a world, verifies the handshake and gating, adds waypoints, disables modules, sends a notification and presence, requests mods, sends malformed / oversized / flood traffic, leaves the world and verifies the reset. Results: [flick-api-test] PASS|FAIL … lines and SUMMARY in the log; screenshots run/screenshots/flick_api_{hud,menu_blocked,settings_blocked,after_leave}_*.png.

  • Manual testing against a real Paper server: install the plugin jar, join with Flick, /flick list.