From 8b3ccfe1c1c4965a61cff75fd38db3ec29d55ff9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Justus=20G=C3=BCttler?= Date: Thu, 22 Jan 2026 02:39:39 +0100 Subject: [PATCH 1/7] =?UTF-8?q?Added=20COMBO=20wave=20=F0=9F=8E=89?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit (cherry picked from commit 5f8c06b877b9b89491a05248061b53ee2f60e199) --- .../garbagemule/MobArena/MASpawnThread.java | 17 +++++- .../MobArena/waves/WaveParser.java | 27 +++++--- .../MobArena/waves/enums/WaveBranch.java | 7 +++ .../MobArena/waves/enums/WaveType.java | 7 +++ .../MobArena/waves/types/ComboWave.java | 61 +++++++++++++++++++ 5 files changed, 111 insertions(+), 8 deletions(-) create mode 100644 src/main/java/com/garbagemule/MobArena/waves/types/ComboWave.java diff --git a/src/main/java/com/garbagemule/MobArena/MASpawnThread.java b/src/main/java/com/garbagemule/MobArena/MASpawnThread.java index ce719869..34969d93 100644 --- a/src/main/java/com/garbagemule/MobArena/MASpawnThread.java +++ b/src/main/java/com/garbagemule/MobArena/MASpawnThread.java @@ -15,6 +15,7 @@ import com.garbagemule.MobArena.waves.WaveManager; import com.garbagemule.MobArena.waves.enums.WaveType; import com.garbagemule.MobArena.waves.types.BossWave; +import com.garbagemule.MobArena.waves.types.ComboWave; import com.garbagemule.MobArena.waves.types.SupplyWave; import com.garbagemule.MobArena.waves.types.UpgradeWave; import org.bukkit.Bukkit; @@ -180,8 +181,22 @@ private void spawnNextWave() { private void spawnWave(int wave) { Wave w = waveManager.next(); + handleWave(w, wave, false); + } + + private void handleWave(Wave w, int wave, boolean comboChild) { + if (w.getType() == WaveType.COMBO) { + ComboWave cw = (ComboWave) w; + handleWave(cw.getWaveA(), wave, true); + handleWave(cw.getWaveB(), wave, true); + + cw.getWaveA().announce(arena, wave); // announce only waveA in outermost COMBO wave. + return; + } - w.announce(arena, wave); + if (!comboChild) { + w.announce(arena, wave); + } arena.getScoreboard().updateWave(wave); diff --git a/src/main/java/com/garbagemule/MobArena/waves/WaveParser.java b/src/main/java/com/garbagemule/MobArena/waves/WaveParser.java index a3dba27a..79f6f0c1 100644 --- a/src/main/java/com/garbagemule/MobArena/waves/WaveParser.java +++ b/src/main/java/com/garbagemule/MobArena/waves/WaveParser.java @@ -15,12 +15,7 @@ import com.garbagemule.MobArena.waves.ability.AbilityManager; import com.garbagemule.MobArena.waves.enums.WaveBranch; import com.garbagemule.MobArena.waves.enums.WaveType; -import com.garbagemule.MobArena.waves.types.BossWave; -import com.garbagemule.MobArena.waves.types.DefaultWave; -import com.garbagemule.MobArena.waves.types.SpecialWave; -import com.garbagemule.MobArena.waves.types.SupplyWave; -import com.garbagemule.MobArena.waves.types.SwarmWave; -import com.garbagemule.MobArena.waves.types.UpgradeWave; +import com.garbagemule.MobArena.waves.types.*; import org.bukkit.ChatColor; import org.bukkit.Location; import org.bukkit.configuration.ConfigurationSection; @@ -98,6 +93,9 @@ public static Wave parseWave(Arena arena, String name, ConfigurationSection conf case BOSS: result = parseBossWave(arena, name, config); break; + case COMBO: + result = parseComboWave(arena, name, config); + break; } // Grab the branch-specific nodes. @@ -122,7 +120,7 @@ public static Wave parseWave(Arena arena, String name, ConfigurationSection conf // Potion effects List effects = getPotionEffects(arena, name, config); - // Recurrent must have priority + frequency, single must have firstWave + // Recurrent must have priority + frequency, single must have firstWave, combo doesn't need this if (branch == WaveBranch.RECURRENT) { if (priority <= 0) { throw new ConfigError("Missing or invalid 'priority' node for recurrent wave " + name + " of arena " + arena.configName()); @@ -350,6 +348,21 @@ private static Wave parseBossWave(Arena arena, String name, ConfigurationSection return result; } + private static Wave parseComboWave(Arena arena, String name, ConfigurationSection config) { + ConfigurationSection waveAConfig = config.getConfigurationSection("waveA"); + if (waveAConfig == null) { + throw new ConfigError("No waveA defined in wave " + name + " of arena " + arena.configName() + "."); + } + Wave waveA = parseWave(arena, name, waveAConfig, WaveBranch.COMBO); + ConfigurationSection waveBConfig = config.getConfigurationSection("waveB"); + if (waveBConfig == null) { + throw new ConfigError("No waveB defined in wave " + name + " of arena " + arena.configName() + "."); + } + Wave waveB = parseWave(arena, name, waveBConfig, WaveBranch.COMBO); + + return new ComboWave(waveA, waveB); + } + /** * Scan the ConfigSection for a "monster" (singular) node, which * must be exactly a single monster. diff --git a/src/main/java/com/garbagemule/MobArena/waves/enums/WaveBranch.java b/src/main/java/com/garbagemule/MobArena/waves/enums/WaveBranch.java index d3f87923..eb0089b3 100644 --- a/src/main/java/com/garbagemule/MobArena/waves/enums/WaveBranch.java +++ b/src/main/java/com/garbagemule/MobArena/waves/enums/WaveBranch.java @@ -19,6 +19,13 @@ public boolean matches(int wave, Wave w) { } return ((wave - w.getFirstWave()) % w.getFrequency() == 0); } + }, + + COMBO { + @Override + public boolean matches(int wave, Wave w) { + return false; // doesn't get used anyway on COMBO branch + } }; public abstract boolean matches(int wave, Wave w); diff --git a/src/main/java/com/garbagemule/MobArena/waves/enums/WaveType.java b/src/main/java/com/garbagemule/MobArena/waves/enums/WaveType.java index 30b9774b..2dda5d73 100644 --- a/src/main/java/com/garbagemule/MobArena/waves/enums/WaveType.java +++ b/src/main/java/com/garbagemule/MobArena/waves/enums/WaveType.java @@ -45,6 +45,13 @@ public void announce(Arena arena, int wave) { public void announce(Arena arena, int wave) { arena.announce(Msg.WAVE_UPGRADE, "" + wave); } + }, + + COMBO { + @Override + public void announce(Arena arena, int wave) { + // announce waveA in MASpawnThread.handleWave() + } }; public abstract void announce(Arena arena, int wave); diff --git a/src/main/java/com/garbagemule/MobArena/waves/types/ComboWave.java b/src/main/java/com/garbagemule/MobArena/waves/types/ComboWave.java new file mode 100644 index 00000000..c34691c3 --- /dev/null +++ b/src/main/java/com/garbagemule/MobArena/waves/types/ComboWave.java @@ -0,0 +1,61 @@ +package com.garbagemule.MobArena.waves.types; + +import com.garbagemule.MobArena.framework.Arena; +import com.garbagemule.MobArena.waves.AbstractWave; +import com.garbagemule.MobArena.waves.MACreature; +import com.garbagemule.MobArena.waves.Wave; +import com.garbagemule.MobArena.waves.WaveUtils; +import com.garbagemule.MobArena.waves.enums.WaveType; +import org.bukkit.Location; + +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +public class ComboWave extends AbstractWave { + + private final Wave waveA; + private final Wave waveB; + + public ComboWave(Wave waveA, Wave waveB) { + this.waveA = waveA; + this.waveB = waveB; + this.setType(WaveType.COMBO); + } + + public Wave getWaveA() { + return waveA; + } + + public Wave getWaveB() { + return waveB; + } + + @Override + public Map getMonstersToSpawn(int wave, int playerCount, Arena arena) { + Map monstersA = this.waveA.getMonstersToSpawn(wave, playerCount, arena); + Map monstersB = this.waveB.getMonstersToSpawn(wave, playerCount, arena); + + Map monstersCombined = new HashMap<>(); + monstersCombined.putAll(monstersA); + monstersCombined.putAll(monstersB); + + return monstersCombined; + } + + public List getSpawnpoints(Arena arena) { + List spawnpointsA = waveA.getSpawnpoints(arena); + List spawnpointsB = waveB.getSpawnpoints(arena); + + List spawnpointsCombined = super.getSpawnpoints(arena); + spawnpointsCombined.addAll(spawnpointsA); + spawnpointsCombined.addAll(spawnpointsB); + + return WaveUtils.getValidSpawnpoints(arena, spawnpointsCombined, arena.getPlayersInArena()); + } + + @Override + public Wave copy() { + return new ComboWave(getWaveA(), getWaveB()); + } +} From 6725bd4d638108c296a662431327811190d56293 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Justus=20G=C3=BCttler?= Date: Thu, 22 Jan 2026 02:39:39 +0100 Subject: [PATCH 2/7] =?UTF-8?q?Added=20COMBO=20wave=20=F0=9F=8E=89?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit (cherry picked from commit 5f8c06b877b9b89491a05248061b53ee2f60e199) --- .../garbagemule/MobArena/MASpawnThread.java | 17 +++++- .../MobArena/waves/WaveParser.java | 27 +++++--- .../MobArena/waves/enums/WaveBranch.java | 7 +++ .../MobArena/waves/enums/WaveType.java | 7 +++ .../MobArena/waves/types/ComboWave.java | 61 +++++++++++++++++++ 5 files changed, 111 insertions(+), 8 deletions(-) create mode 100644 src/main/java/com/garbagemule/MobArena/waves/types/ComboWave.java diff --git a/src/main/java/com/garbagemule/MobArena/MASpawnThread.java b/src/main/java/com/garbagemule/MobArena/MASpawnThread.java index ce719869..34969d93 100644 --- a/src/main/java/com/garbagemule/MobArena/MASpawnThread.java +++ b/src/main/java/com/garbagemule/MobArena/MASpawnThread.java @@ -15,6 +15,7 @@ import com.garbagemule.MobArena.waves.WaveManager; import com.garbagemule.MobArena.waves.enums.WaveType; import com.garbagemule.MobArena.waves.types.BossWave; +import com.garbagemule.MobArena.waves.types.ComboWave; import com.garbagemule.MobArena.waves.types.SupplyWave; import com.garbagemule.MobArena.waves.types.UpgradeWave; import org.bukkit.Bukkit; @@ -180,8 +181,22 @@ private void spawnNextWave() { private void spawnWave(int wave) { Wave w = waveManager.next(); + handleWave(w, wave, false); + } + + private void handleWave(Wave w, int wave, boolean comboChild) { + if (w.getType() == WaveType.COMBO) { + ComboWave cw = (ComboWave) w; + handleWave(cw.getWaveA(), wave, true); + handleWave(cw.getWaveB(), wave, true); + + cw.getWaveA().announce(arena, wave); // announce only waveA in outermost COMBO wave. + return; + } - w.announce(arena, wave); + if (!comboChild) { + w.announce(arena, wave); + } arena.getScoreboard().updateWave(wave); diff --git a/src/main/java/com/garbagemule/MobArena/waves/WaveParser.java b/src/main/java/com/garbagemule/MobArena/waves/WaveParser.java index a3dba27a..79f6f0c1 100644 --- a/src/main/java/com/garbagemule/MobArena/waves/WaveParser.java +++ b/src/main/java/com/garbagemule/MobArena/waves/WaveParser.java @@ -15,12 +15,7 @@ import com.garbagemule.MobArena.waves.ability.AbilityManager; import com.garbagemule.MobArena.waves.enums.WaveBranch; import com.garbagemule.MobArena.waves.enums.WaveType; -import com.garbagemule.MobArena.waves.types.BossWave; -import com.garbagemule.MobArena.waves.types.DefaultWave; -import com.garbagemule.MobArena.waves.types.SpecialWave; -import com.garbagemule.MobArena.waves.types.SupplyWave; -import com.garbagemule.MobArena.waves.types.SwarmWave; -import com.garbagemule.MobArena.waves.types.UpgradeWave; +import com.garbagemule.MobArena.waves.types.*; import org.bukkit.ChatColor; import org.bukkit.Location; import org.bukkit.configuration.ConfigurationSection; @@ -98,6 +93,9 @@ public static Wave parseWave(Arena arena, String name, ConfigurationSection conf case BOSS: result = parseBossWave(arena, name, config); break; + case COMBO: + result = parseComboWave(arena, name, config); + break; } // Grab the branch-specific nodes. @@ -122,7 +120,7 @@ public static Wave parseWave(Arena arena, String name, ConfigurationSection conf // Potion effects List effects = getPotionEffects(arena, name, config); - // Recurrent must have priority + frequency, single must have firstWave + // Recurrent must have priority + frequency, single must have firstWave, combo doesn't need this if (branch == WaveBranch.RECURRENT) { if (priority <= 0) { throw new ConfigError("Missing or invalid 'priority' node for recurrent wave " + name + " of arena " + arena.configName()); @@ -350,6 +348,21 @@ private static Wave parseBossWave(Arena arena, String name, ConfigurationSection return result; } + private static Wave parseComboWave(Arena arena, String name, ConfigurationSection config) { + ConfigurationSection waveAConfig = config.getConfigurationSection("waveA"); + if (waveAConfig == null) { + throw new ConfigError("No waveA defined in wave " + name + " of arena " + arena.configName() + "."); + } + Wave waveA = parseWave(arena, name, waveAConfig, WaveBranch.COMBO); + ConfigurationSection waveBConfig = config.getConfigurationSection("waveB"); + if (waveBConfig == null) { + throw new ConfigError("No waveB defined in wave " + name + " of arena " + arena.configName() + "."); + } + Wave waveB = parseWave(arena, name, waveBConfig, WaveBranch.COMBO); + + return new ComboWave(waveA, waveB); + } + /** * Scan the ConfigSection for a "monster" (singular) node, which * must be exactly a single monster. diff --git a/src/main/java/com/garbagemule/MobArena/waves/enums/WaveBranch.java b/src/main/java/com/garbagemule/MobArena/waves/enums/WaveBranch.java index d3f87923..eb0089b3 100644 --- a/src/main/java/com/garbagemule/MobArena/waves/enums/WaveBranch.java +++ b/src/main/java/com/garbagemule/MobArena/waves/enums/WaveBranch.java @@ -19,6 +19,13 @@ public boolean matches(int wave, Wave w) { } return ((wave - w.getFirstWave()) % w.getFrequency() == 0); } + }, + + COMBO { + @Override + public boolean matches(int wave, Wave w) { + return false; // doesn't get used anyway on COMBO branch + } }; public abstract boolean matches(int wave, Wave w); diff --git a/src/main/java/com/garbagemule/MobArena/waves/enums/WaveType.java b/src/main/java/com/garbagemule/MobArena/waves/enums/WaveType.java index 30b9774b..2dda5d73 100644 --- a/src/main/java/com/garbagemule/MobArena/waves/enums/WaveType.java +++ b/src/main/java/com/garbagemule/MobArena/waves/enums/WaveType.java @@ -45,6 +45,13 @@ public void announce(Arena arena, int wave) { public void announce(Arena arena, int wave) { arena.announce(Msg.WAVE_UPGRADE, "" + wave); } + }, + + COMBO { + @Override + public void announce(Arena arena, int wave) { + // announce waveA in MASpawnThread.handleWave() + } }; public abstract void announce(Arena arena, int wave); diff --git a/src/main/java/com/garbagemule/MobArena/waves/types/ComboWave.java b/src/main/java/com/garbagemule/MobArena/waves/types/ComboWave.java new file mode 100644 index 00000000..c34691c3 --- /dev/null +++ b/src/main/java/com/garbagemule/MobArena/waves/types/ComboWave.java @@ -0,0 +1,61 @@ +package com.garbagemule.MobArena.waves.types; + +import com.garbagemule.MobArena.framework.Arena; +import com.garbagemule.MobArena.waves.AbstractWave; +import com.garbagemule.MobArena.waves.MACreature; +import com.garbagemule.MobArena.waves.Wave; +import com.garbagemule.MobArena.waves.WaveUtils; +import com.garbagemule.MobArena.waves.enums.WaveType; +import org.bukkit.Location; + +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +public class ComboWave extends AbstractWave { + + private final Wave waveA; + private final Wave waveB; + + public ComboWave(Wave waveA, Wave waveB) { + this.waveA = waveA; + this.waveB = waveB; + this.setType(WaveType.COMBO); + } + + public Wave getWaveA() { + return waveA; + } + + public Wave getWaveB() { + return waveB; + } + + @Override + public Map getMonstersToSpawn(int wave, int playerCount, Arena arena) { + Map monstersA = this.waveA.getMonstersToSpawn(wave, playerCount, arena); + Map monstersB = this.waveB.getMonstersToSpawn(wave, playerCount, arena); + + Map monstersCombined = new HashMap<>(); + monstersCombined.putAll(monstersA); + monstersCombined.putAll(monstersB); + + return monstersCombined; + } + + public List getSpawnpoints(Arena arena) { + List spawnpointsA = waveA.getSpawnpoints(arena); + List spawnpointsB = waveB.getSpawnpoints(arena); + + List spawnpointsCombined = super.getSpawnpoints(arena); + spawnpointsCombined.addAll(spawnpointsA); + spawnpointsCombined.addAll(spawnpointsB); + + return WaveUtils.getValidSpawnpoints(arena, spawnpointsCombined, arena.getPlayersInArena()); + } + + @Override + public Wave copy() { + return new ComboWave(getWaveA(), getWaveB()); + } +} From a664617359d8b9e9e999e86f3d68add54f7a4c91 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Justus=20G=C3=BCttler?= Date: Thu, 22 Jan 2026 04:08:53 +0100 Subject: [PATCH 3/7] Added shadow waves. Separate waves that run alongside the normal waves but do not get announced. --- .../garbagemule/MobArena/MASpawnThread.java | 15 +++-- .../MobArena/waves/WaveManager.java | 61 ++++++++++++++++--- 2 files changed, 63 insertions(+), 13 deletions(-) diff --git a/src/main/java/com/garbagemule/MobArena/MASpawnThread.java b/src/main/java/com/garbagemule/MobArena/MASpawnThread.java index 34969d93..c24f2ab7 100644 --- a/src/main/java/com/garbagemule/MobArena/MASpawnThread.java +++ b/src/main/java/com/garbagemule/MobArena/MASpawnThread.java @@ -181,20 +181,25 @@ private void spawnNextWave() { private void spawnWave(int wave) { Wave w = waveManager.next(); - handleWave(w, wave, false); + handleWave(w, wave, true); + + Wave sw = waveManager.getCurrentShadow(); + if (sw != null) { + handleWave(sw, wave, false); + } } - private void handleWave(Wave w, int wave, boolean comboChild) { + private void handleWave(Wave w, int wave, boolean announce) { if (w.getType() == WaveType.COMBO) { ComboWave cw = (ComboWave) w; - handleWave(cw.getWaveA(), wave, true); - handleWave(cw.getWaveB(), wave, true); + handleWave(cw.getWaveA(), wave, false); + handleWave(cw.getWaveB(), wave, false); cw.getWaveA().announce(arena, wave); // announce only waveA in outermost COMBO wave. return; } - if (!comboChild) { + if (announce) { w.announce(arena, wave); } diff --git a/src/main/java/com/garbagemule/MobArena/waves/WaveManager.java b/src/main/java/com/garbagemule/MobArena/waves/WaveManager.java index b1dc2e4f..6f29076c 100644 --- a/src/main/java/com/garbagemule/MobArena/waves/WaveManager.java +++ b/src/main/java/com/garbagemule/MobArena/waves/WaveManager.java @@ -15,7 +15,9 @@ public class WaveManager private ConfigurationSection section; private Wave defaultWave, currentWave, nextWave; + private Wave currentShadowWave, nextShadowWave; private TreeSet recurrentWaves, singleWaves; + private TreeSet recurrentShadowWaves, singleShadowWaves; private int wave, finalWave; private Random random = new Random(); @@ -33,6 +35,7 @@ public void reset() { reloadWaves(); wave = 0; determineNextWave(); + determineNextShadowWave(); } public void reloadWaves() { @@ -42,6 +45,12 @@ public void reloadWaves() { recurrentWaves = WaveParser.parseWaves(arena, rConfig, WaveBranch.RECURRENT); singleWaves = WaveParser.parseWaves(arena, sConfig, WaveBranch.SINGLE); + ConfigurationSection shadowRConfig = section.getConfigurationSection("shadow.recurrent"); + ConfigurationSection shadowSConfig = section.getConfigurationSection("shadow.single"); + + recurrentShadowWaves = WaveParser.parseWaves(arena, shadowRConfig, WaveBranch.RECURRENT); + singleShadowWaves = WaveParser.parseWaves(arena, shadowSConfig, WaveBranch.SINGLE); + // getParent() => go back to the arena-node to access settings finalWave = section.getParent().getInt("settings.final-wave", 0); @@ -62,13 +71,13 @@ public void reloadWaves() { private void determineNextWave() { // Single waves take precedence over recurrent waves - List singles = findSingleWaveCandidates(wave + 1); + List singles = findSingleWaveCandidates(wave + 1, singleWaves); if (!singles.isEmpty()) { nextWave = pickRandomWave(singles); return; } - List recurrents = findRecurrentWaveCandidates(wave + 1); + List recurrents = findRecurrentWaveCandidates(wave + 1, recurrentWaves); if (!recurrents.isEmpty()) { nextWave = pickRandomWave(recurrents); } else { @@ -76,9 +85,23 @@ private void determineNextWave() { } } - private List findSingleWaveCandidates(int wave) { - List candidates = new ArrayList<>(singleWaves.size()); - for (Wave w : singleWaves) { + private void determineNextShadowWave() { + // Single waves take precedence over recurrent waves + List singles = findSingleWaveCandidates(wave + 1, singleShadowWaves); + if (!singles.isEmpty()) { + nextShadowWave = pickRandomWave(singles); + return; + } + + List recurrents = findRecurrentWaveCandidates(wave + 1, recurrentShadowWaves); + if (!recurrents.isEmpty()) { + nextShadowWave = pickRandomWave(recurrents); + } + } + + private List findSingleWaveCandidates(int wave, TreeSet waves) { + List candidates = new ArrayList<>(waves.size()); + for (Wave w : waves) { if (w.matches(wave)) { candidates.add(w); } @@ -87,9 +110,9 @@ private List findSingleWaveCandidates(int wave) { return candidates; } - private List findRecurrentWaveCandidates(int wave) { - List matches = new ArrayList<>(recurrentWaves.size()); - for (Wave w : recurrentWaves) { + private List findRecurrentWaveCandidates(int wave, TreeSet waves) { + List matches = new ArrayList<>(waves.size()); + for (Wave w : waves) { if (w.matches(wave)) { matches.add(w); } @@ -123,14 +146,17 @@ private Wave pickRandomWave(List candidates) { /** * Increment the wave number and get the next Wave to be spawned. + * Does NOT return the next Shadow Wave. * Note that this method is a mutator. * @return the next Wave */ public Wave next() { currentWave = nextWave; + currentShadowWave = nextShadowWave; wave++; determineNextWave(); + determineNextShadowWave(); return currentWave; } @@ -154,6 +180,25 @@ public Wave getCurrent() { return currentWave; } + /** + * Get the next Shadow Wave to be spawned. This is an accessor and does not + * advance the "counter". Note that the Wave objects, however, are + * mutable. + * @return the next Shadow Wave + */ + public Wave getNextShadow() { + return nextShadowWave; + } + + /** + * Get the current Shadow wave that's being used. + * Note that the current wave might not have spawned yet. + * @return the current Shadow wave + */ + public Wave getCurrentShadow() { + return currentShadowWave; + } + /** * Get the current wave number. * @return the current wave number From 1499061e06fb5f67b1de5448eceb74426baa09af Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Justus=20G=C3=BCttler?= Date: Thu, 22 Jan 2026 04:09:47 +0100 Subject: [PATCH 4/7] Little bittle bug fixed --- src/main/java/com/garbagemule/MobArena/MASpawnThread.java | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/main/java/com/garbagemule/MobArena/MASpawnThread.java b/src/main/java/com/garbagemule/MobArena/MASpawnThread.java index 34969d93..e66a4166 100644 --- a/src/main/java/com/garbagemule/MobArena/MASpawnThread.java +++ b/src/main/java/com/garbagemule/MobArena/MASpawnThread.java @@ -190,7 +190,10 @@ private void handleWave(Wave w, int wave, boolean comboChild) { handleWave(cw.getWaveA(), wave, true); handleWave(cw.getWaveB(), wave, true); - cw.getWaveA().announce(arena, wave); // announce only waveA in outermost COMBO wave. + + if (!comboChild) { + cw.getWaveA().announce(arena, wave); // announce only waveA in outermost COMBO wave. + } return; } From 3644492e895f766f9db141257f30be8abe915af7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Justus=20G=C3=BCttler?= Date: Thu, 22 Jan 2026 04:09:47 +0100 Subject: [PATCH 5/7] Little bittle bug fixed --- src/main/java/com/garbagemule/MobArena/MASpawnThread.java | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/main/java/com/garbagemule/MobArena/MASpawnThread.java b/src/main/java/com/garbagemule/MobArena/MASpawnThread.java index c24f2ab7..a5af4807 100644 --- a/src/main/java/com/garbagemule/MobArena/MASpawnThread.java +++ b/src/main/java/com/garbagemule/MobArena/MASpawnThread.java @@ -195,7 +195,10 @@ private void handleWave(Wave w, int wave, boolean announce) { handleWave(cw.getWaveA(), wave, false); handleWave(cw.getWaveB(), wave, false); - cw.getWaveA().announce(arena, wave); // announce only waveA in outermost COMBO wave. + + if (!comboChild) { + cw.getWaveA().announce(arena, wave); // announce only waveA in outermost COMBO wave. + } return; } From 88bd719bb142f11ef101ddec664dcf5ca3c6b6ba Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Justus=20G=C3=BCttler?= Date: Thu, 22 Jan 2026 04:11:50 +0100 Subject: [PATCH 6/7] Merged COMBO-wave bugfix --- src/main/java/com/garbagemule/MobArena/MASpawnThread.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/java/com/garbagemule/MobArena/MASpawnThread.java b/src/main/java/com/garbagemule/MobArena/MASpawnThread.java index a5af4807..fac5a8bf 100644 --- a/src/main/java/com/garbagemule/MobArena/MASpawnThread.java +++ b/src/main/java/com/garbagemule/MobArena/MASpawnThread.java @@ -196,7 +196,7 @@ private void handleWave(Wave w, int wave, boolean announce) { handleWave(cw.getWaveB(), wave, false); - if (!comboChild) { + if (!announce) { cw.getWaveA().announce(arena, wave); // announce only waveA in outermost COMBO wave. } return; From 3306cc34528ebd05dc39b6a73c5b2fe7c659147a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Justus=20G=C3=BCttler?= Date: Thu, 22 Jan 2026 07:09:51 +0100 Subject: [PATCH 7/7] Added shadow wave blocking. Bugfix combo wave announcement. Announcement from waveA now "bubbles up" if waveA itself is combo wave. --- .../garbagemule/MobArena/MASpawnThread.java | 13 +++++----- .../MobArena/waves/AbstractWave.java | 11 +++++++++ .../com/garbagemule/MobArena/waves/Wave.java | 12 ++++++++++ .../MobArena/waves/WaveParser.java | 12 ++-------- .../garbagemule/MobArena/waves/WaveUtils.java | 24 +++++++++++++++---- .../MobArena/waves/enums/WaveType.java | 2 +- 6 files changed, 52 insertions(+), 22 deletions(-) diff --git a/src/main/java/com/garbagemule/MobArena/MASpawnThread.java b/src/main/java/com/garbagemule/MobArena/MASpawnThread.java index 78b3f995..a19d1f83 100644 --- a/src/main/java/com/garbagemule/MobArena/MASpawnThread.java +++ b/src/main/java/com/garbagemule/MobArena/MASpawnThread.java @@ -9,10 +9,7 @@ import com.garbagemule.MobArena.things.ExperienceThing; import com.garbagemule.MobArena.things.Thing; import com.garbagemule.MobArena.things.ThingPicker; -import com.garbagemule.MobArena.waves.MABoss; -import com.garbagemule.MobArena.waves.MACreature; -import com.garbagemule.MobArena.waves.Wave; -import com.garbagemule.MobArena.waves.WaveManager; +import com.garbagemule.MobArena.waves.*; import com.garbagemule.MobArena.waves.enums.WaveType; import com.garbagemule.MobArena.waves.types.BossWave; import com.garbagemule.MobArena.waves.types.ComboWave; @@ -184,7 +181,7 @@ private void spawnWave(int wave) { handleWave(w, wave, true); Wave sw = waveManager.getCurrentShadow(); - if (sw != null) { + if (sw != null && WaveUtils.waveBlocked(sw, w.getShadowBlacklist())) { handleWave(sw, wave, false); } } @@ -192,11 +189,13 @@ private void spawnWave(int wave) { private void handleWave(Wave w, int wave, boolean announce) { if (w.getType() == WaveType.COMBO) { ComboWave cw = (ComboWave) w; - handleWave(cw.getWaveA(), wave, false); + + Wave waveA = cw.getWaveA(); + handleWave(cw.getWaveA(), wave, waveA.getType() == WaveType.COMBO); handleWave(cw.getWaveB(), wave, false); - if (announce) { + if (announce && waveA.getType() != WaveType.COMBO) { cw.getWaveA().announce(arena, wave); // announce only waveA in outermost COMBO wave. } return; diff --git a/src/main/java/com/garbagemule/MobArena/waves/AbstractWave.java b/src/main/java/com/garbagemule/MobArena/waves/AbstractWave.java index 26079a6d..9fc2b70b 100644 --- a/src/main/java/com/garbagemule/MobArena/waves/AbstractWave.java +++ b/src/main/java/com/garbagemule/MobArena/waves/AbstractWave.java @@ -20,6 +20,7 @@ public abstract class AbstractWave implements Wave private double healthMultiplier, amountMultiplier; private int firstWave, frequency, priority; + private String shadowBlacklist; private List spawnpoints; private List effects; @@ -120,6 +121,16 @@ public void setPriority(int priority) { this.priority = priority; } + @Override + public String getShadowBlacklist() { + return shadowBlacklist; + } + + @Override + public void setShadowBlacklist(String shadowBlacklist) { + this.shadowBlacklist = shadowBlacklist; + } + @Override public double getHealthMultiplier() { return healthMultiplier; diff --git a/src/main/java/com/garbagemule/MobArena/waves/Wave.java b/src/main/java/com/garbagemule/MobArena/waves/Wave.java index 94dce16e..b86ba3df 100644 --- a/src/main/java/com/garbagemule/MobArena/waves/Wave.java +++ b/src/main/java/com/garbagemule/MobArena/waves/Wave.java @@ -160,6 +160,18 @@ public interface Wave */ void setAmountMultiplier(double amountMultiplier); + /** + * Get the wave's shadow blacklist. + * @return a list of shadow wave names + */ + String getShadowBlacklist(); + + /** + * Set the wave's shadow blacklist. + * @param blacklist a list of shadow wave names + */ + void setShadowBlacklist(String blacklist); + /** * Check if this wave matches the wave number. * The SingleWave class does a simple check if its wave == the parameter. diff --git a/src/main/java/com/garbagemule/MobArena/waves/WaveParser.java b/src/main/java/com/garbagemule/MobArena/waves/WaveParser.java index 79f6f0c1..9219aefe 100644 --- a/src/main/java/com/garbagemule/MobArena/waves/WaveParser.java +++ b/src/main/java/com/garbagemule/MobArena/waves/WaveParser.java @@ -22,16 +22,7 @@ import org.bukkit.inventory.ItemStack; import org.bukkit.potion.PotionEffect; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.Collections; -import java.util.HashMap; -import java.util.List; -import java.util.Map; -import java.util.Set; -import java.util.SortedMap; -import java.util.TreeMap; -import java.util.TreeSet; +import java.util.*; import java.util.stream.Collectors; public class WaveParser @@ -138,6 +129,7 @@ public static Wave parseWave(Arena arena, String name, ConfigurationSection conf result.setFirstWave(firstWave); result.setPriority(priority); result.setFrequency(frequency); + result.setShadowBlacklist(Optional.ofNullable(config.getString("noshadow")).orElse("")); // And the multipliers. result.setHealthMultiplier(healthMultiplier); diff --git a/src/main/java/com/garbagemule/MobArena/waves/WaveUtils.java b/src/main/java/com/garbagemule/MobArena/waves/WaveUtils.java index 53320dc8..f6910a83 100644 --- a/src/main/java/com/garbagemule/MobArena/waves/WaveUtils.java +++ b/src/main/java/com/garbagemule/MobArena/waves/WaveUtils.java @@ -8,13 +8,29 @@ import org.bukkit.entity.Entity; import org.bukkit.entity.Player; -import java.util.ArrayList; -import java.util.Collection; -import java.util.Comparator; -import java.util.List; +import java.util.*; public class WaveUtils { + /** + * Test if a wave gets blocked by a given blacklist + */ + public static boolean waveBlocked(Wave wave, String blacklist) { + if (blacklist == null) { + return true; + } + if (blacklist.equals("*")) { + return false; + } + String[] blacklistedWaves = blacklist.split(", ?"); + for (String waveName : blacklistedWaves) { + if (waveName.equalsIgnoreCase(wave.getName())) { + return false; + } + } + return true; + } + /** * Get all the spawnpoints that have players nearby. */ diff --git a/src/main/java/com/garbagemule/MobArena/waves/enums/WaveType.java b/src/main/java/com/garbagemule/MobArena/waves/enums/WaveType.java index 2dda5d73..94cf67a2 100644 --- a/src/main/java/com/garbagemule/MobArena/waves/enums/WaveType.java +++ b/src/main/java/com/garbagemule/MobArena/waves/enums/WaveType.java @@ -50,7 +50,7 @@ public void announce(Arena arena, int wave) { COMBO { @Override public void announce(Arena arena, int wave) { - // announce waveA in MASpawnThread.handleWave() + // announce waveA in MASpawnThread.handleWave(Wave, int, boolean) } };