LogoAraxer's Bestiary

CraftTweaker Integration

Overview

This page shows how to use CraftTweaker (ZenCode) to interact with Araxer's Bestiary on Minecraft 1.20.1 (Forge). You can read player ranks, check thresholds, and react in scripts using the Java helper shipped in this mod. No extra addons are required.

What you get

  • Read player’s Bestiary rank (letter or numeric) for any entity
  • Check if a player meets a threshold (E, D, C, B, A, S, X)
  • React on login or periodically to grant rewards, set variables, or call commands
  • Optional (where supported): listen to the Bestiary rank change event from CraftTweaker’s class-based event listeners

Versions

  • Minecraft: 1.20.1 (Forge)
  • CraftTweaker: 1.20.1 branch (v14+)
  • This mod: v1.3.9+ (provides BestiaryJS helper and BestiaryRankChangedEvent helpers)

Rank mapping

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

Key helper class (Java)

  • Class: com.araxer.araxers_bestiary.integration.kubejs.BestiaryJS
  • Why we use it: It’s a small static Java helper with no KubeJS dependency; ZenCode can import and call it directly.

Methods (summary)

  • BestiaryJS.rankCode(playerNameOrUUID, entityId) → int 0..7
  • BestiaryJS.rank(playerNameOrUUID, entityId) → string "" | E | D | C | B | A | S | X
  • BestiaryJS.meets(playerNameOrUUID, entityId, threshold) → boolean
  • BestiaryJS.isObserved(playerNameOrUUID, entityId) → boolean
  • BestiaryJS.isDiscovered(playerNameOrUUID, entityId) → boolean

Notes

  • playerNameOrUUID can be the player’s exact name (event.player.name) or UUID (event.player.uuid). UUID is preferred to avoid name changes.
  • If inputs are invalid or the player is offline, helpers return safe defaults (0, false, or empty string).

Example A — Check on player login (give reward or message)

Create scripts/bestiary_login.zs:

import com.araxer.araxers_bestiary.integration.kubejs.BestiaryJS;
// Class-based CraftTweaker event type (1.20.1). If your IDE can’t resolve, see the notes below.
import crafttweaker.api.event.entity.player.PlayerLoggedInEvent;
// Listen for player logins
// Modern CraftTweaker (1.20.1) uses class-based listeners:
eventManager.listen<PlayerLoggedInEvent>((event) => {
val player = event.player;
val name = player.name; // exact name
val uuid = player.uuid; // preferred for uniqueness
// Check if the player has reached S on zombie
if (BestiaryJS.meets(uuid, "minecraft:zombie", "S")) {
// Send a chat message
player.sendMessage("You already reached S on zombie!");
// Example: grant a reward item (vanilla give command via server)
event.server.executeCommand("give " + name + " minecraft:nether_star");
}
});

If your CraftTweaker version doesn’t support class-based event imports, try the string ID variant:

import com.araxer.araxers_bestiary.integration.kubejs.BestiaryJS;
// String-based event name fallback (varies between CrT versions)
events.listen<crafttweaker.api.event.entity.player.PlayerLoggedInEvent>((event) => {
val uuid = event.player.uuid;
if (BestiaryJS.meets(uuid, "minecraft:zombie", "S")) {
event.player.sendMessage("Welcome back, Zombie Master!");
}
});

Example B — Periodic rank checks (every ~10 seconds)

Sometimes you want passive, periodic checks (e.g., update a variable, or trigger once when condition becomes true). You can use a simple server tick listener and a counter.

Create scripts/bestiary_periodic.zs:

import com.araxer.araxers_bestiary.integration.kubejs.BestiaryJS;
import crafttweaker.api.event.lifecycle.ServerTickEvent;
var tickCounter as int = 0;
// Check every 200 ticks (~10 seconds)
val PERIOD = 200;
// Which entities and thresholds to watch
val WATCH = [
{ entity: "minecraft:creeper", threshold: "A" },
{ entity: "minecraft:skeleton", threshold: "B" }
];
// Keep a simple memory to avoid spamming actions repeatedly (per player per key)
var granted as string[string][string];
function keyOf(entity as string, threshold as string) as string {
return entity ~ "@" ~ threshold;
}
function tryDoOnce(player as crafttweaker.api.player.Player, entity as string, threshold as string) {
val k = keyOf(entity, threshold);
val name = player.name;
if (!granted.containsKey(name)) granted[name] = {};
if (granted[name].containsKey(k)) return; // already processed
// Mark done
granted[name][k] = "1";
// Do your one-time action here
player.sendMessage("Condition met: " ~ threshold ~ "+ on " ~ entity);
// Example give command
player.server.executeCommand("give " ~ name ~ " minecraft:diamond");
}
// Server tick listener
eventManager.listen<ServerTickEvent>((event) => {
tickCounter += 1;
if (tickCounter % PERIOD != 0) return;
// Iterate online players
for player in event.server.players {
val id = player.uuid; // prefer UUID for lookup
for entry in WATCH {
if (BestiaryJS.meets(id, entry.entity, entry.threshold)) {
tryDoOnce(player, entry.entity, entry.threshold);
}
}
}
});

Example C — Listen to rank changes directly (where supported)

If your CraftTweaker build allows listening to arbitrary Forge events via class-based listeners, you can subscribe to Araxer’s Bestiary rank change event and react immediately (no polling).

Event class: com.araxer.araxers_bestiary.api.BestiaryRankChangedEvent

Create scripts/bestiary_rank_changed.zs:

import com.araxer.araxers_bestiary.api.BestiaryRankChangedEvent;
// Subscribe directly to the Forge event through CrT’s eventManager (when supported)
eventManager.listen<BestiaryRankChangedEvent>((event) => {
val entityId = event.entityId; // e.g., "minecraft:zombie"
val newRank = event.newRank; // e.g., "S"
val player = event.player; // crafttweaker-wrapped ServerPlayer
if (entityId == "minecraft:zombie" && newRank == "S") {
player.sendMessage("You reached S on zombie!");
player.server.executeCommand("give " ~ player.name ~ " minecraft:nether_star");
}
});

Notes

  • Not all CraftTweaker builds expose arbitrary Forge events to ZenCode. If yours doesn’t, use Example A (login) and/or Example B (periodic) instead.
  • The event also exposes helper getters: getEntityId(), getPlayerName(), getPlayerUUID(), getOldRankCode(), getNewRankCode(). Depending on your CrT wrapper, you may access them as properties (entityId, playerName, etc.) or via methods; adjust if your tooling prefers method syntax.

Using with FTB Quests (optional)

CraftTweaker does not need to control FTB Quests directly. The recommended approach is:

  • Maintain per-entity scoreboard values with periodic or event-driven updates (e.g., via Example B with server.executeCommand calls to /bestiary rank get).
  • In FTB Quests, use a Scoreboard task that requires the objective >= threshold code (E=1..S=6..X=7).

If you prefer Custom tasks, you can run FTB Quests commands from CraftTweaker when conditions are met:

// Complete a Custom task (replace the ID with yours)
player.server.executeCommand("ftbquests change_progress " ~ player.name ~ " complete bestiary_zombie_s");
// or (depending on your FTBQ version)
player.server.executeCommand("ftbquests custom " ~ player.name ~ " bestiary_zombie_s");

Example D — FTB Quests Custom tasks (multi‑entity template, no datapack)

Create scripts/bestiary_ftbq_custom_tasks.zs. This completes FTB Quests Custom tasks when players reach desired Bestiary ranks for specific entities. It runs on login (to credit existing progress) and on rank changes (immediate updates). Replace the task IDs with those you configured in the FTB Quests editor.

import com.araxer.araxers_bestiary.integration.kubejs.BestiaryJS;
import com.araxer.araxers_bestiary.api.BestiaryRankChangedEvent;
import crafttweaker.api.event.entity.player.PlayerLoggedInEvent;
// Map: entity id -> { "threshold": letter, "taskId": custom task id }
val TASKS as string[string][string] = {
"minecraft:zombie": { "threshold": "S", "taskId": "bestiary_zombie_s" },
"minecraft:creeper": { "threshold": "A", "taskId": "bestiary_creeper_a" }
// Add more as needed
};
function tryComplete(server as crafttweaker.api.server.MinecraftServer, playerName as string, taskId as string) {
// Prefer the change_progress command; also try the custom variant for compatibility
server.executeCommand("ftbquests change_progress " ~ playerName ~ " complete " ~ taskId);
server.executeCommand("ftbquests custom " ~ playerName ~ " " ~ taskId);
}
// On login: complete any tasks the player already qualifies for
// (Class-based event listener; if your CrT doesn’t support this, see notes below)
eventManager.listen<PlayerLoggedInEvent>((event) => {
val name = event.player.name;
val uuid = event.player.uuid;
for entityId, cfg in TASKS {
if (BestiaryJS.meets(uuid, entityId, cfg["threshold"])) {
tryComplete(event.server, name, cfg["taskId"]);
}
}
});
// On rank changes: only check the tasks for the entity that changed
// Note: Some CrT builds may not bridge arbitrary Forge events. If this listener doesn’t fire,
// fall back to periodic checks (see Example B) or rely on the login path above.
eventManager.listen<BestiaryRankChangedEvent>((ev) => {
val id = ev.entityId; // e.g., "minecraft:zombie"
if (!TASKS.containsKey(id)) return;
val cfg = TASKS[id];
val name = ev.player.name;
val uuid = ev.player.uuid;
if (BestiaryJS.meets(uuid, id, cfg["threshold"])) {
tryComplete(ev.player.server, name, cfg["taskId"]);
}
});

Notes

  • Custom task ID: In FTB Quests, open your Custom task and copy its string/key/ID field. Use that as taskId.
  • Thresholds: E, D, C, B, A, S, X. The empty rank "" never meets thresholds.
  • Compatibility: If your CrT can’t subscribe to BestiaryRankChangedEvent, you can still achieve the same result using Example B’s periodic checks (every ~10s) plus the login listener above.
  • Commands: If your FTB Quests build supports only one of the two commands, remove the other line in tryComplete.

Troubleshooting

  • Import errors for event types: CraftTweaker’s package names can differ slightly between versions. Use your IDE’s code completion to find the exact type (e.g., PlayerLoggedInEvent and ServerTickEvent live under crafttweaker.api.event.*). See the official docs for 1.20.1 event names.
  • Unknown method executeCommand: On some builds, the method may be named runCommand or similar on the server wrapper. Adjust the call accordingly; the idea is to run a standard server command from your script.
  • No reaction to rank changes: If Example C doesn’t fire, your CrT version likely doesn’t bridge arbitrary Forge events. Use login + periodic checks.
  • Confirm the helper class: Ensure the mod is installed and the class path is correct: com.araxer.araxers_bestiary.integration.kubejs.BestiaryJS.

See also

  • BestiaryJS API: integration:bestiaryjs-api
  • FTB Quests Integration (KubeJS examples): integration:ftb-quests
  • KubeJS alternatives: integration:quests-and-kubejs