Skip to content

feat: add per-player interval mode (configurable) for goal evaluation#29

Open
Bonziman wants to merge 1 commit into
TheGaBr0:mainfrom
Bonziman:per-player-goal-interval
Open

feat: add per-player interval mode (configurable) for goal evaluation#29
Bonziman wants to merge 1 commit into
TheGaBr0:mainfrom
Bonziman:per-player-goal-interval

Conversation

@Bonziman

Copy link
Copy Markdown

This PR adds an optional configuration to enable per-player interval execution for goal checking.

Changes:

  • Added a new boolean setting to toggle per-player interval mode
  • Updated goal scheduling logic to support both:
    • Default global interval behavior (unchanged)
    • Optional per-player interval execution when enabled
  • Adjusted related DAO / event logic to support the new mode

Why:

Some servers may prefer per-player scheduling for more accurate and isolated goal tracking, while others may prefer the existing global interval system for performance reasons or for server wide goals.

This change keeps backward compatibility and does not modify default behavior.

Notes:

  • Default behavior remains unchanged unless the new setting is enabled.
  • This is an opt-in feature.

@TheGaBr0 TheGaBr0 self-assigned this May 21, 2026

@TheGaBr0 TheGaBr0 left a comment

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I need you to change the target branch to dev and rebase this on it (you don't have to open a new PR in case you didn't know)

The PR is missing the automatic update for all existing goals
(see UpdateManager class for understanding the pattern) which requires a new config version for triggering the update (CURRENT_CONFIG_VERSION in main and config-version in config.yml) and a new class that will take care of the update (see Version364to365Updater class). You can add the goals update function like so:

private final GoalsManager goalsManager = GoalsManager.getInstance();


PlaytimeFormatsConfiguration playtimeFormatsConfiguration = PlaytimeFormatsConfiguration.getInstance();
        playtimeFormatsConfiguration.initialize(plugin);

goalsManager.initialize(plugin);
goalsManager.goalsUpdater();

GoalsManager.resetInstance();

PlaytimeFormatsConfiguration must be initialized first as goalsUpdater triggers saveToFile which calls ticksToFormattedPlaytime, which depends on it.

return Timestamp.from(instant);
}

private Instant instantFromDatabase(ResultSet rs, String columnName) throws SQLException {

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't get what you were trying to achieve in the GoalsDAO class since this and getLastCompletionTime are both unusued, maybe something like: compute the next interval from last completion time, not from total playtime milestones?

This needs to be clarified


private boolean perPlayerInterval;
private final Map<String, BukkitTask> perPlayerCheckTasks = new ConcurrentHashMap<>();
private final Map<String, Long> targetPlaytimeByUser = new ConcurrentHashMap<>();

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Correct me if you're implementation is based entirely and only on playtime requirements (which has to be explicitly clarified)

This is a pretty specific use case where the requirement is simply the playtime mapped per player (and works for you). It's probably better tho to implement it generally using the class GoalRewardRequirement.

Suggested change
private final Map<String, Long> targetPlaytimeByUser = new ConcurrentHashMap<>();
private final Map<String, GoalRewardRequirement> targetRequirementsByUser = new ConcurrentHashMap<>();

private boolean perPlayerInterval;
private final Map<String, BukkitTask> perPlayerCheckTasks = new ConcurrentHashMap<>();
private final Map<String, Long> targetPlaytimeByUser = new ConcurrentHashMap<>();
private final Map<String, Integer> completionsByUser = new ConcurrentHashMap<>();

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is currently not used, what is its purpose?

private final Map<String, BukkitTask> perPlayerCheckTasks = new ConcurrentHashMap<>();
private final Map<String, Long> targetPlaytimeByUser = new ConcurrentHashMap<>();
private final Map<String, Integer> completionsByUser = new ConcurrentHashMap<>();
private final Set<String> lastCompletionLoading = ConcurrentHashMap.newKeySet();

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

same here as the previous comment

"per-player-interval:",
" - If true, the completion check interval is applied per player instead of globally.",
" - Only applies when check-time-interval is a number of seconds (not cron).",
" - Cron expressions always run globally.",

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Cron expressions always run globally.

Yep nice idea




private void recordCompletion(DBUser user) {

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What would be the use of this? To record the goal completion in the db?

Which is already done by the async markGoalAsCompletedAsync which calls this method subsequently.

if (!canRunPerPlayerCheck(user)) {
return;
}
checkCompletion(user);

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Be careful here.

checkCompletion checks for all kind of requirements, not only playtime.

This links back to my previous comment (line R62)

}

public void handlePlayerJoin(OnlineUser user) {
if (!active || !perPlayerInterval || useCronExpression) {

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

!active is redundant since schedulePerPlayerCheck already checks for it


if (perPlayerInterval && !useCronExpression) {
scheduleInfo.put("nextCheck", "per-player");
scheduleInfo.put("timeRemaining", "per-player");

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
scheduleInfo.put("timeRemaining", "per-player");
scheduleInfo.put("timeRemaining", checkTimeToText);

Map<String, Object> scheduleInfo = new HashMap<>();

if (perPlayerInterval && !useCronExpression) {
scheduleInfo.put("nextCheck", "per-player");

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
scheduleInfo.put("nextCheck", "per-player");
scheduleInfo.put("nextCheck", "each player's playtime milestone");

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants