Getting started

  1. Drop the jar in and start the server

    There is no second plugin to install, because the framework is inside the jar. On first run Bestiary writes an example mob, skill and drop table, and never overwrites a file once it exists.

  2. Check what it thinks it is running on

    /bestiary platform
    

    Prints the detected server, the scheduler in use, which AI tier was selected and how much content loaded. Worth reading once now so it is familiar when you need it in a bug report.

    The AI line is the one to note: Paper Goal API + NMS, Paper Goal API, or none (Spigot).

  3. Spawn the example

    mobs/bestiary/example.yml is a complete boss with no Java behind it: attributes, AI goals, phases, a bossbar, timed skills and a drop table.

    /bestiary spawn bestiary:example_boss
    

    Then browse everything that loaded:

    /bestiary menu
    
  4. Write a skill

    Create plugins/Bestiary/skills/my_skills.yml:

    frost_nova:
      cooldown: 6s
      skills:
        - sound{s=entity.player.hurt_freeze} @self
        - particle{p=snowflake;shape={type=ring;radius=5;points=40}} @selfLocation
        - damage{amount=6} @playersInRadius{r=5}
        - potion{type=slowness;duration=4s;level=1} @playersInRadius{r=5}

    Files under skills/ nest as deeply as you like and merge into one namespace, so the id is just frost_nova.

  5. Write a mob that uses it

    Create plugins/Bestiary/mobs/aether/frostling.yml:

    frostling:
      type: stray
      display: "<aqua>Frostling"
      health: 60
      damage: 5
    
      skills:
        - skill{s=frost_nova} ~onTimer:120
        - skill{s=frost_nova} ~onDamagedByPlayer

    Under mobs/, the first directory is the namespace, so this is aether:frostling rather than frostling.

  6. Apply it

    /bestiary reload
    /bestiary spawn aether:frostling
    

    If a skill does not do what you expected, run it from yourself with a live trace of every targeter and condition:

    /bestiary cast frost_nova
    

What ends up on disk

plugins/Bestiary/

  • config.ymlguards, storage, anchors, hooks
  • messages.yml
  • skills/nested freely, one flat namespace
    • example.yml
    • my_skills.yml
  • mobs/first directory is the namespace
    • bestiary/
      • example.yml
    • aether/yours
      • frostling.yml
  • droptables/
    • example.yml
  • spawners/spawners, spawn regions and random spawn rules

skills/, droptables/ and spawners/ all merge into one namespace each, however deeply you nest the files. mobs/ is the exception, and deliberately so: mobs/aether/valkyrie_champion.yml defines aether:valkyrie_champion, which is what keeps two content packs from colliding.

Verify the baseline

Bestiary requires Java 17 or newer and Minecraft 1.19.4-26.x. It supports Spigot, Paper, Paper forks and Folia; all plugins listed as soft dependencies are optional. Before adding content, inspect the example with /bestiary info bestiary:example_boss and trace one of its skills with /bestiary cast <skill>. These commands inspect compiled runtime state rather than only the YAML.

Commands you will use

/bestiary menu                  browse every mob and skill
/bestiary spawn <mob> [level] [x y z] [world]
/bestiary list [mobs|skills|droptables|anchors|spawners]
/bestiary info <mob|skill>      attributes, phases, triggers, the whole tree
/bestiary cast <skill> [target] run a skill from yourself, with a trace
/bestiary debug <mob|off>       attach that trace to a running mob
/bestiary reload                re-read config and content
/bestiary platform              what Bestiary thinks it is running on

When a file is wrong

Parse failures are per definition rather than per file, so one broken skill does not take out the other forty beside it. Each failure names the file, the YAML path, the offending value and what was expected:

[error] skills/my_skills.yml -> frost_nova -> skills[2]
        unknown mechanic 'damgae'

A misspelled parameter behaves differently from a misspelled mechanic. Because every mechanic declares its parameters before the parser builds anything, an unknown key is a load-time warning that names the parameters which do exist, rather than a silent default that leaves you wondering why ignore_armour did nothing.