Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
17 commits
Select commit Hold shift + click to select a range
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
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ Tasks only start earning activity credit once revealed, giving players a reason
- **Activities across the server** — supported integrations track actions such as voting, cooking, archaeology, instrument playing, market sales, and vehicle building.
- **Varied progression limits** — daily caps and a voting share of the daily allowance shape how points are earned.
- **Task rerolls** — eligible players can redraw their daily selection within the server's limits.
- **Referrals** — a new player names who invited them (`/referral`); both claim rewards as the new player's AFK-free playtime passes each milestone, and the inviter earns a rare track for invitees who reach the last one. Staff can link, adjust, hold or exclude accounts.

Available activities depend on the gameplay plugins and activity definitions in use.

Expand Down
11 changes: 11 additions & 0 deletions src/main/java/net/tfminecraft/activitytf/ActivityPlugin.java
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,9 @@
import org.bukkit.Bukkit;
import org.bukkit.plugin.java.JavaPlugin;
import net.tfminecraft.activitytf.commands.ActivityCommand;
import net.tfminecraft.activitytf.commands.ReferralCommand;
import net.tfminecraft.activitytf.gui.ActivityGui;
import net.tfminecraft.activitytf.gui.ReferralGui;
import net.tfminecraft.activitytf.hooks.PlaceholderHook;
import net.tfminecraft.activitytf.listeners.AdvancedCraftListener;
import net.tfminecraft.activitytf.listeners.ArchaeologyListener;
Expand All @@ -22,6 +24,7 @@
import net.tfminecraft.activitytf.listeners.MmoItemsStationListener;
import net.tfminecraft.activitytf.listeners.ProfessionUpgradeListener;
import net.tfminecraft.activitytf.listeners.ProfessionXpListener;
import net.tfminecraft.activitytf.listeners.ReferralListener;
import net.tfminecraft.activitytf.listeners.VehicleBuildListener;
import net.tfminecraft.activitytf.listeners.VoteListener;
import net.tfminecraft.activitytf.managers.ActivityManager;
Expand All @@ -38,6 +41,8 @@ public void onEnable() {
manager.initialize();

ActivityGui gui = new ActivityGui(manager);
ReferralGui referralGui = new ReferralGui(manager, gui::open);
gui.setReferralOpener(referralGui::open);

ActivityCommand command = new ActivityCommand(manager, gui);
command.loadKnownPlayers();
Expand All @@ -49,6 +54,12 @@ public void onEnable() {
getServer().getPluginManager().registerEvents(new CraftListener(manager), this);
getServer().getPluginManager().registerEvents(gui, this);

ReferralCommand referralCommand = new ReferralCommand(manager, referralGui);
getCommand("referral").setExecutor(referralCommand);
getCommand("referral").setTabCompleter(referralCommand);
getServer().getPluginManager().registerEvents(new ReferralListener(manager.referrals()), this);
getServer().getPluginManager().registerEvents(referralGui, this);

// Paper can enable soft dependencies after this plugin when their own dependencies form
// a cycle, so re-check them once every plugin has finished enabling.
getServer().getScheduler().runTask(this, () -> {
Expand Down
458 changes: 458 additions & 0 deletions src/main/java/net/tfminecraft/activitytf/commands/ReferralCommand.java

Large diffs are not rendered by default.

Original file line number Diff line number Diff line change
Expand Up @@ -809,43 +809,49 @@
return Map.copyOf(pools);
}

private List<RewardEntry> loadRewardPool(ConfigurationSection config, String path) {
List<RewardEntry> loadRewardPool(ConfigurationSection config, String path) {
List<RewardEntry> pool = new ArrayList<>();
if (!set(config, path)) {
return List.of();
}
int index = 0;
for (Map<?, ?> entry : config.getMapList(path)) {
String where = path + "[" + index++ + "]";

int weight = entry.get("weight") instanceof Number number ? number.intValue() : 1;
if (weight <= 0) {
log().warning(where + " has weight " + weight + " - a weight must be above 0,"
+ " so this reward is skipped and can never be drawn.");
continue;
RewardEntry reward = rewardEntry(entry, path + "[" + index++ + "]");
if (reward != null) {
pool.add(reward);
}
}
return List.copyOf(pool);
}

List<String> commands = new ArrayList<>();
if (entry.get("commands") instanceof List<?> raw) {
for (Object command : raw) {
if (command != null) {
commands.add(String.valueOf(command));
}
}
}
List<RewardEntry.Item> items = loadRewardItems(entry.get("items"), where);
// One pool-style entry (weight, display, items, commands); null, with a warning, when it pays nothing.
RewardEntry rewardEntry(Map<?, ?> entry, String where) {
int weight = entry.get("weight") instanceof Number number ? number.intValue() : 1;
if (weight <= 0) {
log().warning(where + " has weight " + weight + " - a weight must be above 0,"
+ " so this reward is skipped and can never be drawn.");
return null;
}

if (commands.isEmpty() && items.isEmpty()) {
log().warning(where + " has no commands and no items - skipped, since drawing it"
+ " would pay the player nothing.");
continue;
List<String> commands = new ArrayList<>();
if (entry.get("commands") instanceof List<?> raw) {
for (Object command : raw) {
if (command != null) {
commands.add(String.valueOf(command));
}
}
}
List<RewardEntry.Item> items = loadRewardItems(entry.get("items"), where);

Object display = entry.get("display");
pool.add(new RewardEntry(Math.min(weight, 1_000_000),
display == null ? "" : String.valueOf(display), List.copyOf(commands), List.copyOf(items)));
if (commands.isEmpty() && items.isEmpty()) {
log().warning(where + " has no commands and no items - skipped, since drawing it"
+ " would pay the player nothing.");
return null;
}
return List.copyOf(pool);

Object display = entry.get("display");
return new RewardEntry(Math.min(weight, 1_000_000),
display == null ? "" : String.valueOf(display), List.copyOf(commands), List.copyOf(items));
}

private List<RewardEntry.Item> loadRewardItems(Object raw, String where) {
Expand Down Expand Up @@ -874,16 +880,31 @@
if (!validItemPath(value, at)) {
continue;
}
items.add(new RewardEntry.Item(value, rewardAmount(map.get("amount"), at)));
Object name = map.get("name");
String label = name == null || String.valueOf(name).isBlank() ? null : String.valueOf(name).strip();
items.add(new RewardEntry.Item(value, rewardAmount(map.get("amount"), at), label));
}
return items;
}

private boolean validItemPath(String value, String at) {
if (ItemPath.isCookingPath(value)) {
if (ItemPath.cookingPath(value) == null) {
log().warning("Malformed item path '" + Utils.safeForLog(value) + "' at " + at
+ " - expected c.<category>(type=<food>;...) - ignored.");
return false;
}
pluginPathConfigured = true;
if (!pluginEnabled("Cooking")) {
log().warning("Cooking item path '" + Utils.safeForLog(value) + "' at " + at
+ " but Cooking is not enabled - it hands over nothing.");
}
return true;
}
if (ItemPath.isUnsupportedPath(value)) {
log().warning("Unsupported item path '" + Utils.safeForLog(value) + "' at " + at
+ " - only bare Material names, v.<material>, m.<type>.<id> and ia.<namespace:id>"
+ " are supported - ignored.");
+ " - only bare Material names, v.<material>, m.<type>.<id>, ia.<namespace:id> and"
+ " c.<category>(...) are supported - ignored.");
return false;
}
if (ItemPath.isItemsAdderPath(value)) {
Expand Down Expand Up @@ -1132,8 +1153,15 @@
return pool.isEmpty() && ActivityManager.neededPools(milestones, drops).contains(DEFAULT_POOL);
}

static String itemName(String path) {
String last = path.substring(Math.max(path.lastIndexOf('.'), path.lastIndexOf(':')) + 1);
/** The item's configured name, or one built from its path. */
public static String itemName(RewardEntry.Item item) {
return item.name() != null ? item.name() : itemName(item.path());
}

public static String itemName(String path) {
String type = ItemPath.cookingType(path);
String last = type != null ? type
: path.substring(Math.max(path.lastIndexOf('.'), path.lastIndexOf(':')) + 1);
StringBuilder name = new StringBuilder();
for (String word : last.toLowerCase(Locale.ROOT).split("_")) {
if (!word.isEmpty()) {
Expand Down Expand Up @@ -1303,7 +1331,7 @@
return value.toLowerCase(Locale.ROOT);
}
try {
return Sound.valueOf(value.toUpperCase(Locale.ROOT)).getKey().getKey();

Check warning on line 1334 in src/main/java/net/tfminecraft/activitytf/config/ActivityConfiguration.java

View workflow job for this annotation

GitHub Actions / build

getKey() in org.bukkit.Sound has been deprecated and marked for removal

Check warning on line 1334 in src/main/java/net/tfminecraft/activitytf/config/ActivityConfiguration.java

View workflow job for this annotation

GitHub Actions / build

valueOf(java.lang.String) in org.bukkit.Sound has been deprecated and marked for removal
} catch (IllegalArgumentException ignored) {
return value.toLowerCase(Locale.ROOT);
}
Expand Down Expand Up @@ -1501,6 +1529,15 @@
return itemsAdderUsable;
}

// Whether a plugin is on; without a running server (unit tests) it counts as on, so nothing warns.
static boolean pluginEnabled(String name) {
try {
return Bukkit.getPluginManager().isPluginEnabled(name);
} catch (RuntimeException e) {
return true;
}
}

static String itemPathWarning(boolean configured, boolean usable, String missing) {
if (!configured || usable) {
return null;
Expand Down
16 changes: 16 additions & 0 deletions src/main/java/net/tfminecraft/activitytf/config/Messages.java
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,8 @@
import java.io.InputStreamReader;
import java.io.Reader;
import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
import java.util.List;

public class Messages {

Expand Down Expand Up @@ -60,6 +62,20 @@ public String get(String path, Object... placeholderPairs) {
return message;
}

/** A list message, one coloured line per entry, with the same placeholders as {@link #get}. */
public List<String> lines(String path, Object... placeholderPairs) {
List<String> lines = new ArrayList<>();
for (String line : messages.getStringList(path)) {
String message = Utils.colorize(line);
for (int index = 0; index + 1 < placeholderPairs.length; index += 2) {
message = message.replace(String.valueOf(placeholderPairs[index]),
String.valueOf(placeholderPairs[index + 1]));
}
lines.add(message);
}
return lines;
}

public String raw(String path) {
String value = messages.getString(path);
return value == null ? path : value;
Expand Down
Loading
Loading