Addon Development
This page explains the safest ways to build around KMD Travel without copying or republishing KMD itself.
KMD is designed to be extended through dependencies, event profiles, datapacks, commands, config files, and compatibility mods. The safest addon is a separate project that requires KMD Travel and adds content around it.
Quick Rules
Allowed addon patterns:
- Require KMD Travel as a dependency.
- Add custom event profiles.
- Add datapack recipe overrides.
- Add quest-book or command-block integrations.
- Add compatibility with modded mobs, dimensions, biomes, or structures.
- Call KMD commands from your own mod, quest, script, or datapack flow.
Avoid:
- Bundling KMD Travel inside your addon jar.
- Copying KMD source code into your addon.
- Copying KMD models, textures, sounds, UI art, or branding into your addon.
- Publishing a modified KMD jar as your addon.
See the project LICENSE.md for the full license terms.
Stable Extension Points
These are the parts addon makers should prefer because they are least likely to break:
| Extension Point | Best For | Stability |
|---|---|---|
| Event profile JSON | New encounters, custom mobs, biome/dimension rules | Recommended |
| Minecraft commands | Quest mods, command blocks, scripted events | Recommended |
| Datapack recipes | Changing crafting recipes | Recommended |
| Resource packs | Personal/server visual changes | Safe if assets are original |
| Mod dependencies | Java/Kotlin compatibility mods | Good, but avoid internals |
| Internal KMD classes | Direct code calls into KMD systems | Not stable unless documented later |
Mod ID And IDs
KMD Travel uses this mod id:
kmdtravel
Common IDs:
kmdtravel:fast_travel_post
kmdtravel:spruce_fast_travel_post
kmdtravel:birch_fast_travel_post
kmdtravel:jungle_fast_travel_post
kmdtravel:acacia_fast_travel_post
kmdtravel:dark_oak_fast_travel_post
kmdtravel:mangrove_fast_travel_post
kmdtravel:cherry_fast_travel_post
kmdtravel:shared_fast_travel_post
Use these IDs for recipes, commands, tags, datapacks, and compatibility checks.
Adding KMD As A Dependency
Fabric fabric.mod.json
Use depends if your addon requires KMD to load:
{
"schemaVersion": 1,
"id": "my_kmd_addon",
"version": "1.0.0",
"name": "My KMD Addon",
"depends": {
"fabricloader": ">=0.16.0",
"minecraft": "~1.21.1",
"kmdtravel": ">=0.2.0"
}
}
Use suggests only if your addon can still work without KMD:
{
"suggests": {
"kmdtravel": ">=0.2.0"
}
}
NeoForge neoforge.mods.toml
[[dependencies.my_kmd_addon]]
modId="kmdtravel"
type="required"
versionRange="[0.2.0,)"
ordering="AFTER"
side="BOTH"
Forge 1.20.1 mods.toml
[[dependencies.my_kmd_addon]]
modId="kmdtravel"
mandatory=true
versionRange="[0.2.0,)"
ordering="AFTER"
side="BOTH"
Gradle Development Setup
If KMD is not published to a Maven yet, the simplest local addon setup is to place a KMD jar in your addon’s libs folder.
Fabric Example
dependencies {
modImplementation files("libs/kmdtravel-fabric-0.2.0.jar")
}
NeoForge Example
dependencies {
implementation files("libs/kmdtravel-neoforge-0.2.0.jar")
}
Forge Example
dependencies {
implementation fg.deobf(files("libs/kmdtravel-forge-0.2.0.jar"))
}
If KMD is later published to a Maven, prefer the Maven dependency instead of a local libs jar.
Public Java API V1
KMD now exposes a small Java API package for addons:
com.kmdtravel.api.KMDTravelApi
com.kmdtravel.api.KMDTravelEventListener
com.kmdtravel.api.TravelLocationView
com.kmdtravel.api.TravelEventView
Use this package instead of calling KMD internals such as travel managers, packet classes, saved-data classes, or GUI classes directly.
What The API Supports
| API Method | Use Case |
|---|---|
KMDTravelApi.isTraveling(player) | Check whether a player is currently fast travelling. |
KMDTravelApi.requestTravel(player, sourceId, destinationId) | Start KMD fast travel from another mod. |
KMDTravelApi.finishEncounter(player) | Finish the current encounter and resume fast travel. |
KMDTravelApi.cancelTravel(player) | Cancel travel without forcing the player back to start. |
KMDTravelApi.cancelTravelToStart(player) | Cancel travel and return the player to the starting post. |
KMDTravelApi.getTravelLocations(level) | Read all known travel posts for a server/world. |
KMDTravelApi.getTravelLocation(level, uuid) | Read one travel post by UUID. |
KMDTravelApi.findTravelLocationIdByName(level, name) | Find a post UUID by its visible name. |
KMDTravelApi.hasDiscovered(player, uuid) | Check whether a player discovered a post. |
KMDTravelApi.discoverLocation(player, uuid) | Mark a post as discovered for a player. |
KMDTravelApi.getDiscoveredLocationIds(player) | Read the player’s discovered post IDs. |
KMDTravelApi.previewAmbushChance(player, sourceId, destinationId) | Calculate the same ambush percent shown by KMD. |
KMDTravelApi.registerTravelListener(listener) | Listen to travel and encounter lifecycle events. |
Travel Lifecycle Listener Example
This example listens for KMD travel events without touching KMD internals:
import com.kmdtravel.api.KMDTravelApi;
import com.kmdtravel.api.KMDTravelEventListener;
import com.kmdtravel.api.TravelEventView;
import com.kmdtravel.api.TravelLocationView;
import net.minecraft.server.level.ServerPlayer;
public final class MyKmdHooks {
public static void init() {
KMDTravelApi.registerTravelListener(new KMDTravelEventListener() {
@Override
public void onTravelStarted(ServerPlayer player, TravelLocationView source, TravelLocationView destination) {
System.out.println(player.getGameProfile().getName() + " started travelling to " + destination.name());
}
@Override
public void onEncounterStarted(ServerPlayer player, TravelEventView event) {
System.out.println("KMD encounter started: " + event.id());
}
@Override
public void onEncounterFinished(ServerPlayer player, TravelEventView event) {
System.out.println("KMD encounter finished: " + event.id());
}
@Override
public void onTravelFinished(ServerPlayer player, TravelLocationView destination) {
System.out.println(player.getGameProfile().getName() + " arrived at " + destination.name());
}
});
}
}
Starting Travel From An Addon
import com.kmdtravel.api.KMDTravelApi;
import net.minecraft.server.level.ServerLevel;
import net.minecraft.server.level.ServerPlayer;
import java.util.UUID;
public final class MyTravelStarter {
public static void sendPlayerToPost(ServerPlayer player, ServerLevel level, String sourceName, String destinationName) {
UUID source = KMDTravelApi.findTravelLocationIdByName(level, sourceName).orElse(null);
UUID destination = KMDTravelApi.findTravelLocationIdByName(level, destinationName).orElse(null);
if (source != null && destination != null) {
KMDTravelApi.requestTravel(player, source, destination);
}
}
}
Unlocking Posts From An Addon
import com.kmdtravel.api.KMDTravelApi;
import com.kmdtravel.api.TravelLocationView;
import net.minecraft.server.level.ServerLevel;
import net.minecraft.server.level.ServerPlayer;
public final class MyUnlocks {
public static void discoverSharedKingdomRoads(ServerPlayer player, ServerLevel level) {
for (TravelLocationView location : KMDTravelApi.getTravelLocations(level)) {
if (location.name().startsWith("Kingdom Road")) {
KMDTravelApi.discoverLocation(player, location.id());
}
}
}
}
Finishing A KMD Encounter From Code
Use this when your addon has its own boss, puzzle, timer, or quest logic:
import com.kmdtravel.api.KMDTravelApi;
import net.minecraft.server.level.ServerPlayer;
public final class MyEncounterLogic {
public static void onCustomBossDefeated(ServerPlayer player) {
if (KMDTravelApi.isTraveling(player)) {
KMDTravelApi.finishEncounter(player);
}
}
}
API Stability Notes
The com.kmdtravel.api package is intended to stay stable across compatible KMD releases. Internal packages such as com.kmdtravel.travel, com.kmdtravel.network, com.kmdtravel.client, and com.kmdtravel.eventconfig may still change.
If your addon needs something that is not available through KMDTravelApi, open an issue and ask for a new API method instead of depending on internals.
Event Profile Addons
The most addon-friendly way to add content is to ship or document KMD event profile JSON.
KMD event profiles are world/server specific. Server owners can place templates in:
.minecraft/kmdtravel/events/
When a new world is created, KMD copies template profiles into that world’s event folder.
A world-specific event folder looks like:
.minecraft/kmdtravel/events/<world-name>_<world-id>/
Example:
.minecraft/kmdtravel/events/my_world_a1b2c3d4/goblin_roads.json
Minimal Event Profile Example
{
"id": "goblin_roads",
"name": "Goblin Roads",
"events": [
{
"id": "goblin_ambush",
"enabled": true,
"title": "Goblin Ambush",
"description": "A pack of goblins rushes from the roadside.",
"passive": false,
"endsByKill": true,
"durationSeconds": 60,
"dimension": "minecraft:overworld",
"biome": "",
"timeOfDay": "BOTH",
"selectionWeight": 1.0,
"avoidChance": 0.15,
"mobs": [
{
"mobId": "minecraft:zombie",
"amount": 3,
"spawnRange": 16,
"displayName": "Goblin",
"nbt": "{PersistenceRequired:1b}"
}
],
"commands": []
}
]
}
Field names may change as KMD develops. If an event does not load, create a similar event in the in-game editor, save it, then compare the generated JSON.
Custom Mob NBT
KMD stores mob NBT as plain text SNBT inside event JSON.
Simple example:
{PersistenceRequired:1b}
Named skeleton with a sword:
{CustomName:'{"text":"Road Warden","color":"gold"}',PersistenceRequired:1b,HandItems:[{id:"minecraft:iron_sword",count:1b},{id:"minecraft:air",count:0b}]}
Slow invisible mob with only a helmet/head visible:
{Invisible:1b,Silent:1b,CustomNameVisible:0b,PersistenceRequired:1b,Attributes:[{Name:"minecraft:generic.movement_speed",Base:0.08}],ArmorItems:[{id:"minecraft:air",count:0b},{id:"minecraft:air",count:0b},{id:"minecraft:air",count:0b},{id:"minecraft:player_head",count:1b}],HandItems:[{id:"minecraft:air",count:0b},{id:"minecraft:air",count:0b}]}
Minecraft 1.20.1 and 1.21.x use different item/NBT formats in some places. If a complex head or component fails, test it first with Minecraft’s /summon command for that exact version.
Event Commands
KMD event commands run through KMD, so they can use KMD placeholders.
Supported placeholders:
{player}
{uuid}
{x}
{y}
{z}
{event_tag}
Examples:
effect give {player} minecraft:blindness 10 1
playsound minecraft:entity.ghast.scream host {player} ~ ~ ~ 1 0.4
title {player} actionbar {"text":"Something watches you...","color":"dark_red"}
summon minecraft:zombie {x} {y} {z} {Tags:["{event_tag}"]}
Important distinction:
{player}works only inside KMD event commands.- Command blocks, chat, and quest mods should use real selectors like
@p,@a, or a player name.
Finishing Encounters From Addons Or Quest Mods
Use this command when an external system decides the encounter is complete:
/kmdtravel finishencounter <target>
Examples:
/kmdtravel finishencounter @p
/kmdtravel finishencounter Ninjasummoner
Good use cases:
- FTB Quests reward command.
- Command block puzzle completion.
- Server script after a boss dies.
- Custom addon event logic.
Do not type <target> literally. Use a selector or player name.
Calling KMD Commands From A Fabric Addon
This avoids relying on KMD internals. Your addon only needs KMD installed and then can execute KMD commands through the server command system.
import net.fabricmc.fabric.api.command.v2.CommandRegistrationCallback;
import net.minecraft.commands.Commands;
public final class MyKmdAddon {
public static void init() {
CommandRegistrationCallback.EVENT.register((dispatcher, registryAccess, environment) -> {
dispatcher.register(Commands.literal("myaddon_finish_kmd")
.requires(source -> source.hasPermission(2))
.executes(context -> {
String command = "kmdtravel finishencounter @p";
context.getSource().getServer().getCommands().performPrefixedCommand(context.getSource(), command);
return 1;
}));
});
}
}
Calling KMD Commands From A NeoForge Addon
import com.mojang.brigadier.CommandDispatcher;
import net.minecraft.commands.CommandSourceStack;
import net.minecraft.commands.Commands;
import net.neoforged.bus.api.SubscribeEvent;
import net.neoforged.neoforge.event.RegisterCommandsEvent;
public final class MyKmdAddonCommands {
@SubscribeEvent
public static void register(RegisterCommandsEvent event) {
CommandDispatcher<CommandSourceStack> dispatcher = event.getDispatcher();
dispatcher.register(Commands.literal("myaddon_finish_kmd")
.requires(source -> source.hasPermission(2))
.executes(context -> {
String command = "kmdtravel finishencounter @p";
context.getSource().getServer().getCommands().performPrefixedCommand(context.getSource(), command);
return 1;
}));
}
}
Recipe Addons
Use datapacks to replace KMD recipes.
Example path for modern versions:
data/kmdtravel/recipe/shared_fast_travel_post.json
Example shaped recipe:
{
"type": "minecraft:crafting_shaped",
"pattern": [
"DCD",
"FFF",
"DND"
],
"key": {
"D": { "item": "minecraft:diamond" },
"C": { "item": "minecraft:compass" },
"F": { "item": "minecraft:nether_brick_fence" },
"N": { "item": "minecraft:nether_brick" }
},
"result": {
"id": "kmdtravel:shared_fast_travel_post",
"count": 1
}
}
Minecraft 1.20.1 may use slightly different recipe result syntax and the older recipes folder naming in some loader setups.
Tags And Compatibility
If your addon needs to identify KMD blocks/items, prefer IDs and tags rather than copying classes.
Example item tag:
data/my_kmd_addon/tags/item/kmd_posts.json
{
"replace": false,
"values": [
"kmdtravel:fast_travel_post",
"kmdtravel:spruce_fast_travel_post",
"kmdtravel:birch_fast_travel_post",
"kmdtravel:jungle_fast_travel_post",
"kmdtravel:acacia_fast_travel_post",
"kmdtravel:dark_oak_fast_travel_post",
"kmdtravel:mangrove_fast_travel_post",
"kmdtravel:cherry_fast_travel_post",
"kmdtravel:shared_fast_travel_post"
]
}
Suggested Addon Types
Event Pack Addon
Adds themed JSON profiles for a modpack or server.
Examples:
- Undead roads.
- Pirate coasts.
- Nether raids.
- Fantasy kingdom encounters.
Quest Integration Addon
Uses quest rewards or server commands to:
- Assign KMD profiles.
- Finish encounters.
- Trigger effects.
- Unlock new travel danger phases.
Datapack Recipe Addon
Changes crafting costs for:
- Regular travel posts.
- Shared travel posts.
- Server economy balance.
Compatibility Addon
Adds event profiles using mobs from another mod.
Example:
{
"mobId": "examplemod:troll",
"amount": 1,
"spawnRange": 24,
"displayName": "Road Troll",
"nbt": "{PersistenceRequired:1b}"
}
What Is Not Stable Yet
The following are internal implementation details and may change between KMD versions:
- Client map rendering classes.
- Network packet class names.
- In-game editor screen internals.
- Fast travel route/session internals.
- Saved-data implementation classes.
If you need one of these to be stable, open an issue asking for a small public API. That is better than depending on internals that may change.