Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
63 changes: 47 additions & 16 deletions DEVELOPMENT_ROADMAP.md
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
# HyperFactions Development Roadmap

> Last Updated: January 24, 2026 (Added comprehensive GUI overhaul (Phase 2.11), command restructure (/f opens GUI), economy system with taxes/upkeep/war costs, and additional features)
> Version: 0.2.0
> Last Updated: January 24, 2026 (Completed 2.9 Update Checker, 2.10 Spawnkilling Prevention, 3.0 WarZone Per-Zone Configuration)
> Version: 0.3.0
> Repository: https://github.com/HyperSystemsDev/HyperFactions

---
Expand Down Expand Up @@ -477,7 +477,7 @@ Teleport warmups (e.g., `/f home`, `/f unstuck`) currently only cancel on moveme
### 2.9 Update Checker (GitHub Releases)
- **Priority:** P1
- **Effort:** 0.5 day
- **Status:** :red_circle: Not Started
- **Status:** :white_check_mark: **COMPLETE**

**Description:**
Implement GitHub release checking similar to HyperPerms to notify server admins when new versions are available.
Expand Down Expand Up @@ -527,12 +527,20 @@ Implement GitHub release checking similar to HyperPerms to notify server admins
- Downloads to temp file (.jar.tmp) then atomic rename
- All operations fully async

**Implementation Notes:**
- Created `update/UpdateChecker.java` with GitHub Releases API integration
- 5-minute response caching to prevent API spam
- Changelog support and optional auto-download
- Graceful 404 handling (no releases exist yet)
- Integrated into `HyperFactions.java` enable() method
- Config options: `updates.enabled`, `updates.checkUrl`

---

### 2.10 Spawnkilling Prevention
- **Priority:** P1
- **Effort:** 1 day
- **Status:** :red_circle: Not Started
- **Status:** :white_check_mark: **COMPLETE**

**Problem:**
During raids or wars, players who die and respawn at their faction home can be repeatedly killed by enemies camping the spawn point. This creates unfair "spawn camping" scenarios.
Expand Down Expand Up @@ -583,6 +591,13 @@ Grant temporary invulnerability after respawn when player died in PvP and respaw
**Future Enhancement:**
When raid/war system (3.4, 3.7) is implemented, make `onlyDuringRaid` default to true for more realistic combat.

**Implementation Notes:**
- Created `data/SpawnProtection.java` record to track protection state
- Added spawn protection tracking to `CombatTagManager.java`
- Added `DENIED_SPAWN_PROTECTED` to `ProtectionChecker.PvPResult` enum
- Config options added: `combat.spawnProtection.{enabled, durationSeconds, breakOnAttack, breakOnMove}`
- Protection automatically removed when player attacks or leaves own territory

---

### 2.11 GUI System Overhaul & Command Restructure
Expand Down Expand Up @@ -1104,7 +1119,7 @@ When raid/war system (3.4, 3.7) is implemented, make `onlyDuringRaid` default to
### 3.0 WarZone Per-Zone Configuration
- **Priority:** P1
- **Effort:** 1 day
- **Status:** :red_circle: Not Started
- **Status:** :white_check_mark: **COMPLETE**

**Current Problem:**
All WarZones behave identically with global settings. Server admins cannot customize individual zones for different purposes (tournaments vs training grounds vs battlefields).
Expand Down Expand Up @@ -1186,6 +1201,14 @@ Per-zone overrides stored in zone data (zones.json):

**Priority:** P1 - High value for server event customization

**Implementation Notes:**
- Created `data/ZoneFlags.java` with 11 flag constants (ALLOW_PVP, ALLOW_ITEM_DROP, ALLOW_BLOCK_BREAK, ALLOW_BLOCK_PLACE, CONSUME_POWER, etc.)
- Added flags support to `Zone.java` record with builder pattern
- Added `setZoneFlag()`, `clearZoneFlag()`, `getEffectiveFlag()` to `ZoneManager.java`
- Updated `ProtectionChecker.java` to check zone flags for protection decisions
- Added `/f admin zoneflag <zone> <flag> <value>` command to `FactionCommand.java`
- Updated `JsonZoneStorage.java` for flags persistence with JSON serialization

---

### 3.1 Public API for Cross-Mod Integration (NEW)
Expand Down Expand Up @@ -1971,12 +1994,18 @@ Faction B surrenders on day 5:

### Performance Issues

| Issue | Location | Impact | Fix |
|-------|----------|--------|-----|
| O(n) getFactionClaims() | ClaimManager.java | High with many claims | Add reverse index Map<UUID, Set<ChunkKey>> |
| No auto-save | HyperFactions.java | Data loss on crash | Add periodic save task |
| 7 callback params | TeleportManager.java | Hard to use | Create TeleportContext object |
| No invite cleanup | InviteManager.java | Memory leak | Add scheduled cleanup task |
| Issue | Location | Impact | Fix | Status |
|-------|----------|--------|-----|--------|
| O(n) getFactionClaims() | ClaimManager.java | High with many claims | Add reverse index Map<UUID, Set<ChunkKey>> | ✅ Complete |
| No auto-save | HyperFactions.java | Data loss on crash | Add periodic save task | ✅ Complete |
| 7 callback params | TeleportManager.java | Hard to use | Create TeleportContext object | ✅ Complete |
| No invite cleanup | InviteManager.java | Memory leak | Add scheduled cleanup task | ✅ Complete |

**Technical Debt Resolution Notes (v0.3.0):**
- **Claim Reverse Index:** Added `factionClaimsIndex` Map in ClaimManager for O(1) lookups of faction claims
- **Auto-save:** Added configurable periodic save task in HyperFactions.java (default: 5 minutes)
- **TeleportContext:** Created `data/TeleportContext.java` record with builder pattern to replace callback parameters
- **Invite Cleanup:** Integrated invite cleanup into periodic tasks in HyperFactions.java

### Code Quality

Expand Down Expand Up @@ -2343,18 +2372,20 @@ Be explicit about units:
- Combat tagging
- Safe/War zones

### Version 0.3.0 (Planned)
**Critical Pre-Release Features:**
### Version 0.3.0 (Current) - January 24, 2026
**Completed Features:**
- **Update Checker (2.9)** - GitHub Releases API integration with 5-minute caching, changelog support, and auto-download capability
- **Spawnkilling Prevention (2.10)** - Temporary invulnerability after PvP death respawn in own territory, configurable duration and break conditions
- **WarZone Per-Zone Configuration (3.0)** - 11 configurable flags per zone (PvP, item drop, block edit, power loss, etc.) with `/f admin zoneflag` command

**Still Planned:**
- **GUI System Overhaul (2-3 weeks):**
- Complete GUI implementation for all features
- `/f` command opens main menu (sub-commands still work)
- Interactive territory map
- Polished, fully functional interfaces
- Visual permission matrix, economy management, war/raid tracking
- Warmup damage monitoring (fix teleport exploit)
- Update checker (GitHub releases integration)
- Spawnkilling prevention (spawn protection system)
- WarZone per-zone configuration (tournament/training ground support)

**Major Features:**
- Public API for cross-mod integration
Expand Down
1 change: 1 addition & 0 deletions gradle.properties
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
hytaleServerJar=libs/HytaleServer.jar
145 changes: 136 additions & 9 deletions src/main/java/com/hyperfactions/HyperFactions.java
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
import com.hyperfactions.storage.json.JsonFactionStorage;
import com.hyperfactions.storage.json.JsonPlayerStorage;
import com.hyperfactions.storage.json.JsonZoneStorage;
import com.hyperfactions.update.UpdateChecker;
import com.hyperfactions.util.Logger;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
Expand Down Expand Up @@ -54,14 +55,20 @@ public class HyperFactions {
// GUI
private GuiManager guiManager;

// Update checker
private UpdateChecker updateChecker;

// Task management
private final AtomicInteger taskIdCounter = new AtomicInteger(0);
private final Map<Integer, ScheduledTask> scheduledTasks = new ConcurrentHashMap<>();
private int autoSaveTaskId = -1;
private int inviteCleanupTaskId = -1;

// Platform callbacks (set by plugin)
private Consumer<Runnable> asyncExecutor;
private TaskSchedulerCallback taskScheduler;
private TaskCancelCallback taskCanceller;
private RepeatingTaskSchedulerCallback repeatingTaskScheduler;

/**
* Functional interface for scheduling delayed tasks.
Expand All @@ -79,6 +86,14 @@ public interface TaskCancelCallback {
void cancel(int taskId);
}

/**
* Functional interface for scheduling repeating tasks.
*/
@FunctionalInterface
public interface RepeatingTaskSchedulerCallback {
int schedule(int delayTicks, int periodTicks, Runnable task);
}

/**
* Represents a scheduled task.
*/
Expand Down Expand Up @@ -161,26 +176,47 @@ public void enable() {
Logger.info("Player %s combat logged - death penalty applied", playerUuid);
});

// Initialize update checker if enabled
if (HyperFactionsConfig.get().isUpdateCheckEnabled()) {
updateChecker = new UpdateChecker(dataDir, VERSION, HyperFactionsConfig.get().getUpdateCheckUrl());
updateChecker.checkForUpdates();
}

// Start periodic tasks (auto-save, invite cleanup)
// Note: These are started after platform sets callbacks via setRepeatingTaskScheduler()
// The platform should call startPeriodicTasks() after setting up callbacks

Logger.info("HyperFactions enabled");
}

/**
* Starts periodic tasks (auto-save, invite cleanup).
* Should be called by the platform after setting up task scheduler callbacks.
*/
public void startPeriodicTasks() {
startAutoSaveTask();
startInviteCleanupTask();
}

/**
* Disables HyperFactions.
*/
public void disable() {
Logger.info("HyperFactions disabling...");

// Save all data
if (factionManager != null) {
factionManager.saveAll().join();
}
if (powerManager != null) {
powerManager.saveAll().join();
// Cancel periodic tasks first
if (autoSaveTaskId > 0) {
cancelTask(autoSaveTaskId);
autoSaveTaskId = -1;
}
if (zoneManager != null) {
zoneManager.saveAll().join();
if (inviteCleanupTaskId > 0) {
cancelTask(inviteCleanupTaskId);
inviteCleanupTaskId = -1;
}

// Save all data
saveAllData();

// Shutdown storage
if (factionStorage != null) {
factionStorage.shutdown().join();
Expand All @@ -192,7 +228,7 @@ public void disable() {
zoneStorage.shutdown().join();
}

// Cancel all scheduled tasks
// Cancel remaining scheduled tasks
for (int taskId : scheduledTasks.keySet()) {
cancelTask(taskId);
}
Expand Down Expand Up @@ -222,6 +258,10 @@ public void setTaskCanceller(@NotNull TaskCancelCallback canceller) {
this.taskCanceller = canceller;
}

public void setRepeatingTaskScheduler(@NotNull RepeatingTaskSchedulerCallback scheduler) {
this.repeatingTaskScheduler = scheduler;
}

// === Task scheduling ===

/**
Expand Down Expand Up @@ -258,6 +298,83 @@ public void cancelTask(int taskId) {
}
}

/**
* Schedules a repeating task.
*
* @param delayTicks initial delay in ticks
* @param periodTicks period in ticks
* @param task the task
* @return the task ID
*/
public int scheduleRepeatingTask(int delayTicks, int periodTicks, @NotNull Runnable task) {
if (repeatingTaskScheduler != null) {
int id = taskIdCounter.incrementAndGet();
int platformId = repeatingTaskScheduler.schedule(delayTicks, periodTicks, task);
scheduledTasks.put(id, new ScheduledTask(platformId, task));
return id;
}
return -1;
}

/**
* Performs a save of all data.
* Called periodically by auto-save and on shutdown.
*/
public void saveAllData() {
Logger.info("Auto-saving data...");
if (factionManager != null) {
factionManager.saveAll().join();
}
if (powerManager != null) {
powerManager.saveAll().join();
}
if (zoneManager != null) {
zoneManager.saveAll().join();
}
Logger.info("Auto-save complete");
}

/**
* Starts the auto-save periodic task if enabled.
*/
private void startAutoSaveTask() {
HyperFactionsConfig config = HyperFactionsConfig.get();
if (!config.isAutoSaveEnabled()) {
Logger.info("Auto-save is disabled in config");
return;
}

int intervalMinutes = config.getAutoSaveIntervalMinutes();
if (intervalMinutes <= 0) {
Logger.warn("Invalid auto-save interval: %d minutes, using default 5 minutes", intervalMinutes);
intervalMinutes = 5;
}

int periodTicks = intervalMinutes * 60 * 20; // Convert minutes to ticks (20 ticks per second)
autoSaveTaskId = scheduleRepeatingTask(periodTicks, periodTicks, this::saveAllData);

if (autoSaveTaskId > 0) {
Logger.info("Auto-save scheduled every %d minutes", intervalMinutes);
}
}

/**
* Starts the invite cleanup periodic task.
*/
private void startInviteCleanupTask() {
// Run every 5 minutes (6000 ticks)
int periodTicks = 5 * 60 * 20;
inviteCleanupTaskId = scheduleRepeatingTask(periodTicks, periodTicks, () -> {
if (inviteManager != null) {
inviteManager.cleanupExpired();
}
});

if (inviteCleanupTaskId > 0) {
Logger.info("Invite cleanup task scheduled every 5 minutes");
}
}

// === Getters ===

@NotNull
Expand Down Expand Up @@ -314,4 +431,14 @@ public ProtectionChecker getProtectionChecker() {
public GuiManager getGuiManager() {
return guiManager;
}

/**
* Gets the update checker.
*
* @return the update checker, or null if update checking is disabled
*/
@Nullable
public UpdateChecker getUpdateChecker() {
return updateChecker;
}
}
Loading