Scope
- HyperFactions should integrate with HyCitizens (v1.6.0+) to enforce zone protection flags on citizen NPC interactions
- 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
- Integration should use the same reflection-based, fail-open pattern as the existing KyuubiSoft Core integration (
KyuubiSoftIntegration.java)
- Integration should be optional — HyperFactions compiles and runs without HyCitizens present
- NPC role names (
HyCitizens_*, Citizen_*) should be added to isTameableCreatureRole() blocklist so citizens are classified as interactive NPCs, not tameable creatures
- 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:
CitizenInteractEvent — getCitizen(), 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
-
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.
-
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.
-
Plugin load order — if HyCitizens initializes after HyperFactions, HyCitizensPlugin.get() may return null. Need to handle late registration, similar to the KyuubiSoft pattern.
-
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
- HyCitizens Developer API: https://hycitizens.com/docs/api/
- HyCitizens Events: https://hycitizens.com/docs/api/events
- HyCitizens Full Example: https://hycitizens.com/docs/api/full-example
- Existing KyuubiSoft integration (reference):
KyuubiSoftIntegration.java
- HyCitizens decompiled analysis:
resources/docs/HyCitizens/ (local documentation from v1.6.0 inspection)
- HyCitizens GitHub: https://github.com/ElectroGamesDev/HyCitizens
Scope
NPC_INTERACTis denied, the interaction should be blocked with a protection denial messageKyuubiSoftIntegration.java)HyCitizens_*,Citizen_*) should be added toisTameableCreatureRole()blocklist so citizens are classified as interactive NPCs, not tameable creatures/f admin integration) should show HyCitizens detection statusDeveloper API
HyCitizens provides a public developer API with documented event listeners and cancellable events:
HyCitizensPlugin.get().getCitizensManager()manager.addCitizenInteractListener(event -> { ... })CitizenInteractEvent—getCitizen(),getPlayerRef(),setCancelled(true)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+):
Key Differences from KyuubiSoft Integration
CoreAPI.getInstance()HyCitizensPlugin.get().getCitizensManager()CitizenDialogInterceptor(return true to block)CitizenInteractListener(setCancelled to block)CitizenData)removeDialogInterceptor()removeCitizenInteractListener()NPC Role Classification
Add to
isTameableCreatureRole()blocklist inProtectionChecker.java:HyCitizens_prefix — all dynamically generated citizen rolesCitizen_prefix — all 48 bundled fallback rolesSuggested File Structure
HyCitizensIntegration.java— new file, mirrorsKyuubiSoftIntegration.javapatternProtectionChecker.java— addHyCitizens_andCitizen_to role blocklistAdminIntegrationHandler.java— add HyCitizens status to integration displayRisks and Alternatives
PacketWatcher bypasses mixin interception — HyCitizens uses raw
PacketWatcher(SyncInteractionChainspackets) instead of Hytale's interaction event system. HyperProtect-Mixin'sSimpleInstantInteractionGatewill never fire for citizen interactions. The listener-based approach via the public API is the correct integration path.Position source — KyuubiSoft checks the player's position for zone lookup. HyCitizens provides both the player position (via
getPlayerRef()) and the citizen position (viaCitizenData). The citizen position is more appropriate since the NPC is the entity being interacted with.Plugin load order — if HyCitizens initializes after HyperFactions,
HyCitizensPlugin.get()may return null. Need to handle late registration, similar to the KyuubiSoft pattern.Alternative: PacketWatcher-level interception — register our own
PacketWatcherto interceptSyncInteractionChainsbefore HyCitizens processes them. Not recommended:PacketAdapters.registerInboundhas no priority system, making execution order unpredictable. The public API is the correct approach.References and Media
KyuubiSoftIntegration.javaresources/docs/HyCitizens/(local documentation from v1.6.0 inspection)