The Java API

Everything goes through one interface, net.feelix.stagecoach.api.StagecoachAPI:

StagecoachAPI api = StagecoachAPI.get();
Method Does
getStops(MinecraftServer server) Every stop on the server
getStops(ServerLevel level) Every stop in one dimension
getStop(MinecraftServer server, long id) One stop by id, or empty
findStopByName(MinecraftServer server, String name) The stop with that name, any capitals; empty when there is none, or more than one
isDiscovered(ServerPlayer player, StopView stop) Whether the player knows it: discovered, their own, public, or discoveryRequired is off
discover(ServerPlayer player, StopView stop) Adds it to the player’s known stops, quietly: no message, no effect, no event, no advancement
forget(ServerPlayer player, StopView stop) Removes it from the player’s known stops
isTravelling(ServerPlayer player) Whether the player is on the road
startJourney(ServerPlayer player, StopView destination, JourneyOptions options) Sends the player on a journey; returns a JourneyResult
cancelJourney(ServerPlayer player) Ends the player’s journey at its origin, with the whole fare back (reason ADMIN)

The collections are copies: changing them changes nothing.

Stops

A StopView is a snapshot of a stop at the moment you asked for it:

Field Type Meaning
id long Its id; stays the same for the stop’s whole life, never reused
name String Its name
dimension ResourceKey<Level> The dimension it stands in
pos BlockPos The controller block
facing Direction The way the coach faces
wood String The wood, e.g. "spruce"
owner Optional<UUID> The player who owns it; empty for stops placed by commands or structures
isPublic, hidden, locked, free boolean Its flags
costMultiplier double Its own fare multiplier
scene Optional<Identifier> Its travel scene, if it has one (ResourceLocation in Mojang names before 1.21.11)
createdBy CreatedBy PLAYER, ADMIN or STRUCTURE

A stop placed by a structure joins the network once the chunk it’s in has generated.

Starting a journey

StopView abbey = api.findStopByName(server, "Old Abbey").orElseThrow();
JourneyResult result = api.startJourney(player, abbey, JourneyOptions.NORMAL);
if (!result.started()) {
    player.sendSystemMessage(Component.literal("Can't go: " + result));
}

JourneyOptions.NORMAL checks everything a player’s own journey checks: the player must stand within useRange of a stop they know (the journey leaves from the nearest one), know the destination, be out of combat and off cooldown, and pay the fare. The other options switch checks off:

// a free ride from wherever the player stands, even to a stop they haven't found
new JourneyOptions(true, false, true, true, false)
Option When true
free No fare
instant No time on the road: the player pays and steps out at the destination at once
ignoreRange The journey leaves from where the player stands, not from a stop; they come back there if it ends early
ignoreDiscovery The destination (and, without ignoreRange, the stop they leave from) needn’t be known
ignoreCombatLock A player hit moments ago can still leave

The permission to travel, the cooldown, the server’s dimension and distance limits, and travel rules still apply, and JourneyPreStartEvent still fires, so other mods can refuse.

JourneyResult Why
STARTED On the road (or arrived, for an instant journey)
NOT_ENOUGH_LEVELS, NOT_ENOUGH_ITEMS Can’t pay the fare
TOO_FAR Farther than maxDistance
CROSS_DIMENSION_DISABLED Another dimension, and the server doesn’t allow it
COMBAT_LOCKED Hit within combatLockSeconds
COOLDOWN Arrived within travelCooldownSeconds
NOT_DISCOVERED Doesn’t know the destination
OUT_OF_RANGE Not near a stop they know
CANCELLED_BY_EVENT A JourneyPreStartEvent listener (or a script) refused
ALREADY_TRAVELLING Already on the road
LOCKED_BY_RULE A travel rule locks it
NO_PERMISSION Lacks stagecoach.travel
UNAVAILABLE Anything else: the stop is gone, the player is a spectator or dead, or the destination is the stop they’re at

result.started() is true only for STARTED.

Discovering for a reason

discover is silent so you can choose the feedback. A quest reward that reveals a stop:

api.findStopByName(server, "Hollow Pass").ifPresent(stop -> {
    if (api.isDiscovered(player, stop)) return;
    api.discover(player, stop);
    player.sendSystemMessage(Component.literal("You learn the road to " + stop.name() + "."));
});

/stagecoach discover <player> <stop> is the other way: it fires StopDiscoverEvent (so listeners can refuse) and the stagecoach:stop_discovered advancement trigger, without the message or the effect.


Back to top

Stagecoach by Feelix. MIT licence with an attribution condition.

This site uses Just the Docs, a documentation theme for Jekyll.