Skip to content
Merged
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
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,8 @@ public class ActivityConfiguration {
private String guiTitle;
private Material rewardMaterial;

private volatile int barMax;
private volatile int rewardEvery;
private volatile int barLength;
private volatile String barFilledChar;
private volatile String barEmptyChar;
Expand Down Expand Up @@ -80,6 +82,8 @@ public void load() {
guiTitle = config.getString("gui.title", "&8Weekly Activity");
rewardMaterial = material(config.getString("gui.reward-material", "CHEST"), "gui.reward-material");

barMax = Math.max(1, config.getInt("bar.max", 20));
rewardEvery = Math.max(1, Math.min(barMax, config.getInt("bar.reward-every", 10)));
barLength = barLength(config.getInt("bar.length", 20));
barFilledChar = config.getString("bar.filled-char", "█");
barEmptyChar = config.getString("bar.empty-char", "░");
Expand Down Expand Up @@ -108,6 +112,15 @@ private void loadActivities(ConfigurationSection section) {
continue;
}

// The old key: its meaning moved to 'every' with 'points' per award,
// so an unconverted entry would hand out its old 50-point award per
// single action. Loud and skipped rather than silently wrong.
if (entry.contains("daily-goal")) {
plugin.getLogger().warning("Activity '" + id + "' still uses 'daily-goal' - skipping it."
+ " Convert to every/points/daily-cap (see the default config.yml).");
continue;
}

int points = wholeNumber(entry, id, "points", 0);
if (points <= 0) {
plugin.getLogger().warning("Activity '" + id + "' is worth " + points
Expand All @@ -119,8 +132,9 @@ private void loadActivities(ConfigurationSection section) {
id,
entry.getString("display", id),
material(entry.getString("material", "PAPER"), "activities." + id + ".material"),
Math.max(1, wholeNumber(entry, id, "daily-goal", 1)),
points
Math.max(1, wholeNumber(entry, id, "every", 1)),
points,
Math.max(0, wholeNumber(entry, id, "daily-cap", 0))
));
}

Expand Down Expand Up @@ -154,19 +168,23 @@ private int barLength(int length) {
}

// ====================================
// A player can only meet every goal once a day, so seven days of every
// activity is the ceiling - if that is under 100 the bar is unreachable
// Seven days of every capped activity is the ceiling - if that is under
// the first milestone nothing can ever be claimed. An uncapped activity
// has no ceiling, so the check is skipped.
// ====================================
private void warnIfBarUnreachable() {
int weekly = 0;
long weekly = 0;
for (ActivityDef def : activities.values()) {
weekly += def.points();
if (def.dailyCap() == 0) {
return;
}
weekly += def.dailyCap();
}
weekly *= 7;

if (weekly < 100) {
plugin.getLogger().warning("All activities together are worth " + weekly
+ " points a week - nobody can reach 100. Raise 'points' or add activities.");
if (weekly < rewardEvery) {
plugin.getLogger().warning("All activities together are capped at " + weekly
+ " points a week - nobody can reach the first reward at " + rewardEvery + ".");
}
}

Expand Down Expand Up @@ -249,6 +267,14 @@ public Material rewardMaterial() {
return rewardMaterial;
}

public int barMax() {
return barMax;
}

public int rewardEvery() {
return rewardEvery;
}

public int barLength() {
return barLength;
}
Expand Down
55 changes: 37 additions & 18 deletions src/main/java/tfmc/justin/activity/gui/ActivityGui.java
Original file line number Diff line number Diff line change
Expand Up @@ -23,9 +23,10 @@
import java.util.List;

// ====================================
// Read-only chest view of the week: the bar on top, one item per activity,
// the reward list at the bottom. Also the click listener - a marker holder
// is the cheapest way to tell our inventory apart from every other one.
// Chest view of the week: the bar on top, one item per activity, the reward
// chest at the bottom - clicking it claims whatever milestones are due. Also
// the click listener - a marker holder is the cheapest way to tell our
// inventory apart from every other one.
// ====================================
public class ActivityGui implements Listener {

Expand Down Expand Up @@ -88,33 +89,37 @@ public Inventory build(Player player) {
}

private ItemStack barItem(ActivityConfiguration config, Messages messages, PlayerData data) {
String bar = Bar.render(data.points(), config.barLength(), config.barFilledChar(), config.barEmptyChar(),
config.barFilledColor(), config.barEmptyColor());
String bar = Bar.render(data.points(), config.barMax(), config.barLength(), config.barFilledChar(),
config.barEmptyChar(), config.barFilledColor(), config.barEmptyColor());

return item(Material.EXPERIENCE_BOTTLE,
messages.get("gui.bar-name", "%percent%", data.points()),
messages.get("gui.bar-name", "%points%", data.points(), "%max%", config.barMax()),
List.of(Utils.colorize(bar)));
}

private ItemStack activityItem(Messages messages, ActivityDef def, PlayerData data) {
int count = data.count(def.id());
boolean done = count >= def.dailyGoal();
int today = def.worth(data.count(def.id()));

List<String> lore = new ArrayList<>();
lore.add(messages.get("gui.activity-lore-progress", "%count%", count, "%goal%", def.dailyGoal()));
lore.add(messages.get("gui.activity-lore-points", "%points%", def.points()));
lore.add(messages.get(done ? "gui.activity-lore-done" : "gui.activity-lore-not-done"));
lore.add(messages.get("gui.activity-lore-points", "%points%", def.points(), "%every%", def.every()));
lore.add(def.dailyCap() > 0
? messages.get("gui.activity-lore-today-capped", "%today%", today, "%cap%", def.dailyCap())
: messages.get("gui.activity-lore-today", "%today%", today));

return item(def.icon(), Utils.colorize(def.display()), lore);
}

private ItemStack rewardItem(ActivityConfiguration config, Messages messages, PlayerData data) {
int due = data.claimable(config.rewardEvery());

List<String> lore = new ArrayList<>();
lore.add(messages.get("gui.reward-lore-header"));
lore.add(messages.get("gui.reward-lore-header", "%every%", config.rewardEvery()));
lore.addAll(Messages.colorize(config.rewardDisplay()));
if (data.rewarded()) {
lore.add(messages.get("gui.reward-claimed"));
}
lore.add(messages.get("gui.reward-lore-claimed", "%claimed%", data.claimed(),
"%total%", config.barMax() / config.rewardEvery()));
lore.add(due > 0
? messages.get("gui.reward-click", "%count%", due)
: messages.get("gui.reward-nothing"));

return item(config.rewardMaterial(), messages.get("gui.reward-name"), lore);
}
Expand All @@ -133,13 +138,27 @@ private ItemStack item(Material material, String name, List<String> lore) {
// ====================================
// Nothing in this view is takeable, so every click in it is cancelled -
// including shift-clicks from the player's own inventory, which is why
// the top inventory is what gets checked rather than the clicked slot
// the top inventory is what gets checked rather than the clicked slot.
// The reward chest is the one live control: it claims and redraws itself.
// ====================================
@EventHandler
public void onClick(InventoryClickEvent event) {
if (event.getView().getTopInventory().getHolder() instanceof Marker) {
event.setCancelled(true);
if (!(event.getView().getTopInventory().getHolder() instanceof Marker)) {
return;
}
event.setCancelled(true);

if (event.getRawSlot() != REWARD_SLOT || !(event.getWhoClicked() instanceof Player player)) {
return;
}

ActivityConfiguration config = manager.getConfiguration();
if (manager.claim(player) == 0) {
player.sendMessage(config.messages().get("reward-nothing"));
return;
}
event.getView().getTopInventory().setItem(REWARD_SLOT,
rewardItem(config, config.messages(), manager.getStore().get(player.getUniqueId())));
}

// ====================================
Expand Down
24 changes: 17 additions & 7 deletions src/main/java/tfmc/justin/activity/hooks/PlaceholderHook.java
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,8 @@
import java.util.Locale;

// ====================================
// %activity_points%, %activity_percent%, %activity_bar%, %activity_done_<id>%
// %activity_points%, %activity_max%, %activity_percent%, %activity_bar%,
// %activity_claimable%, %activity_done_<id>%
// Only registered when PlaceholderAPI is enabled - see ActivityPlugin.
// ====================================
public class PlaceholderHook extends PlaceholderExpansion {
Expand Down Expand Up @@ -61,7 +62,9 @@ public String onRequest(OfflinePlayer player, String params) {
PlayerData data = manager.getStore().peek(player.getUniqueId());

boolean currentWeek = data != null && data.weekKey().equals(config.currentWeekKey());
int points = currentWeek ? data.points() : 0;
// Clamped here too: peek() never runs the clamp get() does, so a lowered
// bar.max would otherwise read over 100% until the next action
int points = currentWeek ? Math.min(data.points(), config.barMax()) : 0;

// ====================================
// Only the fixed keywords and the "done_" prefix are matched
Expand All @@ -74,10 +77,15 @@ public String onRequest(OfflinePlayer player, String params) {

switch (key) {
case "points":
case "percent":
return String.valueOf(points);
case "max":
return String.valueOf(config.barMax());
case "percent":
return String.valueOf(points * 100 / config.barMax());
case "claimable":
return String.valueOf(currentWeek ? data.claimable(config.rewardEvery()) : 0);
case "bar":
return Utils.colorize(Bar.render(points, config.barLength(), config.barFilledChar(),
return Utils.colorize(Bar.render(points, config.barMax(), config.barLength(), config.barFilledChar(),
config.barEmptyChar(), config.barFilledColor(), config.barEmptyColor()));
default:
break;
Expand All @@ -89,9 +97,11 @@ public String onRequest(OfflinePlayer player, String params) {
if (def == null) {
return "";
}
boolean done = currentWeek
&& data.dayKey().equals(config.currentDayKey())
&& data.count(def.id()) >= def.dailyGoal();
// "Done" is the daily cap reached, or - for an uncapped activity -
// at least one award earned today
int today = currentWeek && data.dayKey().equals(config.currentDayKey())
? def.worth(data.count(def.id())) : 0;
boolean done = def.dailyCap() > 0 ? today >= def.dailyCap() : today > 0;
return Utils.colorize(config.messages().raw(done ? "placeholder.done" : "placeholder.not-done"));
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,8 @@
import tfmc.justin.activity.managers.ActivityManager;

// ====================================
// Join is both the rollover trigger for returning players and the moment a
// reward earned while they were offline can finally be handed over
// Join is the rollover trigger for returning players, and a nudge that a
// reward is waiting to be claimed
// ====================================
public class JoinListener implements Listener {

Expand Down
Loading
Loading