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
7 changes: 4 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -15,9 +15,10 @@ commits to the fight, and defeats every monster the lure draws in.
night-only, minimum-height, and terrain rules.
- **Lure raids** — an InteractibleFurniture lure opens a join window, then
releases a paced wave; defeating the full wave clears the infestation.
- **Committed parties** — leaving the province or losing the whole party fails
the lure, bystanders are warned and damaged, and logged-out players get a
grace period.
- **Committed parties** — a committed player who leaves the province takes
damage until `/lure leave` drops them from the fight. Losing the whole party
fails the lure, bystanders who stay are warned and damaged, and logged-out
players get a grace period.
- **Optional spread** — idle infestations can worsen and spread to neighbouring
land, including across a single water or sea province.
- **Map and persistence** — infestation severity is exported to the
Expand Down
19 changes: 12 additions & 7 deletions src/main/java/net/tfminecraft/infestations/Infestations.java
Original file line number Diff line number Diff line change
Expand Up @@ -39,13 +39,8 @@ public void onEnable() {
getServer().getPluginManager().registerEvents(infestationManager, this);
infestationManager.start();

var cmd = getCommand("infestation");
if (cmd != null) {
cmd.setExecutor(commandManager);
cmd.setTabCompleter(commandManager);
} else {
getLogger().severe("Command 'infestation' missing from plugin.yml");
}
registerCommand("infestation");
registerCommand("lure");

getLogger().info("Infestations enabled.");
}
Expand All @@ -62,6 +57,16 @@ public InfestationManager getInfestationManager() {
return infestationManager;
}

private void registerCommand(String name) {
var cmd = getCommand(name);
if (cmd != null) {
cmd.setExecutor(commandManager);
cmd.setTabCompleter(commandManager);
} else {
getLogger().severe("Command '" + name + "' missing from plugin.yml");
}
}

public boolean reloadAll() {
boolean ok = loadConfigs();
if (infestationManager != null) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,9 @@ public final class CommandManager implements CommandExecutor, TabCompleter {

@Override
public boolean onCommand(CommandSender sender, Command command, String label, String[] args) {
if (command.getName().equalsIgnoreCase("lure")) {
return handleLure(sender, args);
}
if (args.length == 0) {
if (!sender.hasPermission("infestations.admin")
&& !sender.hasPermission("infestations.admin.reload")) {
Expand Down Expand Up @@ -57,6 +60,21 @@ public boolean onCommand(CommandSender sender, Command command, String label, St
return true;
}

private boolean handleLure(CommandSender sender, String[] args) {
if (args.length != 1 || !args[0].equalsIgnoreCase("leave")) {
sender.sendMessage(Messages.get("lure-usage"));
return true;
}
if (!(sender instanceof Player player)) {
sender.sendMessage(Messages.get("player-only"));
return true;
}
if (!Infestations.plugin.getInfestationManager().leaveLure(player)) {
player.sendMessage(Messages.get("lure-not-in"));
}
return true;
}

private void handleSet(CommandSender sender, String[] args) {
if (args.length < 4) {
sender.sendMessage(Messages.get("usage"));
Expand Down Expand Up @@ -155,6 +173,12 @@ private Integer resolveProvince(CommandSender sender, String token) {

@Override
public List<String> onTabComplete(CommandSender sender, Command command, String alias, String[] args) {
if (command.getName().equalsIgnoreCase("lure")) {
if (args.length == 1) {
return prefix(List.of("leave"), args[0]);
}
return List.of();
}
if (args.length == 1) {
List<String> options = new ArrayList<>();
if (sender.hasPermission("infestations.admin.reload")) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -213,7 +213,7 @@ private void tick() {
if (infestation.hasLure()) {
LureHologram.tick(infestation);
}
tickDeserters(infestation);
tickOutsideCommitted(infestation);
}

tickSpread();
Expand Down Expand Up @@ -560,23 +560,63 @@ private void warnNonJoiners(Infestation infestation) {
}
}

private void tickDeserters(Infestation infestation) {
private void tickOutsideCommitted(Infestation infestation) {
if (tick % 20 != 0 || infestation.getPhase() == LurePhase.NONE) {
return;
}
String bar = Messages.get("lure-outside-bar");
for (UUID id : List.copyOf(infestation.getCommitted())) {
Player player = Bukkit.getPlayer(id);
if (player == null || !player.isOnline() || player.isDead()) {
continue;
}
if (player.getGameMode() == GameMode.CREATIVE || player.getGameMode() == GameMode.SPECTATOR) {
continue;
}
if (Provinces.at(player) == infestation.getProvinceId()) {
continue;
}
failLure(infestation);
return;
actionBar(player, bar);
player.damage(Cache.deserterDamage);
if (tick % 100 == 0) {
player.sendMessage(Messages.get("lure-outside"));
}
}
}

/**
* Drop a committed player out of every lure. The lure fails only when nobody committed remains.
*
* @return true when the player was committed to at least one lure
*/
public boolean leaveLure(Player player) {
UUID id = player.getUniqueId();
boolean found = false;
for (Infestation infestation : List.copyOf(byProvince.values())) {
if (!infestation.isCommitted(id)) {
continue;
}
if (!found) {
player.sendMessage(Messages.get("lure-left"));
}
found = true;
boolean inProvince = Provinces.at(player) == infestation.getProvinceId();
infestation.getCommitted().remove(id);
infestation.getLogoutGraceUntil().remove(id);
infestation.getDeathOnLogin().remove(id);
if (infestation.getPhase() != LurePhase.NONE && !hasLivingOrGrace(infestation)) {
failLure(infestation);
if (!inProvince) {
player.sendMessage(Messages.get("lure-fail"));
}
}
}
if (found) {
save();
}
return found;
}

private void expireGrace(Infestation infestation, long now) {
Iterator<Map.Entry<UUID, Long>> it = infestation.getLogoutGraceUntil().entrySet().iterator();
while (it.hasNext()) {
Expand Down Expand Up @@ -1044,9 +1084,14 @@ public void onLeaveProvince(PlayerProvinceLeaveEvent event) {
if (infestation.getPhase() == LurePhase.NONE) {
return;
}
if (infestation.isCommitted(event.getPlayer().getUniqueId())) {
failLure(infestation);
Player player = event.getPlayer();
if (!infestation.isCommitted(player.getUniqueId())) {
return;
}
if (player.getGameMode() == GameMode.CREATIVE || player.getGameMode() == GameMode.SPECTATOR) {
return;
}
player.sendMessage(Messages.get("lure-outside"));
}

@EventHandler
Expand Down
2 changes: 2 additions & 0 deletions src/main/resources/config.yml
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,8 @@ lure-spawn-radius: 48
# ambient ring minimum and the lure ring minimum when they are smaller.
min-player-distance: 16
logout-grace-seconds: 300
# Hearts are 2 health. Applied each second to uncommitted players who stay
# after a lure starts, and to committed players who leave the province.
deserter-damage: 2
hologram-view-range: 96

Expand Down
7 changes: 6 additions & 1 deletion src/main/resources/messages.yml
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,12 @@ list-lure-suffix: " #c45c5c[lure]"
no-infestation-here: "{prefix}#c45c5cThere is no infestation here. The lure does nothing."
lure-already: "{prefix}#c45c5cA lure is already placed in this province."
lure-placed: "{prefix}#c9a44aA lure is activating. Leave the province or right-click the lure to join."
lure-join: "{prefix}#7dba7aYou joined the lure. Leave the province and the lure fails."
lure-join: "{prefix}#7dba7aYou joined the lure. Leave the province and you take damage until you run #d4c9ae/lure leave#7dba7a."
lure-outside: "{prefix}#c45c5cYou left the lure's province. Run #d4c9ae/lure leave #c45c5cto stop taking damage."
lure-outside-bar: "#c45c5cRun /lure leave to stop taking damage."
lure-left: "{prefix}#d4c9aeYou left the lure."
lure-not-in: "{prefix}#c45c5cYou are not in a lure."
lure-usage: "{prefix}#d4c9ae/lure leave"
lure-already-joined: "{prefix}#d4c9aeYou are already in the lure."
lure-highlight: "{prefix}#d4c9aeHighlighted {count} remaining lure mobs for 10s."
lure-activating-bar: "#c9a44aLure activating: {seconds}s - leave or right-click to join"
Expand Down
3 changes: 3 additions & 0 deletions src/main/resources/plugin.yml
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,9 @@ commands:
usage: /infestation <reload|set|clear|list>
aliases: [infestations]
permission: infestations.admin
lure:
description: Leave a lure you have joined
usage: /lure leave

permissions:
infestations.admin:
Expand Down