API Documentation

Developer guide for integrating with SteadfastVillagers.

Overview

SteadfastVillagers exposes its functionality through:

  1. Bukkit Events - Listen for villager block actions
  2. Service Classes - Direct access to managers
  3. Data Models - VillagerBlock and StoredTrade objects

Maven/Gradle Dependency

Maven

<repository>
    <id>steadfastsmp</id>
    <url>https://repo.steadfastsmp.com/releases</url>
</repository>

<dependency>
    <groupId>com.steadfastsmp</groupId>
    <artifactId>steadfastvillagers-api</artifactId>
    <version>1.2.0</version>
    <scope>provided</scope>
</dependency>

Gradle

repositories {
    maven("https://repo.steadfastsmp.com/releases")
}

dependencies {
    compileOnly("com.steadfastsmp:steadfastvillagers-api:1.2.0")
}

plugin.yml

depend: [SteadfastVillagers]  # Hard dependency
# or
softdepend: [SteadfastVillagers]  # Optional dependency

Getting the Plugin Instance

import com.steadfastsmp.villagerblocks.bukkit.SteadfastVillagersPlugin;

public class MyPlugin extends JavaPlugin {

    private SteadfastVillagersPlugin steadfastVillagers;

    @Override
    public void onEnable() {
        Plugin plugin = getServer().getPluginManager().getPlugin("SteadfastVillagers");
        if (plugin instanceof SteadfastVillagersPlugin) {
            steadfastVillagers = (SteadfastVillagersPlugin) plugin;
            getLogger().info("SteadfastVillagers integration enabled!");
        }
    }
}

Core Services

VillagerBlockManager

Manages all villager blocks in memory.

VillagerBlockManager blockManager = steadfastVillagers.getBlockManager();

// Get block at location
VillagerBlock block = blockManager.getBlockAt(location);

// Get all blocks
Collection<VillagerBlock> allBlocks = blockManager.getAllBlocks();

// Get blocks by owner
Collection<VillagerBlock> playerBlocks = blockManager.getBlocksByOwner(playerUuid);

// Get blocks in chunk
Collection<VillagerBlock> chunkBlocks = blockManager.getBlocksInChunk(chunk);

// Check if location has a villager block
boolean hasBlock = blockManager.hasBlockAt(location);

DatabaseManager

Handles persistence operations.

DatabaseManager dbManager = steadfastVillagers.getDatabaseManager();

// Save a block (async)
CompletableFuture<Void> future = dbManager.saveBlock(block);

// Save immediately (sync - use sparingly)
int blockId = dbManager.saveBlockSync(block);

// Delete a block
dbManager.deleteBlock(blockId);

// Load all blocks (usually done on startup)
CompletableFuture<List<VillagerBlock>> blocks = dbManager.loadAllBlocks();

ConfigManager

Access configuration values.

ConfigManager config = steadfastVillagers.getConfigManager();

// Conversion settings
Material conversionItem = config.getConversionItem();
int minLevel = config.getMinConversionLevel();
boolean consumeItem = config.isConsumeItem();

// Trade settings
String lockMode = config.getTradeLockMode(); // "VANILLA" or "INFINITE"
int activationRange = config.getActivationRange();
int cooldownTicks = config.getTradeCooldownTicks();

// Feature toggles
boolean xpEnabled = config.isXpEnabled();
boolean levelingEnabled = config.isLevelingEnabled();
boolean discountEnabled = config.isDiscountEnabled();

Data Models

VillagerBlock

Represents a converted villager block.

public interface VillagerBlock {
    // Identity
    int getId();
    Location getLocation();
    UUID getOwnerUuid();

    // Villager data
    Villager.Profession getProfession();
    Villager.Type getVillagerType();
    String getCustomName();
    int getVillagerLevel();
    int getVillagerXp();

    // Trades
    List<StoredTrade> getTrades();
    int getActiveTradeIndex();
    StoredTrade getActiveTrade();

    // Buffers
    Map<ItemStack, Integer> getInputBuffer();
    List<ItemStack> getOutputBuffer();

    // State
    double getDiscountPercent();
    int getStoredXp();
    long getTradesCompleted();
    long getLastTradeTime();

    // Locking
    boolean isWriteLocked();
    void lockForWrite();
    void unlockForWrite();

    // Version tracking (for sync)
    long getBufferVersion();
    void incrementBufferVersion();
}

StoredTrade

Represents a single trade from the villager.

public interface StoredTrade {
    // Trade items
    ItemStack getFirstIngredient();
    ItemStack getSecondIngredient();  // May be null
    ItemStack getResult();

    // Usage tracking
    int getMaxUses();
    int getCurrentUses();
    boolean isLocked();

    // Pricing
    int getOriginalPrice();
    int getDiscountedPrice(double discountPercent);

    // XP
    int getVillagerXpReward();
    int getPlayerXpReward();

    // State
    boolean isEnabled();
    void setEnabled(boolean enabled);
    void restock();
}

Events

VillagerBlockConvertEvent

Fired when a villager is converted to a block.

@EventHandler
public void onConvert(VillagerBlockConvertEvent event) {
    Player player = event.getPlayer();
    Villager villager = event.getVillager();
    Location location = event.getLocation();

    // Cancel the conversion
    if (someCondition) {
        event.setCancelled(true);
        player.sendMessage("Conversion not allowed here!");
        return;
    }

    // Modify the resulting block
    event.setDiscountPercent(0.5);  // Force 50% discount
}

VillagerBlockPlaceEvent

Fired when a villager block item is placed.

@EventHandler
public void onPlace(VillagerBlockPlaceEvent event) {
    Player player = event.getPlayer();
    VillagerBlock block = event.getVillagerBlock();
    Location location = event.getLocation();

    // Cancel placement
    if (someCondition) {
        event.setCancelled(true);
        return;
    }
}

VillagerBlockBreakEvent

Fired when a villager block is broken (with Silk Touch).

@EventHandler
public void onBreak(VillagerBlockBreakEvent event) {
    Player player = event.getPlayer();
    VillagerBlock block = event.getVillagerBlock();
    ItemStack drop = event.getDropItem();

    // Modify or cancel
    if (!canBreakHere(player, block)) {
        event.setCancelled(true);
        return;
    }
}

VillagerBlockTradeEvent

Fired when a trade is executed.

@EventHandler
public void onTrade(VillagerBlockTradeEvent event) {
    VillagerBlock block = event.getVillagerBlock();
    StoredTrade trade = event.getTrade();
    ItemStack input = event.getInput();
    ItemStack output = event.getOutput();

    // This is async - be careful with Bukkit API calls

    // Cancel the trade
    event.setCancelled(true);

    // Modify output (careful with item stacks)
    event.setOutput(modifiedOutput);
}

VillagerBlockUnconvertEvent

Fired when a block is unconverted back to a villager.

@EventHandler
public void onUnconvert(VillagerBlockUnconvertEvent event) {
    Player player = event.getPlayer();
    VillagerBlock block = event.getVillagerBlock();

    // The villager hasn't spawned yet at this point
    // Cancel to prevent unconversion
    event.setCancelled(true);
}

Example: Custom Economy Integration

public class VillagerShopIntegration implements Listener {

    private final Economy economy;
    private final double conversionCost = 1000.0;

    @EventHandler
    public void onConvert(VillagerBlockConvertEvent event) {
        Player player = event.getPlayer();

        // Charge for conversion
        if (!economy.has(player, conversionCost)) {
            event.setCancelled(true);
            player.sendMessage("You need $" + conversionCost + " to convert a villager!");
            return;
        }

        economy.withdrawPlayer(player, conversionCost);
        player.sendMessage("Charged $" + conversionCost + " for villager conversion.");
    }

    @EventHandler
    public void onTrade(VillagerBlockTradeEvent event) {
        // Log trades for economy tracking
        VillagerBlock block = event.getVillagerBlock();
        StoredTrade trade = event.getTrade();

        // Async-safe logging
        Bukkit.getScheduler().runTaskAsynchronously(plugin, () -> {
            logTrade(block.getId(), trade.getResult().getType().name());
        });
    }
}

Example: Custom Protection Integration

public class CustomProtectionHook {

    public boolean canAccess(Player player, Location location) {
        VillagerBlockManager manager = steadfastVillagers.getBlockManager();
        VillagerBlock block = manager.getBlockAt(location);

        if (block == null) return true;

        // Owner always has access
        if (block.getOwnerUuid().equals(player.getUniqueId())) {
            return true;
        }

        // Check your protection plugin
        return myProtectionPlugin.hasAccess(player, location);
    }
}

Thread Safety

Async Operations

  • DatabaseManager.saveBlock() - Returns CompletableFuture, runs async
  • VillagerBlockTradeEvent - Fired async, use scheduler for Bukkit API
  • Buffer modifications - Use lockForWrite() / unlockForWrite()

Sync Operations

  • VillagerBlockManager methods - Safe on main thread
  • DatabaseManager.saveBlockSync() - Blocks thread, avoid in event handlers
  • Events (except Trade) - Fired on main thread

Buffer Access Pattern

VillagerBlock block = ...;

try {
    block.lockForWrite();

    // Safely modify buffers
    Map<ItemStack, Integer> input = block.getInputBuffer();
    // ... modify input ...

    block.incrementBufferVersion();
} finally {
    block.unlockForWrite();
}

// Save changes
dbManager.saveBlock(block);

Performance Considerations

  1. Cache plugin instance - Don't look it up repeatedly
  2. Use async saves - saveBlock() over saveBlockSync()
  3. Batch operations - Modify multiple blocks, then save
  4. Avoid main thread DB - Never call sync DB methods in event handlers
  5. Check activation range - getBlocksInRange() is optimized

Version Compatibility

API Version Plugin Version Minecraft
1.2.x 1.2.0+ 1.21+, 26.1+
1.1.x 1.1.0 1.20.4+
1.0.x 1.0.0 1.20.4+

API follows semantic versioning. Minor version bumps are backwards compatible.

Support