LogoAraxer's Bestiary

KubeJS Integration

Overview

This guide explains how to unlock and complete FTB Quests using Bestiary ranks without relying on advancements. Use the Quickstart below (KubeJS-only, event-driven).

Desired Rank Task (what you’re asking for)

  • FTB Quests already provides a built-in Scoreboard task type that perfectly fits “complete when player reaches desired rank on an entity.”
  • The Bestiary mod exposes a numeric rank via /bestiary rank get (No Rank=0, E=1, D=2, C=3, B=4, A=5, S=6, X=7).
  • The only missing piece is to keep a per-entity scoreboard updated when the rank changes. We recommend an event-driven KubeJS snippet (no datapack).

Copy-paste: event-driven scoreboard updater for one desired entity+rank

// kubejs/server_scripts/bestiary_task_zombie_s.js (KubeJS 6, MC 1.20.1)
const RankEvent = Java.loadClass('com.araxer.araxers_bestiary.api.BestiaryRankChangedEvent');
// Desired entity and threshold rank (letter)
const ENTITY = 'minecraft:zombie';
const THRESHOLD = 'S'; // E/D/C/B/A/S/X
// Objective name FTB Quests will read
function objName(id) { return `brank_${id.replace(':', '_')}`; }
const OBJECTIVE = objName(ENTITY);
// Create the objective when the server loads
ServerEvents.loaded(e => {
e.server.runCommandSilent(`scoreboard objectives add ${OBJECTIVE} dummy`);
});
// Keep the score current on login (covers already-progressed players)
PlayerEvents.loggedIn(e => {
e.server.runCommandSilent(`execute as ${e.player.username} store result score ${e.player.username} ${OBJECTIVE} run bestiary rank get ${e.player.username} ${ENTITY}`);
});
// Update instantly whenever the Bestiary rank changes
RankEvent.rankChanged(event => {
if (event.getEntityId() !== ENTITY) return;
// Refresh the scoreboard value from the authoritative command
event.getPlayer().server.runCommandSilent(`execute as ${event.getPlayerName()} store result score ${event.getPlayerName()} ${OBJECTIVE} run bestiary rank get ${event.getPlayerName()} ${ENTITY}`);
});

FTB Quests task configuration for the desired rank

  • Add a Scoreboard task to your quest:
    • Objective: brank_minecraft_zombie (for minecraft:zombie)
    • Comparison: >=
    • Value: 6 for S (E=1, D=2, C=3, B=4, A=5, S=6, X=7)
  • The task will auto-complete as soon as the player’s rank reaches the threshold and the script updates the scoreboard.

Multiple entities and thresholds (reusable pattern)

// kubejs/server_scripts/bestiary_tasks.js
const BestiaryRankChangedEvent = Java.loadClass('com.araxer.araxers_bestiary.api.BestiaryRankChangedEvent');
// Configure your tasks here: each entry becomes one FTB Quests Scoreboard task
// Example: reach S on zombie, A on creeper
const TASKS = [
{ entity: 'minecraft:zombie', threshold: 'S' },
{ entity: 'minecraft:creeper', threshold: 'A' },
];
function objName(id) { return `brank_${id.replace(':', '_')}`; }
// Create all objectives on server load
ServerEvents.loaded(e => {
for (const t of TASKS) e.server.runCommandSilent(`scoreboard objectives add ${objName(t.entity)} dummy`);
});
// On login: refresh all configured scores for that player
PlayerEvents.loggedIn(e => {
for (const t of TASKS) {
const o = objName(t.entity);
e.server.runCommandSilent(`execute as ${e.player.username} store result score ${e.player.username} ${o} run bestiary rank get ${e.player.username} ${t.entity}`);
}
});
// On actual rank changes: update just the changed entity
BestiaryRankChangedEvent.rankChanged(event => {
const id = event.getEntityId();
if (!TASKS.some(t => t.entity === id)) return;
const o = objName(id);
event.getPlayer().server.runCommandSilent(`execute as ${event.getPlayerName()} store result score ${event.getPlayerName()} ${o} run bestiary rank get ${event.getPlayerName()} ${id}`);
});
  • In FTB Quests, create one Scoreboard task per entry:
    • Objective: brank_<namespace>_<path>
    • Comparison: >=
    • Value: map threshold letter to number with: E=1, D=2, C=3, B=4, A=5, S=6, X=7

Why we use the Scoreboard task instead of a custom Java task

  • It’s built-in to FTB Quests, stable across versions, and requires no extra mod jars or hard dependency.
  • The Bestiary already exposes an authoritative rank-get command and an event; the KubeJS script bridges them.
  • If you later want a full custom Java task type, you can add an optional integration module that depends on FTB Quests and listens to BestiaryRankChangedEvent to call FTB Quests’ internal APIs—but the scoreboard task covers the exact “desired rank” use-case cleanly and simply.

Quickstart — No datapack (KubeJS v6, MC 1.20.1)

  1. Create a scoreboard objective per entity you want to gate by (once):

    • Example for zombie: scoreboard objectives add brank_minecraft_zombie dummy
  2. Add this KubeJS script (kubejs/server_scripts/bestiary_ftbq.js) to update the scoreboard when ranks change and on login:

// KubeJS 6 (MC 1.20.1)
const RankEvent = Java.loadClass('com.araxer.araxers_bestiary.api.BestiaryRankChangedEvent');
// Entities you want to track for quests
const ENTITIES = ['minecraft:zombie']; // add more like 'minecraft:creeper'
function objName(id) { return `brank_${id.replace(':', '_')}`; }
// Ensure objectives exist on server load
ServerEvents.loaded(e => {
for (const id of ENTITIES) e.server.runCommandSilent(`scoreboard objectives add ${objName(id)} dummy`);
});
// Keep the scoreboard correct when players (re)join
PlayerEvents.loggedIn(e => {
for (const id of ENTITIES) {
const o = objName(id);
e.server.runCommandSilent(`execute as ${e.player.username} store result score ${e.player.username} ${o} run bestiary rank get ${e.player.username} ${id}`);
}
});
// Update instantly when a bestiary rank changes
RankEvent.rankChanged(event => {
const id = event.getEntityId();
if (!ENTITIES.includes(id)) return;
const o = objName(id);
event.getPlayer().server.runCommandSilent(`execute as ${event.getPlayerName()} store result score ${event.getPlayerName()} ${o} run bestiary rank get ${event.getPlayerName()} ${id}`);
});
  1. In FTB Quests, add a Scoreboard task to your quest:

    • Objective: brank_minecraft_zombie
    • Comparison: >=
    • Value: 6 for S (5 for A, 4 for B, 3 for C, 2 for D, 1 for E)
  2. Test: As an op, run /bestiary rank set <player> minecraft:zombie S and watch the task complete.

Rank code mapping

  • No Rank = 0
  • E = 1, D = 2, C = 3, B = 4, A = 5, S = 6, X = 7

Core command

  • /bestiary rank get <player> <entityId>
    • Returns the numeric rank code for that player and entity
    • Example: execute store result score @s brank_zombie run bestiary rank get @s minecraft:zombie

KubeJS event-driven updates (recommended)

If you use KubeJS 6 on Forge (MC 1.20.1), you can update scoreboards only when ranks change. This is very light and instant.

kubejs/server_scripts/bestiary_ftbq.js

// KubeJS 6 (MC 1.20.1)
const BestiaryJS = Java.loadClass('com.araxer.araxers_bestiary.integration.kubejs.BestiaryJS');
const RankEvent = Java.loadClass('com.araxer.araxers_bestiary.api.BestiaryRankChangedEvent');
// Configure entities you want to gate quests by
const ENTITIES = ['minecraft:zombie', 'minecraft:creeper'];
function objName(id) {
return `brank_${id.replace(':', '_')}`; // e.g., minecraft:zombie -> brank_minecraft_zombie
}
// Ensure objectives exist when the server loads
ServerEvents.loaded(e => {
for (const id of ENTITIES) {
e.server.runCommandSilent(`scoreboard objectives add ${objName(id)} dummy`);
}
});
// Keep one player's scores correct upon login
PlayerEvents.loggedIn(e => {
for (const id of ENTITIES) {
const o = objName(id);
e.server.runCommandSilent(`execute as ${e.player.username} store result score ${e.player.username} ${o} run bestiary rank get ${e.player.username} ${id}`);
}
});
// Update only on actual rank changes (no ticking overhead)
RankEvent.rankChanged(event => {
const id = event.getEntityId();
if (!ENTITIES.includes(id)) return;
const o = objName(id);
event.getPlayer().server.runCommandSilent(`execute as ${event.getPlayerName()} store result score ${event.getPlayerName()} ${o} run bestiary rank get ${event.getPlayerName()} ${id}`);
});

FTB Quests setup (scoreboard task)

  • Add a Scoreboard task to the quest you want to gate
    • Objective: brank_minecraft_zombie (for minecraft:zombie)
    • Value: 6 for S, 5 for A, 4 for B, 3 for C, 2 for D, 1 for E
    • Comparison: >=
  • To gate visibility, you can require this task (or a parent quest with this task) before the gated quest becomes visible.

Examples

  • Unlock on S rank for minecraft:zombie:
    • Objective: brank_minecraft_zombie
    • Required value: 6
  • Unlock on A or higher for minecraft:creeper:
    • Objective: brank_minecraft_creeper
    • Required value: 5

Tips

  • Bulk entities: add more ENTITIES to the KubeJS array.
  • Performance: The KubeJS event-driven method has near-zero cost and updates instantly.
  • Testing: As an op, set a rank and observe the task complete:
    • /bestiary rank set <player> minecraft:zombie S
    • /scoreboard players get <player> brank_minecraft_zombie

Troubleshooting

  • Command permissions: The commands run from server context (KubeJS) and don’t require players to be opped.
  • Wrong objective name: Ensure the FTB Quests task uses exactly the same objective name you created.
  • KubeJS errors: Ensure you are on KubeJS v6 and placed scripts in kubejs/server_scripts.

Reference

  • Numeric mapping: No Rank=0, E=1, D=2, C=3, B=4, A=5, S=6, X=7
  • Command: /bestiary rank get <player> <entity>
  • Event class (for KubeJS): com.araxer.araxers_bestiary.api.BestiaryRankChangedEvent

Using FTB Quests Custom Task (no scoreboard, no datapack)

Goal

  • You want to complete/unlock an FTB Quests task when a player reaches a desired Bestiary rank on a specific entity, but you prefer using FTB Quests’ Custom task instead of the built‑in Scoreboard task.
  • This section shows a KubeJS‑only, event‑driven setup for MC 1.20.1 (KubeJS v6). It listens for Bestiary rank changes and completes the matching Custom task immediately.

What you’ll set up in FTB Quests

  1. In the FTB Quests editor, add a task to your quest and choose the “Custom” task type.
  2. Give the task a unique ID (sometimes labeled Key/String/Check ID depending on version). Example: bestiary_zombie_s.
  3. Save/export your quest pack. Note the task’s ID/path you set.

How the script completes the Custom task

  • When a Bestiary rank changes, KubeJS receives BestiaryRankChangedEvent (no ForgeEvents global required, we provide a Java helper).
  • If the changed entity meets your threshold, the script runs an FTB Quests command to complete the Custom task for that player.

About FTB Quests commands (version differences)

  • Many modern versions support a generalized progress command:
    • ftbquests change_progress <player> complete <quest_or_task_id>
  • Some builds also provide a direct custom task trigger command:
    • ftbquests custom <player> <custom_task_id>
  • The examples below try the change_progress variant first, then fall back to the custom variant. Keep the task IDs simple (lowercase, no spaces) to avoid quoting hassles.

Copy‑paste: Single Custom task (zombie S)

  • Completes a Custom task with ID bestiary_zombie_s when the player reaches S on minecraft:zombie. Also checks on login in case the player already met the condition earlier.
// kubejs/server_scripts/bestiary_custom_task_zombie_s.js (KubeJS 6, MC 1.20.1)
const RankEvent = Java.loadClass('com.araxer.araxers_bestiary.api.BestiaryRankChangedEvent');
const BestiaryJS = Java.loadClass('com.araxer.araxers_bestiary.integration.kubejs.BestiaryJS');
const ENTITY = 'minecraft:zombie';
const THRESHOLD = 'S';
const CUSTOM_TASK_ID = 'bestiary_zombie_s'; // Set this ID in your FTB Quests Custom task
function tryCompleteCustomTask(server, playerName) {
// Prefer the change_progress command; fall back to the custom command
// Note: runCommandSilent returns a number; we don’t need it here
server.runCommandSilent(`ftbquests change_progress ${playerName} complete ${CUSTOM_TASK_ID}`);
server.runCommandSilent(`ftbquests custom ${playerName} ${CUSTOM_TASK_ID}`);
}
// Ensure players who already meet the condition get credit on login
PlayerEvents.loggedIn(event => {
if (BestiaryJS.meets(event.player.username, ENTITY, THRESHOLD)) {
tryCompleteCustomTask(event.server, event.player.username);
}
});
// Complete as soon as the rank changes
RankEvent.rankChanged(event => {
if (event.getEntityId() !== ENTITY) return;
if (BestiaryJS.meets(event.getPlayerName(), ENTITY, THRESHOLD)) {
tryCompleteCustomTask(event.getPlayer().server, event.getPlayerName());
}
});

Multi‑task template (various entities and thresholds)

  • Configure a list of Custom task entries. The script completes the matching one(s) on login and on rank changes.
// kubejs/server_scripts/bestiary_custom_tasks.js
const RankEvent = Java.loadClass('com.araxer.araxers_bestiary.api.BestiaryRankChangedEvent');
const BestiaryJS = Java.loadClass('com.araxer.araxers_bestiary.integration.kubejs.BestiaryJS');
// Each entry links an entity + threshold rank to a Custom task ID in FTB Quests
const TASKS = [
{ entity: 'minecraft:zombie', threshold: 'S', taskId: 'bestiary_zombie_s' },
{ entity: 'minecraft:creeper', threshold: 'A', taskId: 'bestiary_creeper_a' },
// Add more as needed
];
function tryComplete(server, playerName, taskId) {
server.runCommandSilent(`ftbquests change_progress ${playerName} complete ${taskId}`);
server.runCommandSilent(`ftbquests custom ${playerName} ${taskId}`);
}
// On login: complete any tasks the player already qualifies for
PlayerEvents.loggedIn(e => {
for (const t of TASKS) {
if (BestiaryJS.meets(e.player.username, t.entity, t.threshold)) {
tryComplete(e.server, e.player.username, t.taskId);
}
}
});
// On rank changes: only check tasks for the entity that changed
RankEvent.rankChanged(ev => {
const id = ev.getEntityId();
for (const t of TASKS) {
if (t.entity === id && BestiaryJS.meets(ev.getPlayerName(), t.entity, t.threshold)) {
tryComplete(ev.getPlayer().server, ev.getPlayerName(), t.taskId);
}
}
});

How to get the Custom task ID

  • In the FTB Quests editor, select your Custom task and locate its string/ID/key field. That value is what the script uses as CUSTOM_TASK_ID/taskId.
  • Keep IDs lowercase and without spaces. If your IDs include special characters or colons, wrap them in quotes in the command strings.

Tips

  • Threshold mapping: E, D, C, B, A, S, X are supported. No Rank ('') never meets any threshold.
  • You can mix this Custom task method with Scoreboard tasks in the same pack; they don’t conflict.
  • If nothing happens, run /help ftbquests to confirm the exact subcommands your version provides; adjust the command lines accordingly.
  • For multi‑entity grouped quests, you can gate the quest by multiple Custom tasks (one per entity) or write a single script that completes a single Custom task when all conditions are met.

Troubleshooting

  • Command not found: Your FTB Quests version might use only one of the two shown command variants. Keep the one that works and remove the other.
  • Task never completes: Double‑check the ID you set in the Custom task and ensure it matches exactly in the script. Also verify the player really meets the threshold with: /bestiary rank get <player> <entity>.
  • KubeJS errors: Ensure scripts are in kubejs/server_scripts and you’re on KubeJS v6 for 1.20.1.

FTB Quests KubeJS Custom Task example (periodic rank check)

This example shows how to implement a Custom task in FTB Quests whose progress is driven directly by KubeJS, without scoreboards or datapacks. It periodically checks a player’s Bestiary rank and completes the task once the desired threshold is met.

Notes

  • Requires Araxer's Bestiary v1.3.9 or newer (BestiaryJS helper methods are available).
  • Targets KubeJS 6 on MC 1.20.1.
  • Replace the Custom task ID with yours from the FTB Quests editor. See https://kubejs.com/wiki/events/ftbquests for more on FTBQuestsEvents.
// kubejs/server_scripts/bestiary_ftbq_custom_task_zombie_s.js
// Example of a custom FTB Quests task that checks Bestiary rank
// Note: This requires Araxer's Bestiary v1.3.9 or newer
// Note: This example checks every 10 seconds if the player has reached rank S with zombies
// Note: This example uses the new event subscription system of KubeJS 6 (MC 1.20.1)
// Note: See https://kubejs.com/wiki/events/ftbquests for more information
// Load the helper (shipped by the mod, no addon needed)
const BestiaryJS = Java.loadClass('com.araxer.araxers_bestiary.integration.kubejs.BestiaryJS');
const RankEvent = Java.loadClass('com.araxer.araxers_bestiary.api.BestiaryRankChangedEvent');
FTBQuestsEvents.customTask('72A85FB85E195984', event => {
// How much progress the task needs in total (binary task)
event.setMaxProgress(1);
// How often to run the check (200 ticks = 10s)
event.setCheckTimer(200);
const ENTITY = 'minecraft:zombie';
const THRESHOLD = 'S'; // E/D/C/B/A/S/X
// This function runs for each player that has the task active.
event.setCheck((task, player) => {
const playerName = player.getName().getString();
// Optional debug output (remove in production)
player.tell('Checking zombie rank...');
const rankName = BestiaryJS.rank(playerName, ENTITY);
const rankCode = BestiaryJS.rankCode(playerName, ENTITY);
player.tell('Zombie rank code: ' + rankCode + ' for player: ' + playerName);
player.tell('Zombie rank: ' + rankName + ' for player: ' + playerName);
// Update progress: 1 if the player has reached the threshold rank, otherwise 0
// Handled like a binary task
task.progress = BestiaryJS.meets(playerName, ENTITY, THRESHOLD) ? 1 : 0;
});
});

Tips

  • Change ENTITY and THRESHOLD to target other mobs/ranks. Mapping: E=1, D=2, C=3, B=4, A=5, S=6, X=7 (for reference if you combine with scoreboards elsewhere).
  • Replace '72A85FB85E195984' with your Custom task’s ID/key from the FTB Quests editor.
  • You can attach multiple custom tasks by calling FTBQuestsEvents.customTask again with different IDs.
  • If you prefer event-driven immediate updates, you can also use BestiaryRankChangedEvent.rankChanged to call task.setProgress(1) proactively, but the timer-based check shown above keeps logic contained in the custom task definition.

Global threshold reached event (KubeJS)

The mod now fires a Forge event when a player reaches a global progression threshold (based on the server’s globalRankProgressBarType: S or X). This happens immediately during gameplay when the threshold is crossed, not on relog or config reload.

  • Event class: com.araxer.araxers_bestiary.api.BestiaryGlobalThresholdReachedEvent
  • KubeJS helper: BestiaryGlobalThresholdReachedEvent.thresholdReached(handler)
  • Fields/methods:
    • getPlayer(): ServerPlayer
    • getThresholdIndex(): int (0-based index into Config.globalSRankThresholds)
    • getThresholdValue(): int (the numeric S/X count required at that index)
    • getBarType(): String ("S" or "X")
    • getBenefitDef(): String or null (raw configured benefit at that index, null for skipped/missing)

Example KubeJS script (kubejs/server_scripts/global_thresholds.js):

const GlobalThreshEvent = Java.loadClass('com.araxer.araxers_bestiary.api.BestiaryGlobalThresholdReachedEvent');
GlobalThreshEvent.thresholdReached(event => {
const p = event.getPlayer();
const idx = event.getThresholdIndex();
const value = event.getThresholdValue();
const type = event.getBarType(); // "S" or "X"
const benefit = event.getBenefitDef(); // may be null if skipped
// Notify or reward
p.server.tell(`${p.name.string} reached global ${type} ≥ ${value} (index ${idx})${benefit ? ' benefit='+benefit : ''}`);
});