Make MASpawnThread logic cancellable.

This commit changes how MASpawnThread is started. Instead of simply scheduling a task and letting it run whenever, we now explicitly tell it to stop when the arena ends. This matters, because if the scheduled task does not get cancelled, it will run at the scheduled time. MASpawnThread's run() method has an early return to stop whenever the arena isn't running, but with unlucky timing, it's possible to start a new arena session before the task runs again, meaning the same MASpawnThread keeps scheduling itself on the previous scheduling loop.

Instead of this whacky approach, we now specifically have MASpawnThread track its own BukkitTask. Upon arena start, we instantiate a new MASpawnThread and call its start() method, which in turn schedules itself and stores a reference to the associated BukkitTask. Upon arena end, we call the MASpawnThread's stop() method, which explicitly calls the BukkitTask's cancel() method, effectively stopping/unscheduling the task.

This fixes the (very) long-standing "double wave spawner" bug that we haven't been able to reproduce for so long. Hooray!
This commit is contained in:
Andreas Troelsen 2018-08-19 18:47:42 +02:00
parent fc51c5eff5
commit 96c1f18517
3 changed files with 49 additions and 23 deletions

View File

@ -23,6 +23,7 @@ These changes will (most likely) be included in the next version.
- The new command `/ma ready` (`/ma rdy` for short) can be used as an alternative to the iron block for readying up.
- Total experience is now correctly stored, reset, and restored on arena join/leave. This fixes a potential bug where total experience could increase in the arena, but levels and progress would still get reset at arena end.
- The per-arena setting `keep-exp` returns. If enabled, any experience collected during an arena session is added as a reward on death or when the final wave is reached.
- Waves will no longer intermittently progress at double frequency in some arena sessions. This long-standing bug where waves progress at "double speed" has finally been fixed.
Thanks to:
- Sait for adding the /ma ready command

View File

@ -996,26 +996,15 @@ public class ArenaImpl implements Arena
}
private void startSpawner() {
// Set the spawn flags to enable monster spawning.
world.setSpawnFlags(true, true);
//world.setDifficulty(Difficulty.NORMAL);
// Create a spawner if one doesn't exist, otherwise reset it
if (spawnThread == null) {
spawnThread = new MASpawnThread(plugin, this);
} else {
spawnThread.reset();
if (spawnThread != null) {
spawnThread.stop();
spawnThread = null;
}
// Schedule it for the initial first wave delay.
scheduleTask(spawnThread, settings.getInt("first-wave-delay", 5) * 20);
// Schedule to enable PvP if pvp-enabled: true
scheduleTask(new Runnable() {
public void run() {
eventListener.pvpActivate();
}
}, settings.getInt("first-wave-delay", 5) * 20);
world.setSpawnFlags(true, true);
spawnThread = new MASpawnThread(plugin, this);
spawnThread.start();
}
/**
@ -1030,9 +1019,15 @@ public class ArenaImpl implements Arena
}
private void stopSpawner() {
if (spawnThread == null) {
plugin.getLogger().warning("Can't stop non-existent spawner in arena " + configName() + ". This should never happen.");
return;
}
spawnThread.stop();
spawnThread = null;
world.setSpawnFlags(allowMonsters, allowAnimals);
eventListener.pvpDeactivate();
//world.setDifficulty(spawnMonsters);
}
private void startBouncingSheep()

View File

@ -16,11 +16,13 @@ import com.garbagemule.MobArena.waves.enums.WaveType;
import com.garbagemule.MobArena.waves.types.BossWave;
import com.garbagemule.MobArena.waves.types.SupplyWave;
import com.garbagemule.MobArena.waves.types.UpgradeWave;
import org.bukkit.Bukkit;
import org.bukkit.Location;
import org.bukkit.World;
import org.bukkit.entity.Entity;
import org.bukkit.entity.LivingEntity;
import org.bukkit.entity.Player;
import org.bukkit.scheduler.BukkitTask;
import java.util.ArrayList;
import java.util.List;
@ -41,6 +43,8 @@ public class MASpawnThread implements Runnable
private int waveInterval;
private int nextWaveDelay;
private BukkitTask task;
/**
* Create a new monster spawner for the input arena.
* Note that the arena's WaveManager is reset
@ -75,6 +79,32 @@ public class MASpawnThread implements Runnable
nextWaveDelay = arena.getSettings().getInt("next-wave-delay", 0);
}
public void start() {
if (task != null) {
plugin.getLogger().warning("Starting spawner in arena " + arena.configName() + " with existing spawner still running. This should never happen.");
task.cancel();
task = null;
}
int delay = arena.getSettings().getInt("first-wave-delay", 5) * 20;
task = Bukkit.getScheduler().runTaskLater(plugin, () -> {
arena.getEventListener().pvpActivate();
this.run();
}, delay);
}
public void stop() {
if (task == null) {
plugin.getLogger().warning("Can't stop non-existent spawner in arena " + arena.configName() + ". This should never happen.");
return;
}
arena.getEventListener().pvpDeactivate();
task.cancel();
task = null;
}
public void run() {
// If the arena isn't running or if there are no players in it.
if (!arena.isRunning() || arena.getPlayersInArena().isEmpty()) {
@ -109,7 +139,7 @@ public class MASpawnThread implements Runnable
// Delay the next wave
if (nextWaveDelay > 0) {
arena.scheduleTask(this::spawnNextWave, nextWaveDelay * 20);
task = Bukkit.getScheduler().runTaskLater(plugin, this::spawnNextWave, nextWaveDelay * 20);
} else {
spawnNextWave();
}
@ -146,7 +176,7 @@ public class MASpawnThread implements Runnable
updateStats(nextWave);
// Reschedule the spawner for the next wave.
arena.scheduleTask(this, waveInterval * 20);
task = Bukkit.getScheduler().runTaskLater(plugin, this, waveInterval * 20);
}
private void spawnWave(int wave) {