Rework some of the wave parsing logic.

This commit is a full sweep across the WaveParser. It introduces the ConfigError exception to the WaveParser and throws it whenever something isn't right. There's quite a lot of stuff going on, but in overall:

- Errors are thrown rather than null values being returned
- Defaults are picked in lieu of explicit values when they "make sense"
- Most list-type values can now be either the default comma-separated string or a string list

Common nodes:
- Spawnpoints can now be a string list

Default waves:
- Missing or empty monsters map throws an error
- Missing wave growth defaults to medium
- Invalid wave growth throws an error

Special waves:
- Missing or empty monsters map throws an error

Swarm waves:
- Missing or empty monster node throws an error
- Missing swarm amount defaults to low
- Invalid swarm amount throws an error

Supply waves:
- Missing or empty monsters map throws an error
- Missing or empty drops node throws an error
- Invalid drop throws an error
- Drops can now be a string list

Upgrade waves:
- Missing or empty upgrades map throws an error

Boss waves:
- Missing or empty monster node throws an error
- Missing or empty boss health defaults to medium
- Invalid boss health throws an error
- Invalid ability throws an error
- Invalid drop throws an error
- Abilities and drops can now be string lists
This commit is contained in:
Andreas Troelsen 2018-08-06 01:22:27 +02:00
parent 060a395213
commit 951e83cc55
7 changed files with 163 additions and 186 deletions

View File

@ -2,7 +2,6 @@ package com.garbagemule.MobArena.waves;
import com.garbagemule.MobArena.framework.Arena;
import com.garbagemule.MobArena.waves.enums.WaveBranch;
import com.garbagemule.MobArena.waves.enums.WaveError;
import org.bukkit.configuration.ConfigurationSection;
import java.util.SortedSet;
@ -48,13 +47,18 @@ public class WaveManager
finalWave = section.getParent().getInt("settings.final-wave", 0);
if (recurrentWaves.isEmpty()) {
arena.getPlugin().getLogger().warning(WaveError.NO_RECURRENT_WAVES.format(arena.configName()));
Wave def = WaveParser.createDefaultWave();
recurrentWaves.add(def);
if (singleWaves.isEmpty()) {
arena.getPlugin().getLogger().warning("Found no waves for arena " + arena.configName() + ", using default wave.");
} else {
arena.getPlugin().getLogger().info("Found no 'recurrent' waves for arena " + arena.configName() + ", using default wave.");
}
defaultWave = WaveParser.createDefaultWave();
} else {
if (singleWaves.isEmpty()) {
arena.getPlugin().getLogger().info("Found no 'single' waves for arena " + arena.configName());
}
defaultWave = recurrentWaves.first();
}
defaultWave = recurrentWaves.first();
}
/**

View File

@ -13,7 +13,6 @@ import com.garbagemule.MobArena.waves.ability.AbilityManager;
import com.garbagemule.MobArena.waves.enums.BossHealth;
import com.garbagemule.MobArena.waves.enums.SwarmAmount;
import com.garbagemule.MobArena.waves.enums.WaveBranch;
import com.garbagemule.MobArena.waves.enums.WaveError;
import com.garbagemule.MobArena.waves.enums.WaveGrowth;
import com.garbagemule.MobArena.waves.enums.WaveType;
import com.garbagemule.MobArena.waves.types.BossWave;
@ -33,7 +32,6 @@ import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Set;
import java.util.SortedMap;
import java.util.TreeMap;
@ -48,14 +46,12 @@ public class WaveParser
// If the config is null, return the empty set.
if (config == null) {
arena.getPlugin().getLogger().warning(WaveError.BRANCH_MISSING.format(branch.toString().toLowerCase(), arena.configName()));
return result;
}
// If no waves were found, return the empty set.
Set<String> waves = config.getKeys(false);
if (waves == null) {
arena.getPlugin().getLogger().warning(WaveError.BRANCH_MISSING.format(branch.toString().toLowerCase(), arena.configName()));
return result;
}
@ -63,12 +59,6 @@ public class WaveParser
for (String wave : waves) {
ConfigurationSection waveSection = config.getConfigurationSection(wave);
Wave w = parseWave(arena, wave, waveSection, branch);
// Only add properly parsed waves.
if (w == null) {
continue;
}
result.add(w);
}
@ -81,8 +71,7 @@ public class WaveParser
WaveType type = WaveType.fromString(t);
if (type == null) {
arena.getPlugin().getLogger().warning(WaveError.INVALID_TYPE.format(t, name, arena.configName()));
return null;
throw new ConfigError("Invalid wave type for wave " + name + " of arena " + arena.configName() + ": " + t);
}
// Prepare the result
@ -110,12 +99,6 @@ public class WaveParser
break;
}
// Check that the result isn't null
if (result == null) {
arena.getPlugin().getLogger().warning(WaveError.INVALID_WAVE.format(name, arena.configName()));
return null;
}
// Grab the branch-specific nodes.
int priority = config.getInt("priority", -1);
int frequency = config.getInt("frequency", -1);
@ -139,12 +122,15 @@ public class WaveParser
List<PotionEffect> effects = getPotionEffects(arena, name, config);
// Recurrent must have priority + frequency, single must have firstWave
if (branch == WaveBranch.RECURRENT && (priority == -1 || frequency <= 0)) {
arena.getPlugin().getLogger().warning(WaveError.RECURRENT_NODES.format(name, arena.configName()));
return null;
if (branch == WaveBranch.RECURRENT) {
if (priority <= 0) {
throw new ConfigError("Missing or invalid 'priority' node for recurrent wave " + name + " of arena " + arena.configName());
}
if (frequency <= 0) {
throw new ConfigError("Missing or invalid 'frequency' node for recurrent wave " + name + " of arena " + arena.configName());
}
} else if (branch == WaveBranch.SINGLE && firstWave <= 0) {
arena.getPlugin().getLogger().warning(WaveError.SINGLE_NODES.format(name, arena.configName()));
return null;
throw new ConfigError("Missing or invalid 'wave' node for single wave " + name + " of arena " + arena.configName());
}
// Set the important required values.
@ -169,11 +155,7 @@ public class WaveParser
private static Wave parseDefaultWave(Arena arena, String name, ConfigurationSection config) {
// Grab the monster map.
SortedMap<Integer,MACreature> monsters = getMonsterMap(config);
if (monsters == null || monsters.isEmpty()) {
arena.getPlugin().getLogger().warning(WaveError.MONSTER_MAP_MISSING.format(name, arena.configName()));
return null;
}
SortedMap<Integer,MACreature> monsters = getMonsterMap(arena, name, config);
// Create the wave.
DefaultWave result = new DefaultWave(monsters);
@ -186,53 +168,72 @@ public class WaveParser
}
// Grab the WaveGrowth
String grw = config.getString("growth");
WaveGrowth growth = WaveGrowth.fromString(grw);
result.setGrowth(growth);
String grw = config.getString("growth", null);
if (grw != null && !grw.isEmpty()) {
try {
WaveGrowth growth = WaveGrowth.valueOf(grw.toUpperCase());
result.setGrowth(growth);
} catch (IllegalArgumentException e) {
throw new ConfigError("Failed to parse wave growth for wave " + name + " of arena " + arena.configName() + ": " + grw);
}
} else {
result.setGrowth(WaveGrowth.MEDIUM);
}
return result;
}
private static Wave parseSpecialWave(Arena arena, String name, ConfigurationSection config) {
SortedMap<Integer,MACreature> monsters = getMonsterMap(config);
if (monsters == null || monsters.isEmpty()) {
arena.getPlugin().getLogger().warning(WaveError.MONSTER_MAP_MISSING.format(name, arena.configName()));
return null;
}
SortedMap<Integer,MACreature> monsters = getMonsterMap(arena, name, config);
SpecialWave result = new SpecialWave(monsters);
return result;
return new SpecialWave(monsters);
}
private static Wave parseSwarmWave(Arena arena, String name, ConfigurationSection config) {
MACreature monster = getSingleMonster(config);
if (monster == null) {
arena.getPlugin().getLogger().warning(WaveError.SINGLE_MONSTER_MISSING.format(name, arena.configName()));
return null;
}
MACreature monster = getSingleMonster(arena, name, config);
SwarmWave result = new SwarmWave(monster);
// Grab SwarmAmount
String amnt = config.getString("amount");
SwarmAmount amount = SwarmAmount.fromString(amnt);
result.setAmount(amount);
String amnt = config.getString("amount", null);
if (amnt != null && !amnt.isEmpty()) {
try {
SwarmAmount amount = SwarmAmount.valueOf(amnt.toUpperCase());
result.setAmount(amount);
} catch (IllegalArgumentException e) {
throw new ConfigError("Failed to parse wave amount for wave " + name + " of arena " + arena.configName() + ": " + amnt);
}
} else {
result.setAmount(SwarmAmount.LOW);
}
return result;
}
private static Wave parseSupplyWave(Arena arena, String name, ConfigurationSection config) {
SortedMap<Integer,MACreature> monsters = getMonsterMap(config);
if (monsters == null || monsters.isEmpty()) {
arena.getPlugin().getLogger().warning(WaveError.MONSTER_MAP_MISSING.format(name, arena.configName()));
return null;
}
SortedMap<Integer,MACreature> monsters = getMonsterMap(arena, name, config);
SupplyWave result = new SupplyWave(monsters);
// Grab the loot.
String loot = config.getString("drops");
List<ItemStack> stacks = ItemParser.parseItems(loot);
List<String> loot = config.getStringList("drops");
if (loot == null || loot.isEmpty()) {
String value = config.getString("drops", null);
if (value == null) {
throw new ConfigError("Missing 'drops' node for wave " + name + " of arena " + arena.configName());
}
loot = Arrays.asList(value.split(","));
}
List<ItemStack> stacks = loot.stream()
.map(String::trim)
.map(value -> {
ItemStack stack = ItemParser.parseItem(value, false);
if (stack == null) {
throw new ConfigError("Failed to parse loot for wave " + name + " of arena " + arena.configName() + ": " + value);
}
return stack;
})
.collect(Collectors.toList());
result.setDropList(stacks);
return result;
@ -241,72 +242,69 @@ public class WaveParser
private static Wave parseUpgradeWave(Arena arena, String name, ConfigurationSection config) {
ThingManager thingman = arena.getPlugin().getThingManager();
Map<String,List<Thing>> upgrades = getUpgradeMap(config, name, arena, thingman);
if (upgrades == null || upgrades.isEmpty()) {
arena.getPlugin().getLogger().warning(WaveError.UPGRADE_MAP_MISSING.format(name, arena.configName()));
return null;
}
return new UpgradeWave(upgrades);
}
private static Wave parseBossWave(Arena arena, String name, ConfigurationSection config) {
MACreature monster = getSingleMonster(config);
if (monster == null) {
arena.getPlugin().getLogger().warning(WaveError.SINGLE_MONSTER_MISSING.format(name, arena.configName()));
return null;
}
MACreature monster = getSingleMonster(arena, name, config);
BossWave result = new BossWave(monster);
// Check if there's a specific boss name
String bossName = config.getString("name");
if (bossName != null && !bossName.equals("")) {
String bossName = config.getString("name", null);
if (bossName != null && !bossName.isEmpty()) {
result.setBossName(bossName);
}
// Grab the boss health
String healthString = config.getString("health");
if (healthString == null) {
String warning = "No health value found for boss '%s' in arena '%s'. Defaulting to medium.";
arena.getPlugin().getLogger().warning(String.format(warning, name, arena.configName()));
result.setHealth(BossHealth.MEDIUM);
} else {
BossHealth health = BossHealth.fromString(healthString);
if (health != null) {
String healthString = config.getString("health", null);
if (healthString != null && !healthString.isEmpty()) {
try {
BossHealth health = BossHealth.valueOf(healthString.toUpperCase());
result.setHealth(health);
} else {
int flatHealth = config.getInt("health", 0);
if (flatHealth <= 0) {
String warning = "Unable to parse health of boss '%s' in arena '%s'. Defaulting to medium. Value was '%s'";
arena.getPlugin().getLogger().warning(String.format(warning, name, arena.configName(), healthString));
result.setHealth(BossHealth.MEDIUM);
} else {
result.setFlatHealth(flatHealth);
} catch (IllegalArgumentException e) {
int flatHealth = config.getInt("health", -1);
if (flatHealth < 0) {
throw new ConfigError("Failed to parse boss health for wave " + name + " of arena " + arena.configName() + ": " + healthString);
}
result.setFlatHealth(flatHealth);
}
} else {
result.setHealth(BossHealth.MEDIUM);
}
// And the abilities.
String ablts = config.getString("abilities");
if (ablts != null) {
String[] parts = ablts.split(",");
for (String ability : parts) {
Ability a = AbilityManager.getAbility(ability.trim());
if (a == null) {
arena.getPlugin().getLogger().warning(WaveError.BOSS_ABILITY.format(ability.trim(), name, arena.configName()));
continue;
}
result.addBossAbility(a);
List<String> abilities = config.getStringList("abilities");
if (abilities == null || abilities.isEmpty()) {
String value = config.getString("abilities", null);
if (value == null) {
abilities = Collections.emptyList();
} else {
abilities = Arrays.asList(value.split(","));
}
}
if (abilities.isEmpty()) {
arena.getPlugin().getLogger().warning("No boss abilities for boss wave " + name + " of arena " + arena.configName());
}
abilities.stream()
.map(String::trim)
.map(value -> {
Ability ability = AbilityManager.getAbility(value);
if (ability == null) {
throw new ConfigError("Failed to parse boss ability for boss wave " + name + " of arena " + arena.configName() + ": " + value);
}
return ability;
})
.forEach(result::addBossAbility);
// As well as the ability interval and ability announce.
result.setAbilityInterval(config.getInt("ability-interval", 3) * 20);
result.setAbilityAnnounce(config.getBoolean("ability-announce", true));
// Rewards!
String rew = config.getString("reward");
if (rew != null) {
String rew = config.getString("reward", null);
if (rew != null && !rew.isEmpty()) {
try {
Thing thing = arena.getPlugin().getThingManager().parse(rew.trim());
result.setReward(thing);
@ -316,9 +314,26 @@ public class WaveParser
}
// Drops!
String drp = config.getString("drops");
List<ItemStack> drops = ItemParser.parseItems(drp);
result.setDrops(drops);
List<String> drops = config.getStringList("drops");
if (drops == null || drops.isEmpty()) {
String value = config.getString("drops", null);
if (value == null) {
drops = Collections.emptyList();
} else {
drops = Arrays.asList(value.split(","));
}
}
List<ItemStack> stacks = drops.stream()
.map(String::trim)
.map(value -> {
ItemStack stack = ItemParser.parseItem(value, false);
if (stack == null) {
throw new ConfigError("Failed to parse boss drop in wave " + name + " of arena " + arena.configName() + ": " + value);
}
return stack;
})
.collect(Collectors.toList());
result.setDrops(stacks);
return result;
}
@ -329,13 +344,16 @@ public class WaveParser
* @param config a ConfigSection
* @return an MACreature, if the monster node contains one that is valid
*/
private static MACreature getSingleMonster(ConfigurationSection config) {
String monster = config.getString("monster");
if (monster == null) {
return null;
private static MACreature getSingleMonster(Arena arena, String name, ConfigurationSection config) {
String monster = config.getString("monster", null);
if (monster == null || monster.isEmpty()) {
throw new ConfigError("Missing 'monster' node for wave " + name + " of arena " + arena.configName());
}
MACreature result = MACreature.fromString(monster);
if (result == null) {
throw new ConfigError("Failed to parse monster for wave " + name + " of arena " + arena.configName() + ": " + monster);
}
return result;
}
@ -345,15 +363,15 @@ public class WaveParser
* @param config
* @return a "reverse" map of monsters and numbers
*/
private static SortedMap<Integer,MACreature> getMonsterMap(ConfigurationSection config) {
private static SortedMap<Integer,MACreature> getMonsterMap(Arena arena, String name, ConfigurationSection config) {
ConfigurationSection section = config.getConfigurationSection("monsters");
if (section == null) {
return null;
throw new ConfigError("Missing 'monsters' node for wave " + name + " of arena " + arena.configName());
}
Set<String> monsters = section.getKeys(false);
if (monsters == null || monsters.isEmpty()) {
return null;
throw new ConfigError("Empty 'monsters' node for wave " + name + " of arena " + arena.configName());
}
// Prepare the map.
@ -364,10 +382,14 @@ public class WaveParser
// Check all the monsters.
for (String monster : monsters) {
MACreature creature = MACreature.fromString(monster);
if (creature == null) continue;
if (creature == null) {
throw new ConfigError("Failed to parse monster for wave " + name + " of arena " + arena.configName() + ": " + monster);
}
int prob = config.getInt(path + monster, 0);
if (prob == 0) continue;
int prob = config.getInt(path + monster, -1);
if (prob < 0) {
throw new ConfigError("Failed to parse probability for monster " + monster + " in wave " + name + " of arena " + arena.configName());
}
sum += prob;
monsterMap.put(sum, creature);
@ -377,29 +399,26 @@ public class WaveParser
}
private static List<Location> getSpawnpoints(Arena arena, String name, ConfigurationSection config) {
List<Location> result = new ArrayList<>();
String spawnString = config.getString("spawnpoints");
if (spawnString == null) {
return result;
List<String> spawnpoints = config.getStringList("spawnpoints");
if (spawnpoints == null || spawnpoints.isEmpty()) {
String value = config.getString("spawnpoints", null);
if (value == null || value.isEmpty()) {
return Collections.emptyList();
}
spawnpoints = Arrays.asList(value.split(";"));
}
// Split the string by semicolons
String[] spawns = spawnString.split(";");
ArenaRegion region = arena.getRegion();
for (String spawn : spawns) {
Location spawnpoint = region.getSpawnpoint(spawn.trim());
if (spawnpoint == null) {
arena.getPlugin().getLogger().warning("Spawnpoint '" + spawn + "' in wave '" + name + "' for arena '" + arena.configName() + "' could not be parsed!");
continue;
}
result.add(spawnpoint);
}
return result;
return spawnpoints.stream()
.map(String::trim)
.map(value -> {
Location spawnpoint = region.getSpawnpoint(value);
if (spawnpoint == null) {
throw new ConfigError("Spawnpoint '" + value + "' in wave '" + name + "' for arena '" + arena.configName() + "' could not be parsed!");
}
return spawnpoint;
})
.collect(Collectors.toList());
}
private static List<PotionEffect> getPotionEffects(Arena arena, String name, ConfigurationSection config) {
@ -439,12 +458,12 @@ public class WaveParser
private static Map<String,List<Thing>> getUpgradeMap(ConfigurationSection config, String name, Arena arena, ThingManager thingman) {
ConfigurationSection section = config.getConfigurationSection("upgrades");
if (section == null) {
return null;
throw new ConfigError("Missing 'upgrades' node for wave " + name + " of arena " + arena.configName());
}
Set<String> classes = section.getKeys(false);
if (classes == null || classes.isEmpty()) {
return null;
throw new ConfigError("Empty 'upgrades' node for wave " + name + " of arena " + arena.configName());
}
Map<String,List<Thing>> upgrades = new HashMap<>();

View File

@ -1,7 +1,5 @@
package com.garbagemule.MobArena.waves.enums;
import com.garbagemule.MobArena.waves.WaveUtils;
public enum BossHealth
{
VERYLOW(4), LOW(8), MEDIUM(15), HIGH(25), VERYHIGH(40), PSYCHO(60);
@ -18,8 +16,4 @@ public enum BossHealth
public int getMultiplier() {
return multiplier;
}
public static BossHealth fromString(String string) {
return WaveUtils.getEnumFromString(BossHealth.class, string, null);
}
}

View File

@ -1,7 +1,5 @@
package com.garbagemule.MobArena.waves.enums;
import com.garbagemule.MobArena.waves.WaveUtils;
public enum SwarmAmount
{
LOW(10), MEDIUM(20), HIGH(30), PSYCHO(60);
@ -14,8 +12,4 @@ public enum SwarmAmount
public int getAmount(int playerCount) {
return Math.max(1, playerCount / 2) * multiplier;
}
public static SwarmAmount fromString(String string) {
return WaveUtils.getEnumFromString(SwarmAmount.class, string, LOW);
}
}

View File

@ -1,32 +0,0 @@
package com.garbagemule.MobArena.waves.enums;
public enum WaveError
{
INVALID_WAVE("Wave '%s' for arena '%s' could not be parsed."),
NO_RECURRENT_WAVES("No valid recurrent waves found for arena '%s'. Check the config-file. Using implicit default wave."),
BRANCH_MISSING("The '%s' branch for arena '%s' is empty. Check the config-file."),
INVALID_TYPE("Invalid type '%s' for wave '%s' in arena '%s'. Skipping..."),
RECURRENT_NODES("Recurrent wave '%s' in arena '%s' is missing either the 'frequency' or the 'priority' node. Skipping..."),
SINGLE_NODES("Single wave '%s' in arena '%s' is missing the 'wave' node. Skipping..."),
MONSTER_MAP_MISSING("Missing 'monsters' node for wave '%s' in arena '%s'."),
SINGLE_MONSTER_MISSING("Missing 'monster' node for wave '%s' in arena '%s'."),
BOSS_ABILITY("Invalid boss ability '%s' for wave '%s' in arena '%s'."),
SUPPLY_DROPS("Missing 'drops' node for wave '%s' in arena '%s'."),
UPGRADE_MAP_MISSING("Missing 'upgrades' node for wave '%s' in arena '%s'.");
private String msg;
WaveError(String msg) {
this.msg = msg;
}
public String format(Object... args) {
return String.format(msg, args);
}
}

View File

@ -1,7 +1,5 @@
package com.garbagemule.MobArena.waves.enums;
import com.garbagemule.MobArena.waves.WaveUtils;
public enum WaveGrowth
{
OLD(0), SLOW(0.5), MEDIUM(0.65), FAST(0.8), PSYCHO(1.2);
@ -11,10 +9,6 @@ public enum WaveGrowth
this.exp = exp;
}
public static WaveGrowth fromString(String string) {
return WaveUtils.getEnumFromString(WaveGrowth.class, string, OLD);
}
public int getAmount(int wave, int playerCount) {
if (this == OLD) return wave + playerCount;

View File

@ -51,6 +51,10 @@ public enum WaveType
public abstract void announce(Arena arena, int wave);
public static WaveType fromString(String string) {
return WaveUtils.getEnumFromString(WaveType.class, string);
try {
return WaveType.valueOf(string.toUpperCase());
} catch (IllegalArgumentException e) {
return null;
}
}
}