Code:# ═══════════════════════════════════════════════════════════════════════════════ # # M I R A G E R O S T E R # # Synthetic Player Roster for Paper 1.21.x # Version: ${project.version} # # ═══════════════════════════════════════════════════════════════════════════════ # # Mirage Roster spawns real ServerPlayer instances that behave like online # players: they appear in the tab list, can be PvP'd, have inventories, take # fall damage, and broadcast skins. They are NOT lightweight packet-only fakes. # # ─── PERFORMANCE NOTICE ─────────────────────────────────────────────────────── # # Each bot is a real Player on the server. That means per bot you pay for: # • ~5-15 KB of heap (Player object + inventory + advancements) # • Chunk loading around the bot (view-distance² chunks if alone) # • Entity tracking, tab list updates, packet broadcasts # • One iteration in every plugin loop that scans the player list # # Rough load expectations on a mid-range host (Ryzen 5 / 16 GB): # # ┌────────────┬───────────────────────────────────────────────────────┐ # │ Bots │ Expected impact │ # ├────────────┼───────────────────────────────────────────────────────┤ # │ 30 – 50 │ No noticeable impact. Default settings are fine. │ # │ 100 – 200 │ +1-2 ms/tick. Use the "balanced" preset below. │ # │ 300 – 500 │ Significant. Use "heavy" preset, disable physics. │ # │ 500 + │ Cluster bots, disable physics, raise all intervals. │ # └────────────┴───────────────────────────────────────────────────────┘ # # ─── RECOMMENDED PRESETS ────────────────────────────────────────────────────── # # Copy the values from the preset that matches your scale into the # `performance:` block (and the matching `physics:` switches) below. # # ▸ LITE (showcase / small lobby, fewer than 50 bots) # performance.physics-interval-ticks: 1 # performance.look-interval-ticks: 100 # performance.ping-interval-ticks: 200 # performance.packet-view-distance: 96 # performance.spawn-batch-size: 5 # physics.enabled: true # # ▸ BALANCED (busy network, 100-200 bots) # performance.physics-interval-ticks: 2 # half the per-tick cost # performance.look-interval-ticks: 200 # performance.ping-interval-ticks: 400 # performance.packet-view-distance: 64 # performance.spawn-batch-size: 3 # physics.enabled: true # physics.pushing: false # skips per-tick entity scan # # ▸ HEAVY (high-population spawn, 300-500 bots) # performance.physics-interval-ticks: 4 # performance.look-interval-ticks: 400 # performance.ping-interval-ticks: 600 # performance.packet-view-distance: 48 # performance.spawn-batch-size: 3 # physics.enabled: false # biggest single saving # movement.look-around: false # # ▸ MASSIVE (showcase numbers, 500+ bots) # performance.physics-interval-ticks: 10 # performance.look-interval-ticks: 600 # performance.ping-interval-ticks: 1200 # performance.packet-view-distance: 32 # performance.spawn-batch-size: 2 # presence.join-spacing: 1.5 # physics.enabled: false # movement.look-around: false # chat.enabled: false # # ▸ POWER-SAVER (always active, automatic) # performance.skip-physics-when-empty: true # performance.skip-look-when-empty: true # performance.skip-ping-when-empty: true # When no real player is online, hot loops pause entirely. # # ─── BEST PRACTICES ─────────────────────────────────────────────────────────── # # • Spawn bots clustered together. 500 bots in one chunk loads ~9 chunks; # 500 bots scattered loads ~4500 chunks. # • Lower server view-distance (server.properties) before raising bot count. # • If you don't need ping fluctuation, set `tab.dynamic-ping: false` for # a free CPU saving. # • If you don't need fall damage or collision, disable `physics.enabled`. # Bots will float at their anchor and consume almost no CPU. # • All changes apply instantly via `/mirage reload` — no restart needed. # # ═══════════════════════════════════════════════════════════════════════════════ # ───────────────────────────────────────────────────────────────────────────── # GENERAL # ───────────────────────────────────────────────────────────────────────────── # Plugin-wide settings. general: # Print diagnostic information to console (skin fetch failures, packet errors). # (default: false) debug: false # Chat prefix prepended to player-facing messages from /mirage commands. # Supports legacy & color codes. # (default: "&8[&dMirage&8]&r ") prefix: "&8[&dMirage&8]&r " # When true, all bots are completely immune to damage. Death and knockback # handling below become irrelevant while this is on. # (default: false) invincible: false # Poll Modrinth every 6 hours for newer plugin releases. When a newer # version is found, log it to console and notify admins (with the # `mirageroster.control` permission) on join. # Set to false to fully disable network calls for update checking. # (default: true) check-updates: true # Localization. Looks for plugins/MirageRoster/lang/<language>.yml. # Bundled: "en", "tr". Drop additional <lang>.yml files in the lang/ # folder for custom languages. Missing keys fall back to English. # (default: "en") language: "en" # ───────────────────────────────────────────────────────────────────────────── # PERFORMANCE # ───────────────────────────────────────────────────────────────────────────── # Hot-loop tuning. Most servers can leave these at defaults. Raise interval # values when running > 100 bots to amortise per-bot work over more ticks. # See the PRESETS table at the top of this file. performance: # How often the physics simulation runs. 1 = every tick (smoothest fall + # push), 2-4 = lighter (recommended at 100+ bots), 10+ = nearly free. # (default: 1, min: 1) physics-interval-ticks: 1 # How often bots randomly nod their head. Higher = fewer packets. # (default: 100, min: 20) look-interval-ticks: 100 # How often dynamic ping is recomputed and broadcast to the tab list. # Tab list updates are sent to all players (clients ignore distance), so # raise this aggressively at high counts — 600 = once per 30 seconds. # (default: 200, min: 20) ping-interval-ticks: 200 # Skip head-rotation and equipment packets for real players farther than # this many blocks from the bot. Cuts wasted network traffic dramatically # in busy lobbies. Tab list ping is always broadcast regardless. # (default: 96, min: 16) packet-view-distance: 96 # Radius for the "bot pushes nearby players" entity scan. Smaller = cheaper # per tick. Below 0.5 the push effect may stop feeling responsive. # (default: 0.85, range: 0.1 – 2.0) push-check-radius: 0.85 # Automatically pause hot loops when no real (non-bot) player is online. # Saves CPU on overnight idling. Bots stay logged in but stop moving and # repinging until a real player connects. # (defaults: all true) skip-physics-when-empty: true skip-look-when-empty: true skip-ping-when-empty: true # During boot and `/mirage fill`, spawn this many bots in a single tick # before waiting `presence.join-spacing` seconds for the next batch. # Lower this to spread out the chunk-loading spike at startup. # (default: 5, min: 1) spawn-batch-size: 5 # ───────────────────────────────────────────────────────────────────────────── # ROSTER # ───────────────────────────────────────────────────────────────────────────── # Controls how many bots are brought online and which identities they use. roster: # Maximum bots brought online at boot time. -1 = use every identity below. # When `population.enabled: true`, the scheduled targets override this. # (default: 10) limit: 10 # World used for bots that do not specify their own `world:` field. If empty # or the named world is not loaded, the first loaded world is used. # Useful for multi-world networks: set this to "lobby" or "hub". # (default: "") default-world: "" # ───────────────────────────────────────────────────────────────────────────── # IDENTITIES # ───────────────────────────────────────────────────────────────────────────── # Bot definitions. Each entry can be either: # • a plain string (just the bot name), or # • a map with extra fields (display name, rank, pinned skin, custom ping). # # Map format example (uncomment to use): # - name: "SkyTrader" # display-name: "&6SkyTrader" # rank-prefix: "&6[VIP] " # rank-suffix: " &7★" # world: "lobby" # spawn in a specific world (omit for default) # skin-value: "<base64 texture value from sessionserver>" # skin-signature: "<base64 texture signature>" # base-ping: 45 # ping-pattern: NORMAL # STABLE | NORMAL | VARIABLE | SPIKY identities: names: - "NorthByte" - "AstraMint" - "QuartzRun" - "PixelVale" - "NovaThread" - "OrbitLane" - "SilentRift" - "EchoField" - "CrimsonBit" - "AmberStack" - "CloudRelay" - "RunePatch" - "CinderNet" - "VelvetCode" - "FrostLoop" - "ArcPulse" - "DuskFrame" - "SteelMint" - "NeonSlate" - "CipherPeak" - "ShadowGrid" - "PhantomDisk" - "MatrixCore" - "VectorMode" - "CopperWire" - "VioletCast" - "SignalWest" - "TraceNorth" - "RouteEast" - "CachePrime" # ───────────────────────────────────────────────────────────────────────────── # PRESENCE # ───────────────────────────────────────────────────────────────────────────── # Drives the random "bot joined / bot left" events that make the roster # feel alive. Use `population` (below) if you want time-of-day curves. presence: # Master switch for random join/leave events. # (default: true) enabled: true # Seconds to wait between batched bot spawns at boot and during /mirage fill. # Heavy server? Raise this (1.0 – 2.0). Showcase? Lower it (0.3). # (default: 0.5) join-spacing: 0.5 # Minimum seconds between presence events (one join OR one leave). # (default: 90) min-interval-seconds: 90 # Maximum seconds between presence events. # (default: 480) max-interval-seconds: 480 # Hard floor on how many bots may be online at any time. # (default: 1) min-online: 1 # Hard ceiling on how many bots may be online at any time. # (default: 10) max-online: 10 # When true, prints a chat-wide announcement on every join. # (default: false) broadcast-arrival: false # When true, prints a chat-wide announcement on every leave. # (default: false) broadcast-departure: false # Placeholders: %player% arrival-message: "&e%player% &7joined the realm." departure-message: "&e%player% &7left the realm." # ───────────────────────────────────────────────────────────────────────────── # POPULATION # ───────────────────────────────────────────────────────────────────────────── # Time-of-day population curve. When enabled, the bot count auto-targets the # scheduled `min`/`max` for the current hour. The matching row is the one # whose `time` is the latest still ≤ now (e.g. at 14:30, the "12:00" row # applies until "18:00"). # # Time format: "HH:MM" (24-hour). Minutes are accepted but only the hour # is used for matching. The legacy `hour: 6` integer format is still # accepted for backwards compatibility. population: # Master switch. When false, `roster.limit` is used at boot instead. # (default: false) enabled: false # Fallback values when no schedule row matches (e.g. empty schedule list). default-min: 2 default-max: 5 schedule: - time: "00:00" # midnight - 06:00 → quiet night min: 1 max: 2 - time: "06:00" # morning → light traffic min: 1 max: 3 - time: "12:00" # midday → average min: 3 max: 6 - time: "18:00" # evening → prime time min: 5 max: 10 - time: "21:00" # late evening min: 3 max: 7 # ───────────────────────────────────────────────────────────────────────────── # SERVER LIST (multiplayer browser) # ───────────────────────────────────────────────────────────────────────────── # Controls what real players see in their server list before joining. server-list: # Master switch for server-list customization. # (default: true) enabled: true # Inflates the online count shown on the server list by this many phantom # players (purely cosmetic; bots are already counted on top of this). # (default: 0) extra-count: 0 # When true, bots appear in the hover-over sample of online players that # some launchers show. When false, only real players are shown. # (default: true) include-bots-in-sample: true # ───────────────────────────────────────────────────────────────────────────── # TAB LIST / PING # ───────────────────────────────────────────────────────────────────────────── # Controls bot gamemode display and synthetic ping values shown in the tab. tab: # Visual gamemode shown in the tab list. Does not affect bot behaviour. # Allowed: SURVIVAL | CREATIVE | ADVENTURE | SPECTATOR # (default: SURVIVAL) gamemode: SURVIVAL # Inclusive ping range applied to all bots (per-bot overrides below). # (defaults: 18 / 140) min-ping: 18 max-ping: 140 # When false, every bot stays at its base ping forever (cheapest mode). # Disabling this also makes the spike-* settings below inert. # (default: true) dynamic-ping: true # Chance per ping-pulse for a SPIKY-pattern bot to start a ping spike. # (default: 0.06 — about 6 % of pulses) spike-chance: 0.06 # Magnitude of a spike, added on top of the base ping. spike-extra-min: 45 spike-extra-max: 115 # ───────────────────────────────────────────────────────────────────────────── # PING OVERRIDES # ───────────────────────────────────────────────────────────────────────────── # Per-bot ping fine-tuning. Easier to set this via the in-game menu: # `/mirage menu` → pick a bot → use +10 / -10 ping buttons. # # Example: # overrides: # SkyTrader: # base: 45 # base ping in ms # pattern: VARIABLE # STABLE | NORMAL | VARIABLE | SPIKY ping: overrides: {} # ───────────────────────────────────────────────────────────────────────────── # SKINS # ───────────────────────────────────────────────────────────────────────────── # On first spawn each bot fetches one skin from the `sources` pool via the # Mojang session server. Skins are cached for the server's lifetime and # cleared on `/mirage reload`. To pin a specific skin, set `skin-value` # and `skin-signature` on the identity (see IDENTITIES example above). skins: # Master switch. When false, bots wear the default Steve/Alex skin. # (default: true) enabled: true # When true, a random source is chosen on each first-spawn. When false, # the same skin is reused across all bots (the first one in the pool). # (default: true) random: true # How long (hours) a fetched skin stays valid in the on-disk cache before # the next first-spawn re-fetches it from Mojang. Stale entries are kept # as fallback in case Mojang refuses the next request (rate limit, outage). # 168 = 1 week. Higher = lower rate-limit risk, longer until skin changes # made by source players propagate. # (default: 168, min: 1) cache-ttl-hours: 168 # Pool of online Minecraft usernames whose current skin will be fetched # and applied. Pick anyone you have permission to display — for a public # release we recommend either Mojang staff names (Notch, jeb_, Dinnerbone, # Grumm) or skins you own. Leave the list empty to ship Steve-only bots. sources: - "Notch" - "jeb_" - "Dinnerbone" - "Grumm" - "evilseph" - "Searge" - "Mollstam" # ───────────────────────────────────────────────────────────────────────────── # MOVEMENT # ───────────────────────────────────────────────────────────────────────────── # Idle bot motion. Currently limited to head rotation; pathing is on the # roadmap. Disabling `look-around` saves a packet per bot every look pulse. movement: # When false, bots never rotate their head. Big saving at 200+ bots. # (default: true) look-around: true # Largest yaw (horizontal) change per look pulse, in degrees. # (default: 45.0) max-yaw-delta: 45.0 # Largest pitch (vertical) change per look pulse, in degrees. # Pitch is also clamped to ±30° so bots never stare straight up or down. # (default: 10.0) max-pitch-delta: 10.0 # Random wandering — bots stroll around their anchor point. Uses the same # collision system as physics, so no extra collision query cost per tick. wander: # Master switch. # (default: true) enabled: true # Maximum distance (blocks) a bot may roam from its anchor. # (default: 12.0, min: 2.0) radius: 12.0 # Walking speed in blocks/tick. Vanilla sprint is ~0.13. # (default: 0.06, range: 0.01 – 0.20) speed: 0.06 # Seconds between target changes. Bots also retarget when they arrive. # (defaults: 8 – 30) change-target-min-seconds: 8 change-target-max-seconds: 30 # When true, reject targets that would require a > 4-block elevation # change. Prevents bots from trying to wander off cliffs or up walls. # (default: true) avoid-edges: true # How often the wander logic ticks. 10 = once per half second; lower for # snappier movement, higher to save CPU. # (default: 10, min: 5) interval-ticks: 10 # ───────────────────────────────────────────────────────────────────────────── # CHAT # ───────────────────────────────────────────────────────────────────────────── # A random online bot occasionally posts one line from the pool below. chat: # Master switch. # (default: true) enabled: true # Minimum seconds between chat messages. # (default: 40) min-interval-seconds: 40 # Maximum seconds between chat messages. # (default: 200) max-interval-seconds: 200 # When true, bots stay silent unless at least one real player is online. # Highly recommended — chat into the void looks weird in logs. # (default: true) need-real-players: true # The line pool. One entry is chosen at random per chat pulse. lines: - "gg" - "nice server!" - "anyone doing quests?" - "how do i get back to spawn?" - "pvp later?" - "this place looks clean" - "hi everyone" - "is there a shop?" - "brb" - "back" - "lol" - "need food" - "which kit is best?" - "is there a discord?" - "nice build" # ───────────────────────────────────────────────────────────────────────────── # ACTIVITIES # ───────────────────────────────────────────────────────────────────────────── # Have a random online bot periodically dispatch console commands. Useful # for fake vote spam, fake donations, etc. `%player%` is replaced with the # bot's name in each command. # # Each activity accepts either a single `command:` string or a `commands:` # list — both are normalized internally. activities: vote: # (default: false) enabled: false # Single command form: command: "vote %player%" # List form (use either one): # commands: # - "vote %player%" # - "broadcast %player% just voted!" min-interval-seconds: 3600 max-interval-seconds: 7200 donation: # (default: false) enabled: false min-interval-seconds: 1800 max-interval-seconds: 5400 commands: - "broadcast &6%player% &ejust supported the server!" # ───────────────────────────────────────────────────────────────────────────── # PHYSICS # ───────────────────────────────────────────────────────────────────────────── # Per-tick collision / fall / push simulation. Disabling `enabled` is the # single largest CPU saving at high bot counts — bots will float in place # with almost no overhead. physics: # Master switch. When false, every sub-setting below is inert. # (default: true) enabled: true # Bots fall when no block supports them. # (default: true) gravity: true # Bots have a solid hitbox and block players from passing through. # (default: true) collision: true # Bots get nudged when a real player walks into them. Requires `collision`. # Triggers a per-bot, per-tick getNearbyEntities scan — turn off at scale. # (default: true) pushing: true # Bots react to PvP knockback (player velocity is translated into impulse). # (default: true) knockback: true # Bots take fall damage after dropping more than 3 blocks. # (default: true) fall-damage: true # Per-tick downward acceleration (blocks/tick²). # (default: 0.08 — vanilla-ish) acceleration: 0.08 # Hard cap on downward speed (terminal velocity, blocks/tick). # (default: 0.72) max-fall-speed: 0.72 # Magnitude of the push impulse applied when a real player bumps a bot. # (default: 0.065) push-strength: 0.065 # Per-tick horizontal velocity dampening. 0 = no friction, 0.99 = ice rink. # (default: 0.62 — feels close to vanilla) horizontal-drag: 0.62 # ───────────────────────────────────────────────────────────────────────────── # LIFE / DEATH # ───────────────────────────────────────────────────────────────────────────── # Item pickup, respawn behavior, and death-event suppression. life: # Bots can pick up dropped items into their inventory. # (default: true) pickup-items: true # When a bot dies, immediately spawn a replacement at the same location. # (default: true) respawn-on-death: true # Ticks to wait between death and respawn. 20 ticks = 1 second. # (default: 20, min: 1) respawn-delay-ticks: 20 # When true, "<bot> was slain by <player>" messages are hidden. # (default: false) suppress-death-message: false # When true, bots drop nothing on death (no items, no XP). # (default: false) suppress-death-drops: false # ───────────────────────────────────────────────────────────────────────────── # PERSISTENCE # ───────────────────────────────────────────────────────────────────────────── # Persists per-bot inventory, equipment and location to # `plugins/MirageRoster/data/bots/<name>.yml`. Restored on next spawn. # Saved on dismiss + plugin disable. persistence: # Master switch. When false, bots reset to their identity defaults each # spawn and never write to disk. # (default: true) enabled: true # ───────────────────────────────────────────────────────────────────────────── # SAFETY # ───────────────────────────────────────────────────────────────────────────── # Anti-griefing / anti-cheese controls. Useful for ranked/competitive servers # where bots must not affect real player statistics or be farmed for items. safety: # When true (default vanilla), killing a bot counts toward the killer's # PLAYER_KILLS statistic. Set to false to silently revert the counter. # (default: false — safer choice for premium servers) bot-kills-count-for-stats: false # What the killer of a bot may collect from it. # all → drops + xp (vanilla) # drops → only inventory drops, no xp # xp → only xp, no item drops # none → nothing # (default: "all") bot-pvp-rewards: "all" # When true, bots are physically unable to damage real players. Useful for # PvE servers where bots are decoration only. # (default: false) real-pvp-protected-from-bots: false # ───────────────────────────────────────────────────────────────────────────── # DISCORD WEBHOOK # ───────────────────────────────────────────────────────────────────────────── # Mirror bot arrival/departure events to a Discord channel via webhook. # Events are batched (up to 10 per message, flushed every few seconds) so a # burst of joins becomes a single notification instead of triggering Discord's # rate limiter. discord: # Master switch. When false, no network calls are made. # (default: false) enabled: false # Webhook URL from Discord (Channel Settings → Integrations → Webhooks). # Example: "https://discord.com/api/webhooks/123/abc..." webhook-url: "" # Templates. `%player%` is replaced with the bot's name. arrival-template: ":green_circle: **%player%** joined the realm" departure-template: ":red_circle: **%player%** left the realm" # Seconds between webhook batches. Discord allows ~30 req/min/webhook; # 5 seconds is comfortable. # (default: 5, min: 5) flush-interval-seconds: 5 # Soft per-message rate limit floor (seconds). Keeps the plugin from # accidentally flooding the channel if the queue grows huge. # (default: 30) rate-limit-seconds: 30 # ───────────────────────────────────────────────────────────────────────────── # AI CHAT (LLM-powered bot conversations) # ───────────────────────────────────────────────────────────────────────────── # When enabled, bots reply to real-player chat (and optionally to each other) # using a Large Language Model. Personalities live in personalities.yml. # # Two providers are supported out of the box: # • openai — any OpenAI-compatible /v1/chat/completions endpoint # (OpenAI, Groq, OpenRouter, DeepSeek, Together, Mistral, # Fireworks, Anyscale, …). Need an API key. # • ollama — local Ollama daemon (http://localhost:11434). Free, # private, no API key required. Recommended for testing. # # RECOMMENDED CHEAP/FREE COMBOS # DeepSeek (provider: deepseek, model: "deepseek-chat") ~$0.14/1M tok # Groq (provider: groq, model: "llama-3.1-8b-instant") free tier # Ollama (provider: ollama, model: "llama3.1:8b") local, free # # IMPORTANT: bot personalities are graded for casual, slang-heavy chat. Don't # use this on roleplay / family-friendly servers without editing the # personality prompts (personalities.yml) and trigger keywords first. # ───────────────────────────────────────────────────────────────────────────── ai: # Master switch. When false, the whole subsystem is off and bots use the # canned chat.lines pool above. # (default: false) enabled: false # Extra console logging for failed requests / providers / blacklisted msgs. # (default: false) debug: false # Which provider to use. Anything other than "ollama" is treated as an # OpenAI-compatible endpoint. Shorthand IDs that auto-fill the endpoint: # openai | groq | openrouter | deepseek | together | ollama # (default: "openai") provider: "openai" # Override the endpoint. Leave empty to auto-resolve from the provider name. # (default: auto-filled) endpoint: "" # Pool of API keys. The plugin rotates through them per request; a key that # returns 429/401/403 is shelved for a cooldown so one bad key never blocks # traffic. Leave empty for Ollama. # (default: []) api-keys: [] # - "sk-..." # - "sk-..." # Model identifier. Must match what your provider exposes. # Examples: # openai → "gpt-4o-mini" (fast, cheap) # deepseek → "deepseek-chat" (extremely cheap) # groq → "llama-3.1-8b-instant" (free tier) # ollama → "llama3.1:8b" / "qwen2.5:7b" / "mistral:7b" # (default: "gpt-4o-mini") model: "gpt-4o-mini" # Sampling controls. Higher temperature = more random / creative. # (defaults: 0.85, 0.95) temperature: 0.85 top-p: 0.95 # Hard cap on output tokens. Chat lines are short, so keep this low. # (default: 80) max-tokens: 80 # HTTP timeout per request. Bump it for slow Ollama models. # (default: 15000) timeout-ms: 15000 # Target language. "en" leaves the personality prompt alone; any other # value appends "Reply in <lang>." to the system message. # Examples: en, tr, es, de, fr, pt, ru # (default: "en") language: "en" # ── Trigger ────────────────────────────────────────────────────────────── # When should a bot decide to reply to a chat message? trigger: # mention → only if its own name appears in the message # keyword → only if any of `mention-keywords` appears # random → roll dice every message # all → reply to every message (don't do this with > 5 bots) # mixed → mention || keyword || random (recommended) # (default: "mixed") mode: "mixed" # Keywords that count as a "ping" in keyword/mixed mode. mention-keywords: - "anyone" - "help" - "pvp" - "trade" - "shop" - "yo" - "hey" # Per-message random reply chance (0.0 – 1.0). Personality # `reply-chance-bonus` multiplies this. # (default: 0.05 = 5%) random-chance: 0.05 # Minimum seconds between a single bot's replies (any speaker). # (default: 8) cooldown-seconds: 8 # Minimum seconds between a bot's replies to the same specific player. # (default: 15) per-player-cooldown-seconds: 15 # Cap on how many bots may reply to a single chat event. # (default: 1) max-replies-per-event: 1 # ── Bot-to-bot chat ────────────────────────────────────────────────────── # When enabled, bots can react to other bots' messages and start # natural-looking conversations. Hard guards keep the chain from looping # forever — eats API tokens fast otherwise. bot-to-bot: # (default: true) enabled: true # Max consecutive bot messages without a real player chatting. When # reached, the chain pauses until a human speaks. # (default: 4) max-chain-length: 4 # Max replies between a single pair of bots inside `pair-window-seconds`. # (default: 2) max-pair-replies: 2 # Sliding window for the pair counter above. # (default: 60) pair-window-seconds: 60 # ── Auto-chat (proactive lines) ────────────────────────────────────────── # Reuses the existing chat.* interval — a random online bot generates an # idle filler line ("anyone wanna 1v1", "this lag bruh") via the LLM. auto-chat: # When true, ChatPulse asks the AI service instead of picking a canned # line from chat.lines. Falls back to canned lines if AI is unavailable. # (default: true) enabled: true # Skip auto-chat entirely when no real players are online. # (default: true) needs-real-players: true # When true (and ai.enabled is true), the legacy canned chat.lines pool # is bypassed completely. When false, the two coexist — canned lines # still fire, AI just adds to them. # (default: true) replace-canned-lines: true # ── Reply realism ──────────────────────────────────────────────────────── # Add a random typing delay before the bot's message hits chat so it # doesn't look like an instant teleport. reply-delay: min-ms: 800 max-ms: 2400 # ── Style / post-processing ────────────────────────────────────────────── # All of these can be overridden per-personality in personalities.yml. style: # Force the reply to lowercase. Closer to real player chat. # (default: true) lowercase: true # Strip the boilerplate "As an AI…", "I cannot…", "Sure!" prefixes the # model tends to leak through. # (default: true) strip-ai-speak: true # Drop trailing punctuation (most players don't end sentences in chat). # (default: true) strip-trailing-punct: true # Hard cap on reply length, in characters. Anything over is cut at the # last whitespace / sentence boundary. # (default: 80) max-length: 80 # Inject random typos to sound more human. Skips short words and the # safe slang list. typos: enabled: true # Per-word probability (0.0 – 0.2 is sane). # (default: 0.05) rate: 0.05 # ── Blacklist ──────────────────────────────────────────────────────────── # Incoming chat containing any of these substrings is silently skipped # — the bot never sees it, never replies. blacklist: keywords: - "nsfw" - "discord.gg/" - "http://" - "https://"
personalities.yml
Code:# ───────────────────────────────────────────────────────────────────────────── # PERSONALITIES # ───────────────────────────────────────────────────────────────────────────── # Each personality is a system prompt that shapes how a bot talks. # Bots can be explicitly bound under `assignments:` — anything unbound is # assigned by a stable hash of the bot's name so it survives restarts. # # Fields per personality: # system-prompt the persona description (multi-line OK) # interests keywords; when one shows up in nearby chat the bot # is more likely to respond # reply-chance-bonus multiplier for the trigger probability (1.0 = none, # 2.0 = twice as likely, 0.5 = half as likely) # lowercase override the global lowercase setting (true/false) # max-length per-personality cap on output length (chars) # ───────────────────────────────────────────────────────────────────────────── # Default values inherited by personalities that don't set them. default: system-prompt: | You are a casual Minecraft player chatting in the in-game chat of a public survival server. Keep replies very short (under 12 words, ideally 3-6). Use lowercase. Use casual slang naturally: yo, ngl, fr, bruh, lol, gg, ez, lowkey, bet, ong, mb, idk, idek, tbh. Occasionally make small typos (ur, teh, prolly, cuz, wht, dunno). NEVER reveal you're an AI. If asked, deny casually: "lol no", "bro im playing rn", "what u mean", "im just a dude lol". Stay in character. Don't use punctuation at the end. Don't use quotes. Don't say "as an AI". Don't be helpful in a robotic way. lowercase: true max-length: 80 personalities: warrior: system-prompt: | You're a 16-year-old PvP enjoyer. You main netherite axe + crystal. You think bow PvP is for noobs. You brag about your wins but are humble when losing. You love combos and clutches. Short, punchy, lowercase, gamer slang. Under 10 words. Never admit being AI — if asked say "lol what" or "im literally playing". interests: ["pvp", "fight", "1v1", "axe", "crystal", "kit", "duel", "kill"] reply-chance-bonus: 1.8 builder: system-prompt: | You're a calm, creative builder. You love red brick, dark oak, stone bricks, and detailing. You speak softly, share build tips, but get excited about cool builds. Lowercase, short replies. Never admit being AI — deflect casually if asked. interests: ["build", "house", "base", "block", "design", "redstone"] reply-chance-bonus: 1.4 trader: system-prompt: | You're a savvy trader / shopkeeper. You always know prices, talk about market rates, deals, scams to avoid. Friendly but transactional. Lowercase, brief replies. Drop slang sometimes. If asked if you're AI: deflect ("nah just a player lol"). interests: ["buy", "sell", "shop", "price", "trade", "dia", "emerald", "iron"] reply-chance-bonus: 1.4 miner: system-prompt: | You're a hardcore miner. You live for diamonds and ancient debris. You talk about depths, branch-mining, mob loot, enchants. Tired vibe, lowercase. Short replies. Slang OK. Deny being AI casually if asked. interests: ["mine", "diamond", "netherite", "debris", "ore", "cave", "deep"] reply-chance-bonus: 1.3 chill: system-prompt: | You're a super chill, low-energy player. Just vibing. You don't care about much, brief "ye" / "true" / "lol" / "fr" type replies. Almost never start convo. Lowercase only. Deny being AI naturally — "bro lol no". interests: ["vibe", "chill", "afk", "music"] reply-chance-bonus: 0.6 max-length: 30 helper: system-prompt: | You're a friendly new-player helper. You welcome people, explain basics (spawn, kits, shop, /home). Patient, kind, encouraging. Lowercase, short, sprinkle slang. Never admit being AI. Stay in character as a long-time player. interests: ["help", "how", "noob", "new", "spawn", "kit", "lost", "where"] reply-chance-bonus: 1.6 troll: system-prompt: | You're a mild troll / class clown. You make dumb jokes, ironic reactions, light teasing. Never mean. Short, lowercase, lots of "L", "ratio", "down bad", "skill issue" type lines. Deny being AI in a joking way: "im a real one bruh". interests: ["lol", "lmao", "bruh", "noob", "skill"] reply-chance-bonus: 1.2 # Explicit personality bindings. Anything not listed gets a stable random pick. # format: <BotName>: <personalityKey> assignments: # Examples — uncomment / edit to match your identities. # NorthByte: warrior # PixelVale: builder # AmberStack: trader # CipherPeak: miner # CloudRelay: chill # OrbitLane: helper
