Skip to content

Commit 2f04a55

Browse files
committed
Fix untag race condition
1 parent 4900bf0 commit 2f04a55

2 files changed

Lines changed: 36 additions & 28 deletions

File tree

pvpmanager/src/main/java/me/chancesd/pvpmanager/player/CombatPlayer.java

Lines changed: 15 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@
3030
import me.chancesd.pvpmanager.setting.Permissions;
3131
import me.chancesd.pvpmanager.tasks.NewbieTask;
3232
import me.chancesd.pvpmanager.utils.CombatUtils;
33+
import me.chancesd.sdutils.scheduler.SDTask;
3334
import me.chancesd.sdutils.scheduler.ScheduleUtils;
3435
import me.chancesd.sdutils.utils.Log;
3536
import me.chancesd.sdutils.utils.MCVersion;
@@ -49,6 +50,7 @@ public class CombatPlayer extends EcoPlayer {
4950
private volatile long totalTagTime;
5051
private long lastKillCommandTime;
5152
private NewbieTask newbieTask;
53+
private SDTask pendingUntagTask;
5254
private CombatPlayer enemy;
5355
private final Set<CombatPlayer> lastHitters = new HashSet<>();
5456
private final Map<String, Integer> victim = new HashMap<>();
@@ -151,7 +153,7 @@ public final void setNewbie(final boolean newbie, final long time) {
151153
* @param other The other player involved in the attack
152154
* @param timeMiliseconds How long the player should be tagged for
153155
*/
154-
public final void tag(final boolean isAttacker, final CombatPlayer other, final long timeMiliseconds) {
156+
public final synchronized void tag(final boolean isAttacker, final CombatPlayer other, final long timeMiliseconds) {
155157
if (hasPerm(Permissions.EXEMPT_COMBAT_TAG)) {
156158
Log.debug("Not tagging " + getName() + " because player has permission: " + Permissions.EXEMPT_COMBAT_TAG);
157159
return;
@@ -164,6 +166,13 @@ public final void tag(final boolean isAttacker, final CombatPlayer other, final
164166
return;
165167
}
166168

169+
// Cancel any pending untag task
170+
if (pendingUntagTask != null && !pendingUntagTask.isCancelled()) {
171+
pendingUntagTask.cancel();
172+
Log.debug("Cancelled stale untag task for " + getName());
173+
pendingUntagTask = null;
174+
}
175+
167176
this.totalTagTime = timeMiliseconds;
168177

169178
final PlayerTagEvent event = new PlayerTagEvent(getPlayer(), this, isAttacker, other.getPlayer());
@@ -207,17 +216,17 @@ public final void tag(final boolean isAttacker, final CombatPlayer other) {
207216
/**
208217
* Takes the player out of combat
209218
*/
210-
public final void untag(final UntagReason reason) {
219+
public final synchronized void untag(final UntagReason reason) {
211220
if (!isInCombat()) {
212-
Log.debug("Not untagging " + getName() + " because player is not tagged.");
213221
return;
214222
}
215223
final PlayerUntagEvent event = new PlayerUntagEvent(getPlayer(), this, reason);
216-
ScheduleUtils.ensureMainThread(() -> {
224+
pendingUntagTask = ScheduleUtils.ensureMainThread(() -> {
217225
Bukkit.getPluginManager().callEvent(event);
218226
if (Conf.DISABLE_FLY.asBool() && Conf.RESTORE_FLY.asBool() && getWasAllowedFlight()) {
219227
getPlayer().setAllowFlight(getWasAllowedFlight()); // Sync because there's an async catcher on MC 1.8
220228
}
229+
pendingUntagTask = null;
221230
}, getPlayer());
222231

223232
if (Conf.GLOWING_IN_COMBAT.asBool() && MCVersion.isAtLeast(MCVersion.V1_9)) {
@@ -423,7 +432,7 @@ private void initializeNameTag() {
423432
/**
424433
* Apply loaded player data to this CombatPlayer
425434
*/
426-
public void applyPlayerData(final PlayerData data) {
435+
public synchronized void applyPlayerData(final PlayerData data) {
427436
// Apply loaded data (overriding defaults)
428437
this.pvpState = data.isPvpEnabled();
429438
this.toggleTime = data.getToggleTime();
@@ -443,9 +452,7 @@ public void applyPlayerData(final PlayerData data) {
443452

444453
this.loaded = true;
445454
// Wake up any threads waiting for data to load
446-
synchronized (this) {
447-
notifyAll();
448-
}
455+
notifyAll();
449456
Log.debug("Finished loading data for " + this + (nametag != null ? " with " + nametag.getClass().getSimpleName() : ""));
450457
}
451458

pvpmanager/src/main/java/me/chancesd/pvpmanager/tasks/TagTask.java

Lines changed: 21 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -53,25 +53,13 @@ public final void onPlayerTag(final PlayerTagEvent event) {
5353

5454
@EventHandler
5555
public final void onPlayerUntag(final PlayerUntagEvent event) {
56-
stopTracking(event.getCombatPlayer());
56+
stopTracking(event.getCombatPlayer(), event.getReason());
5757
}
5858

5959
private final void startTracking(final CombatPlayer combatPlayer) {
6060
final ProgressBar progressBar = new ProgressBar(Conf.ACTION_BAR_MESSAGE.asString(), Conf.ACTION_BAR_BARS.asInt(), combatPlayer.getTotalTagTime(),
6161
Conf.ACTION_BAR_SYMBOL.asString());
6262

63-
final TimeProgressSource timeProgressSource = new TimeProgressSource() {
64-
@Override
65-
public long getGoal() {
66-
return combatPlayer.getTotalTagTime();
67-
}
68-
69-
@Override
70-
public long getProgress() {
71-
return System.currentTimeMillis() - combatPlayer.getTaggedTime();
72-
}
73-
};
74-
7563
final CountdownData.Builder builder = new CountdownData.Builder();
7664
if (Conf.ACTION_BAR_ENABLED.asBool()) {
7765
builder.withActionBar(progressBar, timeSource -> {
@@ -83,24 +71,37 @@ public long getProgress() {
8371
if (Conf.BOSS_BAR_ENABLED.asBool()) {
8472
builder.withBossBar(bossBar.build(), timeSource -> {
8573
final double secondsRemaining = (timeSource.getGoal() - timeSource.getProgress()) / 1000.0;
86-
final String message = Conf.BOSS_BAR_MESSAGE.asString().replace("<time>",
87-
Double.toString(Utils.roundTo1Decimal(secondsRemaining)));
74+
final String message = Conf.BOSS_BAR_MESSAGE.asString().replace("<time>", Double.toString(Utils.roundTo1Decimal(secondsRemaining)));
8875
return CombatUtils.processPlaceholders(combatPlayer.getPlayer(), message);
8976
});
9077
}
78+
79+
final TimeProgressSource timeProgressSource = new TimeProgressSource() {
80+
@Override
81+
public long getGoal() {
82+
return combatPlayer.getTotalTagTime();
83+
}
84+
85+
@Override
86+
public long getProgress() {
87+
return System.currentTimeMillis() - combatPlayer.getTaggedTime();
88+
}
89+
};
9190
final CountdownData countdownData = builder.withTimeSource(timeProgressSource)
92-
.onFinish(() -> {
93-
combatPlayer.untag(UntagReason.TIME_EXPIRED);
94-
})
91+
.onFinish(() -> combatPlayer.untag(UntagReason.TIME_EXPIRED))
9592
.build(combatPlayer.getPlayer());
9693

9794
display.createCountdown(combatPlayer.getPlayer(), countdownData);
9895
taggedCountdowns.put(combatPlayer, countdownData);
9996
}
10097

101-
private final void stopTracking(final CombatPlayer combatPlayer) {
98+
private final void stopTracking(final CombatPlayer combatPlayer, final UntagReason reason) {
10299
final CountdownData countdownData = taggedCountdowns.remove(combatPlayer);
103-
display.cancelCountdown(combatPlayer.getPlayer(), countdownData);
100+
101+
// Don't call cancelCountdown for TIME_EXPIRED, already removed by display
102+
if (reason != UntagReason.TIME_EXPIRED) {
103+
display.cancelCountdown(combatPlayer.getPlayer(), countdownData);
104+
}
104105
}
105106

106107
public Set<CombatPlayer> getTaggedPlayers() {

0 commit comments

Comments
 (0)