API Documentation
Developer guide for integrating with SteadfastVillagers.
Overview
SteadfastVillagers exposes its functionality through:
- Bukkit Events - Listen for villager block actions
- Service Classes - Direct access to managers
- 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 asyncVillagerBlockTradeEvent- Fired async, use scheduler for Bukkit API- Buffer modifications - Use
lockForWrite()/unlockForWrite()
Sync Operations
VillagerBlockManagermethods - Safe on main threadDatabaseManager.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
- Cache plugin instance - Don't look it up repeatedly
- Use async saves -
saveBlock()oversaveBlockSync() - Batch operations - Modify multiple blocks, then save
- Avoid main thread DB - Never call sync DB methods in event handlers
- 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
- Issues: GitHub Issues
- Discord: SteadfastSMP Discord