Version: 0.10.0 | 44 subcommands across 10 categories
Architecture documentation for the HyperFactions command system.
HyperFactions uses a subcommand-based dispatcher pattern built on Hytale's AbstractPlayerCommand system. The main /faction command routes to category-specific subcommands.
FactionCommand (dispatcher): /f, /hf, /faction, /hyperfactions
│
├─► FactionSubCommand (base class)
│ │
│ ├─► command/faction/ (7 subcommands: create, disband, rename, desc, color, open, close)
│ ├─► command/member/ (7 subcommands: invite, accept, leave, kick, promote, demote, transfer)
│ ├─► command/territory/ (4 subcommands: claim, unclaim, overclaim, stuck)
│ ├─► command/teleport/ (3 subcommands: home, sethome, delhome)
│ ├─► command/relation/ (4 subcommands: ally, enemy, neutral, relations)
│ ├─► command/info/ (8 subcommands: info, list, map, members, who, power, leaderboard, logs)
│ ├─► command/social/ (3 subcommands: request, invites, chat)
│ ├─► command/ui/ (2 subcommands: gui, settings)
│ ├─► HelpSubCommand (1 subcommand: help)
│ └─► command/admin/ (25+ admin subcommands with nested routing)
│
└─► FactionCommandContext (execution state, --text flag)
| Class | Path | Purpose |
|---|---|---|
| FactionCommand | command/FactionCommand.java |
Main dispatcher, registers all subcommands |
| FactionSubCommand | command/FactionSubCommand.java |
Base class with shared utilities and requireFaction() helper |
| FactionCommandContext | command/FactionCommandContext.java |
Execution context, --text flag parsing |
| CommandUtil | command/util/CommandUtil.java |
Shared utilities (delegates to util/MessageUtil for message composition) |
The main command class:
public class FactionCommand extends AbstractPlayerCommand {
public FactionCommand(HyperFactions hyperFactions, HyperFactionsPlugin plugin) {
super("faction", "Faction management commands");
addAliases("f", "hf", "hyperfactions");
setAllowsExtraArguments(true);
// Register all subcommands by category
// Faction management
addSubCommand(new CreateSubCommand(hyperFactions, plugin));
addSubCommand(new DisbandSubCommand(hyperFactions, plugin));
// ...
// Member management
addSubCommand(new InviteSubCommand(hyperFactions, plugin));
// ...
}
@Override
protected void execute(...) {
// No subcommand provided - open GUI dashboard
hyperFactions.getGuiManager().openFactionMain(player, ...);
}
}Key Points:
- Aliases:
/f,/hf,/hyperfactions - No subcommand → opens GUI (if has
hyperfactions.usepermission) - Disables Hytale's auto-generated permissions via
canGeneratePermission() = false
All subcommands extend this base class which provides:
public abstract class FactionSubCommand extends AbstractPlayerCommand {
protected final HyperFactions hyperFactions;
protected final HyperFactionsPlugin plugin;
// Utility methods
protected Message prefix() { ... } // "[HyperFactions]" prefix
protected Message msg(String, String) { ... } // Colored message
protected boolean hasPermission(PlayerRef, String) { ... }
protected PlayerRef findOnlinePlayer(String) { ... }
protected void broadcastToFaction(UUID, Message) { ... }
protected FactionCommandContext parseContext(String[] args) { ... }
protected Faction requireFaction(PlayerRef, CommandContext) { ... } // Returns player's faction or sends error
// Color constants
protected static final String COLOR_CYAN = "#00FFFF";
protected static final String COLOR_GREEN = "#00FF00";
protected static final String COLOR_RED = "#FF0000";
protected static final String COLOR_YELLOW = "#FFFF00";
protected static final String COLOR_GRAY = "#888888";
}Handles execution context including the --text flag for text-mode output:
public record FactionCommandContext(
boolean textMode, // --text flag present
String[] args // Remaining args after flag extraction
) {
public static FactionCommandContext parse(String[] rawArgs) {
// Extract --text flag and return remaining args
}
}Text Mode:
--textflag disables GUI and uses chat output instead- Useful for console/automation or players preferring text
command/
├── FactionCommand.java # Main dispatcher
├── FactionSubCommand.java # Base class
├── FactionCommandContext.java # Execution context
├── util/CommandUtil.java # Shared utilities
│
├── faction/ # Faction management
│ ├── CreateSubCommand.java
│ ├── DisbandSubCommand.java
│ ├── RenameSubCommand.java
│ ├── DescSubCommand.java
│ ├── ColorSubCommand.java
│ ├── OpenSubCommand.java
│ └── CloseSubCommand.java
│
├── member/ # Membership
│ ├── InviteSubCommand.java
│ ├── AcceptSubCommand.java
│ ├── LeaveSubCommand.java
│ ├── KickSubCommand.java
│ ├── PromoteSubCommand.java
│ ├── DemoteSubCommand.java
│ └── TransferSubCommand.java
│
├── territory/ # Territory claims
│ ├── ClaimSubCommand.java
│ ├── UnclaimSubCommand.java
│ ├── OverclaimSubCommand.java
│ └── StuckSubCommand.java
│
├── teleport/ # Teleportation
│ ├── HomeSubCommand.java
│ ├── SetHomeSubCommand.java
│ └── DelHomeSubCommand.java
│
├── relation/ # Diplomacy
│ ├── AllySubCommand.java
│ ├── EnemySubCommand.java
│ ├── NeutralSubCommand.java
│ └── RelationsSubCommand.java
│
├── info/ # Information
│ ├── InfoSubCommand.java
│ ├── ListSubCommand.java
│ ├── MapSubCommand.java
│ ├── MembersSubCommand.java
│ ├── WhoSubCommand.java
│ ├── PowerSubCommand.java
│ ├── LeaderboardSubCommand.java
│ ├── LogsSubCommand.java
│ └── HelpSubCommand.java
│
├── social/ # Social features
│ ├── RequestSubCommand.java
│ ├── InvitesSubCommand.java
│ └── ChatSubCommand.java
│
├── economy/ # Economy
│ ├── MoneySubCommand.java
│ ├── BalanceSubCommand.java
│ ├── DepositSubCommand.java
│ └── WithdrawSubCommand.java
│
├── ui/ # UI commands
│ ├── GuiSubCommand.java
│ └── SettingsSubCommand.java
│
└── admin/ # Admin commands
├── AdminSubCommand.java # Router delegating to handler/ classes
└── handler/ # Admin command handlers
├── AdminZoneHandler.java
├── AdminBackupHandler.java
├── AdminDebugHandler.java
├── AdminImportHandler.java
├── AdminUpdateHandler.java
├── AdminIntegrationHandler.java
├── AdminPowerHandler.java
├── AdminMapDecayHandler.java
├── AdminInfoHandler.java
├── AdminWorldHandler.java
└── AdminEconomyHandler.java
| Category | Commands | Permission Prefix |
|---|---|---|
| faction | create, disband, rename, desc, color, open, close | hyperfactions.faction.* |
| member | invite, accept, leave, kick, promote, demote, transfer | hyperfactions.member.* |
| territory | claim, unclaim, overclaim, stuck | hyperfactions.territory.* |
| teleport | home, sethome, delhome | hyperfactions.teleport.* |
| economy | money, balance, deposit, withdraw | hyperfactions.economy.* |
| relation | ally, enemy, neutral, relations | hyperfactions.relation.* |
| info | info, list, map, members, who, power, leaderboard, logs, help | hyperfactions.info.* |
| social | request, invites, chat | hyperfactions.member.*, hyperfactions.chat.* |
| ui | gui, settings | hyperfactions.use |
| admin | zone, backup, reload, debug, bypass, info, who, version, log, world | hyperfactions.admin.* |
/f leaderboard (alias /f top) — Opens a sortable faction leaderboard GUI. Sort modes: POWER, TERRITORY, BALANCE, MEMBERS, K/D (default). 10 entries per page with pagination. Also accessible from the faction nav bar.
/f logs — Opens the faction activity log viewer. Shows timestamped entries with type and player filters (1h/24h/7d). Also accessible from the faction nav bar.
/f stuck — Teleports the player to a random safe unclaimed chunk. Walks outward in a random direction from the player's position, increasing the search radius on each failed attempt. Configurable via stuckMinRadius, stuckRadiusIncrease, and stuckMaxAttempts in config.json. Uses the faction teleport warmup/cooldown system.
/f info [faction] --text — Text mode shows ally/enemy counts and bidirectional relation status. Displays "They consider you" and "You consider them" lines using RelationManager.getEffectiveRelation() for accurate bidirectional awareness. Color-coded: green for ally, red for enemy, gray for neutral.
/f claim / /f unclaim — 500ms per-player debounce prevents double-execution from rapid command dispatch.
Example: command/territory/ClaimSubCommand.java
public class ClaimSubCommand extends FactionSubCommand {
public ClaimSubCommand(HyperFactions hyperFactions, HyperFactionsPlugin plugin) {
super("claim", "Claim the current chunk for your faction", hyperFactions, plugin);
}
@Override
protected void execute(CommandContext ctx, Store<EntityStore> store,
Ref<EntityStore> ref, PlayerRef player, World world) {
// 1. Parse context for --text flag
FactionCommandContext fctx = parseContext(ctx.getRemainingArguments());
// 2. Permission check (optional - manager also checks)
if (!hasPermission(player, Permissions.CLAIM)) {
ctx.sendMessage(prefix().insert(msg("No permission.", COLOR_RED)));
return;
}
// 3. Get player position
int chunkX = ChunkUtil.toChunkCoord(player.getPosition().getX());
int chunkZ = ChunkUtil.toChunkCoord(player.getPosition().getZ());
// 4. Call manager (does permission check + business logic)
ClaimResult result = hyperFactions.getClaimManager()
.claim(player.getUuid(), world.getName(), chunkX, chunkZ);
// 5. Handle result
switch (result) {
case SUCCESS -> {
ctx.sendMessage(prefix().insert(msg("Chunk claimed!", COLOR_GREEN)));
}
case NO_PERMISSION -> {
ctx.sendMessage(prefix().insert(msg("No permission.", COLOR_RED)));
}
case INSUFFICIENT_POWER -> {
ctx.sendMessage(prefix().insert(msg("Not enough power.", COLOR_RED)));
}
// ... other cases
}
}
}Permissions are checked at two levels:
- Command Level (optional) - Early rejection with specific error message
- Manager Level (required) - Ensures GUI operations are also protected
// Command checks permission for specific error message
if (!hasPermission(player, Permissions.CLAIM)) {
ctx.sendMessage(msg("You need hyperfactions.territory.claim", COLOR_RED));
return;
}
// Manager also checks (protects GUI path)
ClaimResult result = claimManager.claim(uuid, world, x, z);
if (result == ClaimResult.NO_PERMISSION) {
// Shouldn't reach here if command checked, but GUI might call directly
}command/admin/AdminSubCommand.java
AdminSubCommand acts as a router that delegates to specialized handler classes in command/admin/handler/:
AdminZoneHandler- Zone create/delete/claim/unclaim/radius/list/notify/title/properties, zoneflag, safezone, warzoneAdminBackupHandler- Backup create/list/restore/deleteAdminDebugHandler- Debug toggle, trace, diagnosticsAdminImportHandler- Data import from other faction pluginsAdminUpdateHandler- Update check and notificationAdminIntegrationHandler- Integration status reportingAdminPowerHandler- Power set/reset/modifyAdminMapDecayHandler- Map decay managementAdminInfoHandler- Admin info/who commands (open admin GUIs directly)AdminWorldHandler- Per-world settings management (list/info/set/reset)AdminEconomyHandler- Economy management and treasury adjustments
Admin commands use nested subcommand structure:
/f admin
├── zone # Zone management
│ ├── create
│ ├── delete
│ ├── claim
│ ├── unclaim
│ ├── radius
│ ├── list
│ ├── notify <zone> <true|false> # Toggle zone entry/leave notifications
│ ├── title <zone> upper|lower <text|clear> # Customize zone title text
│ └── properties <zone> # Open zone properties GUI
├── zoneflag # Zone flag management
├── safezone # Quick SafeZone creation
├── warzone # Quick WarZone creation
├── bypass # Toggle admin bypass (persists across restarts)
├── backup # Backup management
│ ├── create
│ ├── list
│ ├── restore
│ └── delete
├── reload # Reload config
├── update # Check for updates
│ ├── mixin # Check/download HyperProtect-Mixin
│ └── toggle-mixin-download # Toggle HP-Mixin auto-download
├── info [faction] # Open admin faction info GUI
├── who [player] # Open admin player info GUI
├── version # Show version and integration status
├── log # Open admin activity log GUI
├── world # Per-world settings management
│ ├── list # List all world overrides
│ ├── info <world> # Show settings for a world
│ ├── set <world> <key> <value> # Set a per-world setting
│ └── reset <world> # Reset world to defaults
└── debug # Debug commands
Commands use the Message API with Message.join():
// Correct: use Message.join() for composition
Message msg = Message.join(
prefix(),
Message.text("Player "),
Message.text(playerName).color(Color.hex(COLOR_CYAN)),
Message.text(" joined your faction!")
);
// Wrong: don't use .then() (legacy API)
// Wrong: don't use legacy color codes (§c, &c)Hytale handles tab completion automatically based on command arguments. For custom completion, override in subcommand:
@Override
protected List<String> tabComplete(CommandContext ctx, PlayerRef player) {
String[] args = ctx.getRemainingArguments();
if (args.length == 1) {
// Complete first argument with faction names
return hyperFactions.getFactionManager().getAllFactions()
.stream()
.map(Faction::name)
.filter(n -> n.toLowerCase().startsWith(args[0].toLowerCase()))
.toList();
}
return List.of();
}- Create subcommand class in appropriate category package
- Extend
FactionSubCommand - Implement
execute()method - Register in
FactionCommandconstructor - Add permission constant to
Permissions.javaif new - Update help system if needed
Example skeleton:
package com.hyperfactions.command.faction;
public class NewSubCommand extends FactionSubCommand {
public NewSubCommand(HyperFactions hyperFactions, HyperFactionsPlugin plugin) {
super("newcmd", "Description of the command", hyperFactions, plugin);
// Optional: add aliases
addAliases("nc");
}
@Override
protected void execute(CommandContext ctx, Store<EntityStore> store,
Ref<EntityStore> ref, PlayerRef player, World world) {
// Implementation
}
}Register in FactionCommand:
addSubCommand(new NewSubCommand(hyperFactions, plugin));| Class | Path |
|---|---|
| FactionCommand | command/FactionCommand.java |
| FactionSubCommand | command/FactionSubCommand.java |
| FactionCommandContext | command/FactionCommandContext.java |
| CommandUtil | command/util/CommandUtil.java |
| AdminSubCommand | command/admin/AdminSubCommand.java |
| Permissions | Permissions.java |