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
13 changes: 13 additions & 0 deletions pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@
<maven.compiler.proc>full</maven.compiler.proc>
<argLine></argLine>
<interactiblefurniture.version>0.2.1</interactiblefurniture.version>
<rpcharacters.version>2.4.0</rpcharacters.version>
<simplefactions.version>3.0.2</simplefactions.version>
<tlibs.version>2.0.1</tlibs.version>
</properties>
Expand Down Expand Up @@ -52,6 +53,18 @@
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>net.tfminecraft</groupId>
<artifactId>rpcharacters</artifactId>
<version>${rpcharacters.version}</version>
<scope>provided</scope>
<exclusions>
<exclusion>
<groupId>*</groupId>
<artifactId>*</artifactId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>net.tfminecraft</groupId>
<artifactId>simplefactions</artifactId>
Expand Down
5 changes: 5 additions & 0 deletions src/main/java/net/tfminecraft/infestations/Infestations.java
Original file line number Diff line number Diff line change
Expand Up @@ -10,9 +10,11 @@

import net.tfminecraft.infestations.cache.Cache;
import net.tfminecraft.infestations.command.CommandManager;
import net.tfminecraft.infestations.infestation.CharacterActivatedListener;
import net.tfminecraft.infestations.infestation.InfestationManager;
import net.tfminecraft.infestations.loader.ConfigLoader;
import net.tfminecraft.infestations.loader.GroupLoader;
import net.tfminecraft.infestations.utils.Characters;

/**
* Province infestations. See the Docs project guide for behaviour and configuration.
Expand All @@ -37,6 +39,9 @@ public void onEnable() {

infestationManager = new InfestationManager(this);
getServer().getPluginManager().registerEvents(infestationManager, this);
if (Characters.enabled()) {
getServer().getPluginManager().registerEvents(new CharacterActivatedListener(infestationManager), this);
}
infestationManager.start();

registerCommand("infestation");
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,4 +25,5 @@ public class InfestationData {
public List<String> committed = new ArrayList<>();
public Map<String, Long> logoutGraceUntil = new HashMap<>();
public List<String> deathOnLogin = new ArrayList<>();
public Map<String, String> logoutCharacter = new HashMap<>();
}
Original file line number Diff line number Diff line change
Expand Up @@ -128,6 +128,15 @@ private static Infestation fromData(InfestationData row) {
}
}
}
if (row.logoutCharacter != null) {
for (Map.Entry<String, String> e : row.logoutCharacter.entrySet()) {
if (e.getKey() == null || e.getValue() == null) continue;
try {
infestation.getLogoutCharacter().put(UUID.fromString(e.getKey()), e.getValue());
} catch (IllegalArgumentException ignored) {
}
}
}
return infestation;
}

Expand Down Expand Up @@ -157,6 +166,9 @@ private static InfestationData toData(Infestation infestation) {
for (UUID id : infestation.getDeathOnLogin()) {
row.deathOnLogin.add(id.toString());
}
for (Map.Entry<UUID, String> e : infestation.getLogoutCharacter().entrySet()) {
row.logoutCharacter.put(e.getKey().toString(), e.getValue());
}
return row;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
package net.tfminecraft.infestations.infestation;

import org.bukkit.event.EventHandler;
import org.bukkit.event.Listener;

import net.tfminecraft.rpcharacters.lifecycle.CharacterActivatedEvent;

/** Registered only with RPCharacters: a character's lure grace ends, or its pending death applies, once active. */
public final class CharacterActivatedListener implements Listener {

private final InfestationManager manager;

public CharacterActivatedListener(InfestationManager manager) {
this.manager = manager;
}

@EventHandler
public void onCharacterActivated(CharacterActivatedEvent event) {
if (event.getOwner() != null) {
manager.characterReturned(event.getOwner());
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,8 @@ public long getLureGeneration() {
private final Set<UUID> committed = new HashSet<>();
private final Map<UUID, Long> logoutGraceUntil = new HashMap<>();
private final Set<UUID> deathOnLogin = new HashSet<>();
/** The character each committed player logged off as, so a death on login reaches only that character. */
private final Map<UUID, String> logoutCharacter = new HashMap<>();

public Infestation(int provinceId, String groupId, Severity severity) {
this.provinceId = provinceId;
Expand Down Expand Up @@ -124,6 +126,7 @@ public void placeLure(Block block, long joinEndsAt, int budget) {
this.committed.clear();
this.logoutGraceUntil.clear();
this.deathOnLogin.clear();
this.logoutCharacter.clear();
this.waveRetryAtTick = 0;
this.lureActivatedAt = 0;
this.lureReleased = 0;
Expand All @@ -144,6 +147,7 @@ public void clearLure() {
committed.clear();
logoutGraceUntil.clear();
deathOnLogin.clear();
logoutCharacter.clear();
waveRetryAtTick = 0;
lureActivatedAt = 0;
lureReleased = 0;
Expand Down Expand Up @@ -292,6 +296,10 @@ public Set<UUID> getDeathOnLogin() {
return deathOnLogin;
}

public Map<UUID, String> getLogoutCharacter() {
return logoutCharacter;
}

public boolean isCommitted(UUID id) {
return committed.contains(id);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -73,6 +73,7 @@
import net.tfminecraft.infestations.spawn.MythicSpawner;
import net.tfminecraft.infestations.spawn.SpawnLog;
import net.tfminecraft.infestations.spawn.SpawnPlanner;
import net.tfminecraft.infestations.utils.Characters;
import net.tfminecraft.infestations.utils.Provinces;

public final class InfestationManager implements Listener, AmbientSpawnService.CollectionInfestations {
Expand Down Expand Up @@ -102,6 +103,7 @@ public void start() {
for (Infestation infestation : InfestationDatabase.load()) {
byProvince.put(infestation.getProvinceId(), infestation);
}
warnUnidentifiedDeaths();
InfestationMapExport.exportAsync(byProvince.values());
task = Bukkit.getScheduler().runTaskTimer(plugin, this::tick, 20L, 1L);
Bukkit.getScheduler().runTaskLater(plugin, () -> {
Expand Down Expand Up @@ -154,6 +156,7 @@ public void reloadFromDisk() {
for (Infestation infestation : InfestationDatabase.load()) {
byProvince.put(infestation.getProvinceId(), infestation);
}
warnUnidentifiedDeaths();
pluginFurnitureRemove = true;
try {
for (Infestation old : previous.values()) {
Expand Down Expand Up @@ -637,7 +640,7 @@ private void tickOutsideCommitted(Infestation infestation) {
if (player.getGameMode() == GameMode.CREATIVE || player.getGameMode() == GameMode.SPECTATOR) {
continue;
}
if (Provinces.at(player) == infestation.getProvinceId()) {
if (Provinces.at(player) == infestation.getProvinceId() || notAnswerable(infestation, player)) {
continue;
}
actionBar(player, bar);
Expand All @@ -657,7 +660,7 @@ public boolean leaveLure(Player player) {
UUID id = player.getUniqueId();
boolean found = false;
for (Infestation infestation : List.copyOf(byProvince.values())) {
if (!infestation.isCommitted(id)) {
if (!infestation.isCommitted(id) || heldByOtherCharacter(infestation, player)) {
continue;
}
if (!found) {
Expand All @@ -668,6 +671,7 @@ public boolean leaveLure(Player player) {
infestation.getCommitted().remove(id);
infestation.getLogoutGraceUntil().remove(id);
infestation.getDeathOnLogin().remove(id);
infestation.getLogoutCharacter().remove(id);
if (infestation.getPhase() != LurePhase.NONE && !hasLivingOrGrace(infestation)) {
failLure(infestation);
if (!inProvince) {
Expand All @@ -688,10 +692,56 @@ private void expireGrace(Infestation infestation, long now) {
if (now >= e.getValue()) {
infestation.getDeathOnLogin().add(e.getKey());
it.remove();
if (isUnidentified(infestation, e.getKey())) {
warnUnidentified(infestation, e.getKey());
}
}
}
}

/**
* True when the account's commitment here belongs to a logged off character other than the active one.
* That character's grace or pending death stays with it, so the active character is not treated as committed.
*/
private static boolean heldByOtherCharacter(Infestation infestation, Player player) {
if (!Characters.enabled()) {
return false;
}
String owner = infestation.getLogoutCharacter().get(player.getUniqueId());
return owner != null && !owner.equals(Characters.activeId(player));
}

/** A death owed with RPCharacters enabled but no recorded character is held, never applied. */
private static boolean isUnidentified(Infestation infestation, UUID id) {
return Characters.enabled() && !infestation.getLogoutCharacter().containsKey(id);
}

/** An unidentified death already owed: it is held, brings no deserter penalties and keeps no lure alive. */
private static boolean isHeldDebt(Infestation infestation, UUID id) {
return infestation.getDeathOnLogin().contains(id) && isUnidentified(infestation, id);
}

/** True when the outside warnings and deserter damage skip this committed player. */
private static boolean notAnswerable(Infestation infestation, Player player) {
return heldByOtherCharacter(infestation, player) || isHeldDebt(infestation, player.getUniqueId());
}

private void warnUnidentifiedDeaths() {
for (Infestation infestation : byProvince.values()) {
for (UUID id : infestation.getDeathOnLogin()) {
if (isUnidentified(infestation, id)) {
warnUnidentified(infestation, id);
}
}
}
}

private void warnUnidentified(Infestation infestation, UUID id) {
plugin.getLogger().warning("[Infestations] Holding the death on login owed by " + id + " in province "
+ infestation.getProvinceId() + ": it has no character, so it will not be applied."
+ " It clears when the lure ends or that account dies or leaves the lure.");
}

private void checkWipe(Infestation infestation) {
if (infestation.getPhase() != LurePhase.ACTIVE) {
return;
Expand All @@ -702,14 +752,20 @@ private void checkWipe(Infestation infestation) {
failLure(infestation);
}

/** Held debts and their commitments are left out, since no character can return to settle them. */
private boolean hasLivingOrGrace(Infestation infestation) {
if (!infestation.getLogoutGraceUntil().isEmpty()) {
return true;
}
if (!infestation.getDeathOnLogin().isEmpty()) {
return true;
for (UUID id : infestation.getDeathOnLogin()) {
if (!isUnidentified(infestation, id)) {
return true;
}
}
for (UUID id : infestation.getCommitted()) {
if (isHeldDebt(infestation, id)) {
continue;
}
Player player = Bukkit.getPlayer(id);
if (player != null && player.isOnline() && !player.isDead()) {
return true;
Expand Down Expand Up @@ -824,6 +880,7 @@ private void tryJoin(Player player, Infestation infestation) {
infestation.getCommitted().add(player.getUniqueId());
infestation.getLogoutGraceUntil().remove(player.getUniqueId());
infestation.getDeathOnLogin().remove(player.getUniqueId());
infestation.getLogoutCharacter().remove(player.getUniqueId());
player.sendMessage(Messages.get("lure-join"));
save();
}
Expand Down Expand Up @@ -1089,28 +1146,37 @@ private record RecentDeath(int provinceId, String kind, int tick) {}

private void onPlayerDeath(Player player) {
for (Infestation infestation : List.copyOf(byProvince.values())) {
if (!infestation.isCommitted(player.getUniqueId())) {
if (!infestation.isCommitted(player.getUniqueId()) || heldByOtherCharacter(infestation, player)) {
continue;
}
infestation.getCommitted().remove(player.getUniqueId());
infestation.getLogoutGraceUntil().remove(player.getUniqueId());
infestation.getDeathOnLogin().remove(player.getUniqueId());
infestation.getLogoutCharacter().remove(player.getUniqueId());
if (infestation.getPhase() != LurePhase.NONE) {
checkWipe(infestation);
}
}
save();
}

@EventHandler
/** Runs before RPCharacters' quit listener drops the player's character data. */
@EventHandler(priority = EventPriority.LOWEST)
public void onQuit(PlayerQuitEvent event) {
Player player = event.getPlayer();
UUID id = player.getUniqueId();
long until = System.currentTimeMillis() + Cache.logoutGraceSeconds * 1000L;
String character = Characters.activeId(player);
for (Infestation infestation : byProvince.values()) {
if (!infestation.isCommitted(player.getUniqueId())) {
// A grace or death already owed keeps its character and deadline.
if (!infestation.isCommitted(id) || infestation.getLogoutCharacter().containsKey(id)
|| infestation.getDeathOnLogin().contains(id)) {
continue;
}
infestation.getLogoutGraceUntil().put(player.getUniqueId(), until);
infestation.getLogoutGraceUntil().put(id, until);
if (character != null) {
infestation.getLogoutCharacter().put(id, character);
}
Comment thread
coderabbitai[bot] marked this conversation as resolved.
}
save();
}
Expand All @@ -1119,18 +1185,56 @@ public void onQuit(PlayerQuitEvent event) {
public void onJoin(PlayerJoinEvent event) {
Player player = event.getPlayer();
Bukkit.getScheduler().runTaskLater(plugin, () -> {
if (!player.isOnline()) {
return;
if (player.isOnline()) {
characterReturned(player);
}
for (Infestation infestation : byProvince.values()) {
infestation.getLogoutGraceUntil().remove(player.getUniqueId());
if (infestation.getDeathOnLogin().remove(player.getUniqueId())
&& infestation.isCommitted(player.getUniqueId())
&& !player.isDead()) {
player.damage(Math.max(40.0, player.getHealth() + 10.0));
}, 5L);
}

/**
* Ends the logout grace of the character now active and kills it if its grace ran out.
* Another character's grace or death waits, and the death is dropped once that character has died.
* Without RPCharacters the account is the character, so any return counts.
*/
public void characterReturned(Player player) {
UUID id = player.getUniqueId();
boolean tracked = Characters.enabled();
String active = Characters.activeId(player);
boolean changed = false;
for (Infestation infestation : List.copyOf(byProvince.values())) {
String target = tracked ? infestation.getLogoutCharacter().get(id) : null;
boolean otherCharacter = target != null && !target.equals(active);
if (!otherCharacter && infestation.getLogoutGraceUntil().remove(id) != null) {
changed = true;
}
if (!infestation.getDeathOnLogin().contains(id)) {
if (!otherCharacter && infestation.getLogoutCharacter().remove(id) != null) {
changed = true;
}
continue;
}
}, 5L);
if (isUnidentified(infestation, id)) {
continue;
}
if (otherCharacter && !Characters.isDead(player, target)) {
continue;
}
infestation.getDeathOnLogin().remove(id);
infestation.getLogoutCharacter().remove(id);
changed = true;
if (otherCharacter) {
// That character already died, which settles what the lure owed.
infestation.getCommitted().remove(id);
if (infestation.getPhase() != LurePhase.NONE) {
checkWipe(infestation);
}
} else if (infestation.isCommitted(id) && !player.isDead()) {
player.damage(Math.max(40.0, player.getHealth() + 10.0));
}
}
if (changed) {
save();
}
}

@EventHandler
Expand All @@ -1143,7 +1247,7 @@ public void onLeaveProvince(PlayerProvinceLeaveEvent event) {
return;
}
Player player = event.getPlayer();
if (!infestation.isCommitted(player.getUniqueId())) {
if (!infestation.isCommitted(player.getUniqueId()) || notAnswerable(infestation, player)) {
return;
}
if (player.getGameMode() == GameMode.CREATIVE || player.getGameMode() == GameMode.SPECTATOR) {
Expand Down
Loading