Skip to content

Example Scripts

This page provides annotated walkthroughs of eight production-ready scripts. Each one demonstrates real patterns you can copy and adapt for your own server. The scripts are based on the mcmmo-style example scripts that ship with Ceremony.


What it does: When a player with Woodcutting level 5 or higher breaks a log block, the entire tree is felled automatically. Sneaking disables the ability.

Key concepts: BFS traversal, re-entrancy guard, Java registry lookup, sneak-to-disable toggle.

/// <reference path="types/ceremony-api.d.ts" />
const BuiltInRegistries = Java.type('net.minecraft.core.registries.BuiltInRegistries');
const BlockPos = Java.type('net.minecraft.core.BlockPos');
const JourneyApi = Java.type('com.briar.journey.api.JourneyApi');
const MAX_BLOCKS = 128;
const processing = new Set();
// Get the string ID of a block (e.g., "minecraft:oak_log")
function getBlockId(state) {
try {
const key = BuiltInRegistries.BLOCK.getKey(state.getBlock());
return key ? key.toString() : '';
} catch (e) { return ''; }
}
// Read the player's Woodcutting skill level from Journey
function getWoodcuttingLevel(player) {
try {
const api = JourneyApi.getInstance();
const profile = api.getPlayerProfile(player.getUUID());
return profile ? (profile.getSkill('woodcutting')?.getLevel() ?? 0) : 0;
} catch (e) { return 0; }
}
// BFS: find all connected log/leaf blocks starting from a position
function findTree(world, startPos) {
const queue = [startPos];
const visited = new Set();
const logs = [];
visited.add(startPos.getX() + ',' + startPos.getY() + ',' + startPos.getZ());
// Check all 26 neighbors (including diagonals) for trees
const offsets = [];
for (let dx = -1; dx <= 1; dx++) {
for (let dy = -1; dy <= 1; dy++) {
for (let dz = -1; dz <= 1; dz++) {
if (dx === 0 && dy === 0 && dz === 0) continue;
offsets.push([dx, dy, dz]);
}
}
}
while (queue.length > 0 && logs.length < MAX_BLOCKS) {
const current = queue.shift();
const state = world.getBlockState(current);
const id = getBlockId(state);
// Match log and wood blocks (oak_log, birch_wood, stripped_spruce_log, etc.)
if (!id.includes('_log') && !id.includes('_wood')) continue;
logs.push(current);
for (const [dx, dy, dz] of offsets) {
const neighbor = new BlockPos(
current.getX() + dx,
current.getY() + dy,
current.getZ() + dz
);
const nKey = neighbor.getX() + ',' + neighbor.getY() + ',' + neighbor.getZ();
if (!visited.has(nKey)) {
visited.add(nKey);
queue.push(neighbor);
}
}
}
return logs;
}
function onEnable() {
Logger.info('[Treecapitator] Loaded!');
Events.on('blockBreak', (player, pos, state, world) => {
// Re-entrancy guard: skip if this break was triggered by us
const key = pos.getX() + ',' + pos.getY() + ',' + pos.getZ();
if (processing.has(key)) return;
// Only activate for log blocks
const blockId = getBlockId(state);
if (!blockId.includes('_log')) return;
// Sneak to disable -- lets players break single logs normally
if (player.isShiftKeyDown()) return;
// Require Woodcutting level 5
if (getWoodcuttingLevel(player) < 5) return;
// Find all connected logs via BFS
const logs = findTree(world, pos);
// Destroy each log, using the re-entrancy guard to prevent recursion
for (const logPos of logs) {
const logKey = logPos.getX() + ',' + logPos.getY() + ',' + logPos.getZ();
processing.add(logKey);
try {
world.destroyBlock(logPos, true, player);
} finally {
processing.delete(logKey);
}
}
// Visual and audio feedback
sound(player, 'block.wood.break', 1.0, 0.8);
msg(player, '<green>Felled <yellow>' + logs.length + '</yellow> logs!');
});
}
function onDisable() {
processing.clear();
}

Customization points:

  • Change MAX_BLOCKS to control the maximum tree size
  • Change the required level from 5 to any value
  • Add or remove block ID suffixes in the match check to support modded log types

What it does: When a player with Mining level 5 or higher sneaks and breaks an ore block, the entire connected vein of the same ore type is mined at once.

Key concepts: BFS with same-block-type matching, cardinal direction neighbors only, sneak-to-activate.

/// <reference path="types/ceremony-api.d.ts" />
const BuiltInRegistries = Java.type('net.minecraft.core.registries.BuiltInRegistries');
const BlockPos = Java.type('net.minecraft.core.BlockPos');
const JourneyApi = Java.type('com.briar.journey.api.JourneyApi');
const MAX_BLOCKS = 64;
const processing = new Set();
function getBlockId(state) {
try {
const key = BuiltInRegistries.BLOCK.getKey(state.getBlock());
return key ? key.toString() : '';
} catch (e) { return ''; }
}
function getMiningLevel(player) {
try {
const api = JourneyApi.getInstance();
const profile = api.getPlayerProfile(player.getUUID());
return profile ? (profile.getSkill('mining')?.getLevel() ?? 0) : 0;
} catch (e) { return 0; }
}
// BFS: find connected blocks of the exact same type (cardinal directions only)
function findVein(world, startPos, targetId) {
const queue = [startPos];
const visited = new Set();
const blocks = [];
visited.add(startPos.getX() + ',' + startPos.getY() + ',' + startPos.getZ());
// Cardinal directions only (6 neighbors, no diagonals)
const directions = [
[1, 0, 0], [-1, 0, 0],
[0, 1, 0], [0, -1, 0],
[0, 0, 1], [0, 0, -1]
];
while (queue.length > 0 && blocks.length < MAX_BLOCKS) {
const current = queue.shift();
const state = world.getBlockState(current);
const id = getBlockId(state);
// Only match the exact same ore type
if (id !== targetId) continue;
blocks.push(current);
for (const [dx, dy, dz] of directions) {
const neighbor = new BlockPos(
current.getX() + dx,
current.getY() + dy,
current.getZ() + dz
);
const nKey = neighbor.getX() + ',' + neighbor.getY() + ',' + neighbor.getZ();
if (!visited.has(nKey)) {
visited.add(nKey);
queue.push(neighbor);
}
}
}
return blocks;
}
function onEnable() {
Logger.info('[Vein Miner] Loaded!');
Events.on('blockBreak', (player, pos, state, world) => {
const key = pos.getX() + ',' + pos.getY() + ',' + pos.getZ();
if (processing.has(key)) return;
const blockId = getBlockId(state);
// Only activate for ore blocks
if (!blockId.endsWith('_ore') && !blockId.includes('raw_')) return;
// Must be sneaking to activate (opposite of treecapitator)
if (!player.isShiftKeyDown()) return;
// Require Mining level 5
if (getMiningLevel(player) < 5) return;
// Find the entire connected vein
const vein = findVein(world, pos, blockId);
// Break all blocks in the vein
for (const blockPos of vein) {
const bKey = blockPos.getX() + ',' + blockPos.getY() + ',' + blockPos.getZ();
processing.add(bKey);
try {
world.destroyBlock(blockPos, true, player);
} finally {
processing.delete(bKey);
}
}
sound(player, 'entity.experience_orb.pickup', 0.8, 1.0);
msg(player, '<aqua>Vein mined <yellow>' + vein.length + '</yellow> blocks!');
});
}
function onDisable() {
processing.clear();
}

Customization points:

  • Change MAX_BLOCKS (64 is conservative, 128 for larger veins)
  • Add diagonal neighbors to the directions array for looser vein matching
  • Modify the block ID check to support modded ores

What it does: When a player with Herbalism level 5 or higher harvests a fully-grown crop, the crop is automatically replanted at age 0 after a short delay.

Key concepts: Java block state properties, Game.scheduler.after for delayed replant, age property checking.

/// <reference path="types/ceremony-api.d.ts" />
const BuiltInRegistries = Java.type('net.minecraft.core.registries.BuiltInRegistries');
const BlockPos = Java.type('net.minecraft.core.BlockPos');
const JourneyApi = Java.type('com.briar.journey.api.JourneyApi');
// Blocks that count as crops
const CROP_IDS = new Set([
'minecraft:wheat',
'minecraft:carrots',
'minecraft:potatoes',
'minecraft:beetroots',
'minecraft:nether_wart'
]);
function getBlockId(state) {
try {
const key = BuiltInRegistries.BLOCK.getKey(state.getBlock());
return key ? key.toString() : '';
} catch (e) { return ''; }
}
function getHerbalismLevel(player) {
try {
const api = JourneyApi.getInstance();
const profile = api.getPlayerProfile(player.getUUID());
return profile ? (profile.getSkill('herbalism')?.getLevel() ?? 0) : 0;
} catch (e) { return 0; }
}
// Check if a crop is fully grown by reading the "age" block state property
function isFullyGrown(state) {
try {
const properties = state.getProperties();
for (const prop of properties) {
if (prop.getName() === 'age') {
const currentAge = state.getValue(prop);
// Get all possible values and find the max
const possibleValues = prop.getPossibleValues();
let maxAge = 0;
for (const val of possibleValues) {
if (val > maxAge) maxAge = val;
}
return currentAge >= maxAge;
}
}
} catch (e) {
Logger.warn('[GreenThumb] Failed to check crop age: ' + e.message);
}
return false;
}
function onEnable() {
Logger.info('[Green Thumb] Loaded!');
Events.on('blockBreak', (player, pos, state, world) => {
const blockId = getBlockId(state);
// Only activate for crop blocks
if (!CROP_IDS.has(blockId)) return;
// Must be fully grown
if (!isFullyGrown(state)) return;
// Require Herbalism level 5
if (getHerbalismLevel(player) < 5) return;
// Remember the block type and position for replanting
const block = state.getBlock();
const replantPos = new BlockPos(pos.getX(), pos.getY(), pos.getZ());
// Replant after a 2-tick delay (after the block is broken)
Game.scheduler.after(2, () => {
try {
// Place the crop back at age 0 (default state)
const defaultState = block.defaultBlockState();
world.setBlock(replantPos, defaultState, 3);
Game.particles.spawn(
world, replantPos.getX() + 0.5, replantPos.getY() + 0.5, replantPos.getZ() + 0.5,
ParticleTypes.HAPPY_VILLAGER, 5
);
} catch (e) {
Logger.warn('[GreenThumb] Failed to replant: ' + e.message);
}
});
msg(player, '<green>Auto-replanted!');
});
}
function onDisable() {}

Customization points:

  • Add modded crop IDs to the CROP_IDS set
  • Change the delay from 2 ticks to a longer value for a more visible replant animation
  • Add a chance-based system where higher Herbalism levels replant more reliably

What it does: When a player with Excavation level 5 or higher breaks dirt, sand, gravel, or clay, they have a chance to find bonus treasure items. Higher levels unlock better tiers with rarer drops.

Key concepts: Tiered reward tables, random chance rolls, command-based item giving.

/// <reference path="types/ceremony-api.d.ts" />
const BuiltInRegistries = Java.type('net.minecraft.core.registries.BuiltInRegistries');
const JourneyApi = Java.type('com.briar.journey.api.JourneyApi');
// Blocks that can yield treasures
const DIGGABLE = new Set([
'minecraft:dirt', 'minecraft:grass_block', 'minecraft:sand',
'minecraft:gravel', 'minecraft:clay', 'minecraft:soul_sand',
'minecraft:red_sand', 'minecraft:coarse_dirt', 'minecraft:rooted_dirt'
]);
// Reward tiers -- checked from best to worst, first match wins
const TIERS = [
{
minLevel: 25, chance: 0.02,
items: ['minecraft:diamond', 'minecraft:emerald', 'minecraft:netherite_scrap']
},
{
minLevel: 15, chance: 0.05,
items: ['minecraft:gold_ingot', 'minecraft:lapis_lazuli', 'minecraft:amethyst_shard']
},
{
minLevel: 10, chance: 0.10,
items: ['minecraft:iron_ingot', 'minecraft:redstone', 'minecraft:quartz']
},
{
minLevel: 5, chance: 0.15,
items: ['minecraft:coal', 'minecraft:flint', 'minecraft:bone', 'minecraft:string']
},
];
function getBlockId(state) {
try {
const key = BuiltInRegistries.BLOCK.getKey(state.getBlock());
return key ? key.toString() : '';
} catch (e) { return ''; }
}
function getExcavationLevel(player) {
try {
const api = JourneyApi.getInstance();
const profile = api.getPlayerProfile(player.getUUID());
return profile ? (profile.getSkill('excavation')?.getLevel() ?? 0) : 0;
} catch (e) { return 0; }
}
function rollTreasure(level) {
for (const tier of TIERS) {
if (level >= tier.minLevel && Math.random() < tier.chance) {
return tier.items[Math.floor(Math.random() * tier.items.length)];
}
}
return null;
}
function runCommand(cmd) {
try {
Server.getCommands().performPrefixedCommand(
Server.createCommandSourceStack().withSuppressedOutput(), cmd
);
} catch (e) {
Logger.warn('[Treasures] Command failed: ' + e.message);
}
}
function onEnable() {
Logger.info('[Excavation Treasures] Loaded!');
Events.on('blockBreak', (player, pos, state, world) => {
const blockId = getBlockId(state);
if (!DIGGABLE.has(blockId)) return;
const level = getExcavationLevel(player);
if (level < 5) return;
const treasure = rollTreasure(level);
if (!treasure) return;
// Give the treasure item via server command
const name = player.getName().getString();
runCommand('give ' + name + ' ' + treasure + ' 1');
// Notify the player
const itemName = treasure.split(':')[1].replace(/_/g, ' ');
msg(player, '<gold>Treasure found! <yellow>' + itemName);
sound(player, 'entity.experience_orb.pickup', 0.6, 1.4);
// Sparkle particles at the dig site
Game.particles.spawn(
world, pos.getX() + 0.5, pos.getY() + 0.5, pos.getZ() + 0.5,
ParticleTypes.HAPPY_VILLAGER, 8
);
});
}
function onDisable() {}

Customization points:

  • Edit the TIERS array to change drop rates, level requirements, and item pools
  • Add modded items to the reward lists
  • Add the DIGGABLE set to include modded soil types

What it does: When a player with Archery level 10 or higher kills a mob with a bow, all mobs within 5 blocks of the killed mob are stunned (given Slowness and Weakness effects) for several seconds.

Key concepts: AABB entity search, MobEffectInstance creation, Java Holder access.

/// <reference path="types/ceremony-api.d.ts" />
const MobEffectInstance = Java.type('net.minecraft.world.effect.MobEffectInstance');
const BuiltInRegistries = Java.type('net.minecraft.core.registries.BuiltInRegistries');
const ResourceLocation = Java.type('net.minecraft.resources.ResourceLocation');
const JourneyApi = Java.type('com.briar.journey.api.JourneyApi');
const AABB = Java.type('net.minecraft.world.phys.AABB');
const STUN_RADIUS = 5.0;
const STUN_DURATION = 80; // 4 seconds in ticks
function getArcheryLevel(player) {
try {
const api = JourneyApi.getInstance();
const profile = api.getPlayerProfile(player.getUUID());
return profile ? (profile.getSkill('archery')?.getLevel() ?? 0) : 0;
} catch (e) { return 0; }
}
// Get a mob effect holder by ID (required for MobEffectInstance in modern MC)
function getEffectHolder(effectId) {
try {
const key = ResourceLocation.parse(effectId);
const effect = BuiltInRegistries.MOB_EFFECT.get(key);
if (!effect) return null;
return BuiltInRegistries.MOB_EFFECT.wrapAsHolder(effect);
} catch (e) {
Logger.warn('[ArcheryDaze] Failed to get effect holder: ' + e.message);
return null;
}
}
function onEnable() {
Logger.info('[Archery Daze] Loaded!');
Events.on('entityKilledOther', (world, killer, killed) => {
// Check if the killer is a player
const ServerPlayer = Java.type('net.minecraft.server.level.ServerPlayer');
if (!(killer instanceof ServerPlayer)) return;
// Require Archery level 10
if (getArcheryLevel(killer) < 10) return;
// Check if the player is holding a bow (killed with ranged weapon)
try {
const mainHand = killer.getMainHandItem();
const itemId = BuiltInRegistries.ITEM.getKey(mainHand.getItem()).toString();
if (itemId !== 'minecraft:bow' && itemId !== 'minecraft:crossbow') return;
} catch (e) { return; }
// Find all mobs within STUN_RADIUS of the killed entity
const pos = killed.position();
const searchBox = new AABB(
pos.x() - STUN_RADIUS, pos.y() - STUN_RADIUS, pos.z() - STUN_RADIUS,
pos.x() + STUN_RADIUS, pos.y() + STUN_RADIUS, pos.z() + STUN_RADIUS
);
const LivingEntity = Java.type('net.minecraft.world.entity.LivingEntity');
const nearby = world.getEntitiesOfClass(LivingEntity.class, searchBox);
// Apply stun effects to nearby mobs (not players)
const slowness = getEffectHolder('minecraft:slowness');
const weakness = getEffectHolder('minecraft:weakness');
let stunCount = 0;
for (const entity of nearby) {
if (entity instanceof ServerPlayer) continue; // Do not stun players
if (entity === killed) continue;
try {
if (slowness) entity.addEffect(new MobEffectInstance(slowness, STUN_DURATION, 2));
if (weakness) entity.addEffect(new MobEffectInstance(weakness, STUN_DURATION, 1));
stunCount++;
} catch (e) {
Logger.warn('[ArcheryDaze] Failed to apply effect: ' + e.message);
}
}
if (stunCount > 0) {
msg(killer, '<yellow>Daze! Stunned <gold>' + stunCount + '</gold> nearby mobs!');
sound(killer, 'entity.elder_guardian.curse', 0.5, 1.5);
}
});
}
function onDisable() {}

Customization points:

  • Adjust STUN_RADIUS and STUN_DURATION to balance the ability
  • Change the effect amplifier (the 2 in MobEffectInstance(slowness, duration, 2))
  • Add more effects like Blindness or Glowing

What it does: When a player with Taming level 10 or higher catches a Pokemon, there is a bonus chance (scaling with level) that the Pokemon becomes shiny.

Key concepts: Cobblemon events, Pokemon manipulation, probability-based mechanics.

/// <reference path="types/ceremony-api.d.ts" />
const JourneyApi = Java.type('com.briar.journey.api.JourneyApi');
// Base shiny chance bonus: 5% at level 10, scaling up to 15% at level 50
const BASE_CHANCE = 0.05;
const MAX_CHANCE = 0.15;
const MIN_LEVEL = 10;
const MAX_LEVEL = 50;
function getTamingLevel(player) {
try {
const api = JourneyApi.getInstance();
const profile = api.getPlayerProfile(player.getUUID());
return profile ? (profile.getSkill('taming')?.getLevel() ?? 0) : 0;
} catch (e) { return 0; }
}
function getShinyChance(level) {
if (level < MIN_LEVEL) return 0;
// Linear scale from BASE_CHANCE at MIN_LEVEL to MAX_CHANCE at MAX_LEVEL
const t = Math.min((level - MIN_LEVEL) / (MAX_LEVEL - MIN_LEVEL), 1.0);
return BASE_CHANCE + t * (MAX_CHANCE - BASE_CHANCE);
}
function onEnable() {
Logger.info('[Shiny Charm] Loaded!');
// Listen for Cobblemon Pokemon capture events
Events.on('pokemonCaptured', (event) => {
try {
const player = event.getPlayer();
const pokemon = event.getPokemon();
// Skip if already shiny
if (pokemon.getShiny()) return;
const level = getTamingLevel(player);
const chance = getShinyChance(level);
if (chance <= 0) return;
// Roll for shiny
if (Math.random() < chance) {
pokemon.setShiny(true);
const pokemonName = pokemon.getSpecies().getName();
msg(player, '<gradient:gold:yellow>Your ' + pokemonName + ' became SHINY!</gradient>');
title(player, '<gold>SHINY!', '<yellow>' + pokemonName, 5, 40, 10);
sound(player, 'entity.player.levelup', 1.0, 1.5);
// Announce to server
const playerName = player.getName().getString();
broadcast('<gold>' + playerName + ' caught a <yellow>SHINY ' + pokemonName + '</yellow>!');
}
} catch (e) {
Logger.warn('[ShinyCharm] Error: ' + e.message);
}
});
}
function onDisable() {}

Customization points:

  • Adjust BASE_CHANCE, MAX_CHANCE, MIN_LEVEL, and MAX_LEVEL to tune the probability curve
  • Change the scaling from linear to exponential for a different feel
  • Add special particle effects or fireworks for shiny catches

What it does: Players with Taming level 5 or higher experience reduced stamina drain when riding Pokemon. The reduction scales with level, and at max level (50), stamina drain is completely eliminated.

Key concepts: Event modification (setRideStamina), level-scaled reduction, infinite stamina at max level.

/// <reference path="types/ceremony-api.d.ts" />
const JourneyApi = Java.type('com.briar.journey.api.JourneyApi');
const MIN_LEVEL = 5;
const MAX_LEVEL = 50;
// At MIN_LEVEL: reduce drain by 20%. At MAX_LEVEL: reduce by 100% (infinite stamina).
const MIN_REDUCTION = 0.20;
const MAX_REDUCTION = 1.00;
function getTamingLevel(player) {
try {
const api = JourneyApi.getInstance();
const profile = api.getPlayerProfile(player.getUUID());
return profile ? (profile.getSkill('taming')?.getLevel() ?? 0) : 0;
} catch (e) { return 0; }
}
function getReduction(level) {
if (level < MIN_LEVEL) return 0;
if (level >= MAX_LEVEL) return MAX_REDUCTION;
const t = (level - MIN_LEVEL) / (MAX_LEVEL - MIN_LEVEL);
return MIN_REDUCTION + t * (MAX_REDUCTION - MIN_REDUCTION);
}
function onEnable() {
Logger.info('[Stamina Master] Loaded!');
// Listen for Pokemon ride stamina drain events
Events.on('rideStaminaDrain', (event) => {
try {
const player = event.getPlayer();
const level = getTamingLevel(player);
const reduction = getReduction(level);
if (reduction <= 0) return;
if (reduction >= 1.0) {
// Max level: infinite stamina, set drain to 0
event.setRideStamina(event.getCurrentStamina());
} else {
// Reduce the stamina drain proportionally
const currentStamina = event.getCurrentStamina();
const newStamina = event.getNewStamina();
const drain = currentStamina - newStamina;
const reducedDrain = drain * (1.0 - reduction);
event.setRideStamina(currentStamina - reducedDrain);
}
} catch (e) {
Logger.warn('[StaminaMaster] Error: ' + e.message);
}
});
}
function onDisable() {}

Customization points:

  • Change MIN_REDUCTION and MAX_REDUCTION to adjust the scaling curve
  • Add an action bar message showing the current stamina reduction percentage
  • Add a visual effect when infinite stamina is active at max level

What it does: Players with Taming level 5 or higher earn bonus currency when they win wild Pokemon battles. The reward scales with their Taming level.

Key concepts: Cobblemon battle events, ActorType checking, economy command integration.

/// <reference path="types/ceremony-api.d.ts" />
const JourneyApi = Java.type('com.briar.journey.api.JourneyApi');
const MIN_LEVEL = 5;
const BASE_REWARD = 50; // Currency at MIN_LEVEL
const MAX_REWARD = 500; // Currency at level 50
const MAX_LEVEL = 50;
function getTamingLevel(player) {
try {
const api = JourneyApi.getInstance();
const profile = api.getPlayerProfile(player.getUUID());
return profile ? (profile.getSkill('taming')?.getLevel() ?? 0) : 0;
} catch (e) { return 0; }
}
function calculateReward(level) {
if (level < MIN_LEVEL) return 0;
const t = Math.min((level - MIN_LEVEL) / (MAX_LEVEL - MIN_LEVEL), 1.0);
return Math.floor(BASE_REWARD + t * (MAX_REWARD - BASE_REWARD));
}
function runCommand(cmd) {
try {
Server.getCommands().performPrefixedCommand(
Server.createCommandSourceStack().withSuppressedOutput(), cmd
);
} catch (e) {
Logger.warn('[BattleRewards] Command failed: ' + e.message);
}
}
function onEnable() {
Logger.info('[Battle Rewards] Loaded!');
Events.on('battleVictory', (event) => {
try {
// Get all battle actors
const winners = event.getWinners();
for (const actor of winners) {
// Only reward player actors, not wild Pokemon or NPCs
const ActorType = Java.type('com.cobblemon.mod.common.api.battles.model.actor.ActorType');
if (actor.getType() !== ActorType.PLAYER) continue;
// Check if this was a wild battle (not PvP or NPC)
if (!event.wasWildBattle()) continue;
const player = actor.getEntity();
if (!player) continue;
const level = getTamingLevel(player);
const reward = calculateReward(level);
if (reward <= 0) continue;
// Give currency via economy command
const name = player.getName().getString();
runCommand('eco give ' + name + ' ' + reward);
msg(player, '<gold>Battle bonus! <yellow>+' + reward + ' coins</yellow> <gray>(Taming Lv.' + level + ')');
sound(player, 'entity.experience_orb.pickup', 0.7, 1.2);
}
} catch (e) {
Logger.warn('[BattleRewards] Error: ' + e.message);
}
});
}
function onDisable() {}

Customization points:

  • Change BASE_REWARD and MAX_REWARD to balance your server economy
  • Replace the eco give command with your economy mod’s specific command
  • Add bonus multipliers for defeating higher-level Pokemon
  • Restrict rewards to specific battle types (wild only, or include NPC trainers)

ScriptSkillMin LevelKey Pattern
TreecapitatorWoodcutting5BFS traversal, re-entrancy guard
Vein MinerMining5BFS with type matching
Green ThumbHerbalism5Block state properties, delayed replant
Excavation TreasuresExcavation5Tiered rewards, random chance
Archery DazeArchery10AABB search, MobEffectInstance
Shiny CharmTaming10Pokemon events, probability scaling
Stamina MasterTaming5Event modification, level scaling
Battle RewardsTaming5Battle events, economy integration