Skip to content

[Feat] HyCitizens NPC zone protection integration #87

@derrickmehaffy

Description

@derrickmehaffy

Scope

  1. HyperFactions should integrate with HyCitizens (v1.6.0+) to enforce zone protection flags on citizen NPC interactions
  2. When a player interacts with a HyCitizens NPC inside a protected zone where NPC_INTERACT is denied, the interaction should be blocked with a protection denial message
  3. Integration should use the same reflection-based, fail-open pattern as the existing KyuubiSoft Core integration (KyuubiSoftIntegration.java)
  4. Integration should be optional — HyperFactions compiles and runs without HyCitizens present
  5. NPC role names (HyCitizens_*, Citizen_*) should be added to isTameableCreatureRole() blocklist so citizens are classified as interactive NPCs, not tameable creatures
  6. Admin status command (/f admin integration) should show HyCitizens detection status

Developer API

HyCitizens provides a public developer API with documented event listeners and cancellable events:

  • Entry point: HyCitizensPlugin.get().getCitizensManager()
  • Listener registration: manager.addCitizenInteractListener(event -> { ... })
  • Event: CitizenInteractEventgetCitizen(), getPlayerRef(), setCancelled(true)
  • Cancellation: blocks all downstream processing (messages, commands, animations)
  • Docs: API Overview | Events | Full Example

The API is stable and documented. We use reflection to access it (avoiding a compile-time dependency on HyCitizens), but the target classes and methods are part of the public API contract.

Implementation Details

Integration Path (reflection against public API)

The following reflection path targets HyCitizens' documented public API (v1.6.0+):

// 1. Get plugin instance (documented entry point)
Class<?> pluginClass = Class.forName("com.electro.hycitizens.HyCitizensPlugin");
Object plugin = pluginClass.getMethod("get").invoke(null);

// 2. Get CitizensManager (documented API surface)
Object manager = pluginClass.getMethod("getCitizensManager").invoke(plugin);

// 3. Load listener interface (public API)
Class<?> listenerClass = Class.forName("com.electro.hycitizens.events.CitizenInteractListener");

// 4. Create proxy that checks zone protection
Object proxy = Proxy.newProxyInstance(listenerClass.getClassLoader(),
    new Class<?>[]{ listenerClass }, (p, method, args) -> {
        if ("onCitizenInteract".equals(method.getName())) {
            Object event = args[0]; // CitizenInteractEvent
            // event.getPlayerRef() → PlayerRef
            // event.getCitizen() → CitizenData (has position, world)
            // Check ProtectionChecker.canInteract() at citizen position
            // If blocked: event.setCancelled(true) + send denial message
        }
        return null;
    });

// 5. Register listener (documented method)
manager.getClass().getMethod("addCitizenInteractListener", listenerClass)
    .invoke(manager, proxy);

Key Differences from KyuubiSoft Integration

Aspect KyuubiSoft HyCitizens
API entry CoreAPI.getInstance() HyCitizensPlugin.get().getCitizensManager()
API docs Minimal Full docs site
Interceptor type CitizenDialogInterceptor (return true to block) CitizenInteractListener (setCancelled to block)
Position source Player position Citizen position (from CitizenData)
Interaction types Dialog only F-key + left-click
Cleanup removeDialogInterceptor() removeCitizenInteractListener()

NPC Role Classification

Add to isTameableCreatureRole() blocklist in ProtectionChecker.java:

  • HyCitizens_ prefix — all dynamically generated citizen roles
  • Citizen_ prefix — all 48 bundled fallback roles

Suggested File Structure

  • HyCitizensIntegration.java — new file, mirrors KyuubiSoftIntegration.java pattern
  • ProtectionChecker.java — add HyCitizens_ and Citizen_ to role blocklist
  • AdminIntegrationHandler.java — add HyCitizens status to integration display

Risks and Alternatives

  1. PacketWatcher bypasses mixin interception — HyCitizens uses raw PacketWatcher (SyncInteractionChains packets) instead of Hytale's interaction event system. HyperProtect-Mixin's SimpleInstantInteractionGate will never fire for citizen interactions. The listener-based approach via the public API is the correct integration path.

  2. Position source — KyuubiSoft checks the player's position for zone lookup. HyCitizens provides both the player position (via getPlayerRef()) and the citizen position (via CitizenData). The citizen position is more appropriate since the NPC is the entity being interacted with.

  3. Plugin load order — if HyCitizens initializes after HyperFactions, HyCitizensPlugin.get() may return null. Need to handle late registration, similar to the KyuubiSoft pattern.

  4. Alternative: PacketWatcher-level interception — register our own PacketWatcher to intercept SyncInteractionChains before HyCitizens processes them. Not recommended: PacketAdapters.registerInbound has no priority system, making execution order unpredictable. The public API is the correct approach.

References and Media

  1. HyCitizens Developer API: https://hycitizens.com/docs/api/
  2. HyCitizens Events: https://hycitizens.com/docs/api/events
  3. HyCitizens Full Example: https://hycitizens.com/docs/api/full-example
  4. Existing KyuubiSoft integration (reference): KyuubiSoftIntegration.java
  5. HyCitizens decompiled analysis: resources/docs/HyCitizens/ (local documentation from v1.6.0 inspection)
  6. HyCitizens GitHub: https://github.com/ElectroGamesDev/HyCitizens

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    Status

    To triage

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions