Skip to content
Open
Changes from 1 commit
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
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ public class VotePlaceholderProvider implements PlaceholderProvider, Runnable, L
private final Map<Integer, VoteSite> voteSites = new HashMap<>();
private final Map<String, VoteUser> users = new HashMap<>();
private final List<TopVoteUser> topVotes = new ArrayList<>();
private volatile VoteGoal goal;

private volatile boolean pendingRefresh = true;
private volatile Instant lastUpdate = Instant.MIN;
Expand Down Expand Up @@ -64,7 +65,9 @@ public List<String> availablePlaceholders() {
"%azlink_vote_sites_[id]_name%",
"%azlink_vote_sites_[id]_url%",
"%azlink_vote_top_[position]_name%",
"%azlink_vote_top_[position]_votes%"
"%azlink_vote_top_[position]_votes%",
"%azlink_vote_goal_target%",
"%azlink_vote_goal_progress%"
);
}

Expand Down Expand Up @@ -94,6 +97,8 @@ public String evaluatePlaceholder(String[] parts, OfflinePlayer player) {
return topPlaceholder(parts);
case "sites":
return sitePlaceholder(parts);
case "goal":
return goalPlaceholder(parts);
default:
return null;
}
Expand Down Expand Up @@ -134,6 +139,23 @@ private String userCanPlaceholder(String[] parts, OfflinePlayer player) throws N
return null;
}

private String goalPlaceholder(String[] parts) throws NumberFormatException {
VoteGoal currentGoal = this.goal;

if (currentGoal == null) {
return "0";
}

switch (parts[1]) {
case "target":
return Integer.toString(currentGoal.target);
case "progress":
return Integer.toString(currentGoal.progress);
Comment on lines +145 to +153
Copy link

Copilot AI Mar 31, 2026

Choose a reason for hiding this comment

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

goalPlaceholder returns "0" whenever goal is null, even if the requested sub-key is invalid (e.g., %azlink_vote_goal_typo% would yield 0 instead of returning null). This makes invalid placeholders harder to detect and is inconsistent with other placeholder handlers. Consider switching on parts[1] first and only returning "0" for the known keys (target/progress) when goal is missing, while still returning null for unknown keys.

Suggested change
if (currentGoal == null) {
return "0";
}
switch (parts[1]) {
case "target":
return Integer.toString(currentGoal.target);
case "progress":
return Integer.toString(currentGoal.progress);
String key = parts.length > 1 ? parts[1] : "";
switch (key) {
case "target":
return currentGoal != null ? Integer.toString(currentGoal.target) : "0";
case "progress":
return currentGoal != null ? Integer.toString(currentGoal.progress) : "0";

Copilot uses AI. Check for mistakes.
default:
return null;
}
}

private String sitePlaceholder(String[] parts) throws NumberFormatException {
if (parts[1].equals("count")) {
return Integer.toString(voteSites.size());
Expand Down Expand Up @@ -229,6 +251,7 @@ private void refreshData() {
this.voteSites.clear();
this.users.clear();
this.topVotes.clear();
this.goal = response.goal;

Comment on lines 245 to 255
Copy link

Copilot AI Mar 31, 2026

Choose a reason for hiding this comment

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

The HTTP request completes on the async executor (HttpClient.request uses supplyAsync), but the thenAccept callback mutates shared state (voteSites/users/topVotes/goal) that is also read from placeholder evaluation on the server thread. This creates a real data-race risk (e.g., inconsistent reads or IndexOutOfBounds/visibility issues). Consider marshalling the state update back onto the Bukkit main thread (scheduler runTask) or swapping in immutable snapshots / concurrent collections for all shared fields (not just goal).

Copilot uses AI. Check for mistakes.
for (VoteSite site : response.sites) {
this.voteSites.put(site.id, site);
Expand Down Expand Up @@ -260,6 +283,12 @@ public static class VoteResponse {
public List<VoteUser> users = new ArrayList<>();
@SerializedName("top_votes")
public List<TopVoteUser> topVotes = new ArrayList<>();
public VoteGoal goal;
}

public static class VoteGoal {
public int target;
public int progress;
}

public static class VoteUser {
Expand Down
Loading