-
Notifications
You must be signed in to change notification settings - Fork 0
developer api
This plugin exposes a Java API through STEMCraftAPI.
import dev.stemcraft.api.STEMCraftAPI;
final class ExamplePluginBootstrap {
private final STEMCraftAPI api = STEMCraftAPI.api();
}From STEMCraftAPI, you can access:
-
commands()- command registration -
coordinateBar()- player-specific coordinate boss-bar additions -
comets()- destructive comet events with optional direction and loot -
config()- YAML config files/sections -
database()- SQL execution/query helpers -
dialogs()- cross-platform Java/Bedrock input dialogs -
events()- event registration helpers -
gifts()- portable multi-item gifts and item-spec parsing -
holograms()- hologram operations -
items()- custom item helpers, includinggetItemName(ItemStack)for the player-facing name of vanilla, renamed, or STEMCraft custom items -
locales()- locale/text resolution -
mailboxes()- queued mail and item delivery -
messages()- formatted messaging and token processing -
minigames()- minigame framework -
motd()- MOTD control -
players()- player utilities/logging service -
placedObjects()- persistent placed block/entity assemblies -
punishments()- punishment records/actions -
playerStats()- stat recording/query/export -
recipes()- custom recipes -
regions()- region registration/listeners -
tabComplete()- tab completion providers -
tasks()- sync/async scheduling + persistent timers -
web()- lightweight web endpoint service -
worlds()- world load/settings/generation services
import dev.stemcraft.api.STEMCraftAPI;
final class ExampleCommands {
void register(STEMCraftAPI api, org.bukkit.plugin.Plugin yourPlugin) {
api.commands().create("example")
.permission("stemcraft.command.example")
.usage("/example")
.executor((plugin, cmd, ctx) -> ctx.returnInfo("Hello from example."))
.register(yourPlugin);
}
}import dev.stemcraft.api.STEMCraftAPI;
final class ExampleEvents {
void register(STEMCraftAPI api) {
api.events().register(org.bukkit.event.player.PlayerJoinEvent.class,
event -> api.messages().info(event.getPlayer(), "Welcome!"));
}
}Companion plugins can append live, player-specific information to /coordbar. Return null when an entry is not currently relevant. Lower priorities render first, and registering the same plugin and ID replaces the previous provider.
import dev.stemcraft.api.STEMCraftAPI;
import org.bukkit.Location;
import net.kyori.adventure.text.Component;
final class ExampleCoordinateBar {
void register(STEMCraftAPI api, org.bukkit.plugin.Plugin yourPlugin, java.util.Map<java.util.UUID, Location> homes) {
api.coordinateBar().register(yourPlugin, "home-distance", 200, player -> {
Location home = homes.get(player.getUniqueId());
if (home == null) return null;
return Component.text("Home " + Math.round(player.getLocation().distance(home)) + "m");
});
}
}Call api.coordinateBar().unregister(yourPlugin, "home-distance") when removing the integration. Registrations owned by disabled plugins are discarded automatically.
MailboxService#send(MailSendRequest) queues a written letter and optional item stacks. Recipients use only their UUID; STEMCraft resolves their current known name internally. Senders use either a player UUID or a system/plugin display string.
import dev.stemcraft.api.STEMCraftAPI;
import dev.stemcraft.api.service.mailbox.MailSendRequest;
import dev.stemcraft.api.service.mailbox.MailSendResult;
import org.bukkit.Material;
import org.bukkit.inventory.ItemStack;
import java.util.List;
final class ExampleMail {
MailSendResult send(STEMCraftAPI api, java.util.UUID recipientUuid) {
return api.mailboxes().send(new MailSendRequest(
"Daily Rewards",
recipientUuid,
"Here is today's reward.",
List.of(new ItemStack(Material.EMERALD, 2))
));
}
}Use result.queued() and result.message() to inspect the outcome. See Mailboxes for the complete feature guide.
Mail uses the configured delivery delay by default (-1). Pass a tick delay as the final constructor argument to override it: 0 delivers on the next queue pass, while a positive value applies that delay. This example delays delivery by 15 seconds:
import dev.stemcraft.api.STEMCraftAPI;
import dev.stemcraft.api.service.mailbox.MailSendRequest;
import java.util.List;
final class ExampleDelayedMail {
void send(STEMCraftAPI api, java.util.UUID recipientUuid, org.bukkit.inventory.ItemStack gift) {
api.mailboxes().send(new MailSendRequest(
"Minigame Rewards",
recipientUuid,
"Congratulations!",
List.of(gift),
300L
));
}
}GiftService creates a single custom Gift containing multiple stacks. Gifts can be built from ItemStack values or compact item specifications and sent through the Mailbox API. See Gifts.
CometService launches a comet at an exact impact location with either a random or supplied horizontal direction. Optional CometLoot entries place randomized reward blocks around the terminal geode.
import dev.stemcraft.api.STEMCraftAPI;
import org.bukkit.util.Vector;
import org.bukkit.Material;
import dev.stemcraft.api.service.comet.CometLoot;
final class ExampleComet {
void launch(STEMCraftAPI api, org.bukkit.Location impactLocation) {
api.comets().launch(
impactLocation,
new Vector(1, 0, -1),
new CometLoot(Material.GOLD_BLOCK, 2, 15),
new CometLoot(Material.EMERALD_BLOCK, 1, 4)
);
}
}See Comets for behavior, overloads, validation, and configuration.
Dynamic holograms are runtime registrations identified by a stable type and context. They can be anchored to a location or an entity UUID and can provide player-specific visibility and content.
import dev.stemcraft.api.STEMCraftAPI;
import net.kyori.adventure.text.Component;
final class ExampleDynamicHologram {
void create(STEMCraftAPI api, String questId, java.util.UUID npcUuid, java.util.function.Predicate<org.bukkit.entity.Player> available) {
api.holograms().createDynamic(
"quest",
questId,
npcUuid,
2.2D,
available,
player -> Component.text("!")
);
}
}State changes should invalidate the affected rendering:
import dev.stemcraft.api.STEMCraftAPI;
final class ExampleHologramRefresh {
void refreshAndDelete(STEMCraftAPI api, String questId, org.bukkit.entity.Player player) {
api.holograms().refreshDynamic("quest", questId, player);
api.holograms().deleteDynamic("quest", questId);
}
}The service manages Java/Bedrock rendering, player movement, world and chunk availability, moving entity anchors, range/line-of-sight rules, and resource-pack token refreshes. Dynamic registrations are intentionally not persisted; the owning feature re-registers them from its own stable records during startup.
DialogService builds one logical form for both Paper's Java dialog UI and Geyser's Bedrock Cumulus forms.
import dev.stemcraft.api.STEMCraftAPI;
import net.kyori.adventure.text.Component;
final class ExampleDialog {
boolean open(STEMCraftAPI api, org.bukkit.entity.Player player) {
return api.dialogs().create("example:feedback")
.title(Component.text("Feedback"))
.body(Component.text("Tell us what happened."))
.textInput("subject", Component.text("Subject"), "", 64)
.multilineTextInput("message", Component.text("Message"), "", 256, 4)
.submit(Component.text("Send"), response -> {
String subject = response.text("subject");
String message = response.text("message");
player.sendMessage(subject + ": " + message);
})
.cancel(Component.text("Cancel"), () -> { })
.open(player);
}
}open returns false if the appropriate client UI cannot be opened. Callbacks run on the server thread.
Messages can provide an explicit MessageType and optional context:
import dev.stemcraft.api.STEMCraftAPI;
import dev.stemcraft.api.service.message.MessageType;
final class ExampleMessage {
void send(STEMCraftAPI api, org.bukkit.entity.Player player) {
api.messages().send(player, MessageType.INFO, "survival", "You have mail");
}
}Configured strings can route themselves with leading directives:
/info/<gold>You have mail/survival//info/<gold>You have mail/info//survival/<gold>You have mail/survival/info/<gold>You have mail
Directives are processed only at the beginning of trusted plugin/config messages. Player-authored chat is not directive-processed. Later directives override earlier values.
Context prefixes are configured under logging.contexts. Optional show-when and hide-when rules support world globs and permissions, including ! negation. With only show-when, the implicit default is hidden; with hide-when, or with both keys, the implicit default is shown. An unknown context simply adds no prefix.
CustomItemDefinition combines an item template, placement mode, placed-object type, and Java/Bedrock client presentation metadata. PlacedObjectService persists stable assemblies containing a primary block plus role-labelled block/entity links. Features should resolve and delete assemblies through this service instead of storing transient Bukkit entity references.
import dev.stemcraft.api.STEMCraftAPI;
final class ExampleDatabase {
void init(STEMCraftAPI api) {
api.database().execute(
"CREATE TABLE IF NOT EXISTS example_data (id TEXT PRIMARY KEY, value TEXT);"
);
api.database().update(
"INSERT INTO example_data (id, value) VALUES (?, ?) " +
"ON CONFLICT(id) DO UPDATE SET value = excluded.value",
ps -> {
ps.setString(1, "row-1");
ps.setString(2, "hello");
}
);
}
}The minigame framework is available through api.minigames(). A minigame can
register a framework-managed team-selection policy and then leave lobby team
selection, provisional assignment, countdown gating, and HUD placeholders to
the shared runtime.
Common API types:
MiniGameServiceMiniGameMiniGameArenaMiniGameArenaHandlerMiniGameTeamMiniGameTeamSelectionInputMiniGameTeamSelectionPolicy
Example:
import dev.stemcraft.api.STEMCraftAPI;
import dev.stemcraft.api.minigame.MiniGame;
import dev.stemcraft.api.minigame.MiniGameArena;
import dev.stemcraft.api.minigame.MiniGameArenaHandler;
import dev.stemcraft.api.minigame.MiniGameTeam;
import dev.stemcraft.api.minigame.MiniGameTeamSelectionInput;
import dev.stemcraft.api.minigame.MiniGameTeamSelectionPolicy;
import org.bukkit.entity.Player;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.Set;
final class ExampleMiniGameBootstrap {
void register(STEMCraftAPI api, MiniGameArenaHandler handler) {
MiniGame game = api.minigames()
.create("examplegame", handler)
.setTeamSelectionPolicy(new MiniGameTeamSelectionPolicy() {
@Override
public List<MiniGameTeam> assignableTeams(MiniGameArena arena, Map<Player, String> preferences) {
return new ArrayList<MiniGameTeam>(arena.getTeams());
}
@Override
public int teamCapacity(MiniGameArena arena, MiniGameTeam team) {
return 2;
}
@Override
public int requiredActiveTeams(MiniGameArena arena) {
return 2;
}
@Override
public Set<MiniGameTeamSelectionInput> supportedInputs(MiniGameArena arena) {
return Set.of(MiniGameTeamSelectionInput.FLOOR, MiniGameTeamSelectionInput.HOTBAR);
}
});
game.registerHud(
MiniGameArena.ArenaStatus.WAITING,
List.of("Example: {arena:name}"),
List.of(
"<gold>Example: <white>{arena:name}",
"Selected: {player:selected-team}",
"Auto: {arena:auto-selected-count}",
"{arena:lobby-team-line-1}",
"{arena:lobby-team-line-2}"
)
);
}
}Arena-side setup used by the framework:
arena.setLobbySpawn(...)arena.setLobbyRegion(...)-
arena.setTeamSelectionInput(MiniGameTeamSelectionInput.FLOOR)orHOTBAR
Framework-owned behavior:
- floor and hotbar team selection
- strict floor validation against
lobbyRegion - provisional lobby assignment and team balancing
- countdown stop/reset when the minimum active-team requirement is no longer met
- shared lobby placeholders such as
{player:selected-team}and{arena:lobby-team-line-1}
Shared supply-drop helpers on MiniGameArena:
-
findRandomSupplyDropLocation(List<Material> allowedSurfaceMaterials, int attempts)searches the arena region for a valid landing column -
spawnSupplyDropCrate(ItemStack item, Location landingLocation)spawns the shared descending crate/parachute presentation and lands it as a loot chest -
clearAllSupplyDrops()removes active drop visuals and landed framework-managed drop chests
Shared player-pull helpers on MiniGameArena:
-
pullPlayer(Player player, Location target, double blocksPerSecond)pulls one player toward a target location -
pullPlayers(Map<Player, Location> targets, double blocksPerSecond)pulls multiple players in one framework-managed sequence -
cancelPlayerPulls()cancels any active arena-managed player pull -
isPlayerBeingPulled(Player player)checks whether the framework currently has that player in a pull sequence
The framework owns the crate animation and cleanup lifecycle. Individual minigames still choose the drop items, valid surface materials, when to trigger drops, and which players should be pulled to which destinations.
Protection service types available through STEMCraftAPI:
ProtectionServiceProtectionTypeProtectionRuleProtectionRequest
This is the shared path for requesting timed protections such as teleport damage immunity while allowing gameplay systems like minigames to deny those protections centrally through registered rules.
ResourcePackGenerator is now an interface-based extension point.
This is a breaking API change from the previous abstract-class model.
Generators written against the older extends ResourcePackGenerator API must
be migrated to the new interface contract.
Plugin authors should usually implement ResourcePackGenerator directly.
AbstractResourcePackGenerator is available as an optional convenience helper
when you want STEMCraft to store the generator id and generator config for you.
Core generator contract:
-
id()Returns the unique generator id used for registration, dependency resolution, config lookup, and logging. -
onLoad(ConfigSectionView)Called after dependency checks and before the generator is activated. Use this to read and cache generator-specific config from disk. -
onUnload()Called when the generator is unregistered or the service unloads. -
generate(ResourcePackBuildContext)Performs one build pass for one target.generate(context)is invoked once per supportedResourcePackBuildTarget. -
requiredGenerators()Returns generator ids that must already be active before this generator can be activated. -
supportedFormats()Declares the pack-format range the generator can build. -
supports(ResourcePackBuildTarget)Optional finer-grained target check. By default this delegates tosupportedFormats().contains(target.packFormat()).
ResourcePackBuildContext provides:
-
target()The explicitResourcePackBuildTargetcurrently being built. -
writer()TheResourcePackWriteroutput abstraction for the current target. -
config()The generator-specificConfigSectionView.
See bundled implementations in:
dev.stemcraft.service.resourcepack.generators.PackMetaGeneratordev.stemcraft.service.resourcepack.generators.GlyphGeneratordev.stemcraft.service.resourcepack.generators.MinecraftPackGenerator
ImageMapService renders a BufferedImage across a wall-mounted mosaic of filled maps. Displays use stable string IDs and can be created, updated, and deleted without callers managing map IDs or item-frame entities.
import dev.stemcraft.api.STEMCraftAPI;
import org.bukkit.block.BlockFace;
final class ExampleImageMap {
void create(STEMCraftAPI api, org.bukkit.Location backingBlock, java.awt.image.BufferedImage image, java.util.function.Consumer<dev.stemcraft.api.service.imagemap.ImageMapClick> handleClick) {
api.imageMaps().create("quests:lobby", backingBlock, BlockFace.NORTH, 4, 3);
api.imageMaps().render("quests:lobby", image);
api.imageMaps().onClick("quests:lobby", handleClick);
api.imageMaps().delete("quests:lobby");
}
}The location is the bottom-left backing block when viewing the display from the front. Click callbacks receive the player and selected map tile on both Java and Geyser-translated Bedrock interactions. Map canvases are contextual per viewer and active maps are resent after player join, ensuring reconnecting players receive dynamic pixels as well as the item frames. Runtime registrations and callbacks are recreated by the owning feature after restart.
- Prefer service interfaces from
api/src/main/java/dev/stemcraft/api/service/.... - Keep world/player data in DB for persistent state; keep YAML for static configuration.
- Use locale keys +
messages()when possible, rather than hardcoding text.
Use api.stemBot() to acquire an exclusive private guide session, speak, toggle following, check action names, run a specific action, move or teleport the guide, and consume replies through a private chat callback. See STEMBot API usage and lifecycle. Callers retain ownership of their interaction rules and reply handling.
- Home
- Architecture
- Services
- Features
- Build a Custom Tool
- Agriculture and Cooking
- Survival quality of life
- Custom items
- Slime and Magma Buckets
- Comets
- Rotten Flesh Uses
- Iron Golem Poppy Luring
- Gifts
- Mailboxes
- Notice boards
- Named regions
- Interactive guide callbacks
- Quests
- Commands
- API
- Minigames
- Minigame placeholders
- Tab completion
- Configuration
- Entitlements and badges
- Player reset service