Skip to content

Add a referral system to the activity menu - #97

Merged
XxFran10xX merged 17 commits into
mainfrom
feat/referral-system
Oct 24, 2026
Merged

XxFran10xX merged 17 commits into
mainfrom
feat/referral-system

Conversation

@XxFran10xX

@XxFran10xX XxFran10xX commented Oct 20, 2026 •

Copy link
Copy Markdown
Contributor

What

A referral system inside ActivityTF that reuses the plugin's reward pools, claim flow and menu style. This is still a draft. The reward values below are the ones agreed with fran for Act I.

  • Linking: a new player opens /referral (or the head button at slot 22 of /activity), clicks Who invited you?, picks an online player or types a name (/referral invitedby <name>), and confirms. Each account gets one link, and the player can never change it. Staff can fix links with /referral link and /referral unlink.
  • Invites are unlimited (link.max-invitees: 0). The rare track still ends at 10 players, so inviting more people after that adds nothing to it. max-links-per-day (3) still applies.
  • Timed rewards: milestones are based on the new player's counted playtime. They are the same in every act and fully set in config (milestones: in referral.yml: any number of milestones, any hours, any reward per side). The new player claims each one once ever per season. The inviter claims each one once per invited player, from the same menu. The row shows seven at a time and scrolls past that.
  • Rare track (inviter only): a separate list (rare-track:). Every invited player who reaches the last milestone (24h) moves the inviter one step along it. Each step is claimed once per season.
  • Acts: what a milestone holds comes from the act in use. acts.<id> sets starts and its own pools, and the latest act that has started applies. A pool is looked up in the act's pools, then the shared pools, then the activity's week-locked rewards.pools.
  • Season: for the first season.open-days (14) days after season.start, any account can link, and its playtime since the season start already counts. After that, only accounts in their first link.new-account-days (7) days can link. Changing season.start moves referrals.yml aside and starts a fresh season. If the store can't be saved first, the season doesn't change (it logs SEVERE), and the next reload tries again.

Rewards (Act I)

Milestone New player Inviter (per invited player)
1h Steel Pickaxe, Hatchet, Shovel and Hoe + 64 Dry Rations + 10 gold denars 5 gold denars
4h 8 Ignitium + 20 gold denars 4 Ignitium
12h 16 Ignitium + 30 gold denars 8 Ignitium
24h 24 Ignitium + 2 raw tin + 2 abyssalite fragments + 40 gold denars 12 Ignitium + 20 gold denars
Total 4 steel tools, 64 rations, 48 Ignitium, 2 tin, 2 fragments, 100 denars 25 denars, 24 Ignitium
Rare track (invited players at 24h) Reward Running total
2 1 raw tin + 1 abyssalite fragment 1 + 1
4 1 + 1 2 + 2
6 2 + 2 4 + 4
8 2 + 2 6 + 6
10 2 + 2 8 + 8

The denars match the economy on Main (2026-10-24): 100d is one median week of earnings and the price of a first paid class change, and 25d is a quarter of a week. There are no denars on the rare track. Its total, 8 tin and 8 fragments, is 30:1 against the 240 Ignitium ten invitees pay their inviter (rule 4 of the Act System's Activity trickle is about 40:1). ACT-SYSTEM.md in the workspace logs referrals as a new one-off source of act materials, with these tables and their impact.

Cooking reward items

Reward items can now be Cooking items: c.<category>(type=<food>;...), such as c.dry_ration(type=dry_ration;quality=1) from the RPCharacters starting kit. They are handed over through TLibs like m. paths. Any reward item can also set name:, what the menu calls it (the shipped m.tools.steel_axe is named "Steel Hatchet", as in game). A malformed path is named at load time, and if Cooking isn't enabled, a warning says the path hands over nothing. They are only accepted as reward items, not as craft keys or icons.

Menu (/referral, 54 slots)

Slots What
0 Inviter head (Invited by: X), Who invited you?, or why linking is closed
4 Progress clock: a bar with milestone markers, e.g. 14h / 24h
8 Rules book
10 / 12 / 14 / 16 Your 1h / 4h / 12h / 24h milestones, each showing its reward item. The lore lists exactly what is inside. Locked: grey name with the time left. Claimable: green and glowing. Claimed: ✔. On hold: locked, with a "on hold - ask staff" line
22 Players you invited (n)
27-44 One head per invited player, with their hours and [CLAIM 20h]. A click claims everything due for that player
9 / 17 Earlier / later milestones, only shown when there are more than seven
45 / 48 / 49 / 50 Back to Weekly Activity / previous page / rare-track star (glows when a step is ready, click to open) / next page

The rare-track menu has a header listing who counts and one item per step (locked / glowing claimable / ✔ claimed, with the step's contents and how many more players it needs).

Anti-abuse

I checked several public referral plugins (RallyRefer, MysticalReferral, InviteRewards, RedeemMaster, ReferMe) and some fraud-prevention write-ups. Most of them pay on a single playtime threshold plus a same-IP check. This PR goes further:

  • AFK time doesn't count. A minute counts only if, within playtime.afk-minutes, the player moved at least move-blocks blocks, chatted, ran a real command, or broke or placed a block, and Paper's idle timer is under the limit too. Turning the camera, swinging or autoclicking alone earns nothing. Movement is sampled every second.
  • Ignored worlds (character_selection): no counting and no claiming there.
  • Connection history: each account keeps its last 16 connections this season, stored as salted hashes. Accounts that shared a connection can't link, and if they share one later, both sides' claims are held. /referral allowip <a> <b> allows one real household pair.
  • No self links and no loops, at any depth.
  • Rare track: an invitee counts only when they have really reached the last milestone, and not when they share a connection with the inviter. Once a step is claimed, the invitees it stood on are tied to that inviter for good (rare-counted-for). The goal is recorded when an invitee reaches it (rare-reached-goal), so raising the goal mid-season doesn't take credit away.
  • Claims are keyed by milestone hours, so a milestone is never paid twice when the act changes. A claim is saved before paying and rolled back if the save or the payout fails. Every link, claim and staff action logs a REFERRAL-AUDIT line.
  • Kill switch: enabled: false.

Commands and permissions

  • /referral (alias /referrals) and /referral invitedby <name>: activity.referral (default true).
  • Staff (activity.referral.admin, op, and a child of activity.admin). /referral help lists these:
    • info <player>: hours, inviter, claims on both sides, invitees and their claims, rare count and who counts, rare claims, ties and flags.
    • link <player> <inviter> / unlink <player>: staff links skip the season, daily and connection checks and replace an existing link, but still refuse self links and loops.
    • addtime <player> <minutes> / settime <player> <minutes>: lowering time below the goal a player reached removes their rare credit, unless a claimed step already ties them.
    • unclaim <player> <hours|all> [new|inviter|both] / markclaimed ...: reopen or close milestones.
    • rare unclaim|markclaimed <inviter> <players|all>: unclaim all also frees the invitee ties, and markclaimed ties the invitees it stands on.
    • rare exclude|include <player>: keep an invitee off every rare track.
    • hold <player> / release <player>: an account on hold can't claim anything, and its inviter can't claim for it.
    • allowip <player> <player>: toggle the household pair.
  • Each staff command saves immediately. If the save fails, the change is undone and staff see "could not be saved".
  • /activity reload also reloads referral.yml.

Deploy notes

  • season.start ships empty, so set it before going live.
  • Playtime only counts from the moment this jar runs.
  • messages.yml: Main's plugins/activity/messages.yml (from 09-18) has no referral: section, so every referral text comes from the jar's defaults and nothing needs editing at deploy. To change a referral text, copy that key from the jar's messages.yml into the file and run /activity reload. Once a key is in the file, later jars won't update it.
  • Water streams and pistons moving a player still look like walking. A macro running a real command every few minutes still counts as activity.

Tests

mvn verify: 1075 tests pass. The new code has 100% line coverage. Locally, 10 lines in the existing ActivityCommand/PlayerStore are uncovered because their POSIX-only tests are skipped on Windows. That is unchanged from main, and Linux CI covers them.

Dev test (TFMCDev01, now activity-DEV-referral-20261024b.jar)

~/referral-test/claims.js ran 40 checks with mineflayer bots that have real characters, and all 40 passed on …24b (the current values), and before that on …24a and …22d. Earlier versions of the run passed 38/38 on …22b and …22c. TestChar2 was the new player and TestChar1 the inviter.

  • Staff commands:
    • Help, allowip, link (and its loop refusal), settime, unclaim all, rare unclaim all and info all answered correctly.
    • hold refused the claim and handed over nothing; release let it through.
  • New player: all four milestones paid. The inventory held exactly 4 steel tools, 64 Dry Rations, 48 Ignitium, 100 gold denars, 2 raw tin and 2 abyssalite fragments, and every milestone then showed ✔. The 1h lore names the Steel Hatchet.
  • Inviter: one click on TestChar2's head claimed all four milestones: 25 gold denars and 24 Ignitium, with no tools or tin.
  • Rare-track menu: it shows the five steps at 2/4/6/8/10 players. The 4-player step lists 1 raw tin + 1 fragment and the 10-player step 2 + 2, and no step lists denars. With 2 players counted, the 2-player step paid 1 tin + 1 fragment.
  • Console checks: markclaimed on an already-claimed milestone changed nothing. rare exclude shows flags: rare-excluded in info, and include undoes it.
  • Dev log: no activity warnings or errors.

Nothing was changed on Main or Tutorial. The old jars are backed up under ~/dev-jar-backup/.

🤖 Generated with Claude Code

XxFran10xX and others added 3 commits October 20, 2026 21:38
New players pick who invited them in /referral (or the new button in
/activity). Both players then claim per-act milestone rewards as the new
player's AFK-aware playtime grows. Links are permanent, refused across
shared connections, loops and inviter caps, and only open in the
season's first days or an account's first week.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
A typo or a first season.start no longer wipes referrals.yml, joining
or an unknown command no longer counts as activity, players online at a
reload keep their connection history, a broken file is read again on
reload, and staff can exempt an account with /referral allowip. Menu
pages no longer drift, the way back needs activity.use, and menu
switches run on the next tick.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
/referral allowip now takes two players and covers only that pair, the
last real season.start survives a blank one so the next date still
starts over, the delayed menu switches check the player is online, and
max-invitees defaults to 10 in code as in referral.yml.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@coderabbitai

coderabbitai Bot commented Oct 20, 2026 •

Copy link
Copy Markdown

Review in Change Stack →

No actionable comments were generated in the recent review.

Recent review info
Run configuration
  • Source mode: Auto
  • Repository: Unavailable
  • Branch: Unavailable
  • File: No configuration file from this repository applied
  • Commit: Unavailable
  • Layers: Organization UI
  • Review profile: CHILL
  • Plan: Advanced
  • Run ID: 074e38a0-c9a5-49ce-9a83-1632c5425a39
Commits

Reviewing files that changed from the base of the PR and between 3e90aa4 and b4fc0b4.

Files selected for processing: 33
  • README.md
  • src/main/java/net/tfminecraft/activitytf/ActivityPlugin.java
  • src/main/java/net/tfminecraft/activitytf/commands/ReferralCommand.java
  • src/main/java/net/tfminecraft/activitytf/config/ActivityConfiguration.java
  • src/main/java/net/tfminecraft/activitytf/config/Messages.java
  • src/main/java/net/tfminecraft/activitytf/config/ReferralConfiguration.java
  • src/main/java/net/tfminecraft/activitytf/gui/ActivityGui.java
  • src/main/java/net/tfminecraft/activitytf/gui/ReferralGui.java
  • src/main/java/net/tfminecraft/activitytf/listeners/ReferralListener.java
  • src/main/java/net/tfminecraft/activitytf/managers/ActivityManager.java
  • src/main/java/net/tfminecraft/activitytf/managers/ReferralManager.java
  • src/main/java/net/tfminecraft/activitytf/models/ReferralData.java
  • src/main/java/net/tfminecraft/activitytf/models/RewardEntry.java
  • src/main/java/net/tfminecraft/activitytf/store/ReferralStore.java
  • src/main/java/net/tfminecraft/activitytf/utils/ItemPath.java
  • src/main/resources/config.yml
  • src/main/resources/messages.yml
  • src/main/resources/plugin.yml
  • src/main/resources/referral.yml
  • src/test/java/net/tfminecraft/activitytf/ActivityPluginTest.java
  • src/test/java/net/tfminecraft/activitytf/commands/ReferralCommandTest.java
  • src/test/java/net/tfminecraft/activitytf/config/ActivityConfigurationRewardsTest.java
  • src/test/java/net/tfminecraft/activitytf/config/MessagesLinesTest.java
  • src/test/java/net/tfminecraft/activitytf/config/ReferralConfigurationTest.java
  • src/test/java/net/tfminecraft/activitytf/gui/ReferralGuiTest.java
  • src/test/java/net/tfminecraft/activitytf/hooks/TLibsItemsTest.java
  • src/test/java/net/tfminecraft/activitytf/listeners/ReferralListenerTest.java
  • src/test/java/net/tfminecraft/activitytf/managers/ActivityManagerRewardItemsTest.java
  • src/test/java/net/tfminecraft/activitytf/managers/ReferralFixture.java
  • src/test/java/net/tfminecraft/activitytf/managers/ReferralManagerTest.java
  • src/test/java/net/tfminecraft/activitytf/models/ReferralDataTest.java
  • src/test/java/net/tfminecraft/activitytf/store/ReferralStoreTest.java
  • src/test/java/net/tfminecraft/activitytf/utils/ItemPathTest.java

Included review availability: This review used your included allowance. Your plan provides up to 1 included review per hour; 0 remain after this review.

📝 Summary

Summary by CodeRabbit

  • New Features
    • Added a referral system with a /referral menu for linking an inviter, tracking AFK-free playtime milestones and claiming rewards.
    • Inviting players can earn rewards as invitees progress, including rare-track rewards for reaching the final milestone.
    • Added staff controls for managing referral links, progress, claims, holds and exclusions.
    • Referral rewards can now use Cooking item paths, and configured item names can appear in reward menus.

This change adds a configurable referral system with activity-based playtime, account linking, milestone and rare-track rewards, player menus, staff commands, and season-scoped persistence. Reward parsing also adds named items and Cooking item paths.

Changes: Referral system

Layer / File(s) Summary
Referral and reward configuration
src/main/java/net/tfminecraft/activitytf/config/ReferralConfiguration.java, src/main/java/net/tfminecraft/activitytf/config/ActivityConfiguration.java, src/main/java/net/tfminecraft/activitytf/models/RewardEntry.java, src/main/java/net/tfminecraft/activitytf/utils/ItemPath.java, src/main/java/net/tfminecraft/activitytf/managers/ActivityManager.java, src/main/resources/referral.yml, src/main/resources/config.yml, src/test/java/net/tfminecraft/activitytf/config/*, src/test/java/net/tfminecraft/activitytf/utils/ItemPathTest.java, src/test/java/net/tfminecraft/activitytf/managers/ActivityManagerRewardItemsTest.java
Referral configuration defines season settings, eligibility rules, milestones, rare-track steps, and reward pools. Reward items can have configured names. Cooking paths are validated and resolved through the TLibs path.
Referral data and persistence
src/main/java/net/tfminecraft/activitytf/models/ReferralData.java, src/main/java/net/tfminecraft/activitytf/store/ReferralStore.java, src/test/java/net/tfminecraft/activitytf/models/ReferralDataTest.java, src/test/java/net/tfminecraft/activitytf/store/ReferralStoreTest.java
Referral account state includes links, playtime, claims, connection hashes, and staff flags. ReferralStore loads and saves records by season, archives data on season changes, and serialises writes.
Activity, eligibility, and reward claims
src/main/java/net/tfminecraft/activitytf/managers/ReferralManager.java, src/main/java/net/tfminecraft/activitytf/managers/ActivityManager.java, src/main/java/net/tfminecraft/activitytf/listeners/ReferralListener.java, src/test/java/net/tfminecraft/activitytf/managers/*Referral*, src/test/java/net/tfminecraft/activitytf/listeners/ReferralListenerTest.java, src/test/java/net/tfminecraft/activitytf/hooks/TLibsItemsTest.java
ReferralManager tracks activity and eligible playtime, enforces linking and rare-track rules, and processes claims and staff changes. Reward claim state is saved before dispatch and reverted if saving or dispatch fails.
Player referral interface
src/main/java/net/tfminecraft/activitytf/gui/ReferralGui.java, src/main/java/net/tfminecraft/activitytf/gui/ActivityGui.java, src/main/java/net/tfminecraft/activitytf/config/Messages.java, src/main/resources/messages.yml, src/test/java/net/tfminecraft/activitytf/gui/ReferralGuiTest.java, src/test/java/net/tfminecraft/activitytf/config/MessagesLinesTest.java, src/test/java/net/tfminecraft/activitytf/ActivityPluginTest.java
ReferralGui displays linking, progress, milestones, invitees, and rare-track status. ActivityGui exposes referral access when enabled and permitted.
Staff commands and plugin registration
src/main/java/net/tfminecraft/activitytf/commands/ReferralCommand.java, src/main/java/net/tfminecraft/activitytf/ActivityPlugin.java, src/main/resources/plugin.yml, src/main/resources/messages.yml, README.md, src/test/java/net/tfminecraft/activitytf/commands/ReferralCommandTest.java
The referral command provides player access and staff operations. Plugin metadata registers the command, alias, and permissions. The README adds a referral feature summary.

Priority: Normal

Estimated code review effort: 4 (Complex) | ~45 minutes

Change: Feature

Sequence Diagram(s)

sequenceDiagram
  actor Player
  participant ReferralGui
  participant ReferralManager
  participant ReferralStore
  participant ActivityManager
  Player->>ReferralGui: Select an eligible reward
  ReferralGui->>ReferralManager: Request claim
  ReferralManager->>ReferralStore: Save claim state
  ReferralManager->>ActivityManager: Dispatch reward
  ActivityManager-->>ReferralManager: Return dispatch result
  ReferralManager-->>ReferralGui: Return claim result
Loading

Suggested reviewers: justinasla, ryanbarlow97

Merge Risk: Minimal · up to b4fc0

The referral feature adds menus, claims and staff commands, and no merge-blocking issue was found in this review. Cooking paths are intentionally accepted only as reward items, not as icons, so that behavior is by design.

- [ ] Autofix · Keep fixing CodeRabbit findings and required CI, and resolving merge conflicts

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@github-actions github-actions Bot added the size:XXL 1,000+ effective changed lines (tests excluded in mixed PRs). label Oct 20, 2026
XxFran10xX and others added 14 commits October 20, 2026 22:14
The Dev bot test showed a player pacing back to the same spot on each
minute boundary never counted as moving. Movement is now sampled every
second, while minutes are still counted once a minute. Audit lines also
drop colour codes from pool reward names.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The milestones are now one list for every act (1h, 4h, 12h, 24h by
default, 24h rare), so each one is claimed once ever. An act only sets
the pools those milestones draw from (acts.<act>.pools, then the shared
pools, then the activity pools), so the loot follows the act. Up to four
milestones sit one slot apart in the menu.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Hovering a milestone that draws from a single-entry pool (the starter
kit) now shows every item in it, as fixed rewards already did.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
An act no longer applies while there are no milestones (an old-layout
referral.yml crashed the menu), the old acts.<act>.milestones key is
named in the log, missing-pool warnings come once per act and not when
disabled, an empty act pool falls back to the shared one, and a claim
uses one act for its rare count and its loot. The rules and
referral.yml explain that milestones are claimed once by their hours.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Milestones are no longer capped at seven: the menu row shows seven at a
time and scrolls. The rare flag and per-act rare-limit are gone; instead
every invitee who reaches the last milestone moves their inviter along a
rare track (rare-track: players/reward), each step claimed once per
season. An invitee counts for one inviter only and not on a shared
connection. Default steps 1/3/5 draw from act pools; 24h now pays
Act materials (large).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
A nether star glints by default, so the rare-track button always looked
claimable on Dev. Stars, milestone and rare-step icons now set their glint
either way.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
An invitee who reached the last milestone with their inviter (or that a
rare step was claimed with) keeps counting for that inviter when a later
milestone raises the goal. rare-reached-with is noted on reaching the goal
and cleared by an unlink, so a staff fix of a wrong link still moves an
unclaimed invitee; rare-counted-for (set by a claim) still blocks every
other inviter. The rare-track title now says how many count.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
New players get steel tools and rations at 1h, then Ignitium and gold
denars, and 2 tin + 2 fragments at 24h. Inviters get 10 gold denars at 1h
and 12/20/32 Ignitium. The rare track moves to 2/4/6/8/10 invitees
(1/2/3/4/6 tin and fragments, no denars), and invitees are unlimited.

Reward items accept Cooking paths (c.<category>(type=...)) through TLibs.

Staff get /referral help, link, settime, unclaim, markclaimed, rare
unclaim/markclaimed/exclude/include and hold/release next to the existing
commands. Every staff change saves at once and is undone and reported when
it can't be saved; held accounts claim nothing and show as held in the menu.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
A reload that changes season.start saved the store and loaded the new
season even when the save failed, dropping progress not yet on disk. Now
the old season and its start stay in use and the next reload retries.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The inviter's 4h/12h/24h pools pay 6/10/16 Ignitium (32 per invitee, was
64). Reward items can set 'name:' for what menus call them; the steel_axe
item is a Steel Hatchet in game.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Steps at 2/4/6/8/10 invitees pay 1/1/2/2/2 tin and fragments (8 each in
total, was 16), keeping 40:1 against the inviter's 320 Ignitium.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
New player 8/16/24 (48, was 64), inviter 4/8/12 (24, was 32). Tin,
fragments, the rare track, tools and denars are unchanged.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
New player 10/20/30/40 gold denars (100, was 50), inviter 5 at 1h and 20
at 24h (25 per invitee, was 10 at 1h).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@XxFran10xX
XxFran10xX marked this pull request as ready for review October 24, 2026 15:12
@XxFran10xX

Copy link
Copy Markdown
Contributor Author

@coderabbitai full review

@coderabbitai

coderabbitai Bot commented Oct 24, 2026 •

Copy link
Copy Markdown
✅ Action performed

Full review finished.

@XxFran10xX
XxFran10xX merged commit 7a18364 into main Oct 24, 2026
7 of 8 checks passed
@XxFran10xX
XxFran10xX deleted the feat/referral-system branch October 24, 2026 15:20
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

size:XXL 1,000+ effective changed lines (tests excluded in mixed PRs).

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant