Skip to content

Commit 314b899

Browse files
authored
feat: daily-cap counts completions per day (#45)
* feat: daily-cap counts completions per day * fix: show uncapped worth in admin check
1 parent 2a9e8d3 commit 314b899

11 files changed

Lines changed: 113 additions & 21 deletions

File tree

‎src/main/java/tfmc/justin/activity/commands/ActivityCommand.java‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -333,7 +333,7 @@ private void handleCheck(CommandSender sender, String[] args) {
333333
// stand in for a placeholder that has not been filled yet
334334
sender.sendMessage(data.isRevealed(id)
335335
? messages().get("admin.check-task-revealed", "%slot%", slot + 1, "%count%", count,
336-
"%points%", def == null ? 0 : def.worth(count), "%activity%", display)
336+
"%points%", def == null ? 0 : def.rawWorth(count), "%activity%", display)
337337
: messages().get("admin.check-task-hidden", "%slot%", slot + 1, "%activity%", display));
338338
}
339339
}

‎src/main/java/tfmc/justin/activity/config/ActivityConfiguration.java‎

Lines changed: 7 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1175,16 +1175,17 @@ private void warnIfVoteShareMisfits() {
11751175
+ " - on a day it is not drawn, the reserved share cannot be earned.");
11761176
}
11771177
int reserved = dailyMax - nonVoteDailyMax();
1178-
if (vote.dailyCap() > 0 && vote.dailyCap() < reserved) {
1179-
plugin.getLogger().warning("activities.vote.daily-cap " + vote.dailyCap() + " is below the "
1178+
if (vote.dailyCap() > 0 && vote.capPoints() < reserved) {
1179+
plugin.getLogger().warning("activities.vote.daily-cap " + vote.dailyCap() + " (" + vote.capPoints()
1180+
+ " points) is below the "
11801181
+ reserved + " points bar.vote-share keeps for voting - the rest of that share is never earned.");
11811182
}
11821183
}
11831184

11841185
// ====================================
11851186
// A player can only earn from the TASKS_PER_DAY activities drawn for them,
11861187
// so the daily ceiling is the worst draw they can get: the lowest
1187-
// TASKS_PER_DAY daily-caps, itself capped by bar.daily-max. Seven days of
1188+
// TASKS_PER_DAY daily-caps in points, itself capped by bar.daily-max. Seven days of
11881189
// that is the weekly ceiling - if it is under the first milestone, an
11891190
// unlucky week can never be claimed on.
11901191
//
@@ -1197,7 +1198,7 @@ private void warnIfBarUnreachable() {
11971198
List<Integer> caps = new ArrayList<>();
11981199
for (ActivityDef def : activities.values()) {
11991200
if (def.dailyCap() > 0) {
1200-
caps.add(def.dailyCap());
1201+
caps.add(def.capPoints());
12011202
}
12021203
}
12031204

@@ -1241,8 +1242,8 @@ static String unreachableWarning(List<Integer> dailyCaps, int activityCount, int
12411242
}
12421243

12431244
// ====================================
1244-
// The arithmetic above, pure so it can be tested: the daily-caps of the
1245-
// capped activities (in any order), how many activities are loaded in
1245+
// The arithmetic above, pure so it can be tested: the daily-caps, in
1246+
// points, of the capped activities (in any order), how many activities are loaded in
12461247
// total, and bar.daily-max. Package-private for the test.
12471248
// ====================================
12481249
static long dailyCeiling(List<Integer> dailyCaps, int activityCount, int dailyMax) {

‎src/main/java/tfmc/justin/activity/gui/ActivityGui.java‎

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -244,7 +244,7 @@ static List<String> activityLore(Messages messages, ActivityDef def, int count)
244244
lore.add(messages.get("gui.activity-lore-progress", "%bar%", Utils.colorize(progress)));
245245
}
246246
lore.add(def.dailyCap() > 0
247-
? messages.get("gui.activity-lore-today-capped", "%today%", def.worth(count), "%cap%", def.dailyCap())
247+
? messages.get("gui.activity-lore-today-capped", "%today%", def.worth(count), "%cap%", def.capPoints())
248248
: messages.get("gui.activity-lore-today", "%today%", def.worth(count)));
249249
return lore;
250250
}
@@ -260,7 +260,7 @@ static List<String> activityLore(Messages messages, ActivityDef def, int count)
260260
// Bukkit inventory.
261261
// ====================================
262262
static String progressBar(ActivityDef def, int count) {
263-
if (def.dailyCap() > 0 && def.worth(count) >= def.dailyCap()) {
263+
if (def.dailyCap() > 0 && def.worth(count) >= def.capPoints()) {
264264
return Bar.render(1, 1, PROGRESS_BAR_LENGTH);
265265
}
266266
if (def.every() > 1) {

‎src/main/java/tfmc/justin/activity/hooks/PlaceholderHook.java‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -110,7 +110,7 @@ public String onRequest(OfflinePlayer player, String params) {
110110
// at least one award earned today
111111
int today = currentWeek && data.dayKey().equals(keys.day())
112112
? def.worth(data.count(def.id())) : 0;
113-
boolean done = def.dailyCap() > 0 ? today >= def.dailyCap() : today > 0;
113+
boolean done = def.dailyCap() > 0 ? today >= def.capPoints() : today > 0;
114114
return Utils.colorize(config.messages().raw(done ? "placeholder.done" : "placeholder.not-done"));
115115
}
116116

‎src/main/java/tfmc/justin/activity/models/ActivityDef.java‎

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,8 @@
66

77
// ====================================
88
// every: actions per award. points: awarded each time 'every' is met.
9-
// dailyCap: most points this activity can add in one day, 0 = unlimited.
9+
// dailyCap: most awards ('every' met) that count in one day, 0 = unlimited;
10+
// capPoints() is the same limit in points.
1011
// iconPath: the item path the GUI icon comes from - either the TLibs
1112
// m.<type>.<id> form or the normalized ItemsAdder ia.<namespace:id> one,
1213
// which is what routes ActivityGui.fromPath to the right hook - or null when
@@ -42,7 +43,13 @@ public ActivityDef(String id, String display, Material icon, String iconPath, in
4243
// What a day's action count is worth in points, after every and dailyCap
4344
public int worth(int count) {
4445
int raw = rawWorth(count);
45-
return dailyCap > 0 ? Math.min(raw, dailyCap) : raw;
46+
return dailyCap > 0 ? Math.min(raw, capPoints()) : raw;
47+
}
48+
49+
// dailyCap in points - the most worth() can reach in a day. Saturates like
50+
// rawWorth. Only meaningful when dailyCap > 0.
51+
public int capPoints() {
52+
return (int) Math.min(Integer.MAX_VALUE, (long) dailyCap * points);
4653
}
4754

4855
// ====================================

‎src/main/java/tfmc/justin/activity/models/PlayerData.java‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -176,7 +176,7 @@ public RecordResult record(int amount, ActivityDef def, int max, int dailyMax, i
176176
// Either the activity has nothing more to give today, or the count
177177
// is simply part-way to its next point - which is a plain success
178178
return new RecordResult(0, 0,
179-
def.dailyCap() > 0 && def.worth(before) >= def.dailyCap()
179+
def.dailyCap() > 0 && def.worth(before) >= def.capPoints()
180180
? Recorded.ACTIVITY_CAP : Recorded.RECORDED);
181181
}
182182
int pointsBefore = points;

‎src/main/resources/config.yml‎

Lines changed: 9 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -4,8 +4,10 @@ reset:
44

55
# Seven of these are drawn at random for each player every day and shown in
66
# /activity; a task earns nothing until the player clicks it to reveal it.
7-
# Every 'every' actions → earn 'points'. 'daily-cap' = most points one
8-
# activity can add per day (0 = no cap). Bar caps at bar.max; bar.daily-max
7+
# Every 'every' actions → earn 'points'. 'daily-cap' = how many times per day
8+
# that can happen (0 = no cap), so a day is worth at most daily-cap × points
9+
# (every: 1, points: 5, daily-cap: 1 → the first action gives 5, later ones
10+
# that day give nothing). Bar caps at bar.max; bar.daily-max
911
# caps what can reach it on any single day, first, and bar.vote-share keeps part
1012
# of that day for voting only. All are bypassed by
1113
# /activity add <player> <activity> <count> --force, which is staff-only and
@@ -15,7 +17,8 @@ reset:
1517
# remaining slots are filled at random as usual. Marking more than seven
1618
# activities guaranteed is reported at load - the draw would then be seven of
1719
# them and nothing else could ever be drawn.
18-
# vote: one point per vote site, and always one of the day's tasks.
20+
# vote: one point per vote site (every: 1, points: 1), daily-cap = how many
21+
# sites count per day, and always one of the day's tasks.
1922
# playtime: 'every' counts minutes online - see the playtime section at the
2023
# bottom, and read what afk-minutes does and does not do before relying on it.
2124
# material: the GUI icon, and craft: the item this activity counts crafts of.
@@ -66,7 +69,8 @@ reset:
6669
# 'every' is XP, not actions. Fractional XP is carried over between gains
6770
# rather than lost. Two activities naming the same profession is a config
6871
# error and only the last one is fed. Always set a daily-cap on these:
69-
# profession XP is unbounded, unlike the one-per-action activities.
72+
# profession XP is unbounded, unlike the one-per-action activities. Here
73+
# daily-cap counts awards, i.e. at most daily-cap × every XP counts a day.
7074
# station: optional, MMOItems only. Counts crafts made at an MMOItems
7175
# crafting station, written as either
7276
# ingot-station any recipe crafted at that station
@@ -193,7 +197,7 @@ activities:
193197
material: BONE
194198
every: 1
195199
points: 5
196-
daily-cap: 5
200+
daily-cap: 1
197201
# InteractibleFurniture: furniture placed in the world
198202
furniture_place:
199203
display: "#e6ca40&lPlace Furniture"

‎src/test/java/tfmc/justin/activity/commands/ActivityAdminCommandTest.java‎

Lines changed: 11 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -494,13 +494,21 @@ private static List<String> milestoneCrossedBy(int barMax, int startPoints, Send
494494

495495
@Test
496496
void checkPrintsEveryFieldOfThePlayersDay() {
497-
ActivityManager manager = manager();
497+
// daily-cap 1 makes worth(3) = 1, distinct from rawWorth(3) = 3, so
498+
// the "before any cap" text below is checked against the uncapped
499+
// figure the message promises rather than the capped one - which
500+
// 'check' must read off config's own def, not merely echo the count
501+
ActivityManager manager = TestManagers.manager(
502+
new ActivityDef("vote", "Vote", Material.PAPER, null, 1, 1, 1),
503+
new ActivityDef("quest", "Quest", Material.PAPER, null, 2, 1, 0));
504+
TestManagers.messages(manager);
505+
TestManagers.rerollsPerDay(manager, 1);
498506
UUID player = UUID.randomUUID();
499507
manager.reveal(player, manager.tasks(player).tasks().indexOf("vote"));
500508
PlayerData data = manager.getStore().peek(player);
501509
// Straight through the POJO: crediting a point through the manager
502510
// would announce it, and that needs a running server
503-
data.record(3, new ActivityDef("vote", "Vote", Material.PAPER, null, 2, 1, 0), 50, 10, List.of(10, 20));
511+
data.record(3, new ActivityDef("vote", "Vote", Material.PAPER, null, 1, 1, 1), 50, 10, List.of(10, 20));
504512

505513
Sender sender = admin();
506514
command(manager, player, "Steve").onCommand(sender.bukkit, null, "activity", new String[] {"check", "Steve"});
@@ -521,7 +529,7 @@ void checkPrintsEveryFieldOfThePlayersDay() {
521529
assertTrue(out.contains("Vote"), out);
522530
assertTrue(out.contains("revealed"), out);
523531
assertTrue(out.contains("count 3"), out);
524-
assertTrue(out.contains("that count is worth 1 points before any cap"), out);
532+
assertTrue(out.contains("that count is worth 3 points before any cap"), out);
525533
// and every slot of the draw is listed
526534
assertEquals(data.tasks().size(),
527535
sender.sent.stream().filter(line -> line.contains("revealed") || line.contains("hidden")).count());

‎src/test/java/tfmc/justin/activity/gui/ActivityGuiTest.java‎

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -275,6 +275,20 @@ void theDescriptionSitsAboveTheProgressAndTodayLines() {
275275
messages.get("gui.activity-lore-today-capped", "%today%", 0, "%cap%", 5)), lore);
276276
}
277277

278+
// daily-cap counts awards, so the lore's cap is shown in points
279+
// (daily-cap × points) alongside today's points, and the bar reads full
280+
@Test
281+
void theTodayLineShowsTheCapInPoints() {
282+
Messages messages = shippedMessages();
283+
ActivityDef def = activity("injured", 1, 5, 1);
284+
285+
List<String> lore = ActivityGui.activityLore(messages, def, 3);
286+
287+
assertEquals(List.of(
288+
messages.get("gui.activity-lore-progress", "%bar%", Utils.colorize(Bar.render(1, 1, 20))),
289+
messages.get("gui.activity-lore-today-capped", "%today%", 5, "%cap%", 5)), lore);
290+
}
291+
278292
// No description configured - the lore is exactly what it has always been
279293
@Test
280294
void anActivityWithoutADescriptionRendersTheSameTwoLines() {

‎src/test/java/tfmc/justin/activity/models/ActivityDefTest.java‎

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,4 +28,42 @@ void theFullConstructorRoundTripsIconPath() {
2828

2929
assertEquals("m.item.ballot", def.iconPath());
3030
}
31+
32+
// daily-cap counts awards, not points: a day is worth min(count / every,
33+
// dailyCap) * points
34+
@Test
35+
void dailyCapCountsAwardsNotPoints() {
36+
ActivityDef def = new ActivityDef("injured", "Injured", Material.BONE, null, 1, 5, 1);
37+
38+
assertEquals(5, def.worth(1));
39+
assertEquals(5, def.worth(3));
40+
assertEquals(5, def.capPoints());
41+
}
42+
43+
@Test
44+
void dailyCapWithEveryAboveOne() {
45+
ActivityDef def = new ActivityDef("x", "X", Material.PAPER, null, 2, 3, 2);
46+
47+
assertEquals(0, def.worth(1));
48+
assertEquals(3, def.worth(2));
49+
assertEquals(3, def.worth(3));
50+
assertEquals(6, def.worth(4));
51+
assertEquals(6, def.worth(5));
52+
}
53+
54+
@Test
55+
void zeroDailyCapIsUnlimited() {
56+
ActivityDef def = new ActivityDef("x", "X", Material.PAPER, null, 1, 5, 0);
57+
58+
assertEquals(500, def.worth(100));
59+
}
60+
61+
@Test
62+
void capPointsAndWorthSaturateInsteadOfOverflowing() {
63+
ActivityDef def = new ActivityDef("x", "X", Material.PAPER, null, 1, 1_000_000, 1_000_000);
64+
65+
assertEquals(Integer.MAX_VALUE, def.capPoints());
66+
assertEquals(Integer.MAX_VALUE, def.worth(Integer.MAX_VALUE));
67+
assertEquals(1_000_000, def.worth(1));
68+
}
3169
}

0 commit comments

Comments
 (0)