Rewrite game goals; resolves #1057

This commit is contained in:
Daniel Saukel 2021-10-22 23:16:58 +02:00
parent b61e1267d0
commit 50a8374b1c
10 changed files with 131 additions and 61 deletions

View File

@ -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<Integer> 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<Integer> 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<Integer> 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<GameGoal> 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.
* <p>
* SCORE_GOAL > 0: The game ends when a group reachs a specific score.
* <p>
* 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;
}
}
}

View File

@ -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<V> {
/**
* The goal of the game that defines what makes it end.
*/
public static final GameRule<GameGoal> GAME_GOAL = new GameRule<>(GameGoal.class, "gameGoal", GameGoal.END);
public static final GameRule<GameGoal> GAME_GOAL = new GameRule<>(GameGoal.class, "gameGoal", GameGoal.DEFAULT, GameGoal.READER);
/**
* The Vanilla game mode.
*/
@ -164,14 +159,6 @@ public class GameRule<V> {
* Alternatively to {@link #INITIAL_LIVES player lives}, you can use group lives.
*/
public static final GameRule<Integer> INITIAL_GROUP_LIVES = new GameRule<>(Integer.class, "initialGroupLives", -1);
/**
* Score used for capture the flag and similar game types.
*/
public static final GameRule<Integer> 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<Integer> 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<V> {
* The cooldown between two mob waves.
*/
public static final GameRule<Integer> 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<Integer> 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<V> {
* 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 {

View File

@ -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<GameRule<?>, Object> rules;
protected Map<GameRule<?>, Object> rules;
/**
* Initializes an emtpy GameRuleContainer.

View File

@ -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);

View File

@ -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

View File

@ -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

View File

@ -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();
}

View File

@ -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()) {

View File

@ -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);

View File

@ -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;