ZScriptEngine engine = new ZScriptEngine();
ScriptableObject scope = engine.createSecureScope();
// Load script
engine.evaluateFile(Paths.get("animations/my_animation.js"), scope);
// Execute function
AnimationContext context = new AnimationContext(player, crate, inventory);
engine.executeFunction(scope, "onStart", context);
public record Animation(
String id,
List<AnimationPhase> phases,
JavaScriptFunction onComplete,
JavaScriptFunction onCancel
) {}
public record AnimationPhase(
String name,
long duration, // milliseconds
int interval, // ticks between onTick calls
SpeedCurve speedCurve, // Easing function
JavaScriptFunction onStart,
JavaScriptFunction onTick,
JavaScriptFunction onComplete
) {}
public enum SpeedCurve {
LINEAR, // Constant speed
EASE_IN, // Starts slow, accelerates
EASE_OUT, // Starts fast, decelerates
EASE_IN_OUT // Slow → Fast → Slow
}
public class AnimationExecutor {
void start(Player player, Animation animation, AnimationContext context);
void stop(Player player);
boolean isRunning(Player player);
}
public record AnimationContext(
PlayerWrapper player,
InventoryWrapper inventory,
CrateWrapper crate
) {}
public record TickData(
int tickNumber, // Current tick in phase
double progress, // 0.0 to 1.0 (with speed curve applied)
long elapsedTime // Milliseconds since phase start
) {}
public record RandomAlgorithm(
String id,
JavaScriptFunction selector
) {}
public record AlgorithmContext(
RewardsWrapper rewards,
HistoryWrapper history,
String crateId,
UUID playerUuid
) {}
algorithms.register("pity_system", function(context) {
var rewards = context.rewards();
var history = context.history();
// Get last 50 openings
var recentOpenings = history.getRecent(50);
// Count openings without rare reward
var openingsWithoutRare = 0;
for (var i = 0; i < recentOpenings.size(); i++) {
var opening = recentOpenings.get(i);
if (opening.crateId() === context.crateId()) {
var reward = rewards.getById(opening.rewardId());
if (reward && reward.weight() > 5.0) {
openingsWithoutRare++;
} else {
break;
}
}
}
// If 50 openings without rare, guarantee one
if (openingsWithoutRare >= 50) {
return rewards.filterByMaxWeight(5.0).weightedRandom();
}
return rewards.weightedRandom();
});
public record PlacedCrate(
UUID id,
String crateId,
String worldName,
int x, int y, int z,
DisplayType displayType,
String displayValue,
float yaw
) {}
@EventHandler
public void onRewardGiven(RewardGivenEvent event) {
Player player = event.getPlayer();
Crate crate = event.getCrate();
Reward reward = event.getReward();
// Log to external system
myLogger.log(player.getName() + " won " + reward.id() + " from " + crate.id());
// Broadcast rare rewards
if (reward.weight() < 5.0) {
Bukkit.broadcastMessage(
player.getName() + " won a rare reward from " + crate.displayName()
);
}
}
package fr.traqueur.crates.hooks.myplugin;
import fr.traqueur.crates.api.annotations.AutoHook;
import fr.traqueur.crates.api.hooks.Hook;
import fr.traqueur.crates.api.registries.*;
@AutoHook("MyPlugin") // Name of target plugin
public class MyPluginHook implements Hook {
@Override
public void onEnable() {
// Register providers, factories, etc.
ItemsProvidersRegistry itemsRegistry = Registry.get(ItemsProvidersRegistry.class);
itemsRegistry.register("MyPlugin", (player, itemId) -> {
// Return ItemStack from your plugin
return MyPlugin.getAPI().getItem(itemId);
});
CrateDisplayFactoriesRegistry displayRegistry = Registry.get(CrateDisplayFactoriesRegistry.class);
displayRegistry.register(DisplayType.MY_TYPE, new MyDisplayFactory());
}
}
// RewardsWrapper methods
var rewards = context.rewards();
rewards.weightedRandom(); // Standard weighted random
rewards.weightedRandom([reward1, reward2]); // From specific list
rewards.getAll(); // All rewards as list
rewards.size(); // Total count
rewards.getById("reward_id"); // Find by ID
rewards.filterByMinWeight(5.0); // Filter by min weight
rewards.filterByMaxWeight(10.0); // Filter by max weight
// HistoryWrapper methods
var history = context.history();
history.getRecent(50); // Last N openings
history.getRecentForCrate("crate_id", 50); // Last N for specific crate
history.getAll(); // All openings
// Convert JavaScript arrays to Java int[] arrays
var javaArray = ArrayHelper.toIntArray([1, 2, 3, 4, 5]);
context.inventory().rotateItems(javaArray);
CREATE TABLE users (
id UUID PRIMARY KEY
);
CREATE TABLE user_keys (
user_id UUID NOT NULL,
key_name VARCHAR(255) NOT NULL,
amount INT NOT NULL,
PRIMARY KEY (user_id, key_name),
FOREIGN KEY (user_id) REFERENCES users(id)
);
CREATE TABLE crate_openings (
id UUID PRIMARY KEY,
player_id UUID NOT NULL,
crate_id VARCHAR(255) NOT NULL,
reward_id VARCHAR(255) NOT NULL,
timestamp BIGINT NOT NULL
);
// Always check if event is cancelled
@EventHandler(priority = EventPriority.HIGHEST)
public void onRewardGiven(RewardGivenEvent event) {
if (event.isCancelled()) return; // If event becomes cancellable in future
// Your logic
}
// Listen at appropriate priority
// LOWEST - Execute first
// NORMAL - Default
// HIGHEST - Execute last
// MONITOR - Observe only (don't modify)
// Cache registry references
private final CratesRegistry cratesRegistry = Registry.get(CratesRegistry.class);
// Don't call Registry.get() in loops
for (Player player : players) {
Crate crate = cratesRegistry.getById("legendary"); // Good
}
// Use async for database operations
usersManager.loadUser(uuid).thenAccept(user -> {
// Process user data
}).exceptionally(throwable -> {
Logger.error("Failed to load user", throwable);
return null;
});
// Return to main thread for Bukkit API
usersManager.loadUser(uuid).thenAcceptAsync(user -> {
player.sendMessage("Loaded!");
}, Bukkit.getScheduler().getMainThreadExecutor(plugin));
// Use speedCurve for smooth animations
{
speedCurve: "EASE_OUT", // Decelerates naturally
onTick: function(context, tickData) {
var progress = tickData.progress(); // 0.0 to 1.0
// Use progress for smooth transitions
}
}
// Clean up in onCancel
onCancel: function(context) {
context.inventory().clear();
context.player().sendMessage("<red>Cancelled");
}
// Cache rewards list
algorithms.register("my_algo", function(context) {
var rewards = context.rewards();
var allRewards = rewards.getAll(); // Cache
// Don't call getAll() in loops
for (var i = 0; i < 100; i++) {
// Use allRewards
}
return rewards.weightedRandom();
});
// Always handle Optional properly
Optional<Crate> optCrate = registry.getById("legendary");
optCrate.ifPresent(crate -> {
// Process crate
});
// Or with fallback
Crate crate = registry.getById("legendary")
.orElseThrow(() -> new IllegalStateException("Crate not found"));