Moving Citizens
HyCitizens lets you control citizen movement entirely from code — send a citizen to a position, start or stop a patrol route, build patrol paths on the fly, and query what any citizen is currently doing.
All movement methods are accessed through CitizensManager:
CitizensManager manager = HyCitizensPlugin.get().getCitizensManager();
Movement requires a PATROL citizen
For moveCitizenToPosition and patrol to work, the citizen's MovementBehavior type must be set to "PATROL" when setting up citizens you intend to move programmatically.
Moving to a Position
moveCitizenToPosition
void moveCitizenToPosition(@Nonnull String citizenId, @Nonnull Vector3d position)
Commands a citizen to walk to a specific world coordinate. Under the hood, this spawns an invisible target entity at the destination and tells the NPC to pathfind toward it. The citizen will walk at their configured speed and stop when they arrive.
If the citizen is currently patrolling, this movement overrides the patrol temporarily. The patrol is not cancelled — call stopCitizenPatrol explicitly if you don't want them to resume it.
stopCitizenMovement
void stopCitizenMovement(@Nonnull String citizenId)
Cancels any in-progress moveCitizenToPosition command and removes the target entity. This does not stop an active patrol — use stopCitizenPatrol for that.
// Send a citizen to the town square when an event starts
manager.moveCitizenToPosition(herald.getId(), new Vector3d(200, 64, 300));
// Stop them when the event ends
manager.stopCitizenMovement(herald.getId());
Controlling Patrols
startCitizenPatrol
void startCitizenPatrol(@Nonnull String citizenId, @Nonnull String pathName)
Starts a citizen patrolling along a named PatrolPath. The path must already exist — either created in the UI or registered with patrolManager.savePath(path). The citizen immediately begins walking toward the first waypoint, following the path's configured loop mode (LOOP or PING_PONG).
stopCitizenPatrol
void stopCitizenPatrol(@Nonnull String citizenId)
Ends the citizen's patrol session. The citizen stops at their current position.
isCitizenPatrolling
boolean isCitizenPatrolling(@Nonnull String citizenId)
Returns true if this citizen has an active patrol session.
getCitizenActivePatrolPath
@Nullable String getCitizenActivePatrolPath(@Nonnull String citizenId)
Returns the name of the patrol path the citizen is currently following, or null if they're not patrolling.
// Start patrolling
manager.startCitizenPatrol(guard.getId(), "NorthGateRoute");
// Check their status
if (manager.isCitizenPatrolling(guard.getId())) {
String route = manager.getCitizenActivePatrolPath(guard.getId());
System.out.println("Guard is patrolling: " + route);
}
// Stop the patrol (e.g. when combat starts)
manager.stopCitizenPatrol(guard.getId());
Managing Patrol Paths
Use PatrolManager directly for path CRUD operations. Get it from the manager:
PatrolManager patrolManager = manager.getPatrolManager();
Creating a Path
// PatrolManager API
void savePath(@Nonnull PatrolPath path)
boolean deletePath(@Nonnull String name)
PatrolPath getPath(@Nonnull String name)
Collection<PatrolPath> getAllPaths()
List<String> getAllPathNames()
Build a PatrolPath, add waypoints to it, then call savePath to register and persist it. After saving, any citizen can be assigned to it with startCitizenPatrol.
// Build a patrol route from code
PatrolPath route = new PatrolPath("NorthGateRoute", PatrolPath.LoopMode.PING_PONG);
route.addWaypoint(new PatrolWaypoint(100, 64, 200, 2.0f)); // 2-second pause here
route.addWaypoint(new PatrolWaypoint(120, 64, 200, 0.0f));
route.addWaypoint(new PatrolWaypoint(140, 64, 200, 1.0f));
PatrolManager patrolManager = manager.getPatrolManager();
patrolManager.savePath(route); // register + save to disk
// Assign it to a citizen
manager.startCitizenPatrol(guard.getId(), "NorthGateRoute");
Modifying an Existing Path
// Add a waypoint to an existing path
boolean addWaypoint(@Nonnull String pathName, @Nonnull PatrolWaypoint waypoint)
// Remove a waypoint by index
boolean removeWaypoint(@Nonnull String pathName, int index)
// Change the pause time at a specific waypoint
boolean setWaypointPause(@Nonnull String pathName, int index, float pauseSeconds)
All three methods return false if the path name doesn't exist or the index is out of bounds.
PatrolManager pm = manager.getPatrolManager();
// Add a new stop to an existing path
pm.addWaypoint("NorthGateRoute", new PatrolWaypoint(160, 64, 200, 3.0f));
// Change the pause at waypoint index 0 to 5 seconds
pm.setWaypointPause("NorthGateRoute", 0, 5.0f);
// Remove the second waypoint (index 1)
pm.removeWaypoint("NorthGateRoute", 1);
Deleting a Path
// Stop all citizens on this path before deleting it
for (CitizenData c : manager.getAllCitizens()) {
if ("OldRoute".equals(manager.getCitizenActivePatrolPath(c.getId()))) {
manager.stopCitizenPatrol(c.getId());
}
}
patrolManager.deletePath("OldRoute");
Assigning a Patrol Path via PathConfig
Alternatively, you can assign a patrol path through a citizen's PathConfig, which persists the assignment in the citizen's data so they resume the patrol automatically after a server restart.
// Set a citizen to always patrol "VillageLoop" on spawn
PathConfig pathConfig = new PathConfig();
pathConfig.setFollowPath(true);
pathConfig.setPathName("VillageLoop");
pathConfig.setLoopMode("LOOP");
citizen.setPathConfig(pathConfig);
// Use the one-call convenience method to save + respawn
manager.setCitizenPathConfig(citizen.getId(), pathConfig);
PathConfig vs. startCitizenPatrol
PathConfig persists the patrol assignment in the citizen's data. The citizen will resume the patrol when the server restarts. Use this for permanent patrol routes.
startCitizenPatrol is a runtime-only command — it doesn't persist to disk. Use this for temporary or event-driven movement. The patrol stops if the citizen is respawned.
Full Example: Event-Driven Citizen Movement
This example shows a guard who patrols normally, then runs to a specific position when an alarm sounds, and returns to patrol when the alarm ends.
CitizensManager manager = HyCitizensPlugin.get().getCitizensManager();
CitizenData guard = manager.getCitizen(guardId);
// Set up patrol on startup
PatrolPath route = new PatrolPath("GatePath", PatrolPath.LoopMode.PING_PONG);
route.addWaypoint(new PatrolWaypoint(100, 64, 200, 1.0f));
route.addWaypoint(new PatrolWaypoint(130, 64, 200, 1.0f));
manager.getPatrolManager().savePath(route);
manager.startCitizenPatrol(guard.getId(), "GatePath");
// When the alarm fires:
public void onAlarm(Vector3d alarmPosition) {
manager.stopCitizenPatrol(guard.getId());
manager.moveCitizenToPosition(guard.getId(), alarmPosition);
// Change attitude to aggressive
guard.setAttitude("AGGRESSIVE");
manager.updateCitizenNPC(guard, true);
}
// When the alarm ends:
public void onAlarmClear() {
manager.stopCitizenMovement(guard.getId());
manager.startCitizenPatrol(guard.getId(), "GatePath");
// Reset to passive
guard.setAttitude("PASSIVE");
manager.updateCitizenNPC(guard, true);
}