Skip to main content

Documentation Index

Fetch the complete documentation index at: https://mintlify.com/ElectroGamesDev/HyCitizens/llms.txt

Use this file to discover all available pages before exploring further.

This page collects practical patterns for integrating HyCitizens into your own Hytale plugin. Each example is self-contained and focuses on a single common use case — combine them freely to build more complex NPC systems.

Shop Keeper NPC

Create a CitizenData with an F-key command action that opens a shop when a player interacts with it. The CommandAction constructor accepts the command string, a flag for whether to run it as the server, an optional delay, the interaction trigger ("F_KEY", "LEFT_CLICK", or "BOTH"), and a chance percentage.
CitizensManager manager = HyCitizensPlugin.get().getCitizensManager();

// Build a command action that opens a shop when F is pressed
CommandAction openShop = new CommandAction(
    "shop open blacksmith",  // command to run
    true,                    // run as server
    0.0f,                    // no delay
    "F_KEY",                 // trigger: F-key only
    100.0f                   // 100% chance
);

CitizenData shopKeeper = new CitizenData(
    UUID.randomUUID().toString(),
    "Blacksmith",
    "Human",
    worldId,
    new Vector3d(50, 64, 50),
    new Vector3f(0, 0, 0),
    1.0f, null, null,
    "", "",
    List.of(openShop),
    false, false, "", null, 0L,
    true  // rotateTowardsPlayer
);

manager.addCitizen(shopKeeper, true);

Cancelling Interactions Conditionally

Register a CitizenInteractListener via CitizensManager.addCitizenInteractListener() to intercept interactions before HyCitizens processes them. Call event.setCancelled(true) to suppress all commands and messages for that interaction. Listeners are called in registration order and stop as soon as one cancels the event.
manager.addCitizenInteractListener(event -> {
    // Only gate Citizens in the "QuestGiver" group
    if (!"QuestGiver".equals(event.getCitizen().getGroup())) return;

    // Check a hypothetical inventory condition
    if (!playerHasQuestItem(event.getPlayer())) {
        event.setCancelled(true);
        event.getPlayer().sendMessage(
            Message.raw("You must complete the previous quest first.").color(Color.YELLOW)
        );
    }
});

Reacting to Citizen Death

Register a CitizenDeathListener via CitizensManager.addCitizenDeathListener() to react when a Citizen is killed. The event exposes the CitizenData and the optional PlayerRef of the killer. Calling event.setCancelled(true) prevents the default respawn logic from running.
manager.addCitizenDeathListener(event -> {
    CitizenData citizen = event.getCitizen();
    if (!"Bosses".equals(citizen.getGroup())) return;

    PlayerRef killer = event.getKiller();
    if (killer == null) return;

    // Log the kill and schedule a delayed respawn
    System.out.println(killer.getUsername() + " defeated the boss " + citizen.getName());

    // Schedule a manual respawn after 60 seconds
    manager.scheduleCitizenRespawn(citizen, 60_000L);

    // Prevent default (immediate) respawn logic
    event.setCancelled(true);
});

Finding Nearby Citizens

CitizensManager.getCitizensNear() performs a simple 3-D distance check against each Citizen’s stored position and returns all Citizens within the given radius. The check uses the Citizen’s last known position (currentPosition) rather than querying the live entity transform, making it safe to call from any thread.
Vector3d playerPos = playerRef.getTransform().getPosition();
List<CitizenData> nearby = manager.getCitizensNear(playerPos, 20.0);

for (CitizenData citizen : nearby) {
    System.out.println("Nearby citizen: " + citizen.getName()
        + " at " + citizen.getPosition());
}

Respawning a Group of Citizens

CitizensManager.respawnCitizensInGroup() queues a full despawn-and-respawn cycle for every Citizen in the named group. Pass true for includeChildren to also affect sub-groups. The method returns the number of Citizens that were queued for respawn.
// Respawn all Citizens in the "Town/Guards" group and its sub-groups
int count = manager.respawnCitizensInGroup("Town/Guards", true, true);
System.out.println("Respawned " + count + " guard citizens.");
Use slash-delimited group names (e.g. Town/Guards) to organise Citizens hierarchically. respawnCitizensInGroup with includeChildren = true will also respawn Citizens in Town/Guards/Elite and any other nested sub-group.
All CitizensManager methods are thread-safe and can be called from any thread. Internally, operations that touch live world entities (spawning, despawning, position updates) are dispatched onto the world thread via World.execute().

Build docs developers (and LLMs) love