From 50a8374b1c1cc019d577dbe198ec8465b4b6d4d5 Mon Sep 17 00:00:00 2001 From: Daniel Saukel Date: Fri, 22 Oct 2021 23:16:58 +0200 Subject: [PATCH] Rewrite game goals; resolves #1057 --- .../dungeonsxl/api/dungeon/GameGoal.java | 116 ++++++++++++++++-- .../dungeonsxl/api/dungeon/GameRule.java | 31 ++--- .../api/dungeon/GameRuleContainer.java | 13 +- .../de/erethon/dungeonsxl/DungeonsXL.java | 3 +- .../erethon/dungeonsxl/dungeon/DDungeon.java | 3 +- .../de/erethon/dungeonsxl/dungeon/DGame.java | 6 +- .../dungeonsxl/player/DGamePlayer.java | 4 +- .../de/erethon/dungeonsxl/player/DGroup.java | 6 +- .../dungeonsxl/player/TimeIsRunningTask.java | 8 -- .../dungeonsxl/sign/button/EndSign.java | 2 +- 10 files changed, 131 insertions(+), 61 deletions(-) diff --git a/api/src/main/java/de/erethon/dungeonsxl/api/dungeon/GameGoal.java b/api/src/main/java/de/erethon/dungeonsxl/api/dungeon/GameGoal.java index 601b30a2..f7b2e3c5 100644 --- a/api/src/main/java/de/erethon/dungeonsxl/api/dungeon/GameGoal.java +++ b/api/src/main/java/de/erethon/dungeonsxl/api/dungeon/GameGoal.java @@ -14,36 +14,126 @@ */ package de.erethon.dungeonsxl.api.dungeon; +import de.erethon.commons.misc.EnumUtil; +import org.bukkit.configuration.ConfigurationSection; + /** * A game goal defines what the players have to do in order to finish the game. * * @author Daniel Saukel */ -public enum GameGoal { +public class GameGoal extends GameRuleContainer { /** - * The default goal. The game ends when the end is reached. + * Score used for capture the flag and similar game types. */ - END, + public static final GameRule INITIAL_SCORE = new GameRule<>(Integer.class, "initialScore", 3); /** - * The game does not end. Instead, the goal is to survive as long as possible to beat a highscore. + * The amount of goals to score before the game ends. -1 = not used. */ - HIGHSCORE, + public static final GameRule SCORE_GOAL = new GameRule<>(Integer.class, "scoreGoal", -1); /** - * The game ends when a player dies and only one group is left. + * The time left to finish the game; -1 if no timer is used. */ - LAST_MAN_STANDING, + public static final GameRule TIME_TO_FINISH = new GameRule<>(Integer.class, "timeToFinish", -1); + /** - * The game ends when a group reachs a specific score. + * The default game goal: {@link Type#END} without TIME_TO_FINISH */ - REACH_SCORE, + public static final GameGoal DEFAULT = new GameGoal(Type.END); + + static { + DEFAULT.setState(TIME_TO_FINISH, TIME_TO_FINISH.getDefaultValue()); + } + /** - * The game ends after a specific time. The goal is to get the highest score until then. + * The reader to deserialize a game goal from a configuration. */ - TIME_SCORE, + public static final ConfigReader READER = (api, value) -> { + if (!(value instanceof ConfigurationSection)) { + return DEFAULT; + } + ConfigurationSection config = (ConfigurationSection) value; + Type type = EnumUtil.getEnumIgnoreCase(Type.class, config.getString("type", "END")); + GameGoal goal = new GameGoal(type); + for (GameRule rule : type.getComponents()) { + rule.fromConfig(api, goal, config); + if (!goal.rules.containsKey(rule)) { + goal.setState(rule, rule.getDefaultValue()); + } + } + return goal; + }; + + private Type type; + + public GameGoal(Type type) { + this.type = type; + } + /** - * The game ends after a specific time. The goal is to survive until then. + * Returns the type of the game goal. + * + * @return the type */ - TIME_SURVIVAL; + public Type getType() { + return type; + } + + /** + * Determines the behavior of the game goal and which settings apply to it. + */ + public enum Type { + /** + * The default goal. The game ends when the end is reached. + */ + END(TIME_TO_FINISH), + /** + * The game ends when a player dies and only one group is left. + */ + LAST_MAN_STANDING, + /** + * SCORE_GOAL = -1: The game does not end. Instead, the goal is to survive as long as possible to beat a highscore. + *

+ * SCORE_GOAL > 0: The game ends when a group reachs a specific score. + *

+ * TIME_TO_FINISH != -1: The game ends after a specific time. The goal is to get the highest score until then. + */ + SCORE(INITIAL_SCORE, SCORE_GOAL, TIME_TO_FINISH), + /** + * The game ends after a specific time. The goal is to survive until then. + */ + TIME_SURVIVAL; + + private GameRule[] components; + + Type(GameRule... components) { + this.components = components; + } + + /** + * Returns an array of the game rules that apply to game goals of this type. + * + * @return an array of the game rules that apply to game goals of this type + */ + public GameRule[] getComponents() { + return components; + } + + /** + * Returns whether the given game rule applies to game goals of this type. + * + * @param component the game rule + * @return whether the given game rule applies to game goals of this type + */ + public boolean hasComponent(GameRule component) { + for (GameRule c : components) { + if (c == component) { + return true; + } + } + return false; + } + } } diff --git a/api/src/main/java/de/erethon/dungeonsxl/api/dungeon/GameRule.java b/api/src/main/java/de/erethon/dungeonsxl/api/dungeon/GameRule.java index c72b89db..5b10f62f 100644 --- a/api/src/main/java/de/erethon/dungeonsxl/api/dungeon/GameRule.java +++ b/api/src/main/java/de/erethon/dungeonsxl/api/dungeon/GameRule.java @@ -14,9 +14,7 @@ */ package de.erethon.dungeonsxl.api.dungeon; -import de.erethon.caliburn.CaliburnAPI; import de.erethon.caliburn.item.ExItem; -import de.erethon.caliburn.item.VanillaItem; import de.erethon.caliburn.mob.ExMob; import de.erethon.commons.chat.MessageUtil; import de.erethon.commons.misc.EnumUtil; @@ -24,7 +22,6 @@ import de.erethon.commons.misc.NumberUtil; import de.erethon.dungeonsxl.api.DungeonsAPI; import de.erethon.dungeonsxl.api.Requirement; import de.erethon.dungeonsxl.api.Reward; -import de.erethon.dungeonsxl.api.world.GameWorld; import java.lang.reflect.Constructor; import java.lang.reflect.Field; import java.lang.reflect.InvocationTargetException; @@ -36,9 +33,7 @@ import java.util.Map; import java.util.Set; import org.bukkit.Difficulty; import org.bukkit.GameMode; -import org.bukkit.block.Block; import org.bukkit.configuration.ConfigurationSection; -import org.bukkit.entity.Player; /** * Represents a game rule for a {@link Game}. @@ -79,7 +74,7 @@ public class GameRule { /** * The goal of the game that defines what makes it end. */ - public static final GameRule GAME_GOAL = new GameRule<>(GameGoal.class, "gameGoal", GameGoal.END); + public static final GameRule GAME_GOAL = new GameRule<>(GameGoal.class, "gameGoal", GameGoal.DEFAULT, GameGoal.READER); /** * The Vanilla game mode. */ @@ -164,14 +159,6 @@ public class GameRule { * Alternatively to {@link #INITIAL_LIVES player lives}, you can use group lives. */ public static final GameRule INITIAL_GROUP_LIVES = new GameRule<>(Integer.class, "initialGroupLives", -1); - /** - * Score used for capture the flag and similar game types. - */ - public static final GameRule INITIAL_SCORE = new GameRule<>(Integer.class, "initialScore", 3); - /** - * The amount of goals to score before the game ends. -1 = not used. - */ - public static final GameRule SCORE_GOAL = new GameRule<>(Integer.class, "scoreGoal", -1); /** * When loot may be taken away out of the dungeon again. */ @@ -180,10 +167,6 @@ public class GameRule { * The cooldown between two mob waves. */ public static final GameRule TIME_TO_NEXT_WAVE = new GameRule<>(Integer.class, "timeToNextWave", 10); - /** - * The time left to finish the game; -1 if no timer is used. - */ - public static final GameRule TIME_TO_FINISH = new GameRule<>(Integer.class, "timeToFinish", -1); /** * Time until a player is kicked out of a group after he leaves the server. */ @@ -337,10 +320,20 @@ public class GameRule { * An array of all game rules that exist natively in DungeonsXL. */ public static final GameRule[] VALUES = values(); + /** + * A container of all rules with their default value. This is used internally as the most subsidiary container that fills missing rules if they are not set. + */ + public static final GameRuleContainer DEFAULT_VALUES = new GameRuleContainer(); + + static { + for (GameRule rule : VALUES) { + DEFAULT_VALUES.setState(rule, rule.getDefaultValue()); + } + } private static GameRule[] values() { Field[] fields = GameRule.class.getFields(); - GameRule[] values = new GameRule[fields.length - 1]; + GameRule[] values = new GameRule[fields.length - 2]; int i = 0; for (Field field : fields) { try { diff --git a/api/src/main/java/de/erethon/dungeonsxl/api/dungeon/GameRuleContainer.java b/api/src/main/java/de/erethon/dungeonsxl/api/dungeon/GameRuleContainer.java index 9d04e9e0..b83f6fcc 100644 --- a/api/src/main/java/de/erethon/dungeonsxl/api/dungeon/GameRuleContainer.java +++ b/api/src/main/java/de/erethon/dungeonsxl/api/dungeon/GameRuleContainer.java @@ -24,18 +24,7 @@ import java.util.Map; */ public class GameRuleContainer { - /** - * A container of all rules with their default value. This is used internally as the most subsidiary container that fills missing rules if they are not set. - */ - public static final GameRuleContainer DEFAULT_VALUES = new GameRuleContainer(); - - static { - for (GameRule rule : GameRule.VALUES) { - DEFAULT_VALUES.setState(rule, rule.getDefaultValue()); - } - } - - private Map, Object> rules; + protected Map, Object> rules; /** * Initializes an emtpy GameRuleContainer. diff --git a/core/src/main/java/de/erethon/dungeonsxl/DungeonsXL.java b/core/src/main/java/de/erethon/dungeonsxl/DungeonsXL.java index 57f79790..3fdbbe99 100644 --- a/core/src/main/java/de/erethon/dungeonsxl/DungeonsXL.java +++ b/core/src/main/java/de/erethon/dungeonsxl/DungeonsXL.java @@ -28,7 +28,6 @@ import de.erethon.dungeonsxl.api.Reward; import de.erethon.dungeonsxl.api.dungeon.Dungeon; import de.erethon.dungeonsxl.api.dungeon.Game; import de.erethon.dungeonsxl.api.dungeon.GameRule; -import de.erethon.dungeonsxl.api.dungeon.GameRuleContainer; import de.erethon.dungeonsxl.api.event.group.GroupCreateEvent; import de.erethon.dungeonsxl.api.mob.DungeonMob; import de.erethon.dungeonsxl.api.mob.ExternalMobProvider; @@ -172,7 +171,7 @@ public class DungeonsXL extends DREPlugin implements DungeonsAPI { public void add(String key, GameRule rule) { super.add(key, rule); if (loaded) { - GameRuleContainer.DEFAULT_VALUES.setState(rule, rule.getDefaultValue()); + GameRule.DEFAULT_VALUES.setState(rule, rule.getDefaultValue()); mainConfig.getDefaultWorldConfig().updateGameRule(rule); for (Dungeon apiDungeon : dungeonRegistry) { DDungeon dungeon = ((DDungeon) apiDungeon); diff --git a/core/src/main/java/de/erethon/dungeonsxl/dungeon/DDungeon.java b/core/src/main/java/de/erethon/dungeonsxl/dungeon/DDungeon.java index 3df2f2e2..9898328f 100644 --- a/core/src/main/java/de/erethon/dungeonsxl/dungeon/DDungeon.java +++ b/core/src/main/java/de/erethon/dungeonsxl/dungeon/DDungeon.java @@ -18,6 +18,7 @@ package de.erethon.dungeonsxl.dungeon; import de.erethon.dungeonsxl.DungeonsXL; import de.erethon.dungeonsxl.api.dungeon.Dungeon; +import de.erethon.dungeonsxl.api.dungeon.GameRule; import de.erethon.dungeonsxl.api.dungeon.GameRuleContainer; import de.erethon.dungeonsxl.api.world.ResourceWorld; import java.io.File; @@ -198,7 +199,7 @@ public class DDungeon implements Dungeon { rules = new GameRuleContainer(); } rules.merge(plugin.getMainConfig().getDefaultWorldConfig()); - rules.merge(GameRuleContainer.DEFAULT_VALUES); + rules.merge(GameRule.DEFAULT_VALUES); } @Override diff --git a/core/src/main/java/de/erethon/dungeonsxl/dungeon/DGame.java b/core/src/main/java/de/erethon/dungeonsxl/dungeon/DGame.java index 26fdc64f..1ca9e44c 100644 --- a/core/src/main/java/de/erethon/dungeonsxl/dungeon/DGame.java +++ b/core/src/main/java/de/erethon/dungeonsxl/dungeon/DGame.java @@ -19,6 +19,7 @@ package de.erethon.dungeonsxl.dungeon; import de.erethon.dungeonsxl.DungeonsXL; import de.erethon.dungeonsxl.api.dungeon.Dungeon; import de.erethon.dungeonsxl.api.dungeon.Game; +import de.erethon.dungeonsxl.api.dungeon.GameGoal; import de.erethon.dungeonsxl.api.dungeon.GameRule; import de.erethon.dungeonsxl.api.player.PlayerGroup; import de.erethon.dungeonsxl.api.sign.DungeonSign; @@ -105,7 +106,10 @@ public class DGame implements Game { ((DGroup) group).setGame(this); group.setInitialLives(getRules().getState(GameRule.INITIAL_GROUP_LIVES)); group.setLives(getRules().getState(GameRule.INITIAL_GROUP_LIVES)); - group.setScore(getRules().getState(GameRule.INITIAL_SCORE)); + GameGoal goal = getRules().getState(GameRule.GAME_GOAL); + if (goal.getType().hasComponent(GameGoal.INITIAL_SCORE)) { + group.setScore(goal.getState(GameGoal.INITIAL_SCORE)); + } } @Override diff --git a/core/src/main/java/de/erethon/dungeonsxl/player/DGamePlayer.java b/core/src/main/java/de/erethon/dungeonsxl/player/DGamePlayer.java index 0630ca08..d848ec93 100644 --- a/core/src/main/java/de/erethon/dungeonsxl/player/DGamePlayer.java +++ b/core/src/main/java/de/erethon/dungeonsxl/player/DGamePlayer.java @@ -345,7 +345,7 @@ public class DGamePlayer extends DInstancePlayer implements GamePlayer { getGroup().setScore(getGroup().getScore() + 1); GameGoal goal = rules.getState(GameRule.GAME_GOAL); - if ((goal == GameGoal.REACH_SCORE || goal == GameGoal.TIME_SCORE) && rules.getState(GameRule.SCORE_GOAL) == getGroup().getScore()) { + if (goal.getType().hasComponent(GameGoal.SCORE_GOAL) && goal.getState(GameGoal.SCORE_GOAL) == getGroup().getScore()) { getGroup().winGame(); } @@ -759,7 +759,7 @@ public class DGamePlayer extends DInstancePlayer implements GamePlayer { }.runTaskLater(plugin, 1L); } - if (rules.getState(GameRule.GAME_GOAL) == GameGoal.LAST_MAN_STANDING) { + if (rules.getState(GameRule.GAME_GOAL).getType() == GameGoal.Type.LAST_MAN_STANDING) { if (game.getGroups().size() == 1) { ((DGroup) game.getGroups().get(0)).winGame(); } diff --git a/core/src/main/java/de/erethon/dungeonsxl/player/DGroup.java b/core/src/main/java/de/erethon/dungeonsxl/player/DGroup.java index 6260e430..1eb983bb 100644 --- a/core/src/main/java/de/erethon/dungeonsxl/player/DGroup.java +++ b/core/src/main/java/de/erethon/dungeonsxl/player/DGroup.java @@ -20,6 +20,7 @@ import de.erethon.dungeonsxl.DungeonsXL; import de.erethon.dungeonsxl.api.Reward; import de.erethon.dungeonsxl.api.dungeon.Dungeon; import de.erethon.dungeonsxl.api.dungeon.Game; +import de.erethon.dungeonsxl.api.dungeon.GameGoal; import de.erethon.dungeonsxl.api.dungeon.GameRule; import de.erethon.dungeonsxl.api.dungeon.GameRuleContainer; import de.erethon.dungeonsxl.api.event.group.GroupCollectRewardEvent; @@ -576,8 +577,9 @@ public class DGroup implements PlayerGroup { GameRuleContainer rules = getDungeon().getRules(); initialLives = rules.getState(GameRule.INITIAL_GROUP_LIVES); lives = initialLives; - if (rules.getState(GameRule.TIME_TO_FINISH) != -1) { - timeIsRunningTask = new TimeIsRunningTask(plugin, this, rules.getState(GameRule.TIME_TO_FINISH)).runTaskTimer(plugin, 20, 20); + GameGoal goal = rules.getState(GameRule.GAME_GOAL); + if (goal.getType().hasComponent(GameGoal.TIME_TO_FINISH) && goal.getState(GameGoal.TIME_TO_FINISH) != -1) { + timeIsRunningTask = new TimeIsRunningTask(plugin, this, goal.getState(GameGoal.TIME_TO_FINISH)).runTaskTimer(plugin, 20, 20); } for (UUID playerId : getMembers()) { diff --git a/core/src/main/java/de/erethon/dungeonsxl/player/TimeIsRunningTask.java b/core/src/main/java/de/erethon/dungeonsxl/player/TimeIsRunningTask.java index 68746fed..ac86309d 100644 --- a/core/src/main/java/de/erethon/dungeonsxl/player/TimeIsRunningTask.java +++ b/core/src/main/java/de/erethon/dungeonsxl/player/TimeIsRunningTask.java @@ -17,8 +17,6 @@ package de.erethon.dungeonsxl.player; import de.erethon.dungeonsxl.DungeonsXL; -import de.erethon.dungeonsxl.api.dungeon.GameGoal; -import de.erethon.dungeonsxl.api.dungeon.GameRule; import de.erethon.dungeonsxl.api.event.group.GroupPlayerKickEvent; import de.erethon.dungeonsxl.api.player.GamePlayer; import de.erethon.dungeonsxl.api.player.PlayerGroup; @@ -39,14 +37,12 @@ public class TimeIsRunningTask extends BukkitRunnable { private PlayerGroup group; private int time; private int timeLeft; - private GameGoal goal; public TimeIsRunningTask(DungeonsXL plugin, PlayerGroup group, int time) { this.plugin = plugin; this.group = group; this.time = time; this.timeLeft = time; - goal = group.getDungeon().getRules().getState(GameRule.GAME_GOAL); } @Override @@ -70,10 +66,6 @@ public class TimeIsRunningTask extends BukkitRunnable { continue; } - // Fire trigger - if (goal != GameGoal.TIME_SCORE && goal != GameGoal.TIME_SURVIVAL) { - return; - } GroupPlayerKickEvent groupPlayerKickEvent = new GroupPlayerKickEvent(group, dPlayer, GroupPlayerKickEvent.Cause.TIME_EXPIRED); Bukkit.getServer().getPluginManager().callEvent(groupPlayerKickEvent); diff --git a/core/src/main/java/de/erethon/dungeonsxl/sign/button/EndSign.java b/core/src/main/java/de/erethon/dungeonsxl/sign/button/EndSign.java index 20c98cfc..91b0819a 100644 --- a/core/src/main/java/de/erethon/dungeonsxl/sign/button/EndSign.java +++ b/core/src/main/java/de/erethon/dungeonsxl/sign/button/EndSign.java @@ -87,7 +87,7 @@ public class EndSign extends Button { @Override public void initialize() { GameGoal goal = getGame().getRules().getState(GameRule.GAME_GOAL); - if (goal != GameGoal.END) { + if (goal.getType() != GameGoal.Type.END) { setToAir(); MessageUtil.log(api, "&4An end sign in the dungeon " + getGame().getDungeon().getName() + " is ignored because the game goal is " + goal.toString()); return;