Update from Git Manager GUI
This commit is contained in:
71
src/main/java/io/github/mviper/bomberman/BmPlaceholder.java
Normal file
71
src/main/java/io/github/mviper/bomberman/BmPlaceholder.java
Normal file
@@ -0,0 +1,71 @@
|
||||
package io.github.mviper.bomberman;
|
||||
|
||||
import io.github.mviper.bomberman.events.BmGameListIntent;
|
||||
import io.github.mviper.bomberman.events.BmGameLookupIntent;
|
||||
import io.github.mviper.bomberman.messaging.Context;
|
||||
import io.github.mviper.bomberman.messaging.Expander;
|
||||
import io.github.mviper.bomberman.messaging.Formattable;
|
||||
import io.github.mviper.bomberman.messaging.Message;
|
||||
import io.github.mviper.bomberman.messaging.SenderWrapper;
|
||||
import me.clip.placeholderapi.expansion.PlaceholderExpansion;
|
||||
import org.bukkit.entity.Player;
|
||||
|
||||
public class BmPlaceholder extends PlaceholderExpansion {
|
||||
@Override
|
||||
public String getIdentifier() {
|
||||
return "bomberman";
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getAuthor() {
|
||||
return "mviper";
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getVersion() {
|
||||
return "internal";
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean persist() {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String onPlaceholderRequest(Player player, String params) {
|
||||
String[] content = params.split("_");
|
||||
String command = content.length > 0 ? content[0] : "";
|
||||
switch (command) {
|
||||
case "info": {
|
||||
if (content.length < 2) {
|
||||
return "info <name> <stat>";
|
||||
}
|
||||
String gameName = content[1];
|
||||
Formattable game = BmGameLookupIntent.find(gameName);
|
||||
if (game == null) {
|
||||
return "";
|
||||
}
|
||||
Formattable format = game;
|
||||
for (int i = 2; i < content.length; i++) {
|
||||
format = format.applyModifier(Message.of(content[i]));
|
||||
}
|
||||
return format.format(new Context()).toString();
|
||||
}
|
||||
case "msg": {
|
||||
try {
|
||||
Context context = new Context(false)
|
||||
.plus("games", BmGameListIntent.listGames());
|
||||
if (player != null) {
|
||||
context = context.plus("player", new SenderWrapper(player));
|
||||
}
|
||||
return Expander.expand(params.substring("msg_".length()), context).toString();
|
||||
} catch (RuntimeException e) {
|
||||
String message = e.getMessage() != null ? e.getMessage() : "Error";
|
||||
return Message.error(message).toString();
|
||||
}
|
||||
}
|
||||
default:
|
||||
return "<info|msg> ...";
|
||||
}
|
||||
}
|
||||
}
|
||||
132
src/main/java/io/github/mviper/bomberman/Bomberman.java
Normal file
132
src/main/java/io/github/mviper/bomberman/Bomberman.java
Normal file
@@ -0,0 +1,132 @@
|
||||
package io.github.mviper.bomberman;
|
||||
|
||||
import io.github.mviper.bomberman.commands.BaseCommand;
|
||||
import io.github.mviper.bomberman.game.GamePlayer;
|
||||
import io.github.mviper.bomberman.game.GameSave;
|
||||
import io.github.mviper.bomberman.game.GameSettings;
|
||||
import io.github.mviper.bomberman.utils.DataRestorer;
|
||||
import org.bukkit.Bukkit;
|
||||
import org.bukkit.command.PluginCommand;
|
||||
import org.bukkit.configuration.file.FileConfiguration;
|
||||
import org.bukkit.configuration.serialization.ConfigurationSerialization;
|
||||
import org.bukkit.event.Listener;
|
||||
import org.bukkit.plugin.java.JavaPlugin;
|
||||
|
||||
import javax.annotation.Nonnull;
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.util.Objects;
|
||||
|
||||
public class Bomberman extends JavaPlugin implements Listener {
|
||||
|
||||
@SuppressWarnings("NotNullFieldNotInitialized")
|
||||
@Nonnull
|
||||
public static Bomberman instance;
|
||||
|
||||
@Override
|
||||
public void onEnable() {
|
||||
instance = this;
|
||||
|
||||
ConfigurationSerialization.registerClass(GameSettings.class);
|
||||
ConfigurationSerialization.registerClass(DataRestorer.class, "io.github.mviper.bomberman.game.Game$BuildFlags");
|
||||
|
||||
getDataFolder().mkdirs();
|
||||
|
||||
BaseCommand bmCmd = new BaseCommand();
|
||||
PluginCommand bukkitBmCmd = Objects.requireNonNull(getCommand("bomberman"));
|
||||
bukkitBmCmd.setExecutor(bmCmd);
|
||||
bukkitBmCmd.setTabCompleter(bmCmd);
|
||||
|
||||
if (Bukkit.getPluginManager().getPlugin("PlaceholderAPI") != null) {
|
||||
new BmPlaceholder().register();
|
||||
}
|
||||
|
||||
// Update the old file system
|
||||
GameSave.updatePre080Saves();
|
||||
|
||||
// Archive the old schematics folder
|
||||
File schematics = new File(getDataFolder(), "schematics");
|
||||
if (schematics.exists()) {
|
||||
File schemPath = new File(getDataFolder(), "old/schematics");
|
||||
schemPath.getParentFile().mkdirs();
|
||||
schematics.renameTo(schemPath);
|
||||
}
|
||||
|
||||
// Archive the old config
|
||||
FileConfiguration config = getConfig();
|
||||
if (config.contains("default-game-settings")) {
|
||||
File configFile = new File(getDataFolder(), "config.yml");
|
||||
File configOut = new File(getDataFolder(), "old/config.yml");
|
||||
configOut.getParentFile().mkdirs();
|
||||
configFile.renameTo(configOut);
|
||||
}
|
||||
new File(getDataFolder(), "sample_config.yml").delete();
|
||||
|
||||
// Copy resources
|
||||
saveResource("config.yml", true);
|
||||
saveResource("messages.yml", false);
|
||||
saveResource("default_messages.yml", true);
|
||||
saveResource("games/templates/purple.game.zip", true);
|
||||
saveResource("games/templates/experimental.game.zip", true);
|
||||
saveResource("games/README.yml", true);
|
||||
saveResource("games/templates/README.txt", true);
|
||||
saveResource("temp/README.txt", true);
|
||||
|
||||
GameSave.loadGames();
|
||||
GamePlayer.setupLoginWatcher();
|
||||
}
|
||||
|
||||
public Path gameSaves() {
|
||||
try {
|
||||
Path dir = getDataFolder().toPath().resolve("games");
|
||||
if (!Files.exists(dir)) {
|
||||
Files.createDirectories(dir);
|
||||
}
|
||||
return dir;
|
||||
} catch (IOException e) {
|
||||
throw new RuntimeException("No write access", e);
|
||||
}
|
||||
}
|
||||
|
||||
public Path templates() {
|
||||
try {
|
||||
Path dir = getDataFolder().toPath().resolve("games/templates");
|
||||
if (!Files.exists(dir)) {
|
||||
Files.createDirectories(dir);
|
||||
}
|
||||
return dir;
|
||||
} catch (IOException e) {
|
||||
throw new RuntimeException("No write access", e);
|
||||
}
|
||||
}
|
||||
|
||||
public Path tempGameData() {
|
||||
try {
|
||||
Path dir = getDataFolder().toPath().resolve("temp/game");
|
||||
if (!Files.exists(dir)) {
|
||||
Files.createDirectories(dir);
|
||||
}
|
||||
return dir;
|
||||
} catch (IOException e) {
|
||||
throw new RuntimeException("No write access", e);
|
||||
}
|
||||
}
|
||||
|
||||
public Path tempPlayerData() {
|
||||
try {
|
||||
Path dir = getDataFolder().toPath().resolve("temp/player");
|
||||
if (!Files.exists(dir)) {
|
||||
Files.createDirectories(dir);
|
||||
}
|
||||
return dir;
|
||||
} catch (IOException e) {
|
||||
throw new RuntimeException("No write access", e);
|
||||
}
|
||||
}
|
||||
|
||||
public Path language() {
|
||||
return getDataFolder().toPath().resolve("messages.yml");
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,234 @@
|
||||
package io.github.mviper.bomberman.commands;
|
||||
|
||||
import io.github.mviper.bomberman.commands.game.Configure;
|
||||
import io.github.mviper.bomberman.commands.game.GameCreate;
|
||||
import io.github.mviper.bomberman.commands.game.GameDelete;
|
||||
import io.github.mviper.bomberman.commands.game.GameInfo;
|
||||
import io.github.mviper.bomberman.commands.game.GameJoin;
|
||||
import io.github.mviper.bomberman.commands.game.GameLeave;
|
||||
import io.github.mviper.bomberman.commands.game.GameList;
|
||||
import io.github.mviper.bomberman.commands.game.GameReload;
|
||||
import io.github.mviper.bomberman.commands.game.RunStart;
|
||||
import io.github.mviper.bomberman.commands.game.RunStop;
|
||||
import io.github.mviper.bomberman.commands.game.UndoBuild;
|
||||
import io.github.mviper.bomberman.messaging.CollectionWrapper;
|
||||
import io.github.mviper.bomberman.messaging.Context;
|
||||
import io.github.mviper.bomberman.messaging.Formattable;
|
||||
import io.github.mviper.bomberman.messaging.Message;
|
||||
import io.github.mviper.bomberman.messaging.Text;
|
||||
import org.bukkit.command.Command;
|
||||
import org.bukkit.command.CommandExecutor;
|
||||
import org.bukkit.command.CommandSender;
|
||||
import org.bukkit.command.TabCompleter;
|
||||
import org.bukkit.util.StringUtil;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
import java.util.Locale;
|
||||
import java.util.Map;
|
||||
import java.util.Objects;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
public class BaseCommand extends Cmd implements TabCompleter, CommandExecutor {
|
||||
private static final String F_HELP = "?";
|
||||
|
||||
private final List<Cmd> children = new ArrayList<>();
|
||||
|
||||
public BaseCommand() {
|
||||
this(true);
|
||||
}
|
||||
|
||||
public BaseCommand(boolean addChildren) {
|
||||
super(null);
|
||||
if (addChildren) {
|
||||
addChildren(
|
||||
new Configure(this),
|
||||
new GameCreate(this),
|
||||
new GameInfo(this),
|
||||
new GameJoin(this),
|
||||
new GameLeave(this),
|
||||
new GameDelete(this),
|
||||
new RunStart(this),
|
||||
new RunStop(this),
|
||||
new GameList(this),
|
||||
new GameReload(this),
|
||||
new UndoBuild(this)
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
public void addChildren(Cmd... children) {
|
||||
this.children.addAll(Arrays.asList(children));
|
||||
}
|
||||
|
||||
@Override
|
||||
public Formattable name() {
|
||||
return Message.of("bomberman");
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onCommand(CommandSender sender, Command command, String label, String[] args) {
|
||||
FlagSplit split = separateFlags(args);
|
||||
run(sender, split.arguments, split.flags);
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean run(CommandSender sender, List<String> args, Map<String, String> flags) {
|
||||
if (args.isEmpty()) {
|
||||
Text.COMMAND_GROUP_HELP.format(cmdContext().plus("sender", sender)).sendTo(sender);
|
||||
return true;
|
||||
}
|
||||
|
||||
Cmd child = children.stream()
|
||||
.filter(c -> c.name().format(c.cmdContext()).toString().equalsIgnoreCase(args.get(0)))
|
||||
.findFirst()
|
||||
.orElse(null);
|
||||
|
||||
if (child == null) {
|
||||
Text.UNKNOWN_COMMAND.format(cmdContext().plus("attempt", args.get(0))).sendTo(sender);
|
||||
return true;
|
||||
}
|
||||
|
||||
if (!child.permission().isAllowedBy(sender)) {
|
||||
Text.DENY_PERMISSION.format(child.cmdContext()).sendTo(sender);
|
||||
return true;
|
||||
}
|
||||
|
||||
if (flags.containsKey(F_HELP)) {
|
||||
Text.COMMAND_HELP.format(child.cmdContext().plus("sender", sender)).sendTo(sender);
|
||||
return true;
|
||||
}
|
||||
|
||||
boolean result = child.run(sender, args.subList(1, args.size()), flags);
|
||||
if (!result) {
|
||||
List<Formattable> attempt = args.subList(1, args.size()).stream()
|
||||
.map(Message::of)
|
||||
.collect(Collectors.toList());
|
||||
Text.INCORRECT_USAGE.format(child.cmdContext().plus("attempt", new CollectionWrapper<>(attempt))).sendTo(sender);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<String> onTabComplete(CommandSender sender, Command command, String alias, String[] args) {
|
||||
FlagSplit split = separateFlags(args);
|
||||
List<String> arguments = split.arguments;
|
||||
String currentlyTyping = args[args.length - 1];
|
||||
|
||||
Cmd cmd = children.stream()
|
||||
.filter(c -> c.name().format(c.cmdContext()).toString().equalsIgnoreCase(arguments.isEmpty() ? "" : arguments.get(0)))
|
||||
.findFirst()
|
||||
.orElse(this);
|
||||
if (!cmd.permission().isAllowedBy(sender)) {
|
||||
return List.of();
|
||||
}
|
||||
|
||||
List<String> allOptions;
|
||||
if (currentlyTyping.startsWith("-")) {
|
||||
int splitIndex = currentlyTyping.indexOf('=');
|
||||
if (splitIndex == -1) {
|
||||
allOptions = cmd.flags(sender).stream()
|
||||
.map(flag -> "-" + flag)
|
||||
.collect(Collectors.toList());
|
||||
allOptions.add("-" + F_HELP);
|
||||
} else {
|
||||
String key = currentlyTyping.substring(1, splitIndex);
|
||||
allOptions = cmd.flagOptions(key).stream()
|
||||
.map(option -> "-" + key + "=" + option)
|
||||
.toList();
|
||||
}
|
||||
} else {
|
||||
allOptions = cmd.options(sender, arguments.size() > 0 ? arguments.subList(1, arguments.size()) : List.of());
|
||||
}
|
||||
|
||||
return allOptions.stream()
|
||||
.filter(option -> StringUtil.startsWithIgnoreCase(option, currentlyTyping))
|
||||
.toList();
|
||||
}
|
||||
|
||||
private FlagSplit separateFlags(String[] args) {
|
||||
List<String> flagStrings = new ArrayList<>();
|
||||
List<String> arguments = new ArrayList<>();
|
||||
for (String arg : args) {
|
||||
if (arg.startsWith("-")) {
|
||||
flagStrings.add(arg);
|
||||
} else {
|
||||
arguments.add(arg);
|
||||
}
|
||||
}
|
||||
Map<String, String> flags = flagStrings.stream().collect(Collectors.toMap(
|
||||
flag -> {
|
||||
int separator = flag.indexOf('=');
|
||||
if (separator == -1) {
|
||||
return flag.substring(1);
|
||||
}
|
||||
return flag.substring(1, separator);
|
||||
},
|
||||
flag -> {
|
||||
int separator = flag.indexOf('=');
|
||||
if (separator == -1) {
|
||||
return "";
|
||||
}
|
||||
return flag.substring(separator + 1);
|
||||
},
|
||||
(a, b) -> b
|
||||
));
|
||||
return new FlagSplit(arguments, flags);
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<String> options(CommandSender sender, List<String> args) {
|
||||
if (args.size() <= 1) {
|
||||
return children.stream()
|
||||
.filter(cmd -> cmd.permission().isAllowedBy(sender))
|
||||
.map(cmd -> cmd.name().format(cmd.cmdContext()).toString())
|
||||
.collect(Collectors.toList());
|
||||
}
|
||||
return List.of();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Permission permission() {
|
||||
return Permissions.BASE;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Formattable description() {
|
||||
return Text.BOMBERMAN_DESCRIPTION;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Formattable extra() {
|
||||
return Text.COMMAND_GROUP_EXTRA;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Formattable example() {
|
||||
return Text.COMMAND_GROUP_EXAMPLE;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Formattable usage() {
|
||||
return Text.COMMAND_GROUP_USAGE;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Formattable applyModifier(Message arg) {
|
||||
if ("children".equalsIgnoreCase(arg.toString())) {
|
||||
return new CollectionWrapper<>(children);
|
||||
}
|
||||
return super.applyModifier(arg);
|
||||
}
|
||||
|
||||
private static final class FlagSplit {
|
||||
private final List<String> arguments;
|
||||
private final Map<String, String> flags;
|
||||
|
||||
private FlagSplit(List<String> arguments, Map<String, String> flags) {
|
||||
this.arguments = arguments;
|
||||
this.flags = flags;
|
||||
}
|
||||
}
|
||||
}
|
||||
134
src/main/java/io/github/mviper/bomberman/commands/Cmd.java
Normal file
134
src/main/java/io/github/mviper/bomberman/commands/Cmd.java
Normal file
@@ -0,0 +1,134 @@
|
||||
package io.github.mviper.bomberman.commands;
|
||||
|
||||
import io.github.mviper.bomberman.messaging.CollectionWrapper;
|
||||
import io.github.mviper.bomberman.messaging.Context;
|
||||
import io.github.mviper.bomberman.messaging.Formattable;
|
||||
import io.github.mviper.bomberman.messaging.Message;
|
||||
import org.bukkit.Bukkit;
|
||||
import org.bukkit.command.CommandSender;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.Locale;
|
||||
import java.util.Set;
|
||||
|
||||
public abstract class Cmd implements Formattable {
|
||||
protected Cmd parent;
|
||||
|
||||
protected Cmd(Cmd parent) {
|
||||
this.parent = parent;
|
||||
}
|
||||
|
||||
public abstract Formattable name();
|
||||
|
||||
public abstract List<String> options(CommandSender sender, List<String> args);
|
||||
|
||||
public Set<String> flags(CommandSender sender) {
|
||||
return Collections.emptySet();
|
||||
}
|
||||
|
||||
public Set<String> flagOptions(String flag) {
|
||||
return Collections.emptySet();
|
||||
}
|
||||
|
||||
public Formattable flagExtension(String flag) {
|
||||
return Message.empty;
|
||||
}
|
||||
|
||||
public Formattable flagDescription(String flag) {
|
||||
return Message.empty;
|
||||
}
|
||||
|
||||
public abstract boolean run(CommandSender sender, List<String> args, java.util.Map<String, String> flags);
|
||||
|
||||
public abstract Formattable extra();
|
||||
|
||||
public abstract Formattable example();
|
||||
|
||||
public abstract Formattable description();
|
||||
|
||||
public abstract Formattable usage();
|
||||
|
||||
public abstract Permission permission();
|
||||
|
||||
private String path(String separator) {
|
||||
StringBuilder path = new StringBuilder();
|
||||
if (parent != null) {
|
||||
path.append(parent.path(separator)).append(separator);
|
||||
}
|
||||
path.append(name().format(new Context()).toString());
|
||||
return path.toString();
|
||||
}
|
||||
|
||||
private String path() {
|
||||
return path(" ");
|
||||
}
|
||||
|
||||
public Context cmdContext() {
|
||||
return new Context(false).plus("command", this);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Message format(Context context) {
|
||||
return applyModifier(Message.of("name")).format(context);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Formattable applyModifier(Message arg) {
|
||||
String key = arg.toString().toLowerCase(Locale.ROOT);
|
||||
switch (key) {
|
||||
case "name":
|
||||
return name();
|
||||
case "path":
|
||||
return Message.of(path());
|
||||
case "usage":
|
||||
return usage();
|
||||
case "extra":
|
||||
return extra();
|
||||
case "example":
|
||||
return example();
|
||||
case "description":
|
||||
return description();
|
||||
case "permission":
|
||||
return Message.of(permission().value());
|
||||
case "flags": {
|
||||
List<Formattable> wrapped = new ArrayList<>();
|
||||
for (String flag : flags(Bukkit.getConsoleSender())) {
|
||||
wrapped.add(new FlagWrapper(flag));
|
||||
}
|
||||
return new CollectionWrapper<>(wrapped);
|
||||
}
|
||||
default:
|
||||
throw new IllegalArgumentException("Unknown command value '" + arg + "'");
|
||||
}
|
||||
}
|
||||
|
||||
private final class FlagWrapper implements Formattable {
|
||||
private final String flag;
|
||||
|
||||
private FlagWrapper(String flag) {
|
||||
this.flag = flag;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Formattable applyModifier(Message arg) {
|
||||
String key = arg.toString().toLowerCase(Locale.ROOT);
|
||||
switch (key) {
|
||||
case "name":
|
||||
return Message.of(flag);
|
||||
case "ext":
|
||||
return flagExtension(flag);
|
||||
case "description":
|
||||
return flagDescription(flag);
|
||||
default:
|
||||
throw new IllegalArgumentException("Unknown flag value '" + arg + "'`");
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public Message format(Context context) {
|
||||
return applyModifier(Message.of("name")).format(context);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,38 @@
|
||||
package io.github.mviper.bomberman.commands;
|
||||
|
||||
import io.github.mviper.bomberman.events.BmGameListIntent;
|
||||
import io.github.mviper.bomberman.events.BmGameLookupIntent;
|
||||
import io.github.mviper.bomberman.game.Game;
|
||||
import org.bukkit.command.CommandSender;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
public abstract class GameCommand extends Cmd {
|
||||
protected GameCommand(Cmd parent) {
|
||||
super(parent);
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<String> options(CommandSender sender, List<String> args) {
|
||||
if (args.size() <= 1) {
|
||||
return BmGameListIntent.listGames().stream().map(Game::getName).toList();
|
||||
}
|
||||
return gameOptions(args.subList(1, args.size()));
|
||||
}
|
||||
|
||||
protected abstract List<String> gameOptions(List<String> args);
|
||||
|
||||
@Override
|
||||
public boolean run(CommandSender sender, List<String> args, java.util.Map<String, String> flags) {
|
||||
if (args.isEmpty()) {
|
||||
return false;
|
||||
}
|
||||
Game game = BmGameLookupIntent.find(args.get(0));
|
||||
if (game == null) {
|
||||
return false;
|
||||
}
|
||||
return gameRun(sender, args.subList(1, args.size()), flags, game);
|
||||
}
|
||||
|
||||
protected abstract boolean gameRun(CommandSender sender, List<String> args, java.util.Map<String, String> flags, Game game);
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
package io.github.mviper.bomberman.commands;
|
||||
|
||||
import org.bukkit.command.CommandSender;
|
||||
|
||||
public interface Permission {
|
||||
boolean isAllowedBy(CommandSender sender);
|
||||
|
||||
String value();
|
||||
}
|
||||
@@ -0,0 +1,36 @@
|
||||
package io.github.mviper.bomberman.commands;
|
||||
|
||||
import org.bukkit.command.CommandSender;
|
||||
|
||||
public enum Permissions implements Permission {
|
||||
BASE("bomberman.bm"),
|
||||
CREATE("bomberman.create"),
|
||||
DELETE("bomberman.delete"),
|
||||
UNDO("bomberman.undo"),
|
||||
RELOAD("bomberman.reload"),
|
||||
CONFIGURE("bomberman.configure"),
|
||||
START("bomberman.start"),
|
||||
STOP("bomberman.stop"),
|
||||
INFO("bomberman.info"),
|
||||
LIST("bomberman.list"),
|
||||
JOIN("bomberman.join"),
|
||||
JOIN_REMOTE("bomberman.join.remote"),
|
||||
LEAVE("bomberman.leave"),
|
||||
LEAVE_REMOTE("bomberman.leave.remote");
|
||||
|
||||
private final String permission;
|
||||
|
||||
Permissions(String permission) {
|
||||
this.permission = permission;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isAllowedBy(CommandSender sender) {
|
||||
return sender.hasPermission(permission);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String value() {
|
||||
return permission;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,652 @@
|
||||
package io.github.mviper.bomberman.commands.game;
|
||||
|
||||
import io.github.mviper.bomberman.commands.Cmd;
|
||||
import io.github.mviper.bomberman.commands.GameCommand;
|
||||
import io.github.mviper.bomberman.commands.Permission;
|
||||
import io.github.mviper.bomberman.commands.Permissions;
|
||||
import io.github.mviper.bomberman.game.Game;
|
||||
import io.github.mviper.bomberman.game.GameSettingsBuilder;
|
||||
import io.github.mviper.bomberman.messaging.Formattable;
|
||||
import io.github.mviper.bomberman.messaging.Text;
|
||||
import org.bukkit.GameMode;
|
||||
import org.bukkit.Material;
|
||||
import org.bukkit.command.CommandSender;
|
||||
import org.bukkit.entity.Player;
|
||||
import org.bukkit.inventory.ItemStack;
|
||||
import org.bukkit.inventory.meta.ItemMeta;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.HashMap;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
import java.util.concurrent.atomic.AtomicInteger;
|
||||
import java.util.function.Consumer;
|
||||
|
||||
public class Configure extends GameCommand {
|
||||
public Configure(Cmd parent) {
|
||||
super(parent);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Formattable name() {
|
||||
return Text.CONFIGURE_NAME;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected List<String> gameOptions(List<String> args) {
|
||||
return List.of();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Permission permission() {
|
||||
return Permissions.CONFIGURE;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Formattable extra() {
|
||||
return Text.CONFIGURE_EXTRA;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Formattable example() {
|
||||
return Text.CONFIGURE_EXAMPLE;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Formattable description() {
|
||||
return Text.CONFIGURE_DESCRIPTION;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Formattable usage() {
|
||||
return Text.CONFIGURE_USAGE;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected boolean gameRun(CommandSender sender, List<String> args, Map<String, String> flags, Game game) {
|
||||
if (!args.isEmpty()) {
|
||||
return false;
|
||||
}
|
||||
if (!(sender instanceof Player player)) {
|
||||
Text.MUST_BE_PLAYER.format(cmdContext()).sendTo(sender);
|
||||
return true;
|
||||
}
|
||||
if (player.getGameMode() != GameMode.CREATIVE) {
|
||||
Text.CONFIGURE_PROMPT_CREATIVE.format(cmdContext()).sendTo(sender);
|
||||
return true;
|
||||
}
|
||||
|
||||
showMainMenu(player, game);
|
||||
return true;
|
||||
}
|
||||
|
||||
private void showMainMenu(Player player, Game game) {
|
||||
GuiBuilder.show(
|
||||
player,
|
||||
Text.CONFIGURE_TITLE_MAIN.format(cmdContext()).toString(),
|
||||
new CharSequence[]{
|
||||
" ",
|
||||
" s b l i ",
|
||||
" "
|
||||
},
|
||||
index -> {
|
||||
return switch (index.getSection()) {
|
||||
case 's' -> new GuiBuilder.ItemSlot(Material.REDSTONE)
|
||||
.unMovable()
|
||||
.displayName(Text.CONFIGURE_TITLE_GENERAL.format(cmdContext()).toString());
|
||||
case 'b' -> new GuiBuilder.ItemSlot(Material.DIRT)
|
||||
.unMovable()
|
||||
.displayName(Text.CONFIGURE_TITLE_BLOCKS.format(cmdContext()).toString());
|
||||
case 'l' -> new GuiBuilder.ItemSlot(Material.GOLDEN_APPLE)
|
||||
.unMovable()
|
||||
.displayName(Text.CONFIGURE_TITLE_LOOT.format(cmdContext()).toString());
|
||||
case 'i' -> new GuiBuilder.ItemSlot(Material.CHEST)
|
||||
.unMovable()
|
||||
.displayName(Text.CONFIGURE_TITLE_INVENTORY.format(cmdContext()).toString());
|
||||
default -> GuiBuilder.blank;
|
||||
};
|
||||
},
|
||||
(index, currentItem, cursorItem) -> {
|
||||
switch (index.getSection()) {
|
||||
case 's' -> showGeneralConfig(player, game);
|
||||
case 'b' -> showBlockSettings(player, game);
|
||||
case 'l' -> showLootSettings(player, game);
|
||||
case 'i' -> showInventorySettings(player, game);
|
||||
default -> {
|
||||
}
|
||||
}
|
||||
},
|
||||
slots -> {
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
private void showGeneralConfig(Player player, Game game) {
|
||||
GameSettingsBuilder settings = new GameSettingsBuilder(game.getSettings());
|
||||
GuiBuilder.show(
|
||||
player,
|
||||
Text.CONFIGURE_TITLE_GENERAL.format(cmdContext()).toString(),
|
||||
new CharSequence[]{
|
||||
" ^^^^ ",
|
||||
"< lfbitg ",
|
||||
" vvvv "
|
||||
},
|
||||
index -> {
|
||||
index.getInventory().setMaxStackSize(100000);
|
||||
return switch (index.getSection()) {
|
||||
case '<' -> new GuiBuilder.ItemSlot(Material.PAPER)
|
||||
.unMovable()
|
||||
.displayName(stringify(Text.CONFIGURE_BACK));
|
||||
case 'l' -> new GuiBuilder.ItemSlot(setQty(new ItemStack(Material.PLAYER_HEAD), settings.lives))
|
||||
.unMovable()
|
||||
.displayName(stringify(Text.CONFIGURE_LIVES));
|
||||
case 'f' -> new GuiBuilder.ItemSlot(setQty(new ItemStack(Material.TNT), settings.fuseTicks))
|
||||
.unMovable()
|
||||
.displayName(stringify(Text.CONFIGURE_FUSE_TICKS));
|
||||
case 'b' -> new GuiBuilder.ItemSlot(setQty(new ItemStack(Material.FLINT_AND_STEEL), settings.fireTicks))
|
||||
.unMovable()
|
||||
.displayName(stringify(Text.CONFIGURE_FIRE_TICKS));
|
||||
case 'i' -> new GuiBuilder.ItemSlot(setQty(new ItemStack(Material.MILK_BUCKET), settings.immunityTicks))
|
||||
.unMovable()
|
||||
.displayName(stringify(Text.CONFIGURE_IMMUNITY_TICKS));
|
||||
case 't' -> new GuiBuilder.ItemSlot(settings.bombItem)
|
||||
.unMovable()
|
||||
.displayName(stringify(Text.CONFIGURE_TNT_BLOCK));
|
||||
case 'g' -> new GuiBuilder.ItemSlot(settings.powerItem)
|
||||
.unMovable()
|
||||
.displayName(stringify(Text.CONFIGURE_FIRE_ITEM));
|
||||
case '^' -> new GuiBuilder.ItemSlot(Material.STONE_BUTTON)
|
||||
.unMovable()
|
||||
.displayName("+");
|
||||
case 'v' -> new GuiBuilder.ItemSlot(Material.STONE_BUTTON)
|
||||
.unMovable()
|
||||
.displayName("-");
|
||||
default -> GuiBuilder.blank;
|
||||
};
|
||||
},
|
||||
(index, slot, cursor) -> {
|
||||
index.getInventory().setMaxStackSize(100000);
|
||||
switch (index.getSection()) {
|
||||
case '<' -> showMainMenu(player, game);
|
||||
case '^' -> {
|
||||
ItemStack item = index.getInventory().getItem(index.getInvIndex() + 9);
|
||||
if (item != null) {
|
||||
setQty(item, item.getAmount() + 1);
|
||||
}
|
||||
}
|
||||
case 'v' -> {
|
||||
ItemStack item = index.getInventory().getItem(index.getInvIndex() - 9);
|
||||
if (item != null) {
|
||||
setQty(item, Math.max(item.getAmount() - 1, 1));
|
||||
}
|
||||
}
|
||||
case 't', 'g' -> {
|
||||
if (cursor != null && cursor.getAmount() != 0 && slot != null) {
|
||||
slot.setType(cursor.getType());
|
||||
}
|
||||
}
|
||||
default -> {
|
||||
}
|
||||
}
|
||||
},
|
||||
slots -> {
|
||||
for (GuiBuilder.SlotItem slotItem : slots) {
|
||||
GuiBuilder.Index index = slotItem.getIndex();
|
||||
ItemStack item = slotItem.getItem();
|
||||
if (item == null) {
|
||||
continue;
|
||||
}
|
||||
switch (index.getSection()) {
|
||||
case 'l' -> settings.lives = item.getAmount();
|
||||
case 'f' -> settings.fuseTicks = item.getAmount();
|
||||
case 'b' -> settings.fireTicks = item.getAmount();
|
||||
case 'i' -> settings.immunityTicks = item.getAmount();
|
||||
case 't' -> settings.bombItem = item.getType();
|
||||
case 'g' -> settings.powerItem = item.getType();
|
||||
default -> {
|
||||
}
|
||||
}
|
||||
}
|
||||
game.setSettings(settings.build());
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
private void showBlockSettings(Player player, Game game) {
|
||||
GameSettingsBuilder builder = new GameSettingsBuilder(game.getSettings());
|
||||
showBlockSettings(
|
||||
player,
|
||||
game,
|
||||
builder,
|
||||
0,
|
||||
stringify(Text.CONFIGURE_DESTRUCTIBLE_DESC),
|
||||
game.getSettings().getDestructible(),
|
||||
materials -> builder.destructible = materials
|
||||
);
|
||||
}
|
||||
|
||||
private void showBlockSettings(
|
||||
Player player,
|
||||
Game game,
|
||||
GameSettingsBuilder builder,
|
||||
int selected,
|
||||
String description,
|
||||
Set<Material> types,
|
||||
Consumer<Set<Material>> result
|
||||
) {
|
||||
List<Material> typesList = types.stream()
|
||||
.filter(Material::isItem)
|
||||
.filter(Material::isBlock)
|
||||
.toList();
|
||||
|
||||
GuiBuilder.show(
|
||||
player,
|
||||
Text.CONFIGURE_TITLE_BLOCKS.format(cmdContext()).toString(),
|
||||
new CharSequence[]{
|
||||
"<d s p n ",
|
||||
" c cEc c ",
|
||||
"iiiiiiiii",
|
||||
"iiiiiiiii"
|
||||
},
|
||||
index -> {
|
||||
return switch (index.getSection()) {
|
||||
case 'E' -> new GuiBuilder.ItemSlot(Material.EMERALD)
|
||||
.unMovable()
|
||||
.displayName(description);
|
||||
case '<' -> new GuiBuilder.ItemSlot(Material.PAPER)
|
||||
.unMovable()
|
||||
.displayName(stringify(Text.CONFIGURE_BACK));
|
||||
case 'd' -> new GuiBuilder.ItemSlot(Material.DIRT)
|
||||
.unMovable()
|
||||
.displayName(stringify(Text.CONFIGURE_DESTRUCTIBLE));
|
||||
case 's' -> new GuiBuilder.ItemSlot(Material.OBSIDIAN)
|
||||
.unMovable()
|
||||
.displayName(stringify(Text.CONFIGURE_INDESTRUCTIBLE));
|
||||
case 'p' -> new GuiBuilder.ItemSlot(Material.OXEYE_DAISY)
|
||||
.unMovable()
|
||||
.displayName(stringify(Text.CONFIGURE_PASS_DESTROY));
|
||||
case 'n' -> new GuiBuilder.ItemSlot(Material.OAK_SIGN)
|
||||
.unMovable()
|
||||
.displayName(stringify(Text.CONFIGURE_PASS_KEEP));
|
||||
case 'c' -> {
|
||||
if (index.getSecIndex() == selected) {
|
||||
yield new GuiBuilder.ItemSlot(Material.YELLOW_STAINED_GLASS_PANE)
|
||||
.unMovable()
|
||||
.displayName(" ");
|
||||
}
|
||||
yield GuiBuilder.blank;
|
||||
}
|
||||
case 'i' -> {
|
||||
Material mat = index.getSecIndex() < typesList.size() ? typesList.get(index.getSecIndex()) : null;
|
||||
if (mat == null) {
|
||||
yield new GuiBuilder.ItemSlot((ItemStack) null);
|
||||
}
|
||||
yield new GuiBuilder.ItemSlot(mat);
|
||||
}
|
||||
default -> GuiBuilder.blank;
|
||||
};
|
||||
},
|
||||
(index, currentItem, cursorItem) -> {
|
||||
switch (index.getSection()) {
|
||||
case '<' -> showMainMenu(player, game);
|
||||
case 'd' -> {
|
||||
if (selected != 0) {
|
||||
showBlockSettings(
|
||||
player,
|
||||
game,
|
||||
builder,
|
||||
0,
|
||||
stringify(Text.CONFIGURE_DESTRUCTIBLE_DESC),
|
||||
builder.destructible,
|
||||
materials -> builder.destructible = materials
|
||||
);
|
||||
}
|
||||
}
|
||||
case 's' -> {
|
||||
if (selected != 1) {
|
||||
showBlockSettings(
|
||||
player,
|
||||
game,
|
||||
builder,
|
||||
1,
|
||||
stringify(Text.CONFIGURE_INDESTRUCTIBLE_DESC),
|
||||
builder.indestructible,
|
||||
materials -> builder.indestructible = materials
|
||||
);
|
||||
}
|
||||
}
|
||||
case 'p' -> {
|
||||
if (selected != 2) {
|
||||
showBlockSettings(
|
||||
player,
|
||||
game,
|
||||
builder,
|
||||
2,
|
||||
stringify(Text.CONFIGURE_PASS_DESTROY_DESC),
|
||||
builder.passDestroy,
|
||||
materials -> builder.passDestroy = materials
|
||||
);
|
||||
}
|
||||
}
|
||||
case 'n' -> {
|
||||
if (selected != 3) {
|
||||
showBlockSettings(
|
||||
player,
|
||||
game,
|
||||
builder,
|
||||
3,
|
||||
stringify(Text.CONFIGURE_PASS_KEEP_DESC),
|
||||
builder.passKeep,
|
||||
materials -> builder.passKeep = materials
|
||||
);
|
||||
}
|
||||
}
|
||||
default -> {
|
||||
}
|
||||
}
|
||||
},
|
||||
slots -> {
|
||||
Set<Material> blocks = new HashSet<>();
|
||||
for (GuiBuilder.SlotItem slotItem : slots) {
|
||||
GuiBuilder.Index index = slotItem.getIndex();
|
||||
ItemStack stack = slotItem.getItem();
|
||||
if (index.getSection() == 'i' && stack != null) {
|
||||
blocks.addAll(expandSimilarMaterials(stack.getType()));
|
||||
}
|
||||
}
|
||||
result.accept(blocks);
|
||||
game.setSettings(builder.build());
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
private List<Material> expandSimilarMaterials(Material mat) {
|
||||
String wallVariant = mat.getKey().getKey()
|
||||
.replace("sign", "wall_sign")
|
||||
.replace("banner", "wall_banner")
|
||||
.replace("fan", "wall_fan")
|
||||
.replace("torch", "wall_torch")
|
||||
.replace("head", "wall_head")
|
||||
.replace("skull", "skull_head");
|
||||
Material wallType = Material.matchMaterial(wallVariant);
|
||||
if (wallType == null) {
|
||||
return List.of(mat);
|
||||
}
|
||||
return List.of(mat, wallType);
|
||||
}
|
||||
|
||||
private void showInventorySettings(Player player, Game game) {
|
||||
GuiBuilder.show(
|
||||
player,
|
||||
stringify(Text.CONFIGURE_TITLE_INVENTORY),
|
||||
new CharSequence[]{
|
||||
"< HCLBS ",
|
||||
" aaaas ",
|
||||
"iiiiiiiii",
|
||||
"iiiiiiiii",
|
||||
"iiiiiiiii",
|
||||
"hhhhhhhhh"
|
||||
},
|
||||
index -> {
|
||||
List<ItemStack> initialItems = new ArrayList<>(game.getSettings().getInitialItems());
|
||||
while (initialItems.size() < 9 * 4 + 5) {
|
||||
initialItems.add(null);
|
||||
}
|
||||
return switch (index.getSection()) {
|
||||
case '<' -> new GuiBuilder.ItemSlot(Material.PAPER)
|
||||
.unMovable()
|
||||
.displayName(stringify(Text.CONFIGURE_BACK));
|
||||
case 'a' -> new GuiBuilder.ItemSlot(initialItems.get((3 - index.getSecIndex()) + 9 * 4));
|
||||
case 's' -> new GuiBuilder.ItemSlot(initialItems.get(9 * 4 + 4));
|
||||
case 'h' -> new GuiBuilder.ItemSlot(initialItems.get(index.getSecIndex()));
|
||||
case 'i' -> new GuiBuilder.ItemSlot(initialItems.get(index.getSecIndex() + 9));
|
||||
case 'H' -> new GuiBuilder.ItemSlot(Material.IRON_HELMET)
|
||||
.unMovable()
|
||||
.hideAttributes();
|
||||
case 'C' -> new GuiBuilder.ItemSlot(Material.IRON_CHESTPLATE)
|
||||
.unMovable()
|
||||
.hideAttributes();
|
||||
case 'L' -> new GuiBuilder.ItemSlot(Material.IRON_LEGGINGS)
|
||||
.unMovable()
|
||||
.hideAttributes();
|
||||
case 'B' -> new GuiBuilder.ItemSlot(Material.IRON_BOOTS)
|
||||
.unMovable()
|
||||
.hideAttributes();
|
||||
case 'S' -> new GuiBuilder.ItemSlot(Material.SHIELD)
|
||||
.unMovable()
|
||||
.hideAttributes();
|
||||
default -> GuiBuilder.blank;
|
||||
};
|
||||
},
|
||||
(index, currentItem, cursorItem) -> {
|
||||
if (index.getSection() == '<') {
|
||||
showMainMenu(player, game);
|
||||
}
|
||||
},
|
||||
slots -> {
|
||||
ItemStack[] closingItems = new ItemStack[9 * 4 + 5];
|
||||
for (GuiBuilder.SlotItem slotItem : slots) {
|
||||
GuiBuilder.Index index = slotItem.getIndex();
|
||||
ItemStack item = slotItem.getItem();
|
||||
switch (index.getSection()) {
|
||||
case 'a' -> closingItems[9 * 4 + (3 - index.getSecIndex())] = item;
|
||||
case 's' -> closingItems[9 * 4 + 4] = item;
|
||||
case 'h' -> closingItems[index.getSecIndex()] = item;
|
||||
case 'i' -> closingItems[9 + index.getSecIndex()] = item;
|
||||
default -> {
|
||||
}
|
||||
}
|
||||
}
|
||||
GameSettingsBuilder builder = new GameSettingsBuilder(game.getSettings());
|
||||
builder.initialItems = Arrays.asList(closingItems);
|
||||
game.setSettings(builder.build());
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
private void showLootSettings(Player player, Game game) {
|
||||
Map<Material, Map<ItemStack, Integer>> gameLoot = game.getSettings().getBlockLoot();
|
||||
|
||||
Map<Map<ItemStack, Integer>, Set<Material>> lootBlock = new HashMap<>();
|
||||
for (Map.Entry<Material, Map<ItemStack, Integer>> entry : gameLoot.entrySet()) {
|
||||
lootBlock.computeIfAbsent(entry.getValue(), key -> new HashSet<>()).add(entry.getKey());
|
||||
}
|
||||
|
||||
List<LootSlot> blockLoot = new ArrayList<>();
|
||||
for (Map.Entry<Map<ItemStack, Integer>, Set<Material>> entry : lootBlock.entrySet()) {
|
||||
Map<ItemStack, Integer> loot = entry.getKey();
|
||||
Set<Material> matList = entry.getValue();
|
||||
List<WeightedItem> lootList = new ArrayList<>();
|
||||
for (Map.Entry<ItemStack, Integer> lootEntry : loot.entrySet()) {
|
||||
ItemStack stack = lootEntry.getKey();
|
||||
int weight = lootEntry.getValue();
|
||||
int brokenWeight = weight;
|
||||
do {
|
||||
lootList.add(new WeightedItem(stack, Math.min(brokenWeight, 64)));
|
||||
brokenWeight -= 64;
|
||||
} while (brokenWeight > 0);
|
||||
}
|
||||
blockLoot.add(new LootSlot(new ArrayList<>(matList), lootList));
|
||||
}
|
||||
|
||||
showLootSettings(player, game, 0, blockLoot);
|
||||
}
|
||||
|
||||
private void showLootSettings(Player player, Game game, int slot, List<LootSlot> loot) {
|
||||
GuiBuilder.show(
|
||||
player,
|
||||
stringify(Text.CONFIGURE_TITLE_LOOT),
|
||||
new CharSequence[]{
|
||||
"<SSSSSSSS",
|
||||
"Kkkkkkkkk",
|
||||
" ",
|
||||
"Vvvvvvvvv",
|
||||
"Wwwwwwwww"
|
||||
},
|
||||
index -> {
|
||||
LootSlot selected = slot < loot.size() ? loot.get(slot) : null;
|
||||
return switch (index.getSection()) {
|
||||
case '<' -> new GuiBuilder.ItemSlot(Material.PAPER)
|
||||
.unMovable()
|
||||
.displayName(stringify(Text.CONFIGURE_BACK));
|
||||
case 'S' -> {
|
||||
Material icon;
|
||||
if (index.getSecIndex() == slot) {
|
||||
icon = Material.YELLOW_CONCRETE;
|
||||
} else if (index.getSecIndex() >= loot.size()) {
|
||||
icon = Material.GRAY_CONCRETE;
|
||||
} else {
|
||||
LootSlot candidate = loot.get(index.getSecIndex());
|
||||
if (candidate.materials.isEmpty() && candidate.items.isEmpty()) {
|
||||
icon = Material.GRAY_CONCRETE;
|
||||
} else {
|
||||
icon = Material.WHITE_CONCRETE;
|
||||
}
|
||||
}
|
||||
yield new GuiBuilder.ItemSlot(icon)
|
||||
.unMovable()
|
||||
.displayName(Text.CONFIGURE_LOOT_SLOT.format(cmdContext().plus("slot", index.getSecIndex())).toString());
|
||||
}
|
||||
case 'K' -> new GuiBuilder.ItemSlot(Material.EMERALD)
|
||||
.unMovable()
|
||||
.displayName(Text.CONFIGURE_LOOT_BLOCK.format(cmdContext().plus("slot", slot)).toString());
|
||||
case 'k' -> {
|
||||
ItemStack stack = null;
|
||||
if (selected != null && index.getSecIndex() < selected.materials.size()) {
|
||||
stack = new ItemStack(selected.materials.get(index.getSecIndex()));
|
||||
}
|
||||
yield new GuiBuilder.ItemSlot(stack);
|
||||
}
|
||||
case 'V' -> new GuiBuilder.ItemSlot(Material.EMERALD)
|
||||
.unMovable()
|
||||
.displayName(Text.CONFIGURE_LOOT_ITEM.format(cmdContext().plus("slot", slot)).toString());
|
||||
case 'v' -> {
|
||||
ItemStack stack = null;
|
||||
if (selected != null && index.getSecIndex() < selected.items.size()) {
|
||||
stack = selected.items.get(index.getSecIndex()).item;
|
||||
}
|
||||
yield new GuiBuilder.ItemSlot(stack);
|
||||
}
|
||||
case 'W' -> new GuiBuilder.ItemSlot(Material.EMERALD)
|
||||
.unMovable()
|
||||
.displayName(Text.CONFIGURE_LOOT_WEIGHT.format(cmdContext().plus("slot", slot)).toString());
|
||||
case 'w' -> {
|
||||
int weight = 0;
|
||||
if (selected != null && index.getSecIndex() < selected.items.size()) {
|
||||
weight = selected.items.get(index.getSecIndex()).weight;
|
||||
}
|
||||
yield new GuiBuilder.ItemSlot(Material.GOLD_NUGGET, weight);
|
||||
}
|
||||
default -> GuiBuilder.blank;
|
||||
};
|
||||
},
|
||||
(index, currentItem, cursorItem) -> {
|
||||
if (index.getSection() == '<') {
|
||||
showMainMenu(player, game);
|
||||
} else if (index.getSection() == 'S') {
|
||||
showLootSettings(player, game, index.getSecIndex(), loot);
|
||||
}
|
||||
},
|
||||
slots -> {
|
||||
List<Material> matsSaved = new ArrayList<>();
|
||||
Map<ItemStack, AtomicInteger> itemsSaved = new HashMap<>();
|
||||
|
||||
for (GuiBuilder.SlotItem slotItem : slots) {
|
||||
GuiBuilder.Index index = slotItem.getIndex();
|
||||
ItemStack item = slotItem.getItem();
|
||||
switch (index.getSection()) {
|
||||
case 'k' -> {
|
||||
if (item != null) {
|
||||
matsSaved.add(item.getType());
|
||||
}
|
||||
}
|
||||
case 'v' -> {
|
||||
int weight = 0;
|
||||
ItemStack weightItem = index.getInventory().getItem(index.getInvIndex() + 9);
|
||||
if (weightItem != null) {
|
||||
weight = weightItem.getAmount();
|
||||
}
|
||||
itemsSaved.computeIfAbsent(item, key -> new AtomicInteger(0)).addAndGet(weight);
|
||||
}
|
||||
default -> {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
while (loot.size() <= slot) {
|
||||
loot.add(new LootSlot(new ArrayList<>(), new ArrayList<>()));
|
||||
}
|
||||
|
||||
List<WeightedItem> itemsList = new ArrayList<>();
|
||||
for (Map.Entry<ItemStack, AtomicInteger> entry : itemsSaved.entrySet()) {
|
||||
ItemStack stack = entry.getKey();
|
||||
int weight = entry.getValue().get();
|
||||
if (weight == 0 && stack == null) {
|
||||
continue;
|
||||
}
|
||||
ItemStack resolved = stack != null ? stack : new ItemStack(Material.AIR, 0);
|
||||
itemsList.add(new WeightedItem(resolved, weight));
|
||||
}
|
||||
|
||||
loot.set(slot, new LootSlot(matsSaved, itemsList));
|
||||
|
||||
GameSettingsBuilder builder = new GameSettingsBuilder(game.getSettings());
|
||||
builder.blockLoot = toBlockLootMap(loot);
|
||||
game.setSettings(builder.build());
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
private Map<Material, Map<ItemStack, Integer>> toBlockLootMap(List<LootSlot> loot) {
|
||||
Map<Material, Map<ItemStack, Integer>> result = new HashMap<>();
|
||||
for (LootSlot slot : loot) {
|
||||
Map<ItemStack, Integer> itemWeights = new HashMap<>();
|
||||
for (WeightedItem item : slot.items) {
|
||||
itemWeights.put(item.item, item.weight);
|
||||
}
|
||||
for (Material mat : slot.materials) {
|
||||
for (Material expanded : expandSimilarMaterials(mat)) {
|
||||
result.put(expanded, new HashMap<>(itemWeights));
|
||||
}
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
private ItemStack setQty(ItemStack itemStack, int amount) {
|
||||
itemStack.setAmount(amount);
|
||||
ItemMeta meta = itemStack.getItemMeta();
|
||||
if (meta != null) {
|
||||
meta.setLore(List.of(String.valueOf(amount)));
|
||||
itemStack.setItemMeta(meta);
|
||||
}
|
||||
return itemStack;
|
||||
}
|
||||
|
||||
private String stringify(Text text) {
|
||||
return text.format(cmdContext()).toString();
|
||||
}
|
||||
|
||||
private static final class WeightedItem {
|
||||
private final ItemStack item;
|
||||
private final int weight;
|
||||
|
||||
private WeightedItem(ItemStack item, int weight) {
|
||||
this.item = item;
|
||||
this.weight = weight;
|
||||
}
|
||||
}
|
||||
|
||||
private static final class LootSlot {
|
||||
private final List<Material> materials;
|
||||
private final List<WeightedItem> items;
|
||||
|
||||
private LootSlot(List<Material> materials, List<WeightedItem> items) {
|
||||
this.materials = materials;
|
||||
this.items = items;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,189 @@
|
||||
package io.github.mviper.bomberman.commands.game;
|
||||
|
||||
import io.github.mviper.bomberman.Bomberman;
|
||||
import io.github.mviper.bomberman.commands.Cmd;
|
||||
import io.github.mviper.bomberman.commands.Permission;
|
||||
import io.github.mviper.bomberman.commands.Permissions;
|
||||
import io.github.mviper.bomberman.messaging.Formattable;
|
||||
import io.github.mviper.bomberman.messaging.Message;
|
||||
import org.bukkit.Bukkit;
|
||||
import org.bukkit.ChatColor;
|
||||
import org.bukkit.command.CommandSender;
|
||||
import org.bukkit.event.HandlerList;
|
||||
import org.bukkit.plugin.RegisteredListener;
|
||||
import org.bukkit.scheduler.BukkitTask;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Locale;
|
||||
import java.util.concurrent.atomic.AtomicReference;
|
||||
|
||||
public class DevInfo extends Cmd {
|
||||
public DevInfo(Cmd parent) {
|
||||
super(parent);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Formattable name() {
|
||||
return Message.of("dev");
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<String> options(CommandSender sender, List<String> args) {
|
||||
return List.of(
|
||||
"handlerlist",
|
||||
"handlercount",
|
||||
"handlerwatch",
|
||||
"nocancelled",
|
||||
"tasklist",
|
||||
"taskcount",
|
||||
"taskwatch",
|
||||
"watch",
|
||||
"permissions"
|
||||
);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean run(CommandSender sender, List<String> args, java.util.Map<String, String> flags) {
|
||||
String action = args.isEmpty() ? "" : args.get(0).toLowerCase(Locale.ROOT);
|
||||
switch (action) {
|
||||
case "watch" -> {
|
||||
run(sender, List.of("nocancelled"), flags);
|
||||
run(sender, List.of("handlerwatch"), flags);
|
||||
run(sender, List.of("taskwatch"), flags);
|
||||
run(sender, List.of("taskcount"), flags);
|
||||
run(sender, List.of("handlercount"), flags);
|
||||
return true;
|
||||
}
|
||||
case "handlerlist" -> {
|
||||
String filter = flags.getOrDefault("class", "");
|
||||
var handlers = HandlerList.getRegisteredListeners(Bomberman.instance).stream()
|
||||
.map(listener -> listener.getListener())
|
||||
.filter(listener -> listener.getClass().getName().contains(filter))
|
||||
.toList();
|
||||
sender.sendMessage(handlers.toString());
|
||||
return true;
|
||||
}
|
||||
case "handlercount" -> {
|
||||
var handlers = HandlerList.getRegisteredListeners(Bomberman.instance);
|
||||
sender.sendMessage("Handlers: " + handlers.size());
|
||||
return true;
|
||||
}
|
||||
case "nocancelled" -> {
|
||||
Bukkit.getScheduler().scheduleSyncRepeatingTask(Bomberman.instance, () -> {
|
||||
HandlerList.getRegisteredListeners(Bomberman.instance).forEach(listener -> {
|
||||
if (!listener.isIgnoringCancelled()) {
|
||||
sender.sendMessage("Not watching for cancelled: " + listener.getListener());
|
||||
}
|
||||
});
|
||||
}, 1L, 1L);
|
||||
sender.sendMessage("Watching for handlers watching for ignored events");
|
||||
return true;
|
||||
}
|
||||
case "handlerwatch" -> {
|
||||
AtomicReference<List<RegisteredListener>> handlersRef = new AtomicReference<>(
|
||||
HandlerList.getRegisteredListeners(Bomberman.instance).stream().toList()
|
||||
);
|
||||
int startCount = handlersRef.get().size();
|
||||
Bukkit.getScheduler().scheduleSyncRepeatingTask(Bomberman.instance, () -> {
|
||||
List<RegisteredListener> updated = HandlerList.getRegisteredListeners(Bomberman.instance).stream().toList();
|
||||
List<RegisteredListener> handlers = handlersRef.get();
|
||||
var added = updated.stream().filter(h -> !handlers.contains(h)).toList();
|
||||
var removed = handlers.stream().filter(h -> !updated.contains(h)).toList();
|
||||
for (var handler : added) {
|
||||
sender.sendMessage(" " + ChatColor.RED + "+" + ChatColor.RESET + " " + handler.getListener());
|
||||
}
|
||||
for (var handler : removed) {
|
||||
sender.sendMessage(" " + ChatColor.GREEN + "-" + ChatColor.RESET + " " + handler.getListener());
|
||||
}
|
||||
if (handlers.size() != updated.size()) {
|
||||
int countDiff = updated.size() - startCount;
|
||||
sender.sendMessage((countDiff > 0 ? ChatColor.RED : ChatColor.GREEN) + " " + countDiff + " handlers");
|
||||
}
|
||||
handlersRef.set(updated);
|
||||
}, 1L, 1L);
|
||||
sender.sendMessage("Watching for new handlers");
|
||||
return true;
|
||||
}
|
||||
case "tasklist" -> {
|
||||
Bukkit.getScheduler().getPendingTasks().forEach(task -> {
|
||||
if (task.getOwner() == Bomberman.instance) {
|
||||
sender.sendMessage("Task with id: " + task.getTaskId());
|
||||
}
|
||||
});
|
||||
return true;
|
||||
}
|
||||
case "taskcount" -> {
|
||||
long count = Bukkit.getScheduler().getPendingTasks().stream()
|
||||
.filter(task -> task.getOwner() == Bomberman.instance)
|
||||
.count();
|
||||
sender.sendMessage("Tasks: " + count);
|
||||
return true;
|
||||
}
|
||||
case "taskwatch" -> {
|
||||
AtomicReference<List<BukkitTask>> tasksRef = new AtomicReference<>(
|
||||
Bukkit.getScheduler().getPendingTasks().stream()
|
||||
.filter(task -> task.getOwner() == Bomberman.instance)
|
||||
.toList()
|
||||
);
|
||||
int startCount = tasksRef.get().size() + 1;
|
||||
Bukkit.getScheduler().scheduleSyncRepeatingTask(Bomberman.instance, () -> {
|
||||
List<BukkitTask> updated = Bukkit.getScheduler().getPendingTasks().stream()
|
||||
.filter(task -> task.getOwner() == Bomberman.instance)
|
||||
.toList();
|
||||
List<BukkitTask> tasks = tasksRef.get();
|
||||
var added = updated.stream().filter(t -> !tasks.contains(t)).toList();
|
||||
var removed = tasks.stream().filter(t -> !updated.contains(t)).toList();
|
||||
for (var task : added) {
|
||||
sender.sendMessage(" " + ChatColor.RED + "+" + ChatColor.RESET + " " + task.getTaskId());
|
||||
}
|
||||
for (var task : removed) {
|
||||
sender.sendMessage(" " + ChatColor.GREEN + "-" + ChatColor.RESET + " " + task.getTaskId());
|
||||
}
|
||||
if (tasks.size() != updated.size()) {
|
||||
int countDiff = updated.size() - startCount;
|
||||
sender.sendMessage((countDiff > 0 ? ChatColor.RED : ChatColor.GREEN) + " " + countDiff + " tasks");
|
||||
}
|
||||
tasksRef.set(updated);
|
||||
}, 1L, 1L);
|
||||
sender.sendMessage("Watching for new tasks");
|
||||
return true;
|
||||
}
|
||||
case "permissions" -> {
|
||||
if (args.size() == 1) {
|
||||
sender.getEffectivePermissions().forEach(perm -> sender.sendMessage(" - " + perm.getPermission()));
|
||||
} else {
|
||||
sender.sendMessage(args.get(1) + " : " + sender.hasPermission(args.get(1)));
|
||||
}
|
||||
return true;
|
||||
}
|
||||
default -> {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public Permission permission() {
|
||||
return Permissions.BASE;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Formattable example() {
|
||||
return Message.empty;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Formattable extra() {
|
||||
return Message.empty;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Formattable description() {
|
||||
return Message.of("Dev commands. Was meant to remove this before shipping");
|
||||
}
|
||||
|
||||
@Override
|
||||
public Formattable usage() {
|
||||
return Message.empty;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,291 @@
|
||||
package io.github.mviper.bomberman.commands.game;
|
||||
|
||||
import com.sk89q.worldedit.WorldEdit;
|
||||
import com.sk89q.worldedit.bukkit.BukkitAdapter;
|
||||
import com.sk89q.worldedit.extent.clipboard.io.BuiltInClipboardFormat;
|
||||
import com.sk89q.worldedit.extent.clipboard.io.ClipboardFormats;
|
||||
import com.sk89q.worldedit.session.SessionOwner;
|
||||
import io.github.mviper.bomberman.Bomberman;
|
||||
import io.github.mviper.bomberman.commands.Cmd;
|
||||
import io.github.mviper.bomberman.commands.Permission;
|
||||
import io.github.mviper.bomberman.commands.Permissions;
|
||||
import io.github.mviper.bomberman.events.BmGameListIntent;
|
||||
import io.github.mviper.bomberman.events.BmGameLookupIntent;
|
||||
import io.github.mviper.bomberman.game.Game;
|
||||
import io.github.mviper.bomberman.game.GameSave;
|
||||
import io.github.mviper.bomberman.game.GameSettings;
|
||||
import io.github.mviper.bomberman.game.GameSettingsBuilder;
|
||||
import io.github.mviper.bomberman.messaging.Formattable;
|
||||
import io.github.mviper.bomberman.messaging.Message;
|
||||
import io.github.mviper.bomberman.messaging.Text;
|
||||
import io.github.mviper.bomberman.utils.WorldEditUtils;
|
||||
import org.bukkit.command.CommandSender;
|
||||
import org.bukkit.entity.Player;
|
||||
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.LinkOption;
|
||||
import java.nio.file.Path;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Locale;
|
||||
import java.util.Set;
|
||||
import java.util.logging.Level;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
public class GameCreate extends Cmd {
|
||||
private static final Bomberman PLUGIN = Bomberman.instance;
|
||||
private static final WorldEdit WE = WorldEdit.getInstance();
|
||||
|
||||
private static final String F_SCHEMA = "s";
|
||||
private static final String F_TEMPLATE = "t";
|
||||
private static final String F_GAME = "g";
|
||||
private static final String F_WAND = "w";
|
||||
private static final String F_PLUGIN = "p";
|
||||
|
||||
public GameCreate(Cmd parent) {
|
||||
super(parent);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Formattable name() {
|
||||
return Text.CREATE_NAME;
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<String> options(CommandSender sender, List<String> args) {
|
||||
if (args.size() == 1) {
|
||||
return BmGameListIntent.listGames().stream().map(Game::getName).toList();
|
||||
}
|
||||
return List.of();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Set<String> flags(CommandSender sender) {
|
||||
return Set.of(F_SCHEMA, F_WAND, F_GAME, F_TEMPLATE);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Set<String> flagOptions(String flag) {
|
||||
return switch (flag) {
|
||||
case F_SCHEMA -> {
|
||||
Path weDir = WE.getWorkingDirectoryPath(WE.getConfiguration().saveDir);
|
||||
List<String> schemaExtensions = new ArrayList<>();
|
||||
for (BuiltInClipboardFormat format : BuiltInClipboardFormat.values()) {
|
||||
schemaExtensions.addAll(format.getFileExtensions());
|
||||
}
|
||||
yield allFiles(weDir, weDir).stream()
|
||||
.map(Path::toString)
|
||||
.filter(fileName -> schemaExtensions.stream().anyMatch(fileName::endsWith))
|
||||
.collect(Collectors.toSet());
|
||||
}
|
||||
case F_TEMPLATE -> {
|
||||
Path templatesDir = PLUGIN.templates();
|
||||
yield allFiles(templatesDir, templatesDir).stream()
|
||||
.map(Path::toString)
|
||||
.filter(fileName -> fileName.endsWith(".game.zip"))
|
||||
.map(fileName -> fileName.replaceAll("(.*)\\.game\\.zip", "$1"))
|
||||
.collect(Collectors.toSet());
|
||||
}
|
||||
case F_GAME -> BmGameListIntent.listGames().stream().map(Game::getName).collect(Collectors.toSet());
|
||||
default -> Set.of();
|
||||
};
|
||||
}
|
||||
|
||||
@Override
|
||||
public Formattable flagDescription(String flag) {
|
||||
return switch (flag) {
|
||||
case F_SCHEMA -> Text.CREATE_FLAG_SCHEMA;
|
||||
case F_TEMPLATE -> Text.CREATE_FLAG_TEMPLATE;
|
||||
case F_GAME -> Text.CREATE_FLAG_GAME;
|
||||
case F_WAND -> Text.CREATE_FLAG_WAND;
|
||||
default -> Message.empty;
|
||||
};
|
||||
}
|
||||
|
||||
@Override
|
||||
public Formattable flagExtension(String flag) {
|
||||
return switch (flag) {
|
||||
case F_SCHEMA -> Text.CREATE_FLAG_SCHEMA_EXT;
|
||||
case F_TEMPLATE -> Text.CREATE_FLAG_TEMPLATE_EXT;
|
||||
case F_GAME -> Text.CREATE_FLAG_GAME_EXT;
|
||||
default -> Message.empty;
|
||||
};
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean run(CommandSender sender, List<String> args, java.util.Map<String, String> flags) {
|
||||
try {
|
||||
if (args.size() != 1) {
|
||||
return false;
|
||||
}
|
||||
if (!(sender instanceof Player player)) {
|
||||
Text.MUST_BE_PLAYER.format(cmdContext()).sendTo(sender);
|
||||
return true;
|
||||
}
|
||||
String gameName = args.get(0);
|
||||
Game existing = BmGameLookupIntent.find(gameName);
|
||||
if (existing != null) {
|
||||
Text.CREATE_GAME_EXISTS.format(cmdContext().plus("game", existing)).sendTo(sender);
|
||||
return true;
|
||||
}
|
||||
if (Files.exists(PLUGIN.gameSaves().resolve(GameSave.sanitize(gameName + ".game.zip")))) {
|
||||
Text.CREATE_GAME_FILE_CONFLICT.format(cmdContext()
|
||||
.plus("game", gameName)
|
||||
.plus("file", GameSave.sanitize(gameName + ".game.zip")))
|
||||
.sendTo(sender);
|
||||
return true;
|
||||
}
|
||||
|
||||
int flagCount = (flags.containsKey(F_WAND) ? 1 : 0)
|
||||
+ (flags.containsKey(F_GAME) ? 1 : 0)
|
||||
+ (flags.containsKey(F_TEMPLATE) ? 1 : 0)
|
||||
+ (flags.containsKey(F_SCHEMA) ? 1 : 0)
|
||||
+ (flags.containsKey(F_PLUGIN) ? 1 : 0);
|
||||
if (flagCount != 1) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (flags.containsKey(F_WAND)) {
|
||||
makeFromSelection(gameName, player, new GameSettingsBuilder().build());
|
||||
return true;
|
||||
}
|
||||
|
||||
var schemaAndSettings = flags.containsKey(F_SCHEMA)
|
||||
? fromSchema(flags.get(F_SCHEMA), sender)
|
||||
: flags.containsKey(F_GAME)
|
||||
? fromGame(flags.get(F_GAME))
|
||||
: flags.containsKey(F_TEMPLATE)
|
||||
? fromTemplate(flags.get(F_TEMPLATE), sender)
|
||||
: fromPlugin(flags.get(F_PLUGIN));
|
||||
|
||||
if (schemaAndSettings == null) {
|
||||
return true;
|
||||
}
|
||||
|
||||
Game game = Game.buildGameFromSchema(gameName, player.getLocation(), schemaAndSettings.schema, schemaAndSettings.settings);
|
||||
Text.CREATE_SUCCESS.format(cmdContext().plus("game", game)).sendTo(sender);
|
||||
return true;
|
||||
} catch (Exception e) {
|
||||
Text.CREATE_ERROR.format(cmdContext().plus("error", e.getMessage() != null ? e.getMessage() : ""))
|
||||
.sendTo(sender);
|
||||
PLUGIN.getLogger().log(Level.WARNING, "Error creating game", e);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
private SchemaSettings fromSchema(String file, CommandSender sender) throws Exception {
|
||||
if (file == null || file.isEmpty()) {
|
||||
return null;
|
||||
}
|
||||
Path path = WE.getWorkingDirectoryPath(WE.getConfiguration().saveDir).resolve(file);
|
||||
if (!Files.exists(path)) {
|
||||
Text.CREATE_GAME_FILE_NOT_FOUND.format(cmdContext()
|
||||
.plus("file", path.toString())
|
||||
.plus("filename", path.getFileName().toString()))
|
||||
.sendTo(sender);
|
||||
return null;
|
||||
}
|
||||
var format = ClipboardFormats.findByFile(path.toFile());
|
||||
if (format == null) {
|
||||
throw new IllegalArgumentException("Unknown file format: '" + file + "'");
|
||||
}
|
||||
var clipboard = format.getReader(Files.newInputStream(path)).read();
|
||||
return new SchemaSettings(clipboard, new GameSettingsBuilder().build());
|
||||
}
|
||||
|
||||
private SchemaSettings fromGame(String name) throws Exception {
|
||||
Game existing = BmGameLookupIntent.find(name);
|
||||
if (existing == null) {
|
||||
return null;
|
||||
}
|
||||
return new SchemaSettings(existing.getClipboard(), existing.getSettings());
|
||||
}
|
||||
|
||||
private SchemaSettings fromTemplate(String file, CommandSender sender) throws Exception {
|
||||
if (file == null || file.isEmpty()) {
|
||||
return null;
|
||||
}
|
||||
String fullFileName = file.toLowerCase(Locale.ROOT).endsWith(".game.zip") ? file : file + ".game.zip";
|
||||
Path path = PLUGIN.templates().resolve(fullFileName);
|
||||
if (!Files.exists(path)) {
|
||||
Text.CREATE_GAME_FILE_NOT_FOUND.format(cmdContext()
|
||||
.plus("file", path.toString())
|
||||
.plus("filename", path.getFileName().toString()))
|
||||
.sendTo(sender);
|
||||
return null;
|
||||
}
|
||||
GameSave save = GameSave.loadSave(path);
|
||||
return new SchemaSettings(save.getSchematic(), save.getSettings());
|
||||
}
|
||||
|
||||
private SchemaSettings fromPlugin(String value) throws Exception {
|
||||
if (value != null && value.toLowerCase(Locale.ROOT).equals("bm")) {
|
||||
Path path = PLUGIN.templates().resolve("purple.game.zip");
|
||||
GameSave save = GameSave.loadSave(path);
|
||||
return new SchemaSettings(save.getSchematic(), save.getSettings());
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
private void makeFromSelection(String gameName, Player sender, GameSettings settings) {
|
||||
SessionOwner owner = BukkitAdapter.adapt(sender);
|
||||
var session = WorldEdit.getInstance().getSessionManager().getIfPresent(owner);
|
||||
if (session == null || session.getSelectionWorld() == null || !session.isSelectionDefined(session.getSelectionWorld())) {
|
||||
Text.CREATE_NEED_SELECTION.format(cmdContext()).sendTo(sender);
|
||||
} else {
|
||||
try {
|
||||
var region = session.getSelection(session.getSelectionWorld());
|
||||
var box = WorldEditUtils.selectionBounds(region);
|
||||
Game game = Game.buildGameFromRegion(gameName, box, settings);
|
||||
Text.CREATE_SUCCESS.format(cmdContext().plus("game", game)).sendTo(sender);
|
||||
} catch (com.sk89q.worldedit.IncompleteRegionException e) {
|
||||
Text.CREATE_NEED_SELECTION.format(cmdContext()).sendTo(sender);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public Permission permission() {
|
||||
return Permissions.CREATE;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Formattable example() {
|
||||
return Text.CREATE_EXAMPLE;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Formattable extra() {
|
||||
return Text.CREATE_EXTRA;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Formattable description() {
|
||||
return Text.CREATE_DESCRIPTION;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Formattable usage() {
|
||||
return Text.CREATE_USAGE;
|
||||
}
|
||||
|
||||
private static List<Path> allFiles(Path root, Path relative) {
|
||||
if (!Files.isDirectory(root, LinkOption.NOFOLLOW_LINKS)) {
|
||||
return List.of();
|
||||
}
|
||||
List<Path> fileList = new ArrayList<>();
|
||||
try (var stream = Files.newDirectoryStream(root)) {
|
||||
for (Path file : stream) {
|
||||
if (Files.isDirectory(file, LinkOption.NOFOLLOW_LINKS)) {
|
||||
fileList.addAll(allFiles(file, relative));
|
||||
} else {
|
||||
fileList.add(relative != null ? relative.relativize(file) : file);
|
||||
}
|
||||
}
|
||||
} catch (Exception ignored) {
|
||||
}
|
||||
return fileList;
|
||||
}
|
||||
|
||||
private record SchemaSettings(com.sk89q.worldedit.extent.clipboard.Clipboard schema, GameSettings settings) {}
|
||||
}
|
||||
@@ -0,0 +1,64 @@
|
||||
package io.github.mviper.bomberman.commands.game;
|
||||
|
||||
import io.github.mviper.bomberman.commands.Cmd;
|
||||
import io.github.mviper.bomberman.commands.GameCommand;
|
||||
import io.github.mviper.bomberman.commands.Permission;
|
||||
import io.github.mviper.bomberman.commands.Permissions;
|
||||
import io.github.mviper.bomberman.events.BmGameDeletedIntent;
|
||||
import io.github.mviper.bomberman.game.Game;
|
||||
import io.github.mviper.bomberman.messaging.Formattable;
|
||||
import io.github.mviper.bomberman.messaging.Text;
|
||||
import org.bukkit.command.CommandSender;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
public class GameDelete extends GameCommand {
|
||||
public GameDelete(Cmd parent) {
|
||||
super(parent);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Formattable name() {
|
||||
return Text.DELETE_NAME;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected List<String> gameOptions(List<String> args) {
|
||||
return List.of();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected boolean gameRun(CommandSender sender, List<String> args, java.util.Map<String, String> flags, Game game) {
|
||||
if (!args.isEmpty()) {
|
||||
return false;
|
||||
}
|
||||
BmGameDeletedIntent.delete(game, true);
|
||||
Text.DELETE_SUCCESS.format(cmdContext().plus("game", game)).sendTo(sender);
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Permission permission() {
|
||||
return Permissions.DELETE;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Formattable example() {
|
||||
return Text.DELETE_EXAMPLE;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Formattable extra() {
|
||||
return Text.DELETE_EXTRA;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Formattable description() {
|
||||
return Text.DELETE_DESCRIPTION;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Formattable usage() {
|
||||
return Text.DELETE_USAGE;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,62 @@
|
||||
package io.github.mviper.bomberman.commands.game;
|
||||
|
||||
import io.github.mviper.bomberman.commands.Cmd;
|
||||
import io.github.mviper.bomberman.commands.GameCommand;
|
||||
import io.github.mviper.bomberman.commands.Permission;
|
||||
import io.github.mviper.bomberman.commands.Permissions;
|
||||
import io.github.mviper.bomberman.game.Game;
|
||||
import io.github.mviper.bomberman.messaging.Formattable;
|
||||
import io.github.mviper.bomberman.messaging.Text;
|
||||
import org.bukkit.command.CommandSender;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
public class GameInfo extends GameCommand {
|
||||
public GameInfo(Cmd parent) {
|
||||
super(parent);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Formattable name() {
|
||||
return Text.INFO_NAME;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Permission permission() {
|
||||
return Permissions.INFO;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected List<String> gameOptions(List<String> args) {
|
||||
return List.of();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected boolean gameRun(CommandSender sender, List<String> args, java.util.Map<String, String> flags, Game game) {
|
||||
if (!args.isEmpty()) {
|
||||
return false;
|
||||
}
|
||||
Text.INFO_DETAILS.format(cmdContext().plus("game", game)).sendTo(sender);
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Formattable extra() {
|
||||
return Text.INFO_EXTRA;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Formattable example() {
|
||||
return Text.INFO_EXAMPLE;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Formattable description() {
|
||||
return Text.INFO_DESCRIPTION;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Formattable usage() {
|
||||
return Text.INFO_USAGE;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,140 @@
|
||||
package io.github.mviper.bomberman.commands.game;
|
||||
|
||||
import io.github.mviper.bomberman.commands.Cmd;
|
||||
import io.github.mviper.bomberman.commands.GameCommand;
|
||||
import io.github.mviper.bomberman.commands.Permission;
|
||||
import io.github.mviper.bomberman.commands.Permissions;
|
||||
import io.github.mviper.bomberman.events.BmPlayerJoinGameIntent;
|
||||
import io.github.mviper.bomberman.game.Game;
|
||||
import io.github.mviper.bomberman.messaging.Formattable;
|
||||
import io.github.mviper.bomberman.messaging.Message;
|
||||
import io.github.mviper.bomberman.messaging.Text;
|
||||
import org.bukkit.Bukkit;
|
||||
import org.bukkit.command.CommandSender;
|
||||
import org.bukkit.entity.Player;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
|
||||
public class GameJoin extends GameCommand {
|
||||
private static final String F_TARGET = "t";
|
||||
|
||||
public GameJoin(Cmd parent) {
|
||||
super(parent);
|
||||
}
|
||||
|
||||
public static List<Player> select(String target, CommandSender source) throws IllegalArgumentException {
|
||||
return Bukkit.selectEntities(source, target).stream()
|
||||
.filter(entity -> entity instanceof Player)
|
||||
.map(entity -> (Player) entity)
|
||||
.toList();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Formattable name() {
|
||||
return Text.JOIN_NAME;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected List<String> gameOptions(List<String> args) {
|
||||
return List.of();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Set<String> flags(CommandSender sender) {
|
||||
return Set.of(F_TARGET);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Set<String> flagOptions(String flag) {
|
||||
if (F_TARGET.equals(flag)) {
|
||||
Set<String> options = Bukkit.getOnlinePlayers().stream().map(Player::getName).collect(java.util.stream.Collectors.toSet());
|
||||
options.addAll(List.of("@a", "@p", "@r", "@s"));
|
||||
return options;
|
||||
}
|
||||
return Set.of();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Formattable flagDescription(String flag) {
|
||||
return F_TARGET.equals(flag) ? Text.JOIN_FLAG_TARGET : Message.empty;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Formattable flagExtension(String flag) {
|
||||
return F_TARGET.equals(flag) ? Text.JOIN_FLAG_TARGET_EXT : Message.empty;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected boolean gameRun(CommandSender sender, List<String> args, java.util.Map<String, String> flags, Game game) {
|
||||
if (!args.isEmpty()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
List<Player> targets;
|
||||
if (flags.get(F_TARGET) != null) {
|
||||
String selection = flags.get(F_TARGET);
|
||||
if (!Permissions.JOIN_REMOTE.isAllowedBy(sender)) {
|
||||
Text.DENY_PERMISSION.format(cmdContext()).sendTo(sender);
|
||||
return true;
|
||||
}
|
||||
try {
|
||||
targets = select(selection, sender);
|
||||
} catch (IllegalArgumentException e) {
|
||||
Text.INVALID_TARGET_SELECTOR.format(cmdContext().plus("selector", selection)).sendTo(sender);
|
||||
return true;
|
||||
}
|
||||
} else {
|
||||
if (!(sender instanceof Player player)) {
|
||||
Text.MUST_BE_PLAYER.format(cmdContext()).sendTo(sender);
|
||||
return true;
|
||||
}
|
||||
targets = List.of(player);
|
||||
}
|
||||
|
||||
for (Player target : targets) {
|
||||
var event = BmPlayerJoinGameIntent.join(game, target);
|
||||
if (event.isCancelled()) {
|
||||
if (event.cancelledReason() != null) {
|
||||
event.cancelledReason().sendTo(sender);
|
||||
} else {
|
||||
Text.COMMAND_CANCELLED.format(cmdContext()
|
||||
.plus("game", game)
|
||||
.plus("player", target))
|
||||
.sendTo(target);
|
||||
}
|
||||
} else {
|
||||
Text.JOIN_SUCCESS.format(cmdContext()
|
||||
.plus("game", game)
|
||||
.plus("player", target))
|
||||
.sendTo(target);
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Permission permission() {
|
||||
return Permissions.JOIN;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Formattable extra() {
|
||||
return Text.JOIN_EXTRA;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Formattable example() {
|
||||
return Text.JOIN_EXAMPLE;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Formattable description() {
|
||||
return Text.JOIN_DESCRIPTION;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Formattable usage() {
|
||||
return Text.JOIN_USAGE;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,124 @@
|
||||
package io.github.mviper.bomberman.commands.game;
|
||||
|
||||
import io.github.mviper.bomberman.commands.Cmd;
|
||||
import io.github.mviper.bomberman.commands.Permission;
|
||||
import io.github.mviper.bomberman.commands.Permissions;
|
||||
import io.github.mviper.bomberman.events.BmPlayerLeaveGameIntent;
|
||||
import io.github.mviper.bomberman.messaging.Formattable;
|
||||
import io.github.mviper.bomberman.messaging.Message;
|
||||
import io.github.mviper.bomberman.messaging.Text;
|
||||
import org.bukkit.Bukkit;
|
||||
import org.bukkit.command.CommandSender;
|
||||
import org.bukkit.entity.Player;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
|
||||
public class GameLeave extends Cmd {
|
||||
private static final String F_TARGET = "t";
|
||||
|
||||
public GameLeave(Cmd parent) {
|
||||
super(parent);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Formattable name() {
|
||||
return Text.LEAVE_NAME;
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<String> options(CommandSender sender, List<String> args) {
|
||||
return List.of();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Set<String> flags(CommandSender sender) {
|
||||
return Set.of(F_TARGET);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Set<String> flagOptions(String flag) {
|
||||
if (F_TARGET.equals(flag)) {
|
||||
Set<String> options = Bukkit.getOnlinePlayers().stream().map(Player::getName).collect(java.util.stream.Collectors.toSet());
|
||||
options.addAll(List.of("@a", "@p", "@r", "@s"));
|
||||
return options;
|
||||
}
|
||||
return Set.of();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Formattable flagDescription(String flag) {
|
||||
return F_TARGET.equals(flag) ? Text.LEAVE_FLAG_TARGET : Message.empty;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Formattable flagExtension(String flag) {
|
||||
return F_TARGET.equals(flag) ? Text.LEAVE_FLAG_TARGET_EXT : Message.empty;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean run(CommandSender sender, List<String> args, java.util.Map<String, String> flags) {
|
||||
if (!args.isEmpty()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
List<Player> targets;
|
||||
if (flags.get(F_TARGET) != null) {
|
||||
String selection = flags.get(F_TARGET);
|
||||
if (!Permissions.LEAVE_REMOTE.isAllowedBy(sender)) {
|
||||
Text.DENY_PERMISSION.format(cmdContext()).sendTo(sender);
|
||||
return true;
|
||||
}
|
||||
try {
|
||||
targets = GameJoin.select(selection, sender);
|
||||
} catch (IllegalArgumentException e) {
|
||||
Text.INVALID_TARGET_SELECTOR.format(cmdContext().plus("selector", selection)).sendTo(sender);
|
||||
return true;
|
||||
}
|
||||
} else {
|
||||
if (!(sender instanceof Player player)) {
|
||||
Text.MUST_BE_PLAYER.format(cmdContext()).sendTo(sender);
|
||||
return true;
|
||||
}
|
||||
targets = List.of(player);
|
||||
}
|
||||
|
||||
for (Player target : targets) {
|
||||
var event = BmPlayerLeaveGameIntent.leave(target);
|
||||
if (event.isHandled()) {
|
||||
Text.LEAVE_SUCCESS.format(cmdContext()
|
||||
.plus("player", target)
|
||||
.plus("game", event.getGame() != null ? event.getGame() : Message.error("none")))
|
||||
.sendTo(target);
|
||||
} else {
|
||||
Text.LEAVE_NOT_JOINED.format(cmdContext().plus("player", target)).sendTo(target);
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Permission permission() {
|
||||
return Permissions.LEAVE;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Formattable extra() {
|
||||
return Text.LEAVE_EXTRA;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Formattable description() {
|
||||
return Text.LEAVE_DESCRIPTION;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Formattable usage() {
|
||||
return Text.LEAVE_USAGE;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Formattable example() {
|
||||
return Text.JOIN_EXAMPLE;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,62 @@
|
||||
package io.github.mviper.bomberman.commands.game;
|
||||
|
||||
import io.github.mviper.bomberman.commands.Cmd;
|
||||
import io.github.mviper.bomberman.commands.Permission;
|
||||
import io.github.mviper.bomberman.commands.Permissions;
|
||||
import io.github.mviper.bomberman.events.BmGameListIntent;
|
||||
import io.github.mviper.bomberman.messaging.Formattable;
|
||||
import io.github.mviper.bomberman.messaging.Text;
|
||||
import org.bukkit.command.CommandSender;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
public class GameList extends Cmd {
|
||||
public GameList(Cmd parent) {
|
||||
super(parent);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Formattable name() {
|
||||
return Text.GAMELIST_NAME;
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<String> options(CommandSender sender, List<String> args) {
|
||||
return List.of();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean run(CommandSender sender, List<String> args, java.util.Map<String, String> flags) {
|
||||
if (!args.isEmpty()) {
|
||||
return false;
|
||||
}
|
||||
var games = BmGameListIntent.listGames();
|
||||
Text.GAMELIST_GAMES.format(cmdContext().plus("games", games)).sendTo(sender);
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Permission permission() {
|
||||
return Permissions.LIST;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Formattable example() {
|
||||
return Text.GAMELIST_EXAMPLE;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Formattable extra() {
|
||||
return Text.GAMELIST_EXTRA;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Formattable description() {
|
||||
return Text.GAMELIST_DESCRIPTION;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Formattable usage() {
|
||||
return Text.GAMELIST_USAGE;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,76 @@
|
||||
package io.github.mviper.bomberman.commands.game;
|
||||
|
||||
import io.github.mviper.bomberman.Bomberman;
|
||||
import io.github.mviper.bomberman.commands.Cmd;
|
||||
import io.github.mviper.bomberman.commands.GameCommand;
|
||||
import io.github.mviper.bomberman.commands.Permission;
|
||||
import io.github.mviper.bomberman.commands.Permissions;
|
||||
import io.github.mviper.bomberman.events.BmGameBuildIntent;
|
||||
import io.github.mviper.bomberman.events.BmGameDeletedIntent;
|
||||
import io.github.mviper.bomberman.game.Game;
|
||||
import io.github.mviper.bomberman.game.GameSave;
|
||||
import io.github.mviper.bomberman.messaging.Formattable;
|
||||
import io.github.mviper.bomberman.messaging.Text;
|
||||
import org.bukkit.command.CommandSender;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.List;
|
||||
|
||||
public class GameReload extends GameCommand {
|
||||
public GameReload(Cmd parent) {
|
||||
super(parent);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Formattable name() {
|
||||
return Text.RELOAD_NAME;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected boolean gameRun(CommandSender sender, List<String> args, java.util.Map<String, String> flags, Game game) {
|
||||
if (!args.isEmpty()) {
|
||||
return false;
|
||||
}
|
||||
BmGameDeletedIntent.delete(game, false);
|
||||
try {
|
||||
Game newGame = GameSave.loadGame(
|
||||
Bomberman.instance.gameSaves().resolve(GameSave.sanitize(game.getName() + ".game.zip"))
|
||||
);
|
||||
BmGameBuildIntent.build(newGame);
|
||||
Text.RELOAD_SUCCESS.format(cmdContext().plus("game", newGame)).sendTo(sender);
|
||||
} catch (IOException e) {
|
||||
Text.RELOAD_CANNOT_LOAD.format(cmdContext().plus("game", game.getName())).sendTo(sender);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Permission permission() {
|
||||
return Permissions.RELOAD;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected List<String> gameOptions(List<String> args) {
|
||||
return List.of();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Formattable extra() {
|
||||
return Text.RELOAD_EXTRA;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Formattable example() {
|
||||
return Text.RELOAD_EXAMPLE;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Formattable description() {
|
||||
return Text.RELOAD_DESCRIPTION;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Formattable usage() {
|
||||
return Text.RELOAD_USAGE;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,244 @@
|
||||
package io.github.mviper.bomberman.commands.game;
|
||||
|
||||
import io.github.mviper.bomberman.Bomberman;
|
||||
import org.bukkit.Bukkit;
|
||||
import org.bukkit.Material;
|
||||
import org.bukkit.NamespacedKey;
|
||||
import org.bukkit.entity.Player;
|
||||
import org.bukkit.event.EventHandler;
|
||||
import org.bukkit.event.Listener;
|
||||
import org.bukkit.event.inventory.InventoryClickEvent;
|
||||
import org.bukkit.event.inventory.InventoryCloseEvent;
|
||||
import org.bukkit.inventory.Inventory;
|
||||
import org.bukkit.inventory.InventoryView;
|
||||
import org.bukkit.inventory.ItemFlag;
|
||||
import org.bukkit.inventory.ItemStack;
|
||||
import org.bukkit.inventory.meta.ItemMeta;
|
||||
import org.bukkit.inventory.meta.tags.ItemTagType;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.atomic.AtomicInteger;
|
||||
import java.util.function.Consumer;
|
||||
import java.util.function.Function;
|
||||
|
||||
public class GuiBuilder implements Listener {
|
||||
public static final class Index {
|
||||
private final int x;
|
||||
private final int y;
|
||||
private final int invIndex;
|
||||
private final char section;
|
||||
private final int secIndex;
|
||||
private final Inventory inventory;
|
||||
|
||||
public Index(int x, int y, int invIndex, char section, int secIndex, Inventory inventory) {
|
||||
this.x = x;
|
||||
this.y = y;
|
||||
this.invIndex = invIndex;
|
||||
this.section = section;
|
||||
this.secIndex = secIndex;
|
||||
this.inventory = inventory;
|
||||
}
|
||||
|
||||
public int getX() {
|
||||
return x;
|
||||
}
|
||||
|
||||
public int getY() {
|
||||
return y;
|
||||
}
|
||||
|
||||
public int getInvIndex() {
|
||||
return invIndex;
|
||||
}
|
||||
|
||||
public char getSection() {
|
||||
return section;
|
||||
}
|
||||
|
||||
public int getSecIndex() {
|
||||
return secIndex;
|
||||
}
|
||||
|
||||
public Inventory getInventory() {
|
||||
return inventory;
|
||||
}
|
||||
}
|
||||
|
||||
public static final class ItemSlot {
|
||||
private final ItemStack item;
|
||||
|
||||
public ItemSlot(ItemStack item) {
|
||||
this.item = item;
|
||||
}
|
||||
|
||||
public ItemSlot(Material type, int qty) {
|
||||
this(new ItemStack(type, qty));
|
||||
}
|
||||
|
||||
public ItemSlot(Material type) {
|
||||
this(new ItemStack(type));
|
||||
}
|
||||
|
||||
public ItemStack getItem() {
|
||||
return item;
|
||||
}
|
||||
|
||||
private ItemSlot alterMeta(Consumer<ItemMeta> mod) {
|
||||
if (item == null) {
|
||||
return this;
|
||||
}
|
||||
ItemStack newItem = item.clone();
|
||||
ItemMeta itemMeta = newItem.getItemMeta();
|
||||
if (itemMeta == null) {
|
||||
return new ItemSlot(newItem);
|
||||
}
|
||||
mod.accept(itemMeta);
|
||||
newItem.setItemMeta(itemMeta);
|
||||
return new ItemSlot(newItem);
|
||||
}
|
||||
|
||||
public ItemSlot unMovable() {
|
||||
return alterMeta(meta -> meta.getCustomTagContainer().setCustomTag(NO_MOVE_KEY, ItemTagType.BYTE, (byte) 1));
|
||||
}
|
||||
|
||||
public ItemSlot hideAttributes() {
|
||||
return alterMeta(meta -> meta.addItemFlags(ItemFlag.HIDE_ATTRIBUTES));
|
||||
}
|
||||
|
||||
public ItemSlot displayName(String title) {
|
||||
return alterMeta(meta -> meta.setDisplayName(title));
|
||||
}
|
||||
}
|
||||
|
||||
public static final class SlotItem {
|
||||
private final Index index;
|
||||
private final ItemStack item;
|
||||
|
||||
public SlotItem(Index index, ItemStack item) {
|
||||
this.index = index;
|
||||
this.item = item;
|
||||
}
|
||||
|
||||
public Index getIndex() {
|
||||
return index;
|
||||
}
|
||||
|
||||
public ItemStack getItem() {
|
||||
return item;
|
||||
}
|
||||
}
|
||||
|
||||
@FunctionalInterface
|
||||
public interface ClickHandler {
|
||||
void handle(Index index, ItemStack currentItem, ItemStack cursorItem);
|
||||
}
|
||||
|
||||
public static void show(
|
||||
Player player,
|
||||
String name,
|
||||
CharSequence[] contents,
|
||||
Function<Index, ItemSlot> onInit
|
||||
) {
|
||||
show(player, name, contents, onInit, (index, currentItem, cursorItem) -> {
|
||||
}, slots -> {
|
||||
});
|
||||
}
|
||||
|
||||
public static void show(
|
||||
Player player,
|
||||
String name,
|
||||
CharSequence[] contents,
|
||||
Function<Index, ItemSlot> onInit,
|
||||
ClickHandler onClick,
|
||||
Consumer<List<SlotItem>> onClose
|
||||
) {
|
||||
int size = contents.length * 9;
|
||||
Inventory inventory = Bukkit.createInventory(null, size, name);
|
||||
InventoryView view = player.openInventory(inventory);
|
||||
if (view == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
List<Index> slotLookup = new ArrayList<>();
|
||||
Map<Character, AtomicInteger> sectionCount = new HashMap<>();
|
||||
for (int i = 0; i < size; i++) {
|
||||
int x = i % 9;
|
||||
int y = i / 9;
|
||||
char c = contents[y].charAt(x);
|
||||
AtomicInteger count = sectionCount.computeIfAbsent(c, key -> new AtomicInteger(0));
|
||||
Index index = new Index(x, y, i, c, count.getAndIncrement(), inventory);
|
||||
slotLookup.add(index);
|
||||
ItemSlot slot = onInit.apply(index);
|
||||
inventory.setItem(i, slot != null ? slot.getItem() : null);
|
||||
}
|
||||
LOOKUP.put(view, new InvMemory(slotLookup, onClick, onClose));
|
||||
}
|
||||
|
||||
private static final Bomberman PLUGIN = Bomberman.instance;
|
||||
private static final NamespacedKey NO_MOVE_KEY = new NamespacedKey(PLUGIN, "no-move");
|
||||
private static final Map<InventoryView, InvMemory> LOOKUP = new HashMap<>();
|
||||
public static final ItemSlot blank = new ItemSlot(Material.BLACK_STAINED_GLASS_PANE)
|
||||
.hideAttributes()
|
||||
.displayName(" ")
|
||||
.unMovable();
|
||||
|
||||
static {
|
||||
Bukkit.getPluginManager().registerEvents(new GuiBuilder(), PLUGIN);
|
||||
}
|
||||
|
||||
private static boolean isNotMovable(ItemStack item) {
|
||||
ItemMeta meta = item.getItemMeta();
|
||||
if (meta == null) {
|
||||
return false;
|
||||
}
|
||||
Byte value = meta.getCustomTagContainer().getCustomTag(NO_MOVE_KEY, ItemTagType.BYTE);
|
||||
return value != null && value == (byte) 1;
|
||||
}
|
||||
|
||||
private static final class InvMemory {
|
||||
private final List<Index> slots;
|
||||
private final ClickHandler onClick;
|
||||
private final Consumer<List<SlotItem>> onClose;
|
||||
|
||||
private InvMemory(List<Index> slots, ClickHandler onClick, Consumer<List<SlotItem>> onClose) {
|
||||
this.slots = slots;
|
||||
this.onClick = onClick;
|
||||
this.onClose = onClose;
|
||||
}
|
||||
}
|
||||
|
||||
@EventHandler
|
||||
public void onInventoryClosed(InventoryCloseEvent event) {
|
||||
InvMemory memory = LOOKUP.remove(event.getView());
|
||||
if (memory == null) {
|
||||
return;
|
||||
}
|
||||
Inventory inventory = event.getInventory();
|
||||
List<SlotItem> items = new ArrayList<>();
|
||||
for (Index index : memory.slots) {
|
||||
items.add(new SlotItem(index, inventory.getItem(index.getInvIndex())));
|
||||
}
|
||||
memory.onClose.accept(items);
|
||||
}
|
||||
|
||||
@EventHandler
|
||||
public void onInventoryItemClicked(InventoryClickEvent event) {
|
||||
InvMemory memory = LOOKUP.get(event.getView());
|
||||
if (memory == null) {
|
||||
return;
|
||||
}
|
||||
if (event.getClickedInventory() != event.getInventory()) {
|
||||
return;
|
||||
}
|
||||
Index index = memory.slots.get(event.getSlot());
|
||||
ItemStack currentItem = event.getCurrentItem();
|
||||
ItemStack cursorItem = event.getCursor();
|
||||
memory.onClick.handle(index, currentItem, cursorItem);
|
||||
if ((currentItem != null && isNotMovable(currentItem)) || (cursorItem != null && isNotMovable(cursorItem))) {
|
||||
event.setCancelled(true);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,116 @@
|
||||
package io.github.mviper.bomberman.commands.game;
|
||||
|
||||
import io.github.mviper.bomberman.commands.Cmd;
|
||||
import io.github.mviper.bomberman.commands.GameCommand;
|
||||
import io.github.mviper.bomberman.commands.Permission;
|
||||
import io.github.mviper.bomberman.commands.Permissions;
|
||||
import io.github.mviper.bomberman.events.BmRunStartCountDownIntent;
|
||||
import io.github.mviper.bomberman.game.Game;
|
||||
import io.github.mviper.bomberman.messaging.Formattable;
|
||||
import io.github.mviper.bomberman.messaging.Message;
|
||||
import io.github.mviper.bomberman.messaging.Text;
|
||||
import org.bukkit.command.CommandSender;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
|
||||
public class RunStart extends GameCommand {
|
||||
public RunStart(Cmd parent) {
|
||||
super(parent);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Formattable name() {
|
||||
return Text.START_NAME;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected List<String> gameOptions(List<String> args) {
|
||||
return List.of();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Set<String> flags(CommandSender sender) {
|
||||
return Set.of("d", "o");
|
||||
}
|
||||
|
||||
@Override
|
||||
public Formattable flagExtension(String flag) {
|
||||
return switch (flag) {
|
||||
case "d" -> Text.START_FLAG_DELAY_EXT;
|
||||
default -> Message.empty;
|
||||
};
|
||||
}
|
||||
|
||||
@Override
|
||||
public Formattable flagDescription(String flag) {
|
||||
return switch (flag) {
|
||||
case "d" -> Text.START_FLAG_DELAY_DESC;
|
||||
case "o" -> Text.START_FLAG_OVERRIDE_DESC;
|
||||
default -> Message.empty;
|
||||
};
|
||||
}
|
||||
|
||||
@Override
|
||||
protected boolean gameRun(CommandSender sender, List<String> args, Map<String, String> flags, Game game) {
|
||||
if (!args.isEmpty()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
String delayValue = flags.containsKey("d") ? flags.get("d") : flags.get("delay");
|
||||
int delay;
|
||||
if (delayValue == null) {
|
||||
delay = 3;
|
||||
} else {
|
||||
try {
|
||||
delay = Integer.parseInt(delayValue);
|
||||
} catch (NumberFormatException ex) {
|
||||
Text.INVALID_NUMBER.format(cmdContext().plus("number", delayValue)).sendTo(sender);
|
||||
return true;
|
||||
}
|
||||
if (delay < 0) {
|
||||
Text.INVALID_NUMBER.format(cmdContext().plus("number", delayValue)).sendTo(sender);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
BmRunStartCountDownIntent event = BmRunStartCountDownIntent.startGame(game, delay, flags.containsKey("o"));
|
||||
if (event.isCancelled()) {
|
||||
Formattable reason = event.getCancelledReason();
|
||||
if (reason == null) {
|
||||
Text.COMMAND_CANCELLED.format(cmdContext().plus("game", game)).sendTo(sender);
|
||||
} else {
|
||||
reason.format(cmdContext()).sendTo(sender);
|
||||
}
|
||||
} else {
|
||||
Text.GAME_START_SUCCESS.format(cmdContext().plus("game", game)).sendTo(sender);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Permission permission() {
|
||||
return Permissions.START;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Formattable extra() {
|
||||
return Text.START_EXTRA;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Formattable example() {
|
||||
return Text.START_EXAMPLE;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Formattable description() {
|
||||
return Text.START_DESCRIPTION;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Formattable usage() {
|
||||
return Text.START_USAGE;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,73 @@
|
||||
package io.github.mviper.bomberman.commands.game;
|
||||
|
||||
import io.github.mviper.bomberman.commands.Cmd;
|
||||
import io.github.mviper.bomberman.commands.GameCommand;
|
||||
import io.github.mviper.bomberman.commands.Permission;
|
||||
import io.github.mviper.bomberman.commands.Permissions;
|
||||
import io.github.mviper.bomberman.events.BmRunStoppedIntent;
|
||||
import io.github.mviper.bomberman.game.Game;
|
||||
import io.github.mviper.bomberman.messaging.Formattable;
|
||||
import io.github.mviper.bomberman.messaging.Text;
|
||||
import org.bukkit.command.CommandSender;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
public class RunStop extends GameCommand {
|
||||
public RunStop(Cmd parent) {
|
||||
super(parent);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Formattable name() {
|
||||
return Text.STOP_NAME;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected boolean gameRun(CommandSender sender, List<String> args, java.util.Map<String, String> flags, Game game) {
|
||||
if (!args.isEmpty()) {
|
||||
return false;
|
||||
}
|
||||
BmRunStoppedIntent event = BmRunStoppedIntent.stopGame(game);
|
||||
if (!event.isCancelled()) {
|
||||
Text.STOP_SUCCESS.format(cmdContext().plus("game", game)).sendTo(sender);
|
||||
} else {
|
||||
Formattable reason = event.cancelledReason();
|
||||
if (reason == null) {
|
||||
Text.COMMAND_CANCELLED.format(cmdContext().plus("command", this)).sendTo(sender);
|
||||
} else {
|
||||
reason.format(cmdContext()).sendTo(sender);
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Permission permission() {
|
||||
return Permissions.STOP;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected List<String> gameOptions(List<String> args) {
|
||||
return List.of();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Formattable extra() {
|
||||
return Text.STOP_EXTRA;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Formattable example() {
|
||||
return Text.STOP_EXAMPLE;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Formattable description() {
|
||||
return Text.STOP_DESCRIPTION;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Formattable usage() {
|
||||
return Text.STOP_USAGE;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,161 @@
|
||||
package io.github.mviper.bomberman.commands.game;
|
||||
|
||||
import com.sk89q.worldedit.WorldEdit;
|
||||
import com.sk89q.worldedit.bukkit.BukkitAdapter;
|
||||
import com.sk89q.worldedit.extent.clipboard.BlockArrayClipboard;
|
||||
import com.sk89q.worldedit.extent.clipboard.Clipboard;
|
||||
import com.sk89q.worldedit.function.operation.ForwardExtentCopy;
|
||||
import com.sk89q.worldedit.function.operation.Operations;
|
||||
import com.sk89q.worldedit.math.BlockVector3;
|
||||
import com.sk89q.worldedit.session.ClipboardHolder;
|
||||
import io.github.mviper.bomberman.Bomberman;
|
||||
import io.github.mviper.bomberman.commands.Cmd;
|
||||
import io.github.mviper.bomberman.commands.Permission;
|
||||
import io.github.mviper.bomberman.commands.Permissions;
|
||||
import io.github.mviper.bomberman.events.BmGameDeletedIntent;
|
||||
import io.github.mviper.bomberman.events.BmGameLookupIntent;
|
||||
import io.github.mviper.bomberman.game.Game;
|
||||
import io.github.mviper.bomberman.messaging.Formattable;
|
||||
import io.github.mviper.bomberman.messaging.Text;
|
||||
import io.github.mviper.bomberman.utils.Box;
|
||||
import io.github.mviper.bomberman.utils.BukkitUtils;
|
||||
import io.github.mviper.bomberman.utils.WorldEditUtils;
|
||||
import org.bukkit.Bukkit;
|
||||
import org.bukkit.Location;
|
||||
import org.bukkit.command.CommandSender;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
public class UndoBuild extends Cmd {
|
||||
private static final Map<String, UndoHistory> GAME_MEMORY = new HashMap<>();
|
||||
|
||||
public UndoBuild(Cmd parent) {
|
||||
super(parent);
|
||||
}
|
||||
|
||||
public static void retainHistory(String name, Box box) {
|
||||
var region = WorldEditUtils.convert(box);
|
||||
Clipboard clipboard = new BlockArrayClipboard(region);
|
||||
try (var editSession = WorldEdit.getInstance().newEditSession(BukkitAdapter.adapt(box.world))) {
|
||||
ForwardExtentCopy forwardExtentCopy = new ForwardExtentCopy(
|
||||
editSession,
|
||||
region,
|
||||
clipboard,
|
||||
region.getMinimumPoint()
|
||||
);
|
||||
try {
|
||||
Operations.complete(forwardExtentCopy);
|
||||
} catch (com.sk89q.worldedit.WorldEditException e) {
|
||||
throw new RuntimeException("Failed to copy region", e);
|
||||
}
|
||||
}
|
||||
|
||||
int[] handle = new int[1];
|
||||
handle[0] = Bukkit.getScheduler().scheduleSyncDelayedTask(Bomberman.instance, () -> {
|
||||
UndoHistory memory = GAME_MEMORY.get(name);
|
||||
if (memory != null && memory.taskId == handle[0]) {
|
||||
GAME_MEMORY.remove(name);
|
||||
Bomberman.instance.getLogger().info("Game '" + name + "' undo history expired");
|
||||
}
|
||||
}, 10 * 60 * 20L);
|
||||
|
||||
GAME_MEMORY.put(name, new UndoHistory(BukkitUtils.boxLoc1(box), clipboard, handle[0]));
|
||||
}
|
||||
|
||||
public static void removeHistory(String name) {
|
||||
GAME_MEMORY.remove(name);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Formattable name() {
|
||||
return Text.UNDO_NAME;
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<String> options(CommandSender sender, List<String> args) {
|
||||
if (args.size() == 1) {
|
||||
List<String> names = new ArrayList<>(GAME_MEMORY.keySet());
|
||||
names.sort(String::compareTo);
|
||||
return names;
|
||||
}
|
||||
return List.of();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean run(CommandSender sender, List<String> args, java.util.Map<String, String> flags) {
|
||||
if (args.size() != 1) {
|
||||
return false;
|
||||
}
|
||||
String gameName = args.get(0);
|
||||
|
||||
UndoHistory memory = GAME_MEMORY.get(gameName);
|
||||
if (memory == null || memory.clipboard == null || memory.origin == null) {
|
||||
Text.UNDO_UNKNOWN_GAME.format(cmdContext().plus("game", gameName)).sendTo(sender);
|
||||
return true;
|
||||
}
|
||||
|
||||
Game game = BmGameLookupIntent.find(gameName);
|
||||
if (game != null) {
|
||||
BmGameDeletedIntent.delete(game, true);
|
||||
Text.UNDO_DELETED.format(cmdContext().plus("game", game)).sendTo(sender);
|
||||
}
|
||||
|
||||
try (var editSession = WorldEdit.getInstance().newEditSession(BukkitAdapter.adapt(memory.origin.getWorld()))) {
|
||||
var operation = new ClipboardHolder(memory.clipboard)
|
||||
.createPaste(editSession)
|
||||
.to(BlockVector3.at(memory.origin.getBlockX(), memory.origin.getBlockY(), memory.origin.getBlockZ()))
|
||||
.copyEntities(true)
|
||||
.build();
|
||||
try {
|
||||
Operations.complete(operation);
|
||||
} catch (com.sk89q.worldedit.WorldEditException e) {
|
||||
throw new RuntimeException("Failed to paste clipboard", e);
|
||||
}
|
||||
}
|
||||
|
||||
GAME_MEMORY.remove(gameName);
|
||||
|
||||
Text.UNDO_SUCCESS.format(cmdContext().plus("game", gameName)).sendTo(sender);
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Permission permission() {
|
||||
return Permissions.UNDO;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Formattable example() {
|
||||
return Text.UNDO_EXAMPLE;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Formattable extra() {
|
||||
return Text.UNDO_EXTRA;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Formattable description() {
|
||||
return Text.UNDO_DESCRIPTION;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Formattable usage() {
|
||||
return Text.UNDO_USAGE;
|
||||
}
|
||||
|
||||
private static final class UndoHistory {
|
||||
private final Location origin;
|
||||
private final Clipboard clipboard;
|
||||
private final int taskId;
|
||||
|
||||
private UndoHistory(Location origin, Clipboard clipboard, int taskId) {
|
||||
this.origin = origin;
|
||||
this.clipboard = clipboard;
|
||||
this.taskId = taskId;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,20 @@
|
||||
package io.github.mviper.bomberman.events;
|
||||
|
||||
import org.bukkit.event.Cancellable;
|
||||
|
||||
/**
|
||||
* Simple implementation of Cancellable interface.
|
||||
*/
|
||||
public class BmCancellable implements Cancellable {
|
||||
private boolean cancelled = false;
|
||||
|
||||
@Override
|
||||
public boolean isCancelled() {
|
||||
return cancelled;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setCancelled(boolean cancel) {
|
||||
cancelled = cancel;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,52 @@
|
||||
package io.github.mviper.bomberman.events;
|
||||
|
||||
import io.github.mviper.bomberman.game.Explosion.BlockPlan;
|
||||
import io.github.mviper.bomberman.game.Game;
|
||||
import org.bukkit.Location;
|
||||
import org.bukkit.entity.Player;
|
||||
import org.bukkit.event.HandlerList;
|
||||
import org.bukkit.inventory.ItemStack;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
|
||||
/**
|
||||
* Called when a bomb turns from a tnt block into a fire '+'.
|
||||
*/
|
||||
public class BmDropLootEvent extends BmEvent implements org.bukkit.event.Cancellable {
|
||||
private static final HandlerList HANDLER_LIST = new HandlerList();
|
||||
|
||||
public final Game game;
|
||||
public final Player cause;
|
||||
public final Set<BlockPlan> ignited;
|
||||
public final Map<Location, Set<ItemStack>> drops;
|
||||
|
||||
private final BmCancellable delegate = new BmCancellable();
|
||||
|
||||
public BmDropLootEvent(Game game, Player cause, Set<BlockPlan> ignited, Map<Location, Set<ItemStack>> drops) {
|
||||
this.game = game;
|
||||
this.cause = cause;
|
||||
this.ignited = ignited;
|
||||
this.drops = new HashMap<>(drops);
|
||||
}
|
||||
|
||||
@Override
|
||||
public HandlerList getHandlers() {
|
||||
return HANDLER_LIST;
|
||||
}
|
||||
|
||||
public static HandlerList getHandlerList() {
|
||||
return HANDLER_LIST;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isCancelled() {
|
||||
return delegate.isCancelled();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setCancelled(boolean cancel) {
|
||||
delegate.setCancelled(cancel);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
package io.github.mviper.bomberman.events;
|
||||
|
||||
import org.bukkit.event.Event;
|
||||
|
||||
/**
|
||||
* All Bomberman events extend this.
|
||||
*/
|
||||
public abstract class BmEvent extends Event {
|
||||
}
|
||||
@@ -0,0 +1,47 @@
|
||||
package io.github.mviper.bomberman.events;
|
||||
|
||||
import io.github.mviper.bomberman.game.Explosion.BlockPlan;
|
||||
import io.github.mviper.bomberman.game.Game;
|
||||
import org.bukkit.entity.Player;
|
||||
import org.bukkit.event.HandlerList;
|
||||
|
||||
import java.util.HashSet;
|
||||
import java.util.Set;
|
||||
|
||||
/**
|
||||
* Called when a bomb turns from a tnt block into a fire '+'.
|
||||
*/
|
||||
public class BmExplosionEvent extends BmEvent implements org.bukkit.event.Cancellable {
|
||||
private static final HandlerList HANDLER_LIST = new HandlerList();
|
||||
|
||||
public final Game game;
|
||||
public final Player cause;
|
||||
public final Set<BlockPlan> igniting;
|
||||
|
||||
private final BmCancellable delegate = new BmCancellable();
|
||||
|
||||
public BmExplosionEvent(Game game, Player cause, Set<BlockPlan> igniting) {
|
||||
this.game = game;
|
||||
this.cause = cause;
|
||||
this.igniting = new HashSet<>(igniting);
|
||||
}
|
||||
|
||||
@Override
|
||||
public HandlerList getHandlers() {
|
||||
return HANDLER_LIST;
|
||||
}
|
||||
|
||||
public static HandlerList getHandlerList() {
|
||||
return HANDLER_LIST;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isCancelled() {
|
||||
return delegate.isCancelled();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setCancelled(boolean cancel) {
|
||||
delegate.setCancelled(cancel);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,49 @@
|
||||
package io.github.mviper.bomberman.events;
|
||||
|
||||
import io.github.mviper.bomberman.game.Game;
|
||||
import org.bukkit.Bukkit;
|
||||
import org.bukkit.event.HandlerList;
|
||||
|
||||
/**
|
||||
* Called when a game is built.
|
||||
*/
|
||||
public class BmGameBuildIntent extends BmEvent implements Intent {
|
||||
private static final HandlerList HANDLER_LIST = new HandlerList();
|
||||
|
||||
public final Game game;
|
||||
private final BmIntent delegate = new BmIntent();
|
||||
|
||||
private BmGameBuildIntent(Game game) {
|
||||
this.game = game;
|
||||
}
|
||||
|
||||
public static void build(Game game) {
|
||||
BmGameBuildIntent event = new BmGameBuildIntent(game);
|
||||
Bukkit.getPluginManager().callEvent(event);
|
||||
event.verifyHandled();
|
||||
}
|
||||
|
||||
@Override
|
||||
public HandlerList getHandlers() {
|
||||
return HANDLER_LIST;
|
||||
}
|
||||
|
||||
public static HandlerList getHandlerList() {
|
||||
return HANDLER_LIST;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isHandled() {
|
||||
return delegate.isHandled();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setHandled() {
|
||||
delegate.setHandled();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void verifyHandled() {
|
||||
delegate.verifyHandled();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,62 @@
|
||||
package io.github.mviper.bomberman.events;
|
||||
|
||||
import io.github.mviper.bomberman.game.Game;
|
||||
import org.bukkit.Bukkit;
|
||||
import org.bukkit.event.HandlerList;
|
||||
|
||||
/**
|
||||
* Called when a game is completely deleted from the server.
|
||||
*/
|
||||
public class BmGameDeletedIntent extends BmEvent implements IntentCancellable {
|
||||
private static final HandlerList HANDLER_LIST = new HandlerList();
|
||||
|
||||
public final Game game;
|
||||
public boolean isDeletingSave = false;
|
||||
|
||||
private final BmIntentCancellable delegate = new BmIntentCancellable();
|
||||
|
||||
private BmGameDeletedIntent(Game game) {
|
||||
this.game = game;
|
||||
}
|
||||
|
||||
public static void delete(Game game, boolean deleteSave) {
|
||||
BmGameDeletedIntent event = new BmGameDeletedIntent(game);
|
||||
event.isDeletingSave = deleteSave;
|
||||
Bukkit.getPluginManager().callEvent(event);
|
||||
event.verifyHandled();
|
||||
}
|
||||
|
||||
@Override
|
||||
public HandlerList getHandlers() {
|
||||
return HANDLER_LIST;
|
||||
}
|
||||
|
||||
public static HandlerList getHandlerList() {
|
||||
return HANDLER_LIST;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isHandled() {
|
||||
return delegate.isHandled();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setHandled() {
|
||||
delegate.setHandled();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void verifyHandled() {
|
||||
delegate.verifyHandled();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isCancelled() {
|
||||
return delegate.isCancelled();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setCancelled(boolean cancel) {
|
||||
delegate.setCancelled(cancel);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,32 @@
|
||||
package io.github.mviper.bomberman.events;
|
||||
|
||||
import io.github.mviper.bomberman.game.Game;
|
||||
import org.bukkit.Bukkit;
|
||||
import org.bukkit.event.HandlerList;
|
||||
|
||||
import java.util.HashSet;
|
||||
import java.util.Set;
|
||||
|
||||
/**
|
||||
* Called to find a listing of every active game.
|
||||
*/
|
||||
public class BmGameListIntent extends BmEvent {
|
||||
private static final HandlerList HANDLER_LIST = new HandlerList();
|
||||
|
||||
public final Set<Game> games = new HashSet<>();
|
||||
|
||||
public static Set<Game> listGames() {
|
||||
BmGameListIntent event = new BmGameListIntent();
|
||||
Bukkit.getPluginManager().callEvent(event);
|
||||
return event.games;
|
||||
}
|
||||
|
||||
@Override
|
||||
public HandlerList getHandlers() {
|
||||
return HANDLER_LIST;
|
||||
}
|
||||
|
||||
public static HandlerList getHandlerList() {
|
||||
return HANDLER_LIST;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,34 @@
|
||||
package io.github.mviper.bomberman.events;
|
||||
|
||||
import io.github.mviper.bomberman.game.Game;
|
||||
import org.bukkit.Bukkit;
|
||||
import org.bukkit.event.HandlerList;
|
||||
|
||||
/**
|
||||
* Called to find a game instance.
|
||||
*/
|
||||
public class BmGameLookupIntent extends BmEvent {
|
||||
private static final HandlerList HANDLER_LIST = new HandlerList();
|
||||
|
||||
public final String name;
|
||||
public Game game;
|
||||
|
||||
public BmGameLookupIntent(String name) {
|
||||
this.name = name;
|
||||
}
|
||||
|
||||
public static Game find(String name) {
|
||||
BmGameLookupIntent event = new BmGameLookupIntent(name);
|
||||
Bukkit.getPluginManager().callEvent(event);
|
||||
return event.game;
|
||||
}
|
||||
|
||||
@Override
|
||||
public HandlerList getHandlers() {
|
||||
return HANDLER_LIST;
|
||||
}
|
||||
|
||||
public static HandlerList getHandlerList() {
|
||||
return HANDLER_LIST;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,51 @@
|
||||
package io.github.mviper.bomberman.events;
|
||||
|
||||
import io.github.mviper.bomberman.game.Game;
|
||||
import org.bukkit.Bukkit;
|
||||
import org.bukkit.event.HandlerList;
|
||||
|
||||
/**
|
||||
* Called whenever a game is being removed - may be because game deleted or because
|
||||
* server is shutting down. Is possible game starts back when server starts back.
|
||||
* All event listeners for the game should destroy themselves on this event.
|
||||
*/
|
||||
public class BmGameTerminatedIntent extends BmEvent implements Intent {
|
||||
private static final HandlerList HANDLER_LIST = new HandlerList();
|
||||
|
||||
public final Game game;
|
||||
private final BmIntent delegate = new BmIntent();
|
||||
|
||||
private BmGameTerminatedIntent(Game game) {
|
||||
this.game = game;
|
||||
}
|
||||
|
||||
public static void terminateGame(Game game) {
|
||||
BmGameTerminatedIntent event = new BmGameTerminatedIntent(game);
|
||||
Bukkit.getPluginManager().callEvent(event);
|
||||
event.verifyHandled();
|
||||
}
|
||||
|
||||
@Override
|
||||
public HandlerList getHandlers() {
|
||||
return HANDLER_LIST;
|
||||
}
|
||||
|
||||
public static HandlerList getHandlerList() {
|
||||
return HANDLER_LIST;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isHandled() {
|
||||
return delegate.isHandled();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setHandled() {
|
||||
delegate.setHandled();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void verifyHandled() {
|
||||
delegate.verifyHandled();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,22 @@
|
||||
package io.github.mviper.bomberman.events;
|
||||
|
||||
public class BmIntent implements Intent {
|
||||
private boolean handled = false;
|
||||
|
||||
@Override
|
||||
public boolean isHandled() {
|
||||
return handled;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setHandled() {
|
||||
handled = true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void verifyHandled() {
|
||||
if (!handled) {
|
||||
throw new RuntimeException("Event not handled: " + this);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,33 @@
|
||||
package io.github.mviper.bomberman.events;
|
||||
|
||||
public class BmIntentCancellable implements IntentCancellable {
|
||||
private boolean cancelled = false;
|
||||
private boolean handled = false;
|
||||
|
||||
@Override
|
||||
public void verifyHandled() {
|
||||
if (!handled && !isCancelled()) {
|
||||
throw new RuntimeException("Event not handled: " + this);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isCancelled() {
|
||||
return cancelled;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setCancelled(boolean cancel) {
|
||||
cancelled = cancel;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isHandled() {
|
||||
return handled;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setHandled() {
|
||||
handled = true;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
package io.github.mviper.bomberman.events;
|
||||
|
||||
import io.github.mviper.bomberman.messaging.Message;
|
||||
|
||||
public class BmIntentCancellableReasoned extends BmIntentCancellable implements IntentCancellableReasoned {
|
||||
private Message reason;
|
||||
|
||||
@Override
|
||||
public void cancelFor(Message reason) {
|
||||
this.reason = reason;
|
||||
setCancelled(true);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Message cancelledReason() {
|
||||
return reason;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,63 @@
|
||||
package io.github.mviper.bomberman.events;
|
||||
|
||||
import org.bukkit.Bukkit;
|
||||
import org.bukkit.entity.Player;
|
||||
import org.bukkit.event.HandlerList;
|
||||
|
||||
/**
|
||||
* Event that occurs whenever a player is standing on a bomb. Will be called every tick that the player remains on the
|
||||
* bomb.
|
||||
*/
|
||||
public class BmPlayerHitIntent extends BmEvent implements IntentCancellable {
|
||||
private static final HandlerList HANDLER_LIST = new HandlerList();
|
||||
|
||||
public final Player player;
|
||||
public final Player cause;
|
||||
|
||||
private final BmIntentCancellable delegate = new BmIntentCancellable();
|
||||
|
||||
private BmPlayerHitIntent(Player player, Player cause) {
|
||||
this.player = player;
|
||||
this.cause = cause;
|
||||
}
|
||||
|
||||
public static void hit(Player player, Player cause) {
|
||||
BmPlayerHitIntent event = new BmPlayerHitIntent(player, cause);
|
||||
Bukkit.getPluginManager().callEvent(event);
|
||||
event.verifyHandled();
|
||||
}
|
||||
|
||||
@Override
|
||||
public HandlerList getHandlers() {
|
||||
return HANDLER_LIST;
|
||||
}
|
||||
|
||||
public static HandlerList getHandlerList() {
|
||||
return HANDLER_LIST;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isHandled() {
|
||||
return delegate.isHandled();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setHandled() {
|
||||
delegate.setHandled();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void verifyHandled() {
|
||||
delegate.verifyHandled();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isCancelled() {
|
||||
return delegate.isCancelled();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setCancelled(boolean cancel) {
|
||||
delegate.setCancelled(cancel);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,65 @@
|
||||
package io.github.mviper.bomberman.events;
|
||||
|
||||
import io.github.mviper.bomberman.game.Game;
|
||||
import org.bukkit.Bukkit;
|
||||
import org.bukkit.entity.Player;
|
||||
import org.bukkit.event.HandlerList;
|
||||
|
||||
/**
|
||||
* Called whenever a bm player takes damage.
|
||||
*/
|
||||
public class BmPlayerHurtIntent extends BmEvent implements IntentCancellable {
|
||||
private static final HandlerList HANDLER_LIST = new HandlerList();
|
||||
|
||||
public final Game game;
|
||||
public final Player player;
|
||||
public final Player attacker;
|
||||
|
||||
private final BmIntentCancellable delegate = new BmIntentCancellable();
|
||||
|
||||
public BmPlayerHurtIntent(Game game, Player player, Player attacker) {
|
||||
this.game = game;
|
||||
this.player = player;
|
||||
this.attacker = attacker;
|
||||
}
|
||||
|
||||
public static void run(Game game, Player player, Player cause) {
|
||||
BmPlayerHurtIntent hurtEvent = new BmPlayerHurtIntent(game, player, cause);
|
||||
Bukkit.getPluginManager().callEvent(hurtEvent);
|
||||
hurtEvent.verifyHandled();
|
||||
}
|
||||
|
||||
@Override
|
||||
public HandlerList getHandlers() {
|
||||
return HANDLER_LIST;
|
||||
}
|
||||
|
||||
public static HandlerList getHandlerList() {
|
||||
return HANDLER_LIST;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isHandled() {
|
||||
return delegate.isHandled();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setHandled() {
|
||||
delegate.setHandled();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void verifyHandled() {
|
||||
delegate.verifyHandled();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isCancelled() {
|
||||
return delegate.isCancelled();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setCancelled(boolean cancel) {
|
||||
delegate.setCancelled(cancel);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,76 @@
|
||||
package io.github.mviper.bomberman.events;
|
||||
|
||||
import io.github.mviper.bomberman.game.Game;
|
||||
import io.github.mviper.bomberman.messaging.Message;
|
||||
import org.bukkit.Bukkit;
|
||||
import org.bukkit.entity.Player;
|
||||
import org.bukkit.event.HandlerList;
|
||||
|
||||
/**
|
||||
* Called whenever a player attempts to join a game. If there are not enough spawns in the game or the player cannot
|
||||
* afford entry, or ..., the event will be cancelled.
|
||||
*/
|
||||
public class BmPlayerJoinGameIntent extends BmEvent implements IntentCancellableReasoned {
|
||||
private static final HandlerList HANDLER_LIST = new HandlerList();
|
||||
|
||||
public final Game game;
|
||||
public final Player player;
|
||||
private final BmIntentCancellableReasoned delegate = new BmIntentCancellableReasoned();
|
||||
|
||||
private BmPlayerJoinGameIntent(Game game, Player player) {
|
||||
this.game = game;
|
||||
this.player = player;
|
||||
}
|
||||
|
||||
public static BmPlayerJoinGameIntent join(Game game, Player player) {
|
||||
BmPlayerJoinGameIntent event = new BmPlayerJoinGameIntent(game, player);
|
||||
Bukkit.getPluginManager().callEvent(event);
|
||||
event.verifyHandled();
|
||||
return event;
|
||||
}
|
||||
|
||||
@Override
|
||||
public HandlerList getHandlers() {
|
||||
return HANDLER_LIST;
|
||||
}
|
||||
|
||||
public static HandlerList getHandlerList() {
|
||||
return HANDLER_LIST;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isHandled() {
|
||||
return delegate.isHandled();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setHandled() {
|
||||
delegate.setHandled();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void verifyHandled() {
|
||||
delegate.verifyHandled();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isCancelled() {
|
||||
return delegate.isCancelled();
|
||||
}
|
||||
|
||||
@Override
|
||||
@Deprecated
|
||||
public void setCancelled(boolean cancel) {
|
||||
delegate.setCancelled(cancel);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void cancelFor(Message reason) {
|
||||
delegate.cancelFor(reason);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Message cancelledReason() {
|
||||
return delegate.cancelledReason();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,55 @@
|
||||
package io.github.mviper.bomberman.events;
|
||||
|
||||
import io.github.mviper.bomberman.game.Game;
|
||||
import org.bukkit.Bukkit;
|
||||
import org.bukkit.entity.Player;
|
||||
import org.bukkit.event.HandlerList;
|
||||
|
||||
/**
|
||||
* Called whenever a bm player is killed.
|
||||
*/
|
||||
public class BmPlayerKilledIntent extends BmEvent implements Intent {
|
||||
private static final HandlerList HANDLER_LIST = new HandlerList();
|
||||
|
||||
public final Game game;
|
||||
public final Player player;
|
||||
public final Player attacker;
|
||||
|
||||
private final BmIntent delegate = new BmIntent();
|
||||
|
||||
private BmPlayerKilledIntent(Game game, Player player, Player attacker) {
|
||||
this.game = game;
|
||||
this.player = player;
|
||||
this.attacker = attacker;
|
||||
}
|
||||
|
||||
public static void kill(Game game, Player player, Player cause) {
|
||||
BmPlayerKilledIntent event = new BmPlayerKilledIntent(game, player, cause);
|
||||
Bukkit.getPluginManager().callEvent(event);
|
||||
event.verifyHandled();
|
||||
}
|
||||
|
||||
@Override
|
||||
public HandlerList getHandlers() {
|
||||
return HANDLER_LIST;
|
||||
}
|
||||
|
||||
public static HandlerList getHandlerList() {
|
||||
return HANDLER_LIST;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isHandled() {
|
||||
return delegate.isHandled();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setHandled() {
|
||||
delegate.setHandled();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void verifyHandled() {
|
||||
delegate.verifyHandled();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,58 @@
|
||||
package io.github.mviper.bomberman.events;
|
||||
|
||||
import io.github.mviper.bomberman.game.Game;
|
||||
import org.bukkit.Bukkit;
|
||||
import org.bukkit.entity.Player;
|
||||
import org.bukkit.event.HandlerList;
|
||||
|
||||
public class BmPlayerLeaveGameIntent extends BmEvent implements Intent {
|
||||
private static final HandlerList HANDLER_LIST = new HandlerList();
|
||||
|
||||
public final Player player;
|
||||
private Game game;
|
||||
private final BmIntent delegate = new BmIntent();
|
||||
|
||||
private BmPlayerLeaveGameIntent(Player player) {
|
||||
this.player = player;
|
||||
}
|
||||
|
||||
public static BmPlayerLeaveGameIntent leave(Player player) {
|
||||
BmPlayerLeaveGameIntent leave = new BmPlayerLeaveGameIntent(player);
|
||||
Bukkit.getPluginManager().callEvent(leave);
|
||||
// Leave event may not be handled if player was not joined
|
||||
return leave;
|
||||
}
|
||||
|
||||
public Game getGame() {
|
||||
return game;
|
||||
}
|
||||
|
||||
public void setHandled(Game game) {
|
||||
this.game = game;
|
||||
setHandled();
|
||||
}
|
||||
|
||||
@Override
|
||||
public HandlerList getHandlers() {
|
||||
return HANDLER_LIST;
|
||||
}
|
||||
|
||||
public static HandlerList getHandlerList() {
|
||||
return HANDLER_LIST;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isHandled() {
|
||||
return delegate.isHandled();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setHandled() {
|
||||
delegate.setHandled();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void verifyHandled() {
|
||||
delegate.verifyHandled();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,42 @@
|
||||
package io.github.mviper.bomberman.events;
|
||||
|
||||
import io.github.mviper.bomberman.game.Game;
|
||||
import org.bukkit.Location;
|
||||
import org.bukkit.entity.Player;
|
||||
import org.bukkit.event.HandlerList;
|
||||
|
||||
/**
|
||||
* Called whenever a bm player moves. Cannot modify the event.
|
||||
*/
|
||||
public class BmPlayerMovedEvent extends BmEvent {
|
||||
private static final HandlerList HANDLER_LIST = new HandlerList();
|
||||
|
||||
public final Game game;
|
||||
public final Player player;
|
||||
private final Location from;
|
||||
private final Location to;
|
||||
|
||||
public BmPlayerMovedEvent(Game game, Player player, Location from, Location to) {
|
||||
this.game = game;
|
||||
this.player = player;
|
||||
this.from = from;
|
||||
this.to = to;
|
||||
}
|
||||
|
||||
public Location getFrom() {
|
||||
return from.clone();
|
||||
}
|
||||
|
||||
public Location getTo() {
|
||||
return to.clone();
|
||||
}
|
||||
|
||||
@Override
|
||||
public HandlerList getHandlers() {
|
||||
return HANDLER_LIST;
|
||||
}
|
||||
|
||||
public static HandlerList getHandlerList() {
|
||||
return HANDLER_LIST;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,50 @@
|
||||
package io.github.mviper.bomberman.events;
|
||||
|
||||
import io.github.mviper.bomberman.game.Game;
|
||||
import io.github.mviper.bomberman.game.GamePlayer;
|
||||
import org.bukkit.block.Block;
|
||||
import org.bukkit.entity.Player;
|
||||
import org.bukkit.event.HandlerList;
|
||||
|
||||
/**
|
||||
* Called when a player places down a block of TNT (or whatever the game configured as the tnt block). Cancelling the
|
||||
* event will remove the tnt from the ground as if the player never clicked.
|
||||
*/
|
||||
public class BmPlayerPlacedBombEvent extends BmEvent implements org.bukkit.event.Cancellable {
|
||||
private static final HandlerList HANDLER_LIST = new HandlerList();
|
||||
|
||||
public final Game game;
|
||||
public final Player player;
|
||||
public final Block block;
|
||||
public int fuse;
|
||||
public final int strength;
|
||||
|
||||
private final BmCancellable delegate = new BmCancellable();
|
||||
|
||||
public BmPlayerPlacedBombEvent(Game game, Player player, Block block, int fuse) {
|
||||
this.game = game;
|
||||
this.player = player;
|
||||
this.block = block;
|
||||
this.fuse = fuse;
|
||||
this.strength = GamePlayer.bombStrength(game, player);
|
||||
}
|
||||
|
||||
@Override
|
||||
public HandlerList getHandlers() {
|
||||
return HANDLER_LIST;
|
||||
}
|
||||
|
||||
public static HandlerList getHandlerList() {
|
||||
return HANDLER_LIST;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isCancelled() {
|
||||
return delegate.isCancelled();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setCancelled(boolean cancel) {
|
||||
delegate.setCancelled(cancel);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,26 @@
|
||||
package io.github.mviper.bomberman.events;
|
||||
|
||||
import io.github.mviper.bomberman.game.Game;
|
||||
import org.bukkit.entity.Player;
|
||||
import org.bukkit.event.HandlerList;
|
||||
|
||||
public class BmPlayerWonEvent extends BmEvent {
|
||||
private static final HandlerList HANDLER_LIST = new HandlerList();
|
||||
|
||||
public final Game game;
|
||||
public final Player player;
|
||||
|
||||
public BmPlayerWonEvent(Game game, Player player) {
|
||||
this.game = game;
|
||||
this.player = player;
|
||||
}
|
||||
|
||||
@Override
|
||||
public HandlerList getHandlers() {
|
||||
return HANDLER_LIST;
|
||||
}
|
||||
|
||||
public static HandlerList getHandlerList() {
|
||||
return HANDLER_LIST;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,76 @@
|
||||
package io.github.mviper.bomberman.events;
|
||||
|
||||
import io.github.mviper.bomberman.game.Game;
|
||||
import io.github.mviper.bomberman.messaging.Message;
|
||||
import org.bukkit.Bukkit;
|
||||
import org.bukkit.event.HandlerList;
|
||||
|
||||
/**
|
||||
* Called whenever a run is attempted to be started.
|
||||
*/
|
||||
public class BmRunStartCountDownIntent extends BmEvent implements IntentCancellable {
|
||||
private static final HandlerList HANDLER_LIST = new HandlerList();
|
||||
|
||||
public final Game game;
|
||||
public int delay;
|
||||
public final boolean override;
|
||||
|
||||
private final BmIntentCancellable delegate = new BmIntentCancellable();
|
||||
private Message cancelReason;
|
||||
|
||||
private BmRunStartCountDownIntent(Game game, int delay, boolean override) {
|
||||
this.game = game;
|
||||
this.delay = delay;
|
||||
this.override = override;
|
||||
}
|
||||
|
||||
public static BmRunStartCountDownIntent startGame(Game game, int delay, boolean override) {
|
||||
BmRunStartCountDownIntent event = new BmRunStartCountDownIntent(game, delay, override);
|
||||
Bukkit.getPluginManager().callEvent(event);
|
||||
event.verifyHandled();
|
||||
return event;
|
||||
}
|
||||
|
||||
@Override
|
||||
public HandlerList getHandlers() {
|
||||
return HANDLER_LIST;
|
||||
}
|
||||
|
||||
public static HandlerList getHandlerList() {
|
||||
return HANDLER_LIST;
|
||||
}
|
||||
|
||||
public Message getCancelledReason() {
|
||||
return cancelReason;
|
||||
}
|
||||
|
||||
public void cancelBecause(Message cancelReason) {
|
||||
this.cancelReason = cancelReason;
|
||||
setCancelled(true);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isHandled() {
|
||||
return delegate.isHandled();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setHandled() {
|
||||
delegate.setHandled();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void verifyHandled() {
|
||||
delegate.verifyHandled();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isCancelled() {
|
||||
return delegate.isCancelled();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setCancelled(boolean cancel) {
|
||||
delegate.setCancelled(cancel);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,49 @@
|
||||
package io.github.mviper.bomberman.events;
|
||||
|
||||
import io.github.mviper.bomberman.game.Game;
|
||||
import org.bukkit.Bukkit;
|
||||
import org.bukkit.event.HandlerList;
|
||||
|
||||
/**
|
||||
* Called whenever a run is attempted to be started.
|
||||
*/
|
||||
public class BmRunStartedIntent extends BmEvent implements Intent {
|
||||
private static final HandlerList HANDLER_LIST = new HandlerList();
|
||||
|
||||
public final Game game;
|
||||
private final BmIntent delegate = new BmIntent();
|
||||
|
||||
private BmRunStartedIntent(Game game) {
|
||||
this.game = game;
|
||||
}
|
||||
|
||||
public static void startRun(Game game) {
|
||||
BmRunStartedIntent event = new BmRunStartedIntent(game);
|
||||
Bukkit.getPluginManager().callEvent(event);
|
||||
event.verifyHandled();
|
||||
}
|
||||
|
||||
@Override
|
||||
public HandlerList getHandlers() {
|
||||
return HANDLER_LIST;
|
||||
}
|
||||
|
||||
public static HandlerList getHandlerList() {
|
||||
return HANDLER_LIST;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isHandled() {
|
||||
return delegate.isHandled();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setHandled() {
|
||||
delegate.setHandled();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void verifyHandled() {
|
||||
delegate.verifyHandled();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,72 @@
|
||||
package io.github.mviper.bomberman.events;
|
||||
|
||||
import io.github.mviper.bomberman.game.Game;
|
||||
import io.github.mviper.bomberman.messaging.Message;
|
||||
import org.bukkit.Bukkit;
|
||||
import org.bukkit.event.HandlerList;
|
||||
|
||||
/**
|
||||
* Called whenever a game run is stopped. May be due to game finishing, game forcefully stopped, server shutdown, etc.
|
||||
*/
|
||||
public class BmRunStoppedIntent extends BmEvent implements IntentCancellableReasoned {
|
||||
private static final HandlerList HANDLER_LIST = new HandlerList();
|
||||
|
||||
public final Game game;
|
||||
private final BmIntentCancellableReasoned delegate = new BmIntentCancellableReasoned();
|
||||
|
||||
private BmRunStoppedIntent(Game game) {
|
||||
this.game = game;
|
||||
}
|
||||
|
||||
public static BmRunStoppedIntent stopGame(Game game) {
|
||||
BmRunStoppedIntent event = new BmRunStoppedIntent(game);
|
||||
Bukkit.getPluginManager().callEvent(event);
|
||||
event.verifyHandled();
|
||||
return event;
|
||||
}
|
||||
|
||||
@Override
|
||||
public HandlerList getHandlers() {
|
||||
return HANDLER_LIST;
|
||||
}
|
||||
|
||||
public static HandlerList getHandlerList() {
|
||||
return HANDLER_LIST;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isHandled() {
|
||||
return delegate.isHandled();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setHandled() {
|
||||
delegate.setHandled();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void verifyHandled() {
|
||||
delegate.verifyHandled();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isCancelled() {
|
||||
return delegate.isCancelled();
|
||||
}
|
||||
|
||||
@Override
|
||||
@Deprecated
|
||||
public void setCancelled(boolean cancel) {
|
||||
delegate.setCancelled(cancel);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void cancelFor(Message reason) {
|
||||
delegate.cancelFor(reason);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Message cancelledReason() {
|
||||
return delegate.cancelledReason();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,28 @@
|
||||
package io.github.mviper.bomberman.events;
|
||||
|
||||
import io.github.mviper.bomberman.game.Game;
|
||||
import org.bukkit.event.HandlerList;
|
||||
|
||||
/**
|
||||
* Called whenever a game run is stopped. May be due to game finishing, game forcefully stopped, server shutdown, etc.
|
||||
*/
|
||||
public class BmTimerCountedEvent extends BmEvent {
|
||||
private static final HandlerList HANDLER_LIST = new HandlerList();
|
||||
|
||||
public final Game game;
|
||||
public int count;
|
||||
|
||||
public BmTimerCountedEvent(Game game, int count) {
|
||||
this.game = game;
|
||||
this.count = count;
|
||||
}
|
||||
|
||||
@Override
|
||||
public HandlerList getHandlers() {
|
||||
return HANDLER_LIST;
|
||||
}
|
||||
|
||||
public static HandlerList getHandlerList() {
|
||||
return HANDLER_LIST;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
package io.github.mviper.bomberman.events;
|
||||
|
||||
public interface Intent {
|
||||
boolean isHandled();
|
||||
|
||||
void setHandled();
|
||||
|
||||
void verifyHandled();
|
||||
}
|
||||
@@ -0,0 +1,6 @@
|
||||
package io.github.mviper.bomberman.events;
|
||||
|
||||
import org.bukkit.event.Cancellable;
|
||||
|
||||
public interface IntentCancellable extends Intent, Cancellable {
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
package io.github.mviper.bomberman.events;
|
||||
|
||||
import io.github.mviper.bomberman.messaging.Message;
|
||||
import org.bukkit.event.Cancellable;
|
||||
|
||||
public interface IntentCancellableReasoned extends Cancellable, Intent {
|
||||
@Deprecated
|
||||
@Override
|
||||
void setCancelled(boolean cancel);
|
||||
|
||||
void cancelFor(Message reason);
|
||||
|
||||
Message cancelledReason();
|
||||
}
|
||||
75
src/main/java/io/github/mviper/bomberman/game/Bomb.java
Normal file
75
src/main/java/io/github/mviper/bomberman/game/Bomb.java
Normal file
@@ -0,0 +1,75 @@
|
||||
package io.github.mviper.bomberman.game;
|
||||
|
||||
import io.github.mviper.bomberman.Bomberman;
|
||||
import io.github.mviper.bomberman.events.BmExplosionEvent;
|
||||
import io.github.mviper.bomberman.events.BmPlayerPlacedBombEvent;
|
||||
import io.github.mviper.bomberman.events.BmRunStoppedIntent;
|
||||
import org.bukkit.Bukkit;
|
||||
import org.bukkit.block.Block;
|
||||
import org.bukkit.entity.Player;
|
||||
import org.bukkit.event.EventHandler;
|
||||
import org.bukkit.event.EventPriority;
|
||||
import org.bukkit.event.HandlerList;
|
||||
import org.bukkit.event.Listener;
|
||||
import org.bukkit.plugin.Plugin;
|
||||
|
||||
public class Bomb implements Listener {
|
||||
private static final Plugin PLUGIN = Bomberman.instance;
|
||||
|
||||
private final Game game;
|
||||
private final Player player;
|
||||
private final Block block;
|
||||
private final int strength;
|
||||
private final int taskId;
|
||||
private boolean noExplode = false;
|
||||
|
||||
private Bomb(Game game, Player player, Block block, int strength, long fuse) {
|
||||
this.game = game;
|
||||
this.player = player;
|
||||
this.block = block;
|
||||
this.strength = strength;
|
||||
this.taskId = Bukkit.getScheduler().scheduleSyncDelayedTask(PLUGIN, this::explode, fuse);
|
||||
}
|
||||
|
||||
@EventHandler(ignoreCancelled = true, priority = EventPriority.HIGH)
|
||||
public void onExplosion(BmExplosionEvent event) {
|
||||
if (event.game != game) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!noExplode && event.igniting.stream().anyMatch(plan -> plan.block.equals(block))) {
|
||||
Bukkit.getScheduler().cancelTask(taskId);
|
||||
explode();
|
||||
}
|
||||
}
|
||||
|
||||
@EventHandler(ignoreCancelled = true, priority = EventPriority.HIGH)
|
||||
public void onRunStopped(BmRunStoppedIntent event) {
|
||||
if (event.game != game) {
|
||||
return;
|
||||
}
|
||||
HandlerList.unregisterAll(this);
|
||||
Bukkit.getScheduler().cancelTask(taskId);
|
||||
noExplode = true;
|
||||
}
|
||||
|
||||
private void explode() {
|
||||
HandlerList.unregisterAll(this);
|
||||
if (noExplode) {
|
||||
return;
|
||||
}
|
||||
noExplode = true;
|
||||
Explosion.spawnExplosion(game, block.getLocation(), player, strength);
|
||||
}
|
||||
|
||||
public static boolean spawnBomb(Game game, Player player, Block block) {
|
||||
BmPlayerPlacedBombEvent event = new BmPlayerPlacedBombEvent(game, player, block, game.getSettings().getFuseTicks());
|
||||
Bukkit.getPluginManager().callEvent(event);
|
||||
if (event.isCancelled()) {
|
||||
return false;
|
||||
}
|
||||
Bomb bomb = new Bomb(game, player, block, event.strength, event.fuse);
|
||||
Bukkit.getPluginManager().registerEvents(bomb, PLUGIN);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
243
src/main/java/io/github/mviper/bomberman/game/Explosion.java
Normal file
243
src/main/java/io/github/mviper/bomberman/game/Explosion.java
Normal file
@@ -0,0 +1,243 @@
|
||||
package io.github.mviper.bomberman.game;
|
||||
|
||||
import io.github.mviper.bomberman.Bomberman;
|
||||
import io.github.mviper.bomberman.events.BmDropLootEvent;
|
||||
import io.github.mviper.bomberman.events.BmExplosionEvent;
|
||||
import io.github.mviper.bomberman.events.BmPlayerHitIntent;
|
||||
import io.github.mviper.bomberman.events.BmPlayerMovedEvent;
|
||||
import io.github.mviper.bomberman.events.BmRunStoppedIntent;
|
||||
import org.bukkit.Bukkit;
|
||||
import org.bukkit.Location;
|
||||
import org.bukkit.Material;
|
||||
import org.bukkit.Sound;
|
||||
import org.bukkit.block.Block;
|
||||
import org.bukkit.block.BlockState;
|
||||
import org.bukkit.entity.Player;
|
||||
import org.bukkit.event.EventHandler;
|
||||
import org.bukkit.event.EventPriority;
|
||||
import org.bukkit.event.HandlerList;
|
||||
import org.bukkit.event.Listener;
|
||||
import org.bukkit.inventory.ItemStack;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.HashSet;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
|
||||
public class Explosion implements Listener {
|
||||
public static class BlockPlan {
|
||||
public final Block block;
|
||||
public final BlockState prior;
|
||||
public final BlockState ignited;
|
||||
public final BlockState destroyed;
|
||||
|
||||
public BlockPlan(Block block, BlockState prior, BlockState ignited, BlockState destroyed) {
|
||||
this.block = block;
|
||||
this.prior = prior;
|
||||
this.ignited = ignited;
|
||||
this.destroyed = destroyed;
|
||||
}
|
||||
}
|
||||
|
||||
private final Game game;
|
||||
private final Set<BlockPlan> blocks;
|
||||
private final Player cause;
|
||||
private final int taskId;
|
||||
private boolean noExplode = false;
|
||||
|
||||
private Explosion(Game game, Set<BlockPlan> blocks, Player cause) {
|
||||
this.game = game;
|
||||
this.blocks = blocks;
|
||||
this.cause = cause;
|
||||
this.taskId = Bukkit.getScheduler().scheduleSyncDelayedTask(
|
||||
Bomberman.instance,
|
||||
this::cleanup,
|
||||
game.getSettings().getFireTicks()
|
||||
);
|
||||
}
|
||||
|
||||
private void cleanup() {
|
||||
if (noExplode) {
|
||||
HandlerList.unregisterAll(this);
|
||||
return;
|
||||
}
|
||||
|
||||
for (BlockPlan plan : blocks) {
|
||||
if (plan.ignited.getType() == plan.block.getType()) {
|
||||
plan.destroyed.update(true);
|
||||
}
|
||||
}
|
||||
|
||||
if (cause.getScoreboardTags().contains("bm_player")) {
|
||||
cause.getInventory().addItem(new ItemStack(game.getSettings().getBombItem(), 1));
|
||||
}
|
||||
|
||||
Map<Location, Set<ItemStack>> dropsPlanned = planDrops();
|
||||
BmDropLootEvent lootEvent = new BmDropLootEvent(game, cause, blocks, dropsPlanned);
|
||||
Bukkit.getPluginManager().callEvent(lootEvent);
|
||||
if (!lootEvent.isCancelled()) {
|
||||
lootEvent.drops.forEach((location, items) -> {
|
||||
for (ItemStack item : items) {
|
||||
if (item.getAmount() > 0) {
|
||||
if (location.getWorld() != null) {
|
||||
location.getWorld().dropItem(location.clone().add(0.5, 0.5, 0.5), item);
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
HandlerList.unregisterAll(this);
|
||||
}
|
||||
|
||||
private Map<Location, Set<ItemStack>> planDrops() {
|
||||
Map<Material, Map<ItemStack, Integer>> loot = game.getSettings().getBlockLoot();
|
||||
Map<Location, Set<ItemStack>> planned = new HashMap<>();
|
||||
for (BlockPlan plan : blocks) {
|
||||
Map<ItemStack, Integer> table = loot.getOrDefault(plan.prior.getType(), Map.of());
|
||||
planned.put(plan.block.getLocation(), lootSelect(table));
|
||||
}
|
||||
return planned;
|
||||
}
|
||||
|
||||
@EventHandler(priority = EventPriority.LOWEST, ignoreCancelled = true)
|
||||
public void onPlayerMove(BmPlayerMovedEvent event) {
|
||||
if (event.game != game) {
|
||||
return;
|
||||
}
|
||||
Set<Block> fireBlocks = new HashSet<>();
|
||||
for (BlockPlan plan : blocks) {
|
||||
fireBlocks.add(plan.block);
|
||||
}
|
||||
if (isTouching(event.player, fireBlocks)) {
|
||||
BmPlayerHitIntent.hit(event.player, cause);
|
||||
}
|
||||
}
|
||||
|
||||
@EventHandler(priority = EventPriority.HIGH, ignoreCancelled = true)
|
||||
public void onAnotherExplosion(BmExplosionEvent event) {
|
||||
blocks.removeIf(thisBlock -> event.igniting.stream().anyMatch(plan -> plan.block.equals(thisBlock.block)));
|
||||
}
|
||||
|
||||
@EventHandler(ignoreCancelled = true, priority = EventPriority.HIGH)
|
||||
public void onRunStopped(BmRunStoppedIntent event) {
|
||||
if (event.game != game) {
|
||||
return;
|
||||
}
|
||||
Bukkit.getScheduler().cancelTask(taskId);
|
||||
HandlerList.unregisterAll(this);
|
||||
noExplode = true;
|
||||
}
|
||||
|
||||
public static boolean spawnExplosion(Game game, Location center, Player cause, int strength) {
|
||||
Set<Block> firePlanned = planFire(center, game, strength);
|
||||
Set<BlockPlan> plannedTypes = new HashSet<>();
|
||||
for (Block block : firePlanned) {
|
||||
BlockState prior = block.getState();
|
||||
BlockState ignited = block.getState();
|
||||
if (!game.getSettings().getPassKeep().contains(block.getType())) {
|
||||
ignited.setType(game.getSettings().getFireType());
|
||||
}
|
||||
BlockState converted = block.getState();
|
||||
if (!game.getSettings().getPassKeep().contains(block.getType())) {
|
||||
converted.setType(Material.AIR);
|
||||
}
|
||||
plannedTypes.add(new BlockPlan(block, prior, ignited, converted));
|
||||
}
|
||||
|
||||
BmExplosionEvent event = new BmExplosionEvent(game, cause, plannedTypes);
|
||||
Bukkit.getPluginManager().callEvent(event);
|
||||
if (event.isCancelled()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
for (BlockPlan plan : event.igniting) {
|
||||
plan.ignited.update(true);
|
||||
}
|
||||
if (center.getWorld() != null) {
|
||||
center.getWorld().playSound(center, Sound.ENTITY_GENERIC_EXPLODE, 1f, (float) Math.random() + 0.5f);
|
||||
}
|
||||
|
||||
Explosion explosion = new Explosion(game, event.igniting, cause);
|
||||
Bukkit.getPluginManager().registerEvents(explosion, Bomberman.instance);
|
||||
return true;
|
||||
}
|
||||
|
||||
public static boolean isTouching(Player player, Set<Block> blocks) {
|
||||
for (Block block : blocks) {
|
||||
double margin = 0.295;
|
||||
Location playerLocation = player.getLocation();
|
||||
Location min = block.getLocation().add(0.0, -1.0, 0.0);
|
||||
Location max = block.getLocation().add(1.0, 2.0, 1.0);
|
||||
if (playerLocation.getX() >= min.getX() - margin && playerLocation.getX() <= max.getX() + margin
|
||||
&& playerLocation.getY() >= min.getY() - margin && playerLocation.getY() <= max.getY() + margin
|
||||
&& playerLocation.getZ() >= min.getZ() - margin && playerLocation.getZ() <= max.getZ() + margin) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
private static Set<Block> planFire(Location center, Game game, int strength) {
|
||||
Set<Block> blocks = new HashSet<>();
|
||||
blocks.addAll(planFire(center, game, strength, 0, 1));
|
||||
blocks.addAll(planFire(center, game, strength, 0, -1));
|
||||
blocks.addAll(planFire(center, game, strength, 1, 0));
|
||||
blocks.addAll(planFire(center, game, strength, -1, 0));
|
||||
|
||||
planFire(center, game, 0, -1, 0, blocks);
|
||||
planFire(center, game, 0, 1, 0, blocks);
|
||||
blocks.add(center.getBlock());
|
||||
|
||||
return blocks;
|
||||
}
|
||||
|
||||
private static Set<Block> planFire(Location center, Game game, int strength, int xstep, int zstep) {
|
||||
Set<Block> blocks = new HashSet<>();
|
||||
for (int i = 1; i <= strength; i++) {
|
||||
planFire(center, game, i * xstep, 1, i * zstep, blocks);
|
||||
planFire(center, game, i * xstep, -1, i * zstep, blocks);
|
||||
if (planFire(center, game, i * xstep, 0, i * zstep, blocks)) {
|
||||
return blocks;
|
||||
}
|
||||
}
|
||||
return blocks;
|
||||
}
|
||||
|
||||
private static boolean planFire(Location center, Game game, int x, int y, int z, Set<Block> blocks) {
|
||||
Location location = center.clone().add((double) z, (double) y, (double) x);
|
||||
Block block = location.getBlock();
|
||||
|
||||
if (isPassing(block, game.getSettings())) {
|
||||
blocks.add(block);
|
||||
return false;
|
||||
}
|
||||
|
||||
if (game.getSettings().getDestructible().contains(block.getType())) {
|
||||
blocks.add(block);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
private static boolean isPassing(Block block, GameSettings settings) {
|
||||
Material type = block.getType();
|
||||
return type == Material.AIR || type == settings.getFireType()
|
||||
|| (block.isPassable() && !(settings.getIndestructible().contains(type) || settings.getDestructible().contains(type)))
|
||||
|| settings.getPassDestroy().contains(type)
|
||||
|| settings.getPassKeep().contains(type);
|
||||
}
|
||||
|
||||
public static <T> Set<T> lootSelect(Map<? extends T, Integer> loot) {
|
||||
int sum = loot.values().stream().mapToInt(Integer::intValue).sum();
|
||||
for (Map.Entry<? extends T, Integer> entry : loot.entrySet()) {
|
||||
if (sum * Math.random() <= entry.getValue()) {
|
||||
return Set.of(entry.getKey());
|
||||
}
|
||||
sum -= entry.getValue();
|
||||
}
|
||||
if (sum == 0) {
|
||||
return Set.of();
|
||||
}
|
||||
throw new RuntimeException("Explosion.drop didn't select (should never happen)");
|
||||
}
|
||||
}
|
||||
648
src/main/java/io/github/mviper/bomberman/game/Game.java
Normal file
648
src/main/java/io/github/mviper/bomberman/game/Game.java
Normal file
@@ -0,0 +1,648 @@
|
||||
package io.github.mviper.bomberman.game;
|
||||
|
||||
import com.sk89q.worldedit.WorldEdit;
|
||||
import com.sk89q.worldedit.bukkit.BukkitAdapter;
|
||||
import com.sk89q.worldedit.extent.clipboard.BlockArrayClipboard;
|
||||
import com.sk89q.worldedit.extent.clipboard.Clipboard;
|
||||
import com.sk89q.worldedit.function.mask.BlockTypeMask;
|
||||
import com.sk89q.worldedit.function.mask.Masks;
|
||||
import com.sk89q.worldedit.function.operation.ForwardExtentCopy;
|
||||
import com.sk89q.worldedit.function.operation.Operations;
|
||||
import com.sk89q.worldedit.math.BlockVector3;
|
||||
import com.sk89q.worldedit.session.ClipboardHolder;
|
||||
import com.sk89q.worldedit.world.block.BlockTypes;
|
||||
import io.github.mviper.bomberman.Bomberman;
|
||||
import io.github.mviper.bomberman.commands.game.UndoBuild;
|
||||
import io.github.mviper.bomberman.events.BmGameBuildIntent;
|
||||
import io.github.mviper.bomberman.events.BmGameDeletedIntent;
|
||||
import io.github.mviper.bomberman.events.BmGameListIntent;
|
||||
import io.github.mviper.bomberman.events.BmGameLookupIntent;
|
||||
import io.github.mviper.bomberman.events.BmGameTerminatedIntent;
|
||||
import io.github.mviper.bomberman.events.BmPlayerJoinGameIntent;
|
||||
import io.github.mviper.bomberman.events.BmPlayerLeaveGameIntent;
|
||||
import io.github.mviper.bomberman.events.BmPlayerMovedEvent;
|
||||
import io.github.mviper.bomberman.events.BmPlayerWonEvent;
|
||||
import io.github.mviper.bomberman.events.BmRunStartCountDownIntent;
|
||||
import io.github.mviper.bomberman.events.BmRunStartedIntent;
|
||||
import io.github.mviper.bomberman.events.BmRunStoppedIntent;
|
||||
import io.github.mviper.bomberman.messaging.CollectionWrapper;
|
||||
import io.github.mviper.bomberman.messaging.Context;
|
||||
import io.github.mviper.bomberman.messaging.Expander;
|
||||
import io.github.mviper.bomberman.messaging.Formattable;
|
||||
import io.github.mviper.bomberman.messaging.Message;
|
||||
import io.github.mviper.bomberman.messaging.RequiredArg;
|
||||
import io.github.mviper.bomberman.messaging.SenderWrapper;
|
||||
import io.github.mviper.bomberman.messaging.Text;
|
||||
import io.github.mviper.bomberman.utils.Box;
|
||||
import io.github.mviper.bomberman.utils.BukkitUtils;
|
||||
import io.github.mviper.bomberman.utils.WorldEditUtils;
|
||||
import org.bukkit.Bukkit;
|
||||
import org.bukkit.Location;
|
||||
import org.bukkit.Material;
|
||||
import org.bukkit.configuration.file.YamlConfiguration;
|
||||
import org.bukkit.entity.Player;
|
||||
import org.bukkit.event.EventHandler;
|
||||
import org.bukkit.event.EventPriority;
|
||||
import org.bukkit.event.HandlerList;
|
||||
import org.bukkit.event.Listener;
|
||||
import org.bukkit.event.entity.EntityDamageEvent;
|
||||
import org.bukkit.event.server.PluginDisableEvent;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.Locale;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
public class Game implements Formattable, Listener {
|
||||
private static final Bomberman PLUGIN = Bomberman.instance;
|
||||
|
||||
private final GameSave save;
|
||||
public final String name;
|
||||
private final Set<Player> players = new HashSet<>();
|
||||
private boolean running = false;
|
||||
private final YamlConfiguration tempData;
|
||||
private Box box;
|
||||
private Set<Location> spawns;
|
||||
|
||||
public Game(GameSave save) {
|
||||
this.save = save;
|
||||
this.name = save.name;
|
||||
|
||||
Path tempPath = tempDataFile(this);
|
||||
if (Files.exists(tempPath)) {
|
||||
try (var reader = Files.newBufferedReader(tempPath)) {
|
||||
tempData = YamlConfiguration.loadConfiguration(reader);
|
||||
} catch (IOException e) {
|
||||
throw new RuntimeException("Failed to read temp data", e);
|
||||
}
|
||||
} else {
|
||||
tempData = new YamlConfiguration();
|
||||
}
|
||||
|
||||
if (tempData.getBoolean("rebuild-needed", false)) {
|
||||
Bukkit.getScheduler().scheduleSyncDelayedTask(PLUGIN, () -> BmGameBuildIntent.build(this));
|
||||
}
|
||||
|
||||
Bukkit.getPluginManager().registerEvents(this, PLUGIN);
|
||||
}
|
||||
|
||||
public static Game buildGameFromRegion(String name, Box box, GameSettings settings) {
|
||||
var region = WorldEditUtils.convert(box);
|
||||
Clipboard clipboard = new BlockArrayClipboard(region);
|
||||
try (var editSession = WorldEdit.getInstance().newEditSession(BukkitAdapter.adapt(box.world))) {
|
||||
ForwardExtentCopy copy = new ForwardExtentCopy(editSession, region, clipboard, region.getMinimumPoint());
|
||||
try {
|
||||
Operations.complete(copy);
|
||||
} catch (com.sk89q.worldedit.WorldEditException e) {
|
||||
throw new RuntimeException("Failed to copy region", e);
|
||||
}
|
||||
}
|
||||
|
||||
GameSave save = GameSave.createNewSave(name, BukkitUtils.boxLoc1(box), settings, clipboard);
|
||||
UndoBuild.removeHistory(name);
|
||||
|
||||
Game game = new Game(save);
|
||||
game.makeCages();
|
||||
return game;
|
||||
}
|
||||
|
||||
public static Game buildGameFromSchema(String name, Location loc, Clipboard clipboard, GameSettings settings) {
|
||||
GameSave save = GameSave.createNewSave(name, loc, settings, clipboard);
|
||||
Game game = new Game(save);
|
||||
UndoBuild.retainHistory(game.name, game.getBox());
|
||||
BmGameBuildIntent.build(game);
|
||||
return game;
|
||||
}
|
||||
|
||||
private static Path tempDataFile(Game game) {
|
||||
return PLUGIN.tempGameData().resolve(GameSave.sanitize(game.name + ".yml"));
|
||||
}
|
||||
|
||||
public GameSettings getSettings() {
|
||||
try {
|
||||
return save.getSettings();
|
||||
} catch (IOException e) {
|
||||
throw new RuntimeException("Failed to load settings", e);
|
||||
}
|
||||
}
|
||||
|
||||
public String getName() {
|
||||
return name;
|
||||
}
|
||||
|
||||
public void setSettings(GameSettings value) {
|
||||
try {
|
||||
save.updateSettings(value);
|
||||
} catch (IOException e) {
|
||||
throw new RuntimeException("Failed to update settings", e);
|
||||
}
|
||||
}
|
||||
|
||||
public Location getOrigin() {
|
||||
return save.origin;
|
||||
}
|
||||
|
||||
public Clipboard getClipboard() throws IOException {
|
||||
return save.getSchematic();
|
||||
}
|
||||
|
||||
private Box getBox() {
|
||||
if (box == null) {
|
||||
try {
|
||||
box = WorldEditUtils.pastedBounds(getOrigin(), getClipboard());
|
||||
} catch (IOException e) {
|
||||
throw new RuntimeException("Failed to read schematic", e);
|
||||
}
|
||||
}
|
||||
return box;
|
||||
}
|
||||
|
||||
private Set<Location> getSpawns() {
|
||||
if (spawns == null) {
|
||||
List<?> list = tempData.getList("spawn-points");
|
||||
if (list != null) {
|
||||
Set<Location> result = new HashSet<>();
|
||||
for (Object item : list) {
|
||||
if (item instanceof Location) {
|
||||
result.add((Location) item);
|
||||
}
|
||||
}
|
||||
spawns = result;
|
||||
} else {
|
||||
spawns = searchSpawns();
|
||||
writeTempData("spawns", new ArrayList<>(spawns));
|
||||
}
|
||||
}
|
||||
return spawns;
|
||||
}
|
||||
|
||||
private void writeTempData(String path, Object obj) {
|
||||
tempData.set(path, obj);
|
||||
try (var writer = Files.newBufferedWriter(tempDataFile(this))) {
|
||||
writer.write(tempData.saveToString());
|
||||
} catch (IOException e) {
|
||||
throw new RuntimeException("Failed to write temp data", e);
|
||||
}
|
||||
}
|
||||
|
||||
private Set<Location> searchSpawns() {
|
||||
PLUGIN.getLogger().info("Searching for spawns...");
|
||||
Set<Location> result = new HashSet<>();
|
||||
try {
|
||||
Clipboard clipboard = getClipboard();
|
||||
for (BlockVector3 loc : clipboard.getRegion()) {
|
||||
var block = clipboard.getFullBlock(loc);
|
||||
var nbt = block.getNbtData();
|
||||
if (nbt != null) {
|
||||
boolean isSpawn = nbt.getString("Text1").contains("[spawn]")
|
||||
|| nbt.getString("Text2").contains("[spawn]")
|
||||
|| nbt.getString("Text3").contains("[spawn]")
|
||||
|| nbt.getString("Text4").contains("[spawn]");
|
||||
if (isSpawn) {
|
||||
Location worldLocation = BukkitAdapter.adapt(getBox().world, loc.subtract(clipboard.getOrigin()))
|
||||
.add(getOrigin()).getBlock().getLocation();
|
||||
result.add(worldLocation);
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch (IOException e) {
|
||||
throw new RuntimeException("Failed to search spawns", e);
|
||||
}
|
||||
PLUGIN.getLogger().info(" " + result.size() + " spawns found");
|
||||
return result;
|
||||
}
|
||||
|
||||
private Location findSpareSpawn() {
|
||||
for (Location spawn : getSpawns()) {
|
||||
boolean occupied = false;
|
||||
for (Player player : players) {
|
||||
Location loc = player.getLocation();
|
||||
if (spawn.getBlockX() == loc.getBlockX()
|
||||
&& spawn.getBlockY() == loc.getBlockY()
|
||||
&& spawn.getBlockZ() == loc.getBlockZ()) {
|
||||
occupied = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (!occupied) {
|
||||
return spawn;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
private void removeCages() {
|
||||
try {
|
||||
try (var editSession = WorldEdit.getInstance().newEditSession(BukkitAdapter.adapt(save.origin.getWorld()))) {
|
||||
BlockVector3 offset = BlockVector3.at(save.origin.getX(), save.origin.getY(), save.origin.getZ())
|
||||
.subtract(save.getSchematic().getOrigin());
|
||||
for (SpawnBlock entry : spawnBlocks()) {
|
||||
if (!entry.isSpawn) {
|
||||
Location loc = entry.block.getLocation();
|
||||
BlockVector3 blockVec = BlockVector3.at(loc.getX(), loc.getY(), loc.getZ());
|
||||
BlockVector3 clipLocation = blockVec.subtract(offset);
|
||||
var blockState = save.getSchematic().getFullBlock(clipLocation);
|
||||
editSession.setBlock(blockVec, blockState);
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch (IOException | com.sk89q.worldedit.WorldEditException e) {
|
||||
throw new RuntimeException("Failed to remove cages", e);
|
||||
}
|
||||
}
|
||||
|
||||
private void makeCages() {
|
||||
for (SpawnBlock entry : spawnBlocks()) {
|
||||
if (entry.isSpawn) {
|
||||
entry.block.setType(Material.AIR);
|
||||
} else if (entry.block.isPassable()) {
|
||||
entry.block.setType(getSettings().getCageBlock());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private List<SpawnBlock> spawnBlocks() {
|
||||
List<SpawnBlock> blocks = new ArrayList<>();
|
||||
for (Location location : getSpawns()) {
|
||||
for (int i = -1; i <= 1; i++) {
|
||||
for (int j = -1; j <= 2; j++) {
|
||||
for (int k = -1; k <= 1; k++) {
|
||||
Location blockLoc = location.clone().add(i, j, k);
|
||||
if (!getBox().contains(blockLoc)) {
|
||||
continue;
|
||||
}
|
||||
var block = blockLoc.getBlock();
|
||||
if ((j == 0 || j == 1) && (i == 0 && k == 0)) {
|
||||
blocks.add(new SpawnBlock(true, block));
|
||||
} else if (((j == 0 || j == 1) && (i == 0 || k == 0)) || (i == 0 && k == 0)) {
|
||||
blocks.add(new SpawnBlock(false, block));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return blocks;
|
||||
}
|
||||
|
||||
@EventHandler(ignoreCancelled = true, priority = EventPriority.LOW)
|
||||
public void onGameListing(BmGameListIntent event) {
|
||||
event.games.add(this);
|
||||
}
|
||||
|
||||
@EventHandler(ignoreCancelled = true, priority = EventPriority.LOW)
|
||||
public void onGameLookup(BmGameLookupIntent event) {
|
||||
if (event.name.equalsIgnoreCase(name)) {
|
||||
event.game = this;
|
||||
}
|
||||
}
|
||||
|
||||
@EventHandler(ignoreCancelled = true, priority = EventPriority.HIGHEST)
|
||||
public void onPlayerJoinGame(BmPlayerJoinGameIntent event) {
|
||||
if (event.game != this) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (running) {
|
||||
event.cancelFor(Text.GAME_ALREADY_STARTED.format(new Context(false)
|
||||
.plus("game", this)
|
||||
.plus("player", event.player)));
|
||||
return;
|
||||
}
|
||||
|
||||
Location gameSpawn = findSpareSpawn();
|
||||
if (gameSpawn == null) {
|
||||
event.cancelFor(Text.JOIN_GAME_FULL.format(new Context(false)
|
||||
.plus("game", this)
|
||||
.plus("player", event.player)));
|
||||
return;
|
||||
}
|
||||
|
||||
GamePlayer.spawnGamePlayer(event.player, this, gameSpawn);
|
||||
players.add(event.player);
|
||||
event.setHandled();
|
||||
}
|
||||
|
||||
@EventHandler(ignoreCancelled = true, priority = EventPriority.LOWEST)
|
||||
public void onPlayerDamaged(EntityDamageEvent event) {
|
||||
if (!players.contains(event.getEntity())) {
|
||||
return;
|
||||
}
|
||||
|
||||
Map<String, Map<String, String>> damageSources = getSettings().getDamageSources();
|
||||
List<Map<String, String>> damageChanges = new ArrayList<>();
|
||||
for (Map.Entry<String, Map<String, String>> entry : damageSources.entrySet()) {
|
||||
if (Pattern.compile(entry.getKey(), Pattern.CASE_INSENSITIVE)
|
||||
.matcher(event.getCause().toString()).matches()) {
|
||||
damageChanges.add(entry.getValue());
|
||||
}
|
||||
}
|
||||
|
||||
if (damageChanges.isEmpty() && event.getCause() != EntityDamageEvent.DamageCause.CUSTOM) {
|
||||
event.setCancelled(true);
|
||||
return;
|
||||
}
|
||||
|
||||
for (Map<String, String> change : damageChanges) {
|
||||
String cancelExpression = null;
|
||||
for (Map.Entry<String, String> rule : change.entrySet()) {
|
||||
if (rule.getKey().equalsIgnoreCase("cancel")) {
|
||||
cancelExpression = rule.getValue();
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (cancelExpression == null) {
|
||||
continue;
|
||||
}
|
||||
String result = Expander.expand(cancelExpression, new Context(false)
|
||||
.plus("base", Message.of(event.getDamage()))
|
||||
.plus("final", Message.of(event.getFinalDamage()))
|
||||
.plus("cause", Message.of(event.getCause().toString()))
|
||||
.plus("player", new SenderWrapper((Player) event.getEntity()))
|
||||
.plus("game", this)).toString();
|
||||
try {
|
||||
double asDouble = Double.parseDouble(result);
|
||||
if (asDouble > 0.000001 || asDouble < -0.000001) {
|
||||
event.setCancelled(true);
|
||||
return;
|
||||
}
|
||||
} catch (NumberFormatException ignored) {
|
||||
}
|
||||
}
|
||||
|
||||
for (Map<String, String> rules : damageChanges) {
|
||||
for (EntityDamageEvent.DamageModifier modifier : EntityDamageEvent.DamageModifier.values()) {
|
||||
if (!event.isApplicable(modifier)) {
|
||||
continue;
|
||||
}
|
||||
for (Map.Entry<String, String> rule : rules.entrySet()) {
|
||||
if (!Pattern.compile(rule.getKey(), Pattern.CASE_INSENSITIVE)
|
||||
.matcher(modifier.toString()).matches()) {
|
||||
continue;
|
||||
}
|
||||
var result = Expander.expand(rule.getValue(), new Context(false)
|
||||
.plus("base", Message.of(event.getDamage()))
|
||||
.plus("damage", Message.of(event.getDamage(modifier)))
|
||||
.plus("final", Message.of(event.getFinalDamage()))
|
||||
.plus("cause", Message.of(event.getCause().toString()))
|
||||
.plus("player", new SenderWrapper((Player) event.getEntity()))
|
||||
.plus("game", this)
|
||||
.plus("modifier", Message.of(modifier.toString())));
|
||||
try {
|
||||
double damage = Double.parseDouble(result.toString());
|
||||
event.setDamage(modifier, damage);
|
||||
} catch (NumberFormatException ignored) {
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@EventHandler(ignoreCancelled = true, priority = EventPriority.HIGHEST)
|
||||
public void onRunStartCountDown(BmRunStartCountDownIntent event) {
|
||||
if (event.game != this) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (running) {
|
||||
event.cancelBecause(Text.GAME_ALREADY_STARTED.format(new Context(false).plus("game", this)));
|
||||
return;
|
||||
}
|
||||
|
||||
if (players.isEmpty()) {
|
||||
event.cancelBecause(Text.GAME_NO_PLAYERS.format(new Context(false).plus("game", this)));
|
||||
return;
|
||||
}
|
||||
|
||||
StartTimer.createTimer(this, event.delay);
|
||||
event.setHandled();
|
||||
}
|
||||
|
||||
@EventHandler(ignoreCancelled = true, priority = EventPriority.HIGH)
|
||||
public void onRunStarted(BmRunStartedIntent event) {
|
||||
if (event.game != this) {
|
||||
return;
|
||||
}
|
||||
running = true;
|
||||
removeCages();
|
||||
GameProtection.protect(this, getBox());
|
||||
writeTempData("rebuild-needed", true);
|
||||
event.setHandled();
|
||||
}
|
||||
|
||||
@EventHandler(ignoreCancelled = true, priority = EventPriority.NORMAL)
|
||||
public void onPlayerMoveOutOfArena(BmPlayerMovedEvent event) {
|
||||
if (event.game != this) {
|
||||
return;
|
||||
}
|
||||
if (!getBox().contains(event.getTo())) {
|
||||
BmPlayerLeaveGameIntent.leave(event.player);
|
||||
}
|
||||
}
|
||||
|
||||
@EventHandler(ignoreCancelled = true, priority = EventPriority.HIGHEST)
|
||||
public void onPlayerLeave(BmPlayerLeaveGameIntent event) {
|
||||
if (players.contains(event.player)) {
|
||||
players.remove(event.player);
|
||||
if (players.size() < 1 || !running) {
|
||||
Bukkit.getScheduler().scheduleSyncDelayedTask(PLUGIN, () -> BmRunStoppedIntent.stopGame(this));
|
||||
} else if (players.size() == 1) {
|
||||
for (Player player : players) {
|
||||
Bukkit.getPluginManager().callEvent(new BmPlayerWonEvent(this, player));
|
||||
}
|
||||
Bukkit.getScheduler().scheduleSyncDelayedTask(PLUGIN, () -> BmRunStoppedIntent.stopGame(this), 5 * 20L);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@EventHandler(ignoreCancelled = true, priority = EventPriority.NORMAL)
|
||||
public void onRunStoppedWhileRunning(BmRunStoppedIntent event) {
|
||||
if (event.game != this) {
|
||||
return;
|
||||
}
|
||||
if (!running) {
|
||||
event.cancelFor(Text.STOP_NOT_STARTED.format(new Context(false).plus("game", this)));
|
||||
}
|
||||
}
|
||||
|
||||
@EventHandler(ignoreCancelled = true, priority = EventPriority.HIGH)
|
||||
public void onRunStopped(BmRunStoppedIntent event) {
|
||||
if (event.game != this) {
|
||||
return;
|
||||
}
|
||||
if (running) {
|
||||
running = false;
|
||||
BmGameBuildIntent.build(this);
|
||||
}
|
||||
event.setHandled();
|
||||
}
|
||||
|
||||
@EventHandler(ignoreCancelled = true, priority = EventPriority.HIGH)
|
||||
public void onGameRebuild(BmGameBuildIntent event) {
|
||||
if (event.game != this) {
|
||||
return;
|
||||
}
|
||||
|
||||
PLUGIN.getLogger().info("Building schematic ...");
|
||||
|
||||
getBox().world.getNearbyEntities(BukkitUtils.convert(getBox()))
|
||||
.stream()
|
||||
.filter(entity -> !(entity instanceof Player))
|
||||
.forEach(entity -> entity.remove());
|
||||
|
||||
try (var editSession = WorldEdit.getInstance().newEditSession(BukkitAdapter.adapt(getBox().world))) {
|
||||
var operation = new ClipboardHolder(getClipboard())
|
||||
.createPaste(editSession)
|
||||
.to(BlockVector3.at(getOrigin().getBlockX(), getOrigin().getBlockY(), getOrigin().getBlockZ()))
|
||||
.copyEntities(true)
|
||||
.maskSource(Masks.negate(new BlockTypeMask(editSession,
|
||||
getSettings().getSourceMask().stream()
|
||||
.map(mat -> BlockTypes.get(mat.getKey().toString()))
|
||||
.filter(obj -> obj != null)
|
||||
.toList())))
|
||||
.build();
|
||||
Operations.complete(operation);
|
||||
} catch (IOException | com.sk89q.worldedit.WorldEditException e) {
|
||||
throw new RuntimeException("Failed to rebuild game", e);
|
||||
}
|
||||
makeCages();
|
||||
|
||||
PLUGIN.getLogger().info("Rebuild done");
|
||||
writeTempData("rebuild-needed", false);
|
||||
event.setHandled();
|
||||
}
|
||||
|
||||
@EventHandler(ignoreCancelled = true, priority = EventPriority.HIGH)
|
||||
public void onGameTerminated(BmGameTerminatedIntent event) {
|
||||
if (event.game != this) {
|
||||
return;
|
||||
}
|
||||
BmRunStoppedIntent.stopGame(this);
|
||||
HandlerList.unregisterAll(this);
|
||||
event.setHandled();
|
||||
}
|
||||
|
||||
@EventHandler(ignoreCancelled = true, priority = EventPriority.HIGH)
|
||||
public void onGameDeleted(BmGameDeletedIntent event) {
|
||||
if (event.game != this) {
|
||||
return;
|
||||
}
|
||||
PLUGIN.getLogger().info("Deleting " + name + (event.isDeletingSave ? "" : " (keeping data)"));
|
||||
BmGameTerminatedIntent.terminateGame(this);
|
||||
try {
|
||||
Files.deleteIfExists(tempDataFile(this));
|
||||
} catch (IOException e) {
|
||||
PLUGIN.getLogger().warning("Unable to delete temp data for " + name);
|
||||
}
|
||||
if (event.isDeletingSave) {
|
||||
try {
|
||||
Files.deleteIfExists(PLUGIN.gameSaves().resolve(GameSave.sanitize(name + ".game.zip")));
|
||||
} catch (IOException e) {
|
||||
PLUGIN.getLogger().warning("Unable to delete save for " + name);
|
||||
}
|
||||
}
|
||||
event.setHandled();
|
||||
}
|
||||
|
||||
@EventHandler(ignoreCancelled = true, priority = EventPriority.HIGH)
|
||||
public void onServerStop(PluginDisableEvent event) {
|
||||
if (event.getPlugin() != PLUGIN) {
|
||||
return;
|
||||
}
|
||||
BmGameTerminatedIntent.terminateGame(this);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Formattable applyModifier(Message arg) {
|
||||
String key = arg.toString().toLowerCase(Locale.ROOT);
|
||||
switch (key) {
|
||||
case "name":
|
||||
return Message.of(name);
|
||||
case "spawns": {
|
||||
List<Formattable> spawnList = new ArrayList<>();
|
||||
for (Location location : getSpawns()) {
|
||||
spawnList.add(new RequiredArg(spawnArg -> {
|
||||
String spawnKey = spawnArg.toString().toLowerCase(Locale.ROOT);
|
||||
return switch (spawnKey) {
|
||||
case "world", "w" -> Message.of(location.getWorld() != null ? location.getWorld().getName() : "unknown");
|
||||
case "x" -> Message.of((int) location.getX());
|
||||
case "y" -> Message.of((int) location.getY());
|
||||
case "z" -> Message.of((int) location.getZ());
|
||||
default -> throw new IllegalArgumentException("Unknown spawn format " + spawnArg);
|
||||
};
|
||||
}));
|
||||
}
|
||||
return new CollectionWrapper<>(spawnList);
|
||||
}
|
||||
case "players": {
|
||||
List<Formattable> playerList = new ArrayList<>();
|
||||
for (Player player : players) {
|
||||
playerList.add(new SenderWrapper(player));
|
||||
}
|
||||
return new CollectionWrapper<>(playerList);
|
||||
}
|
||||
case "power": {
|
||||
int sum = 0;
|
||||
for (var item : getSettings().getInitialItems()) {
|
||||
if (item != null && item.getType() == getSettings().getBombItem()) {
|
||||
sum += item.getAmount();
|
||||
}
|
||||
}
|
||||
return Message.of(sum);
|
||||
}
|
||||
case "bombs": {
|
||||
int sum = 0;
|
||||
for (var item : getSettings().getInitialItems()) {
|
||||
if (item != null && item.getType() == getSettings().getPowerItem()) {
|
||||
sum += item.getAmount();
|
||||
}
|
||||
}
|
||||
return Message.of(sum);
|
||||
}
|
||||
case "lives":
|
||||
return Message.of(Integer.toString(getSettings().getLives()));
|
||||
case "w":
|
||||
case "world":
|
||||
return Message.of(getOrigin().getWorld() != null ? getOrigin().getWorld().getName() : "unknown");
|
||||
case "x":
|
||||
return Message.of((int) getOrigin().getX());
|
||||
case "y":
|
||||
return Message.of((int) getOrigin().getY());
|
||||
case "z":
|
||||
return Message.of((int) getOrigin().getZ());
|
||||
case "xsize":
|
||||
return Message.of(getBox().getSize().x);
|
||||
case "ysize":
|
||||
return Message.of(getBox().getSize().y);
|
||||
case "zsize":
|
||||
return Message.of(getBox().getSize().z);
|
||||
case "running":
|
||||
return Message.of(running ? "true" : "false");
|
||||
case "schema":
|
||||
return this;
|
||||
default:
|
||||
return Message.empty;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public Message format(Context context) {
|
||||
return applyModifier(Message.of("name")).format(context);
|
||||
}
|
||||
|
||||
private static final class SpawnBlock {
|
||||
private final boolean isSpawn;
|
||||
private final org.bukkit.block.Block block;
|
||||
|
||||
private SpawnBlock(boolean isSpawn, org.bukkit.block.Block block) {
|
||||
this.isSpawn = isSpawn;
|
||||
this.block = block;
|
||||
}
|
||||
}
|
||||
}
|
||||
465
src/main/java/io/github/mviper/bomberman/game/GamePlayer.java
Normal file
465
src/main/java/io/github/mviper/bomberman/game/GamePlayer.java
Normal file
@@ -0,0 +1,465 @@
|
||||
package io.github.mviper.bomberman.game;
|
||||
|
||||
import com.sk89q.jnbt.StringTag;
|
||||
import com.sk89q.worldedit.bukkit.BukkitAdapter;
|
||||
import io.github.mviper.bomberman.Bomberman;
|
||||
import io.github.mviper.bomberman.events.BmGameTerminatedIntent;
|
||||
import io.github.mviper.bomberman.events.BmPlayerHitIntent;
|
||||
import io.github.mviper.bomberman.events.BmPlayerHurtIntent;
|
||||
import io.github.mviper.bomberman.events.BmPlayerJoinGameIntent;
|
||||
import io.github.mviper.bomberman.events.BmPlayerKilledIntent;
|
||||
import io.github.mviper.bomberman.events.BmPlayerLeaveGameIntent;
|
||||
import io.github.mviper.bomberman.events.BmPlayerMovedEvent;
|
||||
import io.github.mviper.bomberman.events.BmPlayerWonEvent;
|
||||
import io.github.mviper.bomberman.events.BmRunStartedIntent;
|
||||
import io.github.mviper.bomberman.events.BmRunStoppedIntent;
|
||||
import io.github.mviper.bomberman.events.BmTimerCountedEvent;
|
||||
import io.github.mviper.bomberman.messaging.Context;
|
||||
import io.github.mviper.bomberman.messaging.Text;
|
||||
import org.bukkit.Bukkit;
|
||||
import org.bukkit.GameMode;
|
||||
import org.bukkit.Location;
|
||||
import org.bukkit.attribute.Attribute;
|
||||
import org.bukkit.block.Block;
|
||||
import org.bukkit.configuration.file.YamlConfiguration;
|
||||
import org.bukkit.entity.Item;
|
||||
import org.bukkit.entity.Player;
|
||||
import org.bukkit.event.Event;
|
||||
import org.bukkit.event.EventHandler;
|
||||
import org.bukkit.event.EventPriority;
|
||||
import org.bukkit.event.HandlerList;
|
||||
import org.bukkit.event.Listener;
|
||||
import org.bukkit.event.block.Action;
|
||||
import org.bukkit.event.block.BlockPlaceEvent;
|
||||
import org.bukkit.event.entity.EntityRegainHealthEvent;
|
||||
import org.bukkit.event.player.PlayerInteractEvent;
|
||||
import org.bukkit.event.player.PlayerJoinEvent;
|
||||
import org.bukkit.event.player.PlayerMoveEvent;
|
||||
import org.bukkit.event.player.PlayerQuitEvent;
|
||||
import org.bukkit.event.player.PlayerRespawnEvent;
|
||||
import org.bukkit.inventory.ItemStack;
|
||||
import org.bukkit.potion.PotionEffect;
|
||||
import org.bukkit.potion.PotionEffectType;
|
||||
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Locale;
|
||||
import java.util.Set;
|
||||
import java.util.logging.Level;
|
||||
|
||||
public class GamePlayer implements Listener {
|
||||
private static final Bomberman PLUGIN = Bomberman.instance;
|
||||
|
||||
public static void spawnGamePlayer(Player player, Game game, Location start) {
|
||||
GamePlayer gamePlayer = new GamePlayer(player, game);
|
||||
PLUGIN.getServer().getPluginManager().registerEvents(gamePlayer, PLUGIN);
|
||||
|
||||
YamlConfiguration dataFile = new YamlConfiguration();
|
||||
dataFile.set("location", player.getLocation());
|
||||
dataFile.set("gamemode", player.getGameMode().name().toLowerCase(Locale.ROOT));
|
||||
dataFile.set("health", player.getHealth());
|
||||
dataFile.set("health-scale", player.getHealthScale());
|
||||
dataFile.set("health-max", player.getAttribute(Attribute.GENERIC_MAX_HEALTH).getBaseValue());
|
||||
dataFile.set("food-level", player.getFoodLevel());
|
||||
dataFile.set("inventory", java.util.Arrays.asList(player.getInventory().getContents()));
|
||||
dataFile.set("is-flying", player.isFlying());
|
||||
try (var writer = Files.newBufferedWriter(tempDataFile(player))) {
|
||||
writer.write(dataFile.saveToString());
|
||||
} catch (Exception e) {
|
||||
PLUGIN.getLogger().log(Level.WARNING, "Unable to store player data", e);
|
||||
}
|
||||
|
||||
try {
|
||||
player.addAttachment(PLUGIN, "group.bomberman", true);
|
||||
} catch (Exception e) {
|
||||
PLUGIN.getLogger().log(Level.WARNING, "Unable to add permissions", e);
|
||||
}
|
||||
|
||||
start.getWorld().getNearbyEntities(start, 2.0, 3.0, 2.0).stream()
|
||||
.filter(entity -> entity instanceof Item)
|
||||
.forEach(entity -> entity.remove());
|
||||
|
||||
var maxHealth = player.getAttribute(Attribute.GENERIC_MAX_HEALTH);
|
||||
maxHealth.setBaseValue(game.getSettings().getLives());
|
||||
maxHealth.getModifiers().forEach(maxHealth::removeModifier);
|
||||
player.setHealth(game.getSettings().getLives());
|
||||
Bukkit.getScheduler().scheduleSyncDelayedTask(PLUGIN, () ->
|
||||
player.setHealthScale(game.getSettings().getLives() * 2.0)
|
||||
);
|
||||
|
||||
if (!player.teleport(start.clone().add(0.5, 0.01, 0.5))) {
|
||||
Bukkit.getScheduler().scheduleSyncDelayedTask(PLUGIN, () ->
|
||||
BmPlayerLeaveGameIntent.leave(player)
|
||||
);
|
||||
}
|
||||
player.setGameMode(GameMode.SURVIVAL);
|
||||
player.setExhaustion(0f);
|
||||
player.setFoodLevel(100000);
|
||||
player.setFlying(false);
|
||||
player.getInventory().clear();
|
||||
List<ItemStack> initialItems = game.getSettings().getInitialItems();
|
||||
for (int i = 0; i < initialItems.size() && i < player.getInventory().getSize(); i++) {
|
||||
ItemStack stack = initialItems.get(i);
|
||||
player.getInventory().setItem(i, stack != null ? stack.clone() : null);
|
||||
}
|
||||
removePotionEffects(player);
|
||||
|
||||
player.addScoreboardTag("bm_player");
|
||||
}
|
||||
|
||||
public static int bombStrength(Game game, Player player) {
|
||||
int strength = 1;
|
||||
for (ItemStack stack : player.getInventory().getContents()) {
|
||||
if (stack != null && stack.getType() == game.getSettings().getPowerItem()) {
|
||||
strength += stack.getAmount();
|
||||
}
|
||||
}
|
||||
return Math.max(strength, 1);
|
||||
}
|
||||
|
||||
public static void setupLoginWatcher() {
|
||||
Bukkit.getPluginManager().registerEvents(new Listener() {
|
||||
@EventHandler(ignoreCancelled = true, priority = EventPriority.LOW)
|
||||
public void onPlayerLogin(PlayerJoinEvent event) {
|
||||
Player player = event.getPlayer();
|
||||
if (player.isDead()) {
|
||||
return;
|
||||
}
|
||||
Path save = tempDataFile(player);
|
||||
if (Files.exists(save)) {
|
||||
reset(player);
|
||||
}
|
||||
}
|
||||
|
||||
@EventHandler(ignoreCancelled = true, priority = EventPriority.LOW)
|
||||
public void onPlayerRespawn(PlayerRespawnEvent event) {
|
||||
Player player = event.getPlayer();
|
||||
Path save = tempDataFile(player);
|
||||
if (Files.exists(save)) {
|
||||
event.setRespawnLocation(reset(player));
|
||||
}
|
||||
}
|
||||
}, PLUGIN);
|
||||
}
|
||||
|
||||
private static Location reset(Player player) {
|
||||
Path file = tempDataFile(player);
|
||||
YamlConfiguration dataFile;
|
||||
try (var reader = Files.newBufferedReader(file)) {
|
||||
dataFile = YamlConfiguration.loadConfiguration(reader);
|
||||
} catch (Exception e) {
|
||||
throw new RuntimeException("Unable to load player data", e);
|
||||
}
|
||||
|
||||
String gamemode = dataFile.getString("gamemode");
|
||||
GameMode mode = GameMode.SURVIVAL;
|
||||
if (gamemode != null) {
|
||||
try {
|
||||
mode = GameMode.valueOf(gamemode.toUpperCase(Locale.ROOT));
|
||||
} catch (IllegalArgumentException ignored) {
|
||||
}
|
||||
}
|
||||
player.setGameMode(mode);
|
||||
player.setHealthScale(dataFile.getDouble("health-scale", 20));
|
||||
|
||||
var maxHealth = player.getAttribute(Attribute.GENERIC_MAX_HEALTH);
|
||||
maxHealth.setBaseValue(dataFile.getDouble("health-max", 20.0));
|
||||
maxHealth.getModifiers().forEach(maxHealth::removeModifier);
|
||||
double health = dataFile.getDouble("health", 20.0);
|
||||
player.setHealth(Math.min(health, maxHealth.getValue()));
|
||||
|
||||
player.setFoodLevel(dataFile.getInt("food-level", 20));
|
||||
|
||||
List<?> inventory = dataFile.getList("inventory", new ArrayList<>());
|
||||
ItemStack[] contents = new ItemStack[inventory.size()];
|
||||
for (int i = 0; i < inventory.size(); i++) {
|
||||
Object item = inventory.get(i);
|
||||
contents[i] = item instanceof ItemStack ? (ItemStack) item : null;
|
||||
}
|
||||
player.getInventory().setContents(contents);
|
||||
player.setFlying(dataFile.getBoolean("is-flying", false));
|
||||
|
||||
Location location = dataFile.getLocation("location");
|
||||
if (location == null && !Bukkit.getServer().getWorlds().isEmpty()) {
|
||||
location = Bukkit.getServer().getWorlds().get(0).getSpawnLocation();
|
||||
}
|
||||
if (location != null) {
|
||||
player.teleport(location);
|
||||
}
|
||||
|
||||
player.removeScoreboardTag("bm_player");
|
||||
|
||||
try {
|
||||
player.addAttachment(PLUGIN, "group.bomberman", false);
|
||||
} catch (Exception e) {
|
||||
PLUGIN.getLogger().log(Level.WARNING, "Unable to remove permissions", e);
|
||||
}
|
||||
|
||||
try {
|
||||
Files.deleteIfExists(file);
|
||||
} catch (Exception e) {
|
||||
PLUGIN.getLogger().log(Level.WARNING, "Unable to delete player data", e);
|
||||
}
|
||||
|
||||
removePotionEffects(player);
|
||||
return location;
|
||||
}
|
||||
|
||||
private static void removePotionEffects(Player player) {
|
||||
Bukkit.getScheduler().scheduleSyncDelayedTask(PLUGIN, () -> {
|
||||
player.setFireTicks(0);
|
||||
for (PotionEffect effect : player.getActivePotionEffects()) {
|
||||
player.removePotionEffect(effect.getType());
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private static Path tempDataFile(Player player) {
|
||||
return PLUGIN.tempPlayerData().resolve(player.getName() + ".yml");
|
||||
}
|
||||
|
||||
private final Player player;
|
||||
private final Game game;
|
||||
private boolean immunity = false;
|
||||
|
||||
private GamePlayer(Player player, Game game) {
|
||||
this.player = player;
|
||||
this.game = game;
|
||||
}
|
||||
|
||||
private void resetStuffAndUnregister() {
|
||||
player.getWorld().getNearbyEntities(player.getLocation(), 1.0, 2.0, 1.0).stream()
|
||||
.filter(entity -> entity instanceof Item)
|
||||
.forEach(entity -> entity.remove());
|
||||
|
||||
reset(player);
|
||||
HandlerList.unregisterAll(this);
|
||||
}
|
||||
|
||||
@EventHandler(ignoreCancelled = true, priority = EventPriority.LOW)
|
||||
public void onPlayerJoinGame(BmPlayerJoinGameIntent event) {
|
||||
if (event.player == player) {
|
||||
event.cancelFor(Text.JOIN_ALREADY_JOINED.format(new Context(false)
|
||||
.plus("game", event.game)
|
||||
.plus("player", player)));
|
||||
}
|
||||
}
|
||||
|
||||
@EventHandler(ignoreCancelled = true, priority = EventPriority.HIGH)
|
||||
public void onCount(BmTimerCountedEvent event) {
|
||||
if (event.game != game) {
|
||||
return;
|
||||
}
|
||||
if (event.count > 0) {
|
||||
Text.GAME_COUNT.format(new Context(false)
|
||||
.plus("time", event.count)
|
||||
.plus("game", game))
|
||||
.sendTo(player);
|
||||
}
|
||||
}
|
||||
|
||||
@EventHandler(ignoreCancelled = true, priority = EventPriority.HIGH)
|
||||
public void onRunStarted(BmRunStartedIntent event) {
|
||||
if (event.game != game) {
|
||||
return;
|
||||
}
|
||||
Text.GAME_STARTED.format(new Context(false).plus("game", game)).sendTo(player);
|
||||
event.setHandled();
|
||||
}
|
||||
|
||||
@EventHandler(ignoreCancelled = true, priority = EventPriority.LOWEST)
|
||||
public void onPlayerRegen(EntityRegainHealthEvent event) {
|
||||
if (event.getEntity() != player) {
|
||||
return;
|
||||
}
|
||||
if (event.getRegainReason() == EntityRegainHealthEvent.RegainReason.MAGIC) {
|
||||
event.setAmount(1.0);
|
||||
} else {
|
||||
event.setCancelled(true);
|
||||
}
|
||||
}
|
||||
|
||||
@EventHandler(ignoreCancelled = true, priority = EventPriority.LOW)
|
||||
public void onPlayerBreakBlockWithWrongTool(PlayerInteractEvent event) {
|
||||
if (event.getPlayer() != player) {
|
||||
return;
|
||||
}
|
||||
if (event.getAction() != Action.LEFT_CLICK_BLOCK || !event.hasBlock()) {
|
||||
return;
|
||||
}
|
||||
|
||||
String key = event.getClickedBlock() != null
|
||||
? event.getClickedBlock().getBlockData().getMaterial().getKey().toString()
|
||||
: null;
|
||||
if (event.getItem() != null && key != null) {
|
||||
var nbt = BukkitAdapter.adapt(event.getItem()).getNbtData();
|
||||
var list = nbt != null ? nbt.getList("CanDestroy", StringTag.class) : null;
|
||||
if (list != null && !list.isEmpty()) {
|
||||
boolean allowed = list.stream().anyMatch(tag -> key.equalsIgnoreCase(tag.getValue()));
|
||||
if (allowed) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
event.setCancelled(true);
|
||||
event.setUseInteractedBlock(Event.Result.DENY);
|
||||
event.setUseItemInHand(Event.Result.DENY);
|
||||
event.getPlayer().addPotionEffect(new PotionEffect(PotionEffectType.MINING_FATIGUE, 20, 1));
|
||||
}
|
||||
|
||||
@EventHandler(ignoreCancelled = true, priority = EventPriority.LOW)
|
||||
public void onPlayerPlaceBlock(BlockPlaceEvent event) {
|
||||
if (event.getPlayer() != player) {
|
||||
return;
|
||||
}
|
||||
|
||||
String key = event.getBlockAgainst().getBlockData().getMaterial().getKey().toString();
|
||||
var nbt = BukkitAdapter.adapt(event.getItemInHand()).getNbtData();
|
||||
var list = nbt != null ? nbt.getList("CanPlaceOn", StringTag.class) : null;
|
||||
if (list != null && !list.isEmpty()) {
|
||||
boolean allowed = list.stream().anyMatch(tag -> key.equalsIgnoreCase(tag.getValue()));
|
||||
if (!allowed) {
|
||||
event.setCancelled(true);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@EventHandler(ignoreCancelled = true, priority = EventPriority.HIGHEST)
|
||||
public void onPlayerPlaceTNT(BlockPlaceEvent event) {
|
||||
if (event.getPlayer() != player) {
|
||||
return;
|
||||
}
|
||||
var block = event.getBlock();
|
||||
if (block.getType() == game.getSettings().getBombItem()) {
|
||||
if (!Bomb.spawnBomb(game, player, block)) {
|
||||
event.setCancelled(true);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@EventHandler(ignoreCancelled = true, priority = EventPriority.LOW)
|
||||
public void onPlayerMoved(PlayerMoveEvent event) {
|
||||
if (event.getPlayer() != player) {
|
||||
return;
|
||||
}
|
||||
Location from = event.getFrom();
|
||||
Location to = event.getTo() != null ? event.getTo() : event.getFrom();
|
||||
Bukkit.getPluginManager().callEvent(new BmPlayerMovedEvent(game, player, from, to));
|
||||
}
|
||||
|
||||
@EventHandler(ignoreCancelled = true, priority = EventPriority.HIGH)
|
||||
public void onExplosion(io.github.mviper.bomberman.events.BmExplosionEvent event) {
|
||||
if (event.game != game) {
|
||||
return;
|
||||
}
|
||||
Set<Block> blocks = new java.util.HashSet<>();
|
||||
for (Explosion.BlockPlan plan : event.igniting) {
|
||||
blocks.add(plan.block);
|
||||
}
|
||||
if (Explosion.isTouching(player, blocks)) {
|
||||
BmPlayerHitIntent.hit(player, event.cause);
|
||||
}
|
||||
}
|
||||
|
||||
@EventHandler(ignoreCancelled = true, priority = EventPriority.HIGH)
|
||||
public void onPlayerHit(BmPlayerHitIntent event) {
|
||||
if (event.player != player) {
|
||||
return;
|
||||
}
|
||||
BmPlayerHurtIntent.run(game, player, event.cause);
|
||||
event.setHandled();
|
||||
}
|
||||
|
||||
@EventHandler(ignoreCancelled = true, priority = EventPriority.LOW)
|
||||
public void onPlayerHurtWithImmunity(BmPlayerHurtIntent event) {
|
||||
if (event.player != player) {
|
||||
return;
|
||||
}
|
||||
if (immunity) {
|
||||
event.setCancelled(true);
|
||||
}
|
||||
}
|
||||
|
||||
@EventHandler(ignoreCancelled = true, priority = EventPriority.HIGH)
|
||||
public void onPlayerDamaged(BmPlayerHurtIntent event) {
|
||||
if (event.player != player) {
|
||||
return;
|
||||
}
|
||||
if (player.getHealth() > 1) {
|
||||
player.damage(1.0);
|
||||
immunity = true;
|
||||
player.setFireTicks(game.getSettings().getImmunityTicks());
|
||||
Bukkit.getScheduler().scheduleSyncDelayedTask(PLUGIN, () -> {
|
||||
immunity = false;
|
||||
player.setFireTicks(0);
|
||||
Bukkit.getPluginManager().callEvent(new BmPlayerMovedEvent(game, player, player.getLocation(), player.getLocation()));
|
||||
}, game.getSettings().getImmunityTicks());
|
||||
} else {
|
||||
BmPlayerKilledIntent.kill(game, player, event.attacker);
|
||||
}
|
||||
event.setHandled();
|
||||
}
|
||||
|
||||
@EventHandler(ignoreCancelled = true, priority = EventPriority.HIGH)
|
||||
public void onPlayerKilledInGame(BmPlayerKilledIntent event) {
|
||||
if (event.player != player) {
|
||||
return;
|
||||
}
|
||||
player.setHealth(0.0);
|
||||
BmPlayerLeaveGameIntent.leave(player);
|
||||
event.setHandled();
|
||||
}
|
||||
|
||||
@EventHandler(ignoreCancelled = true, priority = EventPriority.HIGH)
|
||||
public void onPlayerWon(BmPlayerWonEvent event) {
|
||||
if (event.player != player) {
|
||||
return;
|
||||
}
|
||||
Text.PLAYER_WON.format(new Context(false).plus("player", player)).sendTo(player);
|
||||
immunity = true;
|
||||
}
|
||||
|
||||
@EventHandler(ignoreCancelled = true, priority = EventPriority.HIGH)
|
||||
public void onPlayerLeaveGameEvent(BmPlayerLeaveGameIntent event) {
|
||||
if (event.player != player) {
|
||||
return;
|
||||
}
|
||||
|
||||
player.getWorld().getNearbyEntities(player.getLocation(), 2.0, 3.0, 2.0).stream()
|
||||
.filter(entity -> entity instanceof Item)
|
||||
.forEach(entity -> entity.remove());
|
||||
|
||||
if (player.isDead()) {
|
||||
HandlerList.unregisterAll(this);
|
||||
} else {
|
||||
resetStuffAndUnregister();
|
||||
}
|
||||
event.setHandled(game);
|
||||
}
|
||||
|
||||
@EventHandler(ignoreCancelled = true, priority = EventPriority.HIGHEST)
|
||||
public void onGameStopped(BmRunStoppedIntent event) {
|
||||
if (event.game != game) {
|
||||
return;
|
||||
}
|
||||
BmPlayerLeaveGameIntent.leave(player);
|
||||
}
|
||||
|
||||
@EventHandler(ignoreCancelled = true, priority = EventPriority.HIGHEST)
|
||||
public void onGameTerminated(BmGameTerminatedIntent event) {
|
||||
if (event.game != game) {
|
||||
return;
|
||||
}
|
||||
BmPlayerLeaveGameIntent.leave(player);
|
||||
}
|
||||
|
||||
@EventHandler(ignoreCancelled = true, priority = EventPriority.LOW)
|
||||
public void onPlayerLogout(PlayerQuitEvent event) {
|
||||
if (event.getPlayer() == player) {
|
||||
BmPlayerLeaveGameIntent.leave(player);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,65 @@
|
||||
package io.github.mviper.bomberman.game;
|
||||
|
||||
import io.github.mviper.bomberman.Bomberman;
|
||||
import io.github.mviper.bomberman.events.BmRunStoppedIntent;
|
||||
import io.github.mviper.bomberman.utils.Box;
|
||||
import org.bukkit.Bukkit;
|
||||
import org.bukkit.event.EventHandler;
|
||||
import org.bukkit.event.EventPriority;
|
||||
import org.bukkit.event.HandlerList;
|
||||
import org.bukkit.event.Listener;
|
||||
import org.bukkit.event.block.BlockBurnEvent;
|
||||
import org.bukkit.event.block.BlockIgniteEvent;
|
||||
import org.bukkit.event.block.BlockSpreadEvent;
|
||||
import org.bukkit.plugin.Plugin;
|
||||
|
||||
/**
|
||||
* Protects an arena from getting damaged from the game.
|
||||
*
|
||||
* It is up to Server Owners to protect the arena from griefers.
|
||||
*/
|
||||
public class GameProtection implements Listener {
|
||||
private static final Plugin PLUGIN = Bomberman.instance;
|
||||
|
||||
private final Game game;
|
||||
private final Box bounds;
|
||||
|
||||
private GameProtection(Game game, Box bounds) {
|
||||
this.game = game;
|
||||
this.bounds = bounds;
|
||||
}
|
||||
|
||||
@EventHandler(ignoreCancelled = true, priority = EventPriority.HIGH)
|
||||
public void onRunStopped(BmRunStoppedIntent event) {
|
||||
if (event.game == game) {
|
||||
HandlerList.unregisterAll(this);
|
||||
}
|
||||
}
|
||||
|
||||
@EventHandler(ignoreCancelled = true, priority = EventPriority.LOWEST)
|
||||
public void onBlockBurn(BlockBurnEvent event) {
|
||||
if (bounds.contains(event.getBlock().getLocation())) {
|
||||
event.setCancelled(true);
|
||||
}
|
||||
}
|
||||
|
||||
@EventHandler(ignoreCancelled = true, priority = EventPriority.LOWEST)
|
||||
public void onBlockIgnite(BlockIgniteEvent event) {
|
||||
if (event.getCause() == BlockIgniteEvent.IgniteCause.SPREAD
|
||||
&& bounds.contains(event.getBlock().getLocation())) {
|
||||
event.setCancelled(true);
|
||||
}
|
||||
}
|
||||
|
||||
@EventHandler(ignoreCancelled = true, priority = EventPriority.LOW)
|
||||
public void onFireSpread(BlockSpreadEvent event) {
|
||||
if (bounds.contains(event.getBlock().getLocation())) {
|
||||
event.setCancelled(true);
|
||||
}
|
||||
}
|
||||
|
||||
public static void protect(Game game, Box bounds) {
|
||||
GameProtection protection = new GameProtection(game, bounds);
|
||||
Bukkit.getPluginManager().registerEvents(protection, PLUGIN);
|
||||
}
|
||||
}
|
||||
255
src/main/java/io/github/mviper/bomberman/game/GameSave.java
Normal file
255
src/main/java/io/github/mviper/bomberman/game/GameSave.java
Normal file
@@ -0,0 +1,255 @@
|
||||
package io.github.mviper.bomberman.game;
|
||||
|
||||
import com.sk89q.worldedit.extent.clipboard.Clipboard;
|
||||
import com.sk89q.worldedit.extent.clipboard.io.BuiltInClipboardFormat;
|
||||
import com.sk89q.worldedit.extent.clipboard.io.ClipboardFormat;
|
||||
import com.sk89q.worldedit.extent.clipboard.io.ClipboardFormats;
|
||||
import io.github.mviper.bomberman.Bomberman;
|
||||
import org.bukkit.Location;
|
||||
import org.bukkit.Material;
|
||||
import org.bukkit.configuration.file.YamlConfiguration;
|
||||
|
||||
import java.io.ByteArrayInputStream;
|
||||
import java.io.File;
|
||||
import java.io.FileInputStream;
|
||||
import java.io.IOException;
|
||||
import java.lang.ref.WeakReference;
|
||||
import java.net.URI;
|
||||
import java.nio.file.DirectoryStream;
|
||||
import java.nio.file.FileSystem;
|
||||
import java.nio.file.FileSystems;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.NoSuchFileException;
|
||||
import java.nio.file.Path;
|
||||
import java.nio.file.StandardCopyOption;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
import java.util.logging.Level;
|
||||
|
||||
/**
|
||||
* Handles reading/writing a game's data to disk.
|
||||
*/
|
||||
public class GameSave {
|
||||
private static final Bomberman PLUGIN = Bomberman.instance;
|
||||
|
||||
public final String name;
|
||||
public final Location origin;
|
||||
private final Path zipPath;
|
||||
|
||||
private WeakReference<Clipboard> schematicCache;
|
||||
private GameSettings settingsCache;
|
||||
|
||||
private GameSave(String name, Location origin, Path zipPath) {
|
||||
this.name = name;
|
||||
this.origin = origin;
|
||||
this.zipPath = zipPath;
|
||||
}
|
||||
|
||||
public static GameSave createNewSave(String name, Location origin, GameSettings settings, Clipboard schematic) {
|
||||
Path zipPath = PLUGIN.gameSaves().resolve(sanitize(name + ".game.zip"));
|
||||
URI fileUri = zipPath.toUri();
|
||||
URI zipUri = URI.create("jar:" + fileUri);
|
||||
Map<String, String> env = new HashMap<>();
|
||||
if (!Files.exists(zipPath)) {
|
||||
env.put("create", "true");
|
||||
}
|
||||
try (FileSystem fs = FileSystems.newFileSystem(zipUri, env)) {
|
||||
Path arenaPath = fs.getPath("arena.schem");
|
||||
try (var os = Files.newOutputStream(arenaPath)) {
|
||||
try (var writer = BuiltInClipboardFormat.SPONGE_SCHEMATIC.getWriter(os)) {
|
||||
writer.write(schematic);
|
||||
}
|
||||
}
|
||||
|
||||
Path settingsPath = fs.getPath("settings.yml");
|
||||
YamlConfiguration settingsYml = new YamlConfiguration();
|
||||
settingsYml.set("settings", settings);
|
||||
Files.write(settingsPath, java.util.List.of(settingsYml.saveToString()));
|
||||
|
||||
Path configPath = fs.getPath("config.yml");
|
||||
YamlConfiguration configYml = new YamlConfiguration();
|
||||
configYml.set("name", name);
|
||||
configYml.set("origin", origin);
|
||||
Files.write(configPath, java.util.List.of(configYml.saveToString()));
|
||||
|
||||
Path readmePath = fs.getPath("README.txt");
|
||||
try (var in = PLUGIN.getResource("zip README.txt")) {
|
||||
if (in != null) {
|
||||
Files.copy(in, readmePath, StandardCopyOption.REPLACE_EXISTING);
|
||||
}
|
||||
}
|
||||
} catch (IOException e) {
|
||||
throw new RuntimeException("Failed to create save file", e);
|
||||
}
|
||||
|
||||
GameSave save = new GameSave(name, origin, zipPath);
|
||||
save.schematicCache = new WeakReference<>(schematic);
|
||||
save.settingsCache = settings;
|
||||
return save;
|
||||
}
|
||||
|
||||
public static void loadGames() {
|
||||
Path data = PLUGIN.gameSaves();
|
||||
try (DirectoryStream<Path> files = Files.newDirectoryStream(data, "*.game.zip")) {
|
||||
for (Path file : files) {
|
||||
try {
|
||||
loadGame(file);
|
||||
} catch (Exception e) {
|
||||
PLUGIN.getLogger().log(Level.WARNING, "Exception occurred while loading: " + file, e);
|
||||
}
|
||||
}
|
||||
} catch (IOException e) {
|
||||
PLUGIN.getLogger().log(Level.WARNING, "Exception occurred while listing saves", e);
|
||||
}
|
||||
}
|
||||
|
||||
public static GameSave loadSave(Path zipFile) throws IOException {
|
||||
PLUGIN.getLogger().info("Reading " + zipFile);
|
||||
try (FileSystem fs = FileSystems.newFileSystem(zipFile, (ClassLoader) null)) {
|
||||
Path configPath = fs.getPath("config.yml");
|
||||
try (var reader = Files.newBufferedReader(configPath)) {
|
||||
YamlConfiguration config = YamlConfiguration.loadConfiguration(reader);
|
||||
String name = config.getString("name");
|
||||
Location origin = config.getSerializable("origin", Location.class);
|
||||
if (name == null || origin == null) {
|
||||
throw new IOException("Cannot read 'config.yml' in '" + zipFile + "'");
|
||||
}
|
||||
PLUGIN.getLogger().info(" Data read");
|
||||
return new GameSave(name, origin, zipFile);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static Game loadGame(Path zipFile) throws IOException {
|
||||
return new Game(loadSave(zipFile));
|
||||
}
|
||||
|
||||
public static void updatePre080Saves() {
|
||||
try (DirectoryStream<Path> files = Files.newDirectoryStream(PLUGIN.gameSaves(), "*.yml")) {
|
||||
for (Path file : files) {
|
||||
try {
|
||||
PLUGIN.getLogger().info("Updating old save: " + file);
|
||||
|
||||
YamlConfiguration config;
|
||||
try (var reader = Files.newBufferedReader(file)) {
|
||||
config = YamlConfiguration.loadConfiguration(reader);
|
||||
}
|
||||
|
||||
String name = config.getString("name");
|
||||
String schema = config.getString("schema");
|
||||
Location origin = config.getSerializable("origin", Location.class);
|
||||
GameSettings settings = config.getSerializable("settings", GameSettings.class);
|
||||
if (settings == null) {
|
||||
settings = new GameSettingsBuilder().build();
|
||||
}
|
||||
|
||||
GameSettingsBuilder builder = new GameSettingsBuilder(settings);
|
||||
if (config.getBoolean("build-flags.skip-air", false)) {
|
||||
builder.sourceMask = Set.of(Material.AIR);
|
||||
} else {
|
||||
builder.sourceMask = Set.of();
|
||||
}
|
||||
settings = builder.build();
|
||||
|
||||
if (name == null || schema == null || origin == null) {
|
||||
PLUGIN.getLogger().info(" Skipping update as file missing data");
|
||||
continue;
|
||||
}
|
||||
|
||||
File schemaFile = new File(schema);
|
||||
PLUGIN.getLogger().info(" Loading schematic: " + schemaFile.getPath());
|
||||
ClipboardFormat format = ClipboardFormats.findByFile(schemaFile);
|
||||
if (format == null) {
|
||||
throw new IllegalArgumentException("Unknown file format: '" + schemaFile.getPath() + "'");
|
||||
}
|
||||
Clipboard clipboard;
|
||||
try (var input = new FileInputStream(schemaFile)) {
|
||||
clipboard = format.getReader(input).read();
|
||||
}
|
||||
|
||||
createNewSave(name, origin, settings, clipboard);
|
||||
Files.deleteIfExists(file);
|
||||
PLUGIN.getLogger().info(" Save Updated");
|
||||
} catch (Exception e) {
|
||||
PLUGIN.getLogger().log(Level.WARNING, "Exception occurred while updating " + file, e);
|
||||
}
|
||||
}
|
||||
} catch (IOException e) {
|
||||
PLUGIN.getLogger().log(Level.WARNING, "Exception occurred while listing old saves", e);
|
||||
}
|
||||
}
|
||||
|
||||
public static String sanitize(String filename) {
|
||||
return filename.toLowerCase().replaceAll("[^a-z0-9._-]", "_");
|
||||
}
|
||||
|
||||
public Clipboard getSchematic() throws NoSuchFileException {
|
||||
Clipboard cached = schematicCache != null ? schematicCache.get() : null;
|
||||
if (cached != null) {
|
||||
return cached;
|
||||
}
|
||||
|
||||
PLUGIN.getLogger().info("Reading schematic data: " + zipPath.getFileName());
|
||||
try (FileSystem fs = FileSystems.newFileSystem(zipPath, (ClassLoader) null)) {
|
||||
Path arenaPath = fs.getPath("arena.schem");
|
||||
Clipboard schematic;
|
||||
try (var reader = Files.newInputStream(arenaPath)) {
|
||||
schematic = BuiltInClipboardFormat.SPONGE_SCHEMATIC.getReader(reader).read();
|
||||
}
|
||||
PLUGIN.getLogger().info("Data read");
|
||||
schematicCache = new WeakReference<>(schematic);
|
||||
return schematic;
|
||||
} catch (IOException e) {
|
||||
if (e instanceof NoSuchFileException) {
|
||||
throw (NoSuchFileException) e;
|
||||
}
|
||||
throw new RuntimeException("Failed to read schematic", e);
|
||||
}
|
||||
}
|
||||
|
||||
public GameSettings getSettings() throws NoSuchFileException {
|
||||
if (settingsCache != null) {
|
||||
return settingsCache;
|
||||
}
|
||||
|
||||
PLUGIN.getLogger().info("Reading game settings: " + zipPath.getFileName());
|
||||
try (FileSystem fs = FileSystems.newFileSystem(zipPath, (ClassLoader) null)) {
|
||||
Path settingsPath = fs.getPath("settings.yml");
|
||||
try (var reader = Files.newBufferedReader(settingsPath)) {
|
||||
YamlConfiguration settingsYml = YamlConfiguration.loadConfiguration(reader);
|
||||
GameSettings settings = settingsYml.getSerializable("settings", GameSettings.class);
|
||||
if (settings == null) {
|
||||
settings = new GameSettingsBuilder().build();
|
||||
}
|
||||
settingsCache = settings;
|
||||
PLUGIN.getLogger().info("Data read");
|
||||
return settings;
|
||||
}
|
||||
} catch (IOException e) {
|
||||
if (e instanceof NoSuchFileException) {
|
||||
throw (NoSuchFileException) e;
|
||||
}
|
||||
throw new RuntimeException("Failed to read game settings", e);
|
||||
}
|
||||
}
|
||||
|
||||
public void updateSettings(GameSettings settings) throws IOException {
|
||||
settingsCache = null;
|
||||
|
||||
YamlConfiguration yml = new YamlConfiguration();
|
||||
yml.set("settings", settings);
|
||||
String ymlString = yml.saveToString();
|
||||
|
||||
try (FileSystem fs = FileSystems.newFileSystem(zipPath, (ClassLoader) null)) {
|
||||
Path zipConfigPath = fs.getPath("settings.yml");
|
||||
Files.copy(
|
||||
new ByteArrayInputStream(ymlString.getBytes()),
|
||||
zipConfigPath,
|
||||
StandardCopyOption.REPLACE_EXISTING
|
||||
);
|
||||
}
|
||||
|
||||
settingsCache = settings;
|
||||
}
|
||||
}
|
||||
426
src/main/java/io/github/mviper/bomberman/game/GameSettings.java
Normal file
426
src/main/java/io/github/mviper/bomberman/game/GameSettings.java
Normal file
@@ -0,0 +1,426 @@
|
||||
package io.github.mviper.bomberman.game;
|
||||
|
||||
import io.github.mviper.bomberman.Bomberman;
|
||||
import io.github.mviper.bomberman.utils.RefectAccess;
|
||||
import org.bukkit.Material;
|
||||
import org.bukkit.configuration.file.YamlConfiguration;
|
||||
import org.bukkit.configuration.serialization.ConfigurationSerializable;
|
||||
import org.bukkit.inventory.ItemStack;
|
||||
|
||||
import java.io.InputStreamReader;
|
||||
import java.io.Reader;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.HashMap;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Objects;
|
||||
import java.util.Set;
|
||||
|
||||
/**
|
||||
* Defines the settings for how a game operates. Class is immutable.
|
||||
*/
|
||||
public class GameSettings implements ConfigurationSerializable {
|
||||
private final Material bombItem;
|
||||
private final Material powerItem;
|
||||
private final Material fireType;
|
||||
private final Map<Material, Map<ItemStack, Integer>> blockLoot;
|
||||
private final Set<Material> destructible;
|
||||
private final Set<Material> indestructible;
|
||||
private final Set<Material> passKeep;
|
||||
private final Set<Material> passDestroy;
|
||||
private final List<ItemStack> initialItems;
|
||||
private final int lives;
|
||||
private final int fuseTicks;
|
||||
private final int fireTicks;
|
||||
private final int immunityTicks;
|
||||
private final Map<String, Map<String, String>> damageSources;
|
||||
private final Set<Material> sourceMask;
|
||||
private final Material cageBlock;
|
||||
|
||||
private static volatile boolean loadingDefault = false;
|
||||
private static volatile GameSettings defaultSettings;
|
||||
|
||||
public GameSettings(
|
||||
Material bombItem,
|
||||
Material powerItem,
|
||||
Material fireType,
|
||||
Map<Material, Map<ItemStack, Integer>> blockLoot,
|
||||
Set<Material> destructible,
|
||||
Set<Material> indestructible,
|
||||
Set<Material> passKeep,
|
||||
Set<Material> passDestroy,
|
||||
List<ItemStack> initialItems,
|
||||
int lives,
|
||||
int fuseTicks,
|
||||
int fireTicks,
|
||||
int immunityTicks,
|
||||
Map<String, Map<String, String>> damageSources,
|
||||
Set<Material> sourceMask,
|
||||
Material cageBlock
|
||||
) {
|
||||
this.bombItem = bombItem;
|
||||
this.powerItem = powerItem;
|
||||
this.fireType = fireType;
|
||||
this.blockLoot = blockLoot;
|
||||
this.destructible = destructible;
|
||||
this.indestructible = indestructible;
|
||||
this.passKeep = passKeep;
|
||||
this.passDestroy = passDestroy;
|
||||
this.initialItems = initialItems;
|
||||
this.lives = lives;
|
||||
this.fuseTicks = fuseTicks;
|
||||
this.fireTicks = fireTicks;
|
||||
this.immunityTicks = immunityTicks;
|
||||
this.damageSources = damageSources;
|
||||
this.sourceMask = sourceMask;
|
||||
this.cageBlock = cageBlock;
|
||||
}
|
||||
|
||||
public Material getBombItem() {
|
||||
return bombItem;
|
||||
}
|
||||
|
||||
public Material getPowerItem() {
|
||||
return powerItem;
|
||||
}
|
||||
|
||||
public Material getFireType() {
|
||||
return fireType;
|
||||
}
|
||||
|
||||
public Map<Material, Map<ItemStack, Integer>> getBlockLoot() {
|
||||
return blockLoot;
|
||||
}
|
||||
|
||||
public Set<Material> getDestructible() {
|
||||
return destructible;
|
||||
}
|
||||
|
||||
public Set<Material> getIndestructible() {
|
||||
return indestructible;
|
||||
}
|
||||
|
||||
public Set<Material> getPassKeep() {
|
||||
return passKeep;
|
||||
}
|
||||
|
||||
public Set<Material> getPassDestroy() {
|
||||
return passDestroy;
|
||||
}
|
||||
|
||||
public List<ItemStack> getInitialItems() {
|
||||
return initialItems;
|
||||
}
|
||||
|
||||
public int getLives() {
|
||||
return lives;
|
||||
}
|
||||
|
||||
public int getFuseTicks() {
|
||||
return fuseTicks;
|
||||
}
|
||||
|
||||
public int getFireTicks() {
|
||||
return fireTicks;
|
||||
}
|
||||
|
||||
public int getImmunityTicks() {
|
||||
return immunityTicks;
|
||||
}
|
||||
|
||||
public Map<String, Map<String, String>> getDamageSources() {
|
||||
return damageSources;
|
||||
}
|
||||
|
||||
public Set<Material> getSourceMask() {
|
||||
return sourceMask;
|
||||
}
|
||||
|
||||
public Material getCageBlock() {
|
||||
return cageBlock;
|
||||
}
|
||||
|
||||
@RefectAccess
|
||||
public static GameSettings deserialize(Map<String, Object> data) {
|
||||
GameSettings defaults = loadingDefault ? null : getDefaultSettings();
|
||||
|
||||
Material bombItem = readMaterial(data.get("bomb"), defaults != null ? defaults.bombItem : null);
|
||||
Material powerItem = readMaterial(data.get("power"), defaults != null ? defaults.powerItem : null);
|
||||
Material fireType = readMaterial(data.get("fire"), defaults != null ? defaults.fireType : null);
|
||||
|
||||
Map<Material, Map<ItemStack, Integer>> blockLoot = readLootTable(data.get("loot-table"));
|
||||
if (blockLoot == null && defaults != null) {
|
||||
blockLoot = defaults.blockLoot;
|
||||
}
|
||||
|
||||
Set<Material> destructible = readMaterials(data.get("destructible"));
|
||||
if (destructible == null && defaults != null) {
|
||||
destructible = defaults.destructible;
|
||||
}
|
||||
|
||||
Set<Material> indestructible = readMaterials(data.get("indestructible"));
|
||||
if (indestructible == null && defaults != null) {
|
||||
indestructible = defaults.indestructible;
|
||||
}
|
||||
|
||||
Set<Material> passKeep = readMaterials(data.get("pass-keep"));
|
||||
if (passKeep == null && defaults != null) {
|
||||
passKeep = defaults.passKeep;
|
||||
}
|
||||
|
||||
Set<Material> passDestroy = readMaterials(data.get("pass-destroy"));
|
||||
if (passDestroy == null && defaults != null) {
|
||||
passDestroy = defaults.passDestroy;
|
||||
}
|
||||
|
||||
List<ItemStack> initialItems = readItemList(data.get("initial-items"));
|
||||
if (initialItems == null && defaults != null) {
|
||||
initialItems = defaults.initialItems;
|
||||
}
|
||||
|
||||
int lives = readInt(data.get("lives"), defaults != null ? defaults.lives : 0);
|
||||
int fuseTicks = Math.max(0, readInt(data.get("fuse-ticks"), defaults != null ? defaults.fuseTicks : 0));
|
||||
int fireTicks = Math.max(0, readInt(data.get("fire-ticks"), defaults != null ? defaults.fireTicks : 0));
|
||||
int immunityTicks = Math.max(0, readInt(data.get("immunity-ticks"), defaults != null ? defaults.immunityTicks : 0));
|
||||
|
||||
Map<String, Map<String, String>> damageSources = readDamageSources(data.get("damage-source"));
|
||||
if (damageSources == null && defaults != null) {
|
||||
damageSources = defaults.damageSources;
|
||||
}
|
||||
|
||||
Set<Material> sourceMask = readMaterials(data.get("source-mask"));
|
||||
if (sourceMask == null && defaults != null) {
|
||||
sourceMask = defaults.sourceMask;
|
||||
}
|
||||
|
||||
Material cageBlock = readMaterial(data.get("cage-block"), defaults != null ? defaults.cageBlock : null);
|
||||
|
||||
return new GameSettings(
|
||||
bombItem,
|
||||
powerItem,
|
||||
fireType,
|
||||
blockLoot,
|
||||
destructible,
|
||||
indestructible,
|
||||
passKeep,
|
||||
passDestroy,
|
||||
initialItems,
|
||||
lives,
|
||||
fuseTicks,
|
||||
fireTicks,
|
||||
immunityTicks,
|
||||
damageSources,
|
||||
sourceMask,
|
||||
cageBlock
|
||||
);
|
||||
}
|
||||
|
||||
private static int readInt(Object value, int fallback) {
|
||||
if (value instanceof Number) {
|
||||
return ((Number) value).intValue();
|
||||
}
|
||||
return fallback;
|
||||
}
|
||||
|
||||
private static Material readMaterial(Object value, Material fallback) {
|
||||
if (value instanceof String) {
|
||||
Material material = Material.matchMaterial((String) value);
|
||||
if (material != null) {
|
||||
return material;
|
||||
}
|
||||
}
|
||||
return fallback;
|
||||
}
|
||||
|
||||
private static Set<Material> readMaterials(Object value) {
|
||||
if (!(value instanceof List<?>)) {
|
||||
return null;
|
||||
}
|
||||
Set<Material> result = new HashSet<>();
|
||||
for (Object item : (List<?>) value) {
|
||||
if (item instanceof String) {
|
||||
Material material = Material.matchMaterial((String) item);
|
||||
if (material != null) {
|
||||
result.add(material);
|
||||
}
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
private static List<ItemStack> readItemList(Object value) {
|
||||
if (!(value instanceof List<?>)) {
|
||||
return null;
|
||||
}
|
||||
List<ItemStack> result = new ArrayList<>();
|
||||
for (Object item : (List<?>) value) {
|
||||
if (item instanceof ItemStack) {
|
||||
result.add((ItemStack) item);
|
||||
} else {
|
||||
result.add(null);
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
private static Map<Material, Map<ItemStack, Integer>> readLootTable(Object value) {
|
||||
if (!(value instanceof List<?>)) {
|
||||
return null;
|
||||
}
|
||||
Map<Material, Map<ItemStack, Integer>> result = new HashMap<>();
|
||||
for (Object sectionObj : (List<?>) value) {
|
||||
if (!(sectionObj instanceof Map<?, ?>)) {
|
||||
continue;
|
||||
}
|
||||
Map<?, ?> section = (Map<?, ?>) sectionObj;
|
||||
List<?> blocksRaw = (List<?>) section.get("blocks");
|
||||
List<?> lootRaw = (List<?>) section.get("loot");
|
||||
if (blocksRaw == null || lootRaw == null) {
|
||||
continue;
|
||||
}
|
||||
|
||||
List<Material> blocks = new ArrayList<>();
|
||||
for (Object blockObj : blocksRaw) {
|
||||
if (blockObj instanceof String) {
|
||||
Material material = Material.matchMaterial((String) blockObj);
|
||||
if (material != null) {
|
||||
blocks.add(material);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Map<ItemStack, Integer> loot = new HashMap<>();
|
||||
for (Object lootObj : lootRaw) {
|
||||
if (!(lootObj instanceof Map<?, ?>)) {
|
||||
continue;
|
||||
}
|
||||
Map<?, ?> lootEntry = (Map<?, ?>) lootObj;
|
||||
Object weightObj = lootEntry.get("weight");
|
||||
Object itemObj = lootEntry.get("item");
|
||||
if (weightObj instanceof Number && itemObj instanceof ItemStack) {
|
||||
int weight = ((Number) weightObj).intValue();
|
||||
if (weight > 0) {
|
||||
loot.put((ItemStack) itemObj, weight);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!blocks.isEmpty() && !loot.isEmpty()) {
|
||||
for (Material block : blocks) {
|
||||
result.put(block, loot);
|
||||
}
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
private static Map<String, Map<String, String>> readDamageSources(Object value) {
|
||||
if (!(value instanceof Map<?, ?>)) {
|
||||
return null;
|
||||
}
|
||||
Map<String, Map<String, String>> result = new HashMap<>();
|
||||
for (Map.Entry<?, ?> entry : ((Map<?, ?>) value).entrySet()) {
|
||||
String key = Objects.toString(entry.getKey());
|
||||
Object cause = entry.getValue();
|
||||
if (cause instanceof Map<?, ?>) {
|
||||
Map<String, String> nested = new HashMap<>();
|
||||
for (Map.Entry<?, ?> inner : ((Map<?, ?>) cause).entrySet()) {
|
||||
nested.put(Objects.toString(inner.getKey()), Objects.toString(inner.getValue()));
|
||||
}
|
||||
result.put(key, nested);
|
||||
} else if (cause == null) {
|
||||
result.put(key, new HashMap<>());
|
||||
} else {
|
||||
result.put(key, Map.of("base", Objects.toString(cause)));
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Map<String, Object> serialize() {
|
||||
Map<String, Object> objs = new HashMap<>();
|
||||
objs.put("bomb", bombItem.getKey().toString());
|
||||
objs.put("power", powerItem.getKey().toString());
|
||||
objs.put("fire", fireType.getKey().toString());
|
||||
|
||||
Map<Map<ItemStack, Integer>, Set<Material>> lootBlock = new HashMap<>();
|
||||
for (Map.Entry<Material, Map<ItemStack, Integer>> entry : blockLoot.entrySet()) {
|
||||
lootBlock.computeIfAbsent(entry.getValue(), key -> new HashSet<>()).add(entry.getKey());
|
||||
}
|
||||
List<Map<String, Object>> lootTable = new ArrayList<>();
|
||||
for (Map.Entry<Map<ItemStack, Integer>, Set<Material>> entry : lootBlock.entrySet()) {
|
||||
Map<String, Object> section = new HashMap<>();
|
||||
List<String> blocks = new ArrayList<>();
|
||||
for (Material material : entry.getValue()) {
|
||||
blocks.add(material.getKey().toString());
|
||||
}
|
||||
List<Map<String, Object>> loot = new ArrayList<>();
|
||||
for (Map.Entry<ItemStack, Integer> lootEntry : entry.getKey().entrySet()) {
|
||||
Map<String, Object> lootItem = new HashMap<>();
|
||||
lootItem.put("item", lootEntry.getKey());
|
||||
lootItem.put("weight", lootEntry.getValue());
|
||||
loot.add(lootItem);
|
||||
}
|
||||
section.put("blocks", blocks);
|
||||
section.put("loot", loot);
|
||||
lootTable.add(section);
|
||||
}
|
||||
objs.put("loot-table", lootTable);
|
||||
objs.put("destructible", destructible.stream().map(material -> material.getKey().toString()).toList());
|
||||
objs.put("indestructible", indestructible.stream().map(material -> material.getKey().toString()).toList());
|
||||
objs.put("pass-keep", passKeep.stream().map(material -> material.getKey().toString()).toList());
|
||||
objs.put("pass-destroy", passDestroy.stream().map(material -> material.getKey().toString()).toList());
|
||||
objs.put("initial-items", trimTrailingNulls(initialItems));
|
||||
objs.put("lives", lives);
|
||||
objs.put("fuse-ticks", fuseTicks);
|
||||
objs.put("fire-ticks", fireTicks);
|
||||
objs.put("immunity-ticks", immunityTicks);
|
||||
objs.put("damage-source", damageSources);
|
||||
objs.put("source-mask", sourceMask.stream().map(material -> material.getKey().toString()).toList());
|
||||
objs.put("cage-block", cageBlock.getKey().toString());
|
||||
|
||||
return objs;
|
||||
}
|
||||
|
||||
private static List<ItemStack> trimTrailingNulls(List<ItemStack> items) {
|
||||
List<ItemStack> result = new ArrayList<>(items);
|
||||
for (int i = result.size() - 1; i >= 0; i--) {
|
||||
if (result.get(i) != null) {
|
||||
break;
|
||||
}
|
||||
result.remove(i);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
public static GameSettings getDefaultSettings() {
|
||||
if (defaultSettings == null) {
|
||||
synchronized (GameSettings.class) {
|
||||
if (defaultSettings == null) {
|
||||
defaultSettings = loadDefaultSettings();
|
||||
}
|
||||
}
|
||||
}
|
||||
return defaultSettings;
|
||||
}
|
||||
|
||||
private static GameSettings loadDefaultSettings() {
|
||||
try (Reader reader = new InputStreamReader(
|
||||
Objects.requireNonNull(Bomberman.instance.getResource("games/README.yml"))
|
||||
)) {
|
||||
loadingDefault = true;
|
||||
GameSettings result = YamlConfiguration.loadConfiguration(reader)
|
||||
.getSerializable("settings", GameSettings.class);
|
||||
loadingDefault = false;
|
||||
return Objects.requireNonNull(result);
|
||||
} catch (Exception e) {
|
||||
loadingDefault = false;
|
||||
throw new RuntimeException("Failed to load default settings", e);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,71 @@
|
||||
package io.github.mviper.bomberman.game;
|
||||
|
||||
import org.bukkit.Material;
|
||||
import org.bukkit.inventory.ItemStack;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
|
||||
public class GameSettingsBuilder {
|
||||
public Material bombItem;
|
||||
public Material powerItem;
|
||||
public Material fireType;
|
||||
public Map<Material, Map<ItemStack, Integer>> blockLoot;
|
||||
public Set<Material> destructible;
|
||||
public Set<Material> indestructible;
|
||||
public Set<Material> passKeep;
|
||||
public Set<Material> passDestroy;
|
||||
public List<ItemStack> initialItems;
|
||||
public int lives;
|
||||
public int fuseTicks;
|
||||
public int fireTicks;
|
||||
public int immunityTicks;
|
||||
public Map<String, Map<String, String>> damageSources;
|
||||
public Set<Material> sourceMask;
|
||||
public Material cageBlock;
|
||||
|
||||
public GameSettingsBuilder() {
|
||||
this(GameSettings.getDefaultSettings());
|
||||
}
|
||||
|
||||
public GameSettingsBuilder(GameSettings settings) {
|
||||
this.bombItem = settings.getBombItem();
|
||||
this.powerItem = settings.getPowerItem();
|
||||
this.fireType = settings.getFireType();
|
||||
this.blockLoot = settings.getBlockLoot();
|
||||
this.destructible = settings.getDestructible();
|
||||
this.indestructible = settings.getIndestructible();
|
||||
this.passKeep = settings.getPassKeep();
|
||||
this.passDestroy = settings.getPassDestroy();
|
||||
this.initialItems = settings.getInitialItems();
|
||||
this.lives = settings.getLives();
|
||||
this.fuseTicks = settings.getFuseTicks();
|
||||
this.fireTicks = settings.getFireTicks();
|
||||
this.immunityTicks = settings.getImmunityTicks();
|
||||
this.damageSources = settings.getDamageSources();
|
||||
this.sourceMask = settings.getSourceMask();
|
||||
this.cageBlock = settings.getCageBlock();
|
||||
}
|
||||
|
||||
public GameSettings build() {
|
||||
return new GameSettings(
|
||||
bombItem,
|
||||
powerItem,
|
||||
fireType,
|
||||
blockLoot,
|
||||
destructible,
|
||||
indestructible,
|
||||
passKeep,
|
||||
passDestroy,
|
||||
initialItems,
|
||||
lives,
|
||||
fuseTicks,
|
||||
fireTicks,
|
||||
immunityTicks,
|
||||
damageSources,
|
||||
sourceMask,
|
||||
cageBlock
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,81 @@
|
||||
package io.github.mviper.bomberman.game;
|
||||
|
||||
import io.github.mviper.bomberman.Bomberman;
|
||||
import io.github.mviper.bomberman.events.BmRunStartCountDownIntent;
|
||||
import io.github.mviper.bomberman.events.BmRunStartedIntent;
|
||||
import io.github.mviper.bomberman.events.BmRunStoppedIntent;
|
||||
import io.github.mviper.bomberman.events.BmTimerCountedEvent;
|
||||
import io.github.mviper.bomberman.messaging.Context;
|
||||
import io.github.mviper.bomberman.messaging.Text;
|
||||
import org.bukkit.Bukkit;
|
||||
import org.bukkit.event.EventHandler;
|
||||
import org.bukkit.event.EventPriority;
|
||||
import org.bukkit.event.HandlerList;
|
||||
import org.bukkit.event.Listener;
|
||||
|
||||
public class StartTimer implements Runnable, Listener {
|
||||
private static final Bomberman PLUGIN = Bomberman.instance;
|
||||
|
||||
public static void createTimer(Game game, int time) {
|
||||
Bukkit.getPluginManager().registerEvents(new StartTimer(game, time), PLUGIN);
|
||||
}
|
||||
|
||||
private final Game game;
|
||||
private int time;
|
||||
private boolean killed = false;
|
||||
private final int taskId;
|
||||
|
||||
private StartTimer(Game game, int time) {
|
||||
this.game = game;
|
||||
this.time = time;
|
||||
this.taskId = PLUGIN.getServer().getScheduler().scheduleSyncRepeatingTask(PLUGIN, this, 1, 20);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void run() {
|
||||
if (killed) {
|
||||
return;
|
||||
}
|
||||
|
||||
BmTimerCountedEvent event = new BmTimerCountedEvent(game, time);
|
||||
Bukkit.getPluginManager().callEvent(event);
|
||||
time = event.count;
|
||||
if (time > 0) {
|
||||
time--;
|
||||
} else {
|
||||
BmRunStartedIntent.startRun(game);
|
||||
killSelf();
|
||||
}
|
||||
}
|
||||
|
||||
private void killSelf() {
|
||||
killed = true;
|
||||
Bukkit.getScheduler().cancelTask(taskId);
|
||||
HandlerList.unregisterAll(this);
|
||||
}
|
||||
|
||||
@EventHandler(ignoreCancelled = true, priority = EventPriority.LOW)
|
||||
public void onTimerStarted(BmRunStartCountDownIntent event) {
|
||||
if (event.game != game) {
|
||||
return;
|
||||
}
|
||||
if (event.override) {
|
||||
killSelf();
|
||||
} else {
|
||||
event.cancelBecause(Text.GAME_ALREADY_COUNTING.format(new Context(false)
|
||||
.plus("game", game)
|
||||
.plus("time", time)));
|
||||
}
|
||||
}
|
||||
|
||||
@EventHandler(ignoreCancelled = true, priority = EventPriority.LOW)
|
||||
public void onGameStop(BmRunStoppedIntent event) {
|
||||
if (event.game != game) {
|
||||
return;
|
||||
}
|
||||
killSelf();
|
||||
event.cancelFor(Text.STOP_TIMER_STOPPED.format(new Context(false)
|
||||
.plus("time", time)
|
||||
.plus("game", game)));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,23 @@
|
||||
package io.github.mviper.bomberman.messaging;
|
||||
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.function.Function;
|
||||
|
||||
public class AdditionalArgs implements Formattable {
|
||||
private final Function<List<Message>, Formattable> function;
|
||||
|
||||
public AdditionalArgs(Function<List<Message>, Formattable> function) {
|
||||
this.function = function;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Formattable applyModifier(Message arg) {
|
||||
return new ExtraArgsHolder((context, args) -> function.apply(args), arg);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Message format(Context context) {
|
||||
return function.apply(Collections.emptyList()).format(context);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,129 @@
|
||||
package io.github.mviper.bomberman.messaging;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.Comparator;
|
||||
import java.util.List;
|
||||
|
||||
public class CollectionWrapper<T extends Formattable> implements Formattable {
|
||||
private final Collection<T> list;
|
||||
private final Formattable delegate;
|
||||
|
||||
public CollectionWrapper(Collection<T> list) {
|
||||
this.list = list;
|
||||
this.delegate = new DefaultArg("length", argProperty -> {
|
||||
String option = argProperty.toString().toLowerCase();
|
||||
switch (option) {
|
||||
case "length":
|
||||
return Message.of(list.size());
|
||||
case "map":
|
||||
return new RequiredArg(argMapper -> new ContextArg(context -> {
|
||||
String mapper = argMapper.toString();
|
||||
List<Formattable> newList = new ArrayList<>();
|
||||
int i = 0;
|
||||
for (T item : list) {
|
||||
newList.add(Expander.expand(
|
||||
mapper,
|
||||
context.plus("it", item).plus("index", i)
|
||||
));
|
||||
i++;
|
||||
}
|
||||
return new CollectionWrapper<>(newList);
|
||||
}));
|
||||
case "join":
|
||||
return new DefaultArg("", separator -> new ContextArg(context -> {
|
||||
List<Message> formatted = new ArrayList<>();
|
||||
for (T item : list) {
|
||||
formatted.add(item.format(context));
|
||||
}
|
||||
if (formatted.isEmpty()) {
|
||||
formatted.add(Message.empty);
|
||||
}
|
||||
Message result = formatted.get(0);
|
||||
for (int i = 1; i < formatted.size(); i++) {
|
||||
result = result.append(separator).append(formatted.get(i));
|
||||
}
|
||||
return result;
|
||||
}));
|
||||
case "foreach":
|
||||
return new DefaultArg("({index}: {it})", argMapper -> new DefaultArg(" ", argSeparator -> new ContextArg(context -> {
|
||||
String mapper = argMapper.toString();
|
||||
List<Message> mapped = new ArrayList<>();
|
||||
int i = 0;
|
||||
for (T item : list) {
|
||||
mapped.add(Expander.expand(
|
||||
mapper,
|
||||
context.plus("it", item).plus("index", i)
|
||||
));
|
||||
i++;
|
||||
}
|
||||
if (mapped.isEmpty()) {
|
||||
mapped.add(Message.empty);
|
||||
}
|
||||
Message result = mapped.get(0);
|
||||
for (int j = 1; j < mapped.size(); j++) {
|
||||
result = result.append(argSeparator).append(mapped.get(j));
|
||||
}
|
||||
return result;
|
||||
})));
|
||||
case "sort":
|
||||
return new DefaultArg("{it}", argMapper -> new ContextArg(context -> {
|
||||
String mapper = argMapper.toString();
|
||||
List<Indexed<T>> indexed = new ArrayList<>();
|
||||
int i = 0;
|
||||
for (T item : list) {
|
||||
indexed.add(new Indexed<>(i, item));
|
||||
i++;
|
||||
}
|
||||
indexed.sort(Comparator.comparing(entry -> Expander.expand(
|
||||
mapper,
|
||||
context.plus("it", entry.value).plus("index", Message.of(entry.index))
|
||||
).toString()));
|
||||
List<T> sorted = new ArrayList<>();
|
||||
for (Indexed<T> entry : indexed) {
|
||||
sorted.add(entry.value);
|
||||
}
|
||||
return new CollectionWrapper<>(sorted);
|
||||
}));
|
||||
case "filter":
|
||||
return new RequiredArg(argFilter -> new ContextArg(context -> {
|
||||
List<T> filtered = new ArrayList<>();
|
||||
int i = 0;
|
||||
for (T item : list) {
|
||||
String filterOutput = Expander.expand(
|
||||
argFilter.toString(),
|
||||
context.plus("it", item).plus("index", Message.of(i))
|
||||
).toString();
|
||||
if (!filterOutput.isBlank() && !"0".equals(filterOutput) && !"0.0".equals(filterOutput)) {
|
||||
filtered.add(item);
|
||||
}
|
||||
i++;
|
||||
}
|
||||
return new CollectionWrapper<>(filtered);
|
||||
}));
|
||||
default:
|
||||
throw new IllegalArgumentException("Unknown list option: " + argProperty);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
public Formattable applyModifier(Message arg) {
|
||||
return delegate.applyModifier(arg);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Message format(Context context) {
|
||||
return delegate.format(context);
|
||||
}
|
||||
|
||||
private static final class Indexed<T> {
|
||||
private final int index;
|
||||
private final T value;
|
||||
|
||||
private Indexed(int index, T value) {
|
||||
this.index = index;
|
||||
this.value = value;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
package io.github.mviper.bomberman.messaging;
|
||||
|
||||
import org.bukkit.ChatColor;
|
||||
|
||||
public class ColorWrapper implements Formattable {
|
||||
private final Formattable delegate;
|
||||
|
||||
public ColorWrapper(ChatColor color) {
|
||||
this.delegate = new RequiredArg(arg -> arg.color(color));
|
||||
}
|
||||
|
||||
@Override
|
||||
public Formattable applyModifier(Message arg) {
|
||||
return delegate.applyModifier(arg);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Message format(Context context) {
|
||||
return delegate.format(context);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,95 @@
|
||||
package io.github.mviper.bomberman.messaging;
|
||||
|
||||
import org.bukkit.command.CommandSender;
|
||||
import org.bukkit.inventory.ItemStack;
|
||||
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
import java.util.HashMap;
|
||||
import java.util.HashSet;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
import java.util.function.Function;
|
||||
|
||||
/**
|
||||
* An immutable set of things referenced in the current scope.
|
||||
*/
|
||||
public class Context {
|
||||
private final Map<String, Formattable> objects;
|
||||
private final Set<Function<String, String>> functions;
|
||||
public final boolean elevated;
|
||||
|
||||
public Context() {
|
||||
this(false);
|
||||
}
|
||||
|
||||
public Context(boolean elevated) {
|
||||
this.objects = Collections.emptyMap();
|
||||
this.functions = Collections.emptySet();
|
||||
this.elevated = elevated;
|
||||
}
|
||||
|
||||
private Context(Map<String, Formattable> objects, Set<Function<String, String>> functions, boolean elevated) {
|
||||
this.objects = objects;
|
||||
this.functions = functions;
|
||||
this.elevated = elevated;
|
||||
}
|
||||
|
||||
public Formattable get(String key) {
|
||||
return objects.get(key);
|
||||
}
|
||||
|
||||
public String getFunction(String key) {
|
||||
for (Function<String, String> function : functions) {
|
||||
String result = function.apply(key);
|
||||
if (result != null) {
|
||||
return result;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
public Context addFunctions(Function<String, String> function) {
|
||||
Set<Function<String, String>> next = new HashSet<>(functions);
|
||||
next.add(function);
|
||||
return new Context(objects, next, elevated);
|
||||
}
|
||||
|
||||
public Context newScope() {
|
||||
return new Context(Collections.emptyMap(), functions, elevated);
|
||||
}
|
||||
|
||||
public Context plus(String key, Formattable thing) {
|
||||
Map<String, Formattable> next = new HashMap<>(objects);
|
||||
next.put(key, thing);
|
||||
return new Context(next, functions, elevated);
|
||||
}
|
||||
|
||||
public Context plus(Context context) {
|
||||
Map<String, Formattable> next = new HashMap<>(objects);
|
||||
next.putAll(context.objects);
|
||||
Set<Function<String, String>> nextFunctions = new HashSet<>(functions);
|
||||
nextFunctions.addAll(context.functions);
|
||||
return new Context(next, nextFunctions, elevated || context.elevated);
|
||||
}
|
||||
|
||||
public Context plus(String key, String value) {
|
||||
return plus(key, Message.of(value));
|
||||
}
|
||||
|
||||
public Context plus(String key, int value) {
|
||||
return plus(key, Message.of(value));
|
||||
}
|
||||
|
||||
public Context plus(String key, Collection<? extends Formattable> value) {
|
||||
return plus(key, new CollectionWrapper<>(value));
|
||||
}
|
||||
|
||||
public Context plus(String key, ItemStack stack) {
|
||||
return plus(key, new ItemWrapper(stack));
|
||||
}
|
||||
|
||||
public Context plus(String key, CommandSender sender) {
|
||||
return plus(key, new SenderWrapper(sender));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,28 @@
|
||||
package io.github.mviper.bomberman.messaging;
|
||||
|
||||
import java.util.function.Function;
|
||||
|
||||
public class ContextArg implements Formattable {
|
||||
private final Function<Context, Formattable> function;
|
||||
|
||||
public ContextArg(Function<Context, Formattable> function) {
|
||||
this.function = function;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Formattable applyModifier(Message arg) {
|
||||
return new ExtraArgsHolder((context, args) -> {
|
||||
Formattable initial = function.apply(context);
|
||||
Formattable current = initial;
|
||||
for (Message message : args) {
|
||||
current = current.applyModifier(message);
|
||||
}
|
||||
return current;
|
||||
}, arg);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Message format(Context context) {
|
||||
return function.apply(context).format(context);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,30 @@
|
||||
package io.github.mviper.bomberman.messaging;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
public class CustomPath implements Formattable {
|
||||
private final Formattable delegate = new RequiredArg(functionArg ->
|
||||
new AdditionalArgs(args -> new ContextArg(context -> {
|
||||
String function = functionArg.toString();
|
||||
String text = context.getFunction(function);
|
||||
if (text == null) {
|
||||
return Message.error("{#|" + function + "}");
|
||||
}
|
||||
Context callContext = context.newScope();
|
||||
for (int i = 0; i < args.size(); i++) {
|
||||
callContext = callContext.plus("arg" + i, args.get(i));
|
||||
}
|
||||
return Expander.expand(text, callContext);
|
||||
}))
|
||||
);
|
||||
|
||||
@Override
|
||||
public Formattable applyModifier(Message arg) {
|
||||
return delegate.applyModifier(arg);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Message format(Context context) {
|
||||
return delegate.format(context);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,27 @@
|
||||
package io.github.mviper.bomberman.messaging;
|
||||
|
||||
import java.util.function.Function;
|
||||
|
||||
public class DefaultArg implements Formattable {
|
||||
private final Message text;
|
||||
private final Function<Message, Formattable> function;
|
||||
|
||||
public DefaultArg(Message text, Function<Message, Formattable> function) {
|
||||
this.text = text;
|
||||
this.function = function;
|
||||
}
|
||||
|
||||
public DefaultArg(String text, Function<Message, Formattable> function) {
|
||||
this(Message.of(text), function);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Formattable applyModifier(Message arg) {
|
||||
return function.apply(arg);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Message format(Context context) {
|
||||
return function.apply(text).format(context);
|
||||
}
|
||||
}
|
||||
118
src/main/java/io/github/mviper/bomberman/messaging/Equation.java
Normal file
118
src/main/java/io/github/mviper/bomberman/messaging/Equation.java
Normal file
@@ -0,0 +1,118 @@
|
||||
package io.github.mviper.bomberman.messaging;
|
||||
|
||||
import net.objecthunter.exp4j.ExpressionBuilder;
|
||||
import net.objecthunter.exp4j.function.Function;
|
||||
import net.objecthunter.exp4j.operator.Operator;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
|
||||
public class Equation implements Formattable {
|
||||
private static final double EPSILON = 0.00000001d;
|
||||
|
||||
private static final int PRECEDENCE_NOT = Operator.PRECEDENCE_UNARY_MINUS;
|
||||
private static final int PRECEDENCE_AND = Operator.PRECEDENCE_ADDITION - 10;
|
||||
private static final int PRECEDENCE_OR = Operator.PRECEDENCE_ADDITION - 20;
|
||||
private static final int PRECEDENCE_COMPARE = Operator.PRECEDENCE_ADDITION - 100;
|
||||
private static final int PRECEDENCE_EQUAL = Operator.PRECEDENCE_ADDITION - 1000;
|
||||
|
||||
private static final Operator NOT = new Operator("!", 1, true, PRECEDENCE_NOT) {
|
||||
@Override
|
||||
public double apply(double... args) {
|
||||
return (args[0] > -EPSILON && args[0] < EPSILON) ? 1.0 : 0.0;
|
||||
}
|
||||
};
|
||||
|
||||
private static final Operator OR = new Operator("$", 2, true, PRECEDENCE_OR) {
|
||||
@Override
|
||||
public double apply(double... args) {
|
||||
return (((args[0] < -EPSILON) || (args[0] > EPSILON)) || ((args[1] < -EPSILON) || (args[1] > EPSILON))) ? 1.0 : 0.0;
|
||||
}
|
||||
};
|
||||
|
||||
private static final Operator AND = new Operator("&", 2, true, PRECEDENCE_AND) {
|
||||
@Override
|
||||
public double apply(double... args) {
|
||||
return (((args[0] < -EPSILON) || (args[0] > EPSILON)) && ((args[1] < -EPSILON) || (args[1] > EPSILON))) ? 1.0 : 0.0;
|
||||
}
|
||||
};
|
||||
|
||||
private static final Operator GREATER = new Operator(">", 2, true, PRECEDENCE_COMPARE) {
|
||||
@Override
|
||||
public double apply(double... args) {
|
||||
return (args[0] > args[1] + EPSILON) ? 1.0 : 0.0;
|
||||
}
|
||||
};
|
||||
|
||||
private static final Operator LESSER = new Operator("<", 2, true, PRECEDENCE_COMPARE) {
|
||||
@Override
|
||||
public double apply(double... args) {
|
||||
return (args[0] + EPSILON < args[1]) ? 1.0 : 0.0;
|
||||
}
|
||||
};
|
||||
|
||||
private static final Operator GREATER_EQUAL = new Operator(">=", 2, true, PRECEDENCE_COMPARE) {
|
||||
@Override
|
||||
public double apply(double... args) {
|
||||
return (args[0] + EPSILON >= args[1]) ? 1.0 : 0.0;
|
||||
}
|
||||
};
|
||||
|
||||
private static final Operator LESSER_EQUAL = new Operator("<=", 2, true, PRECEDENCE_COMPARE) {
|
||||
@Override
|
||||
public double apply(double... args) {
|
||||
return (args[0] <= args[1] + EPSILON) ? 1.0 : 0.0;
|
||||
}
|
||||
};
|
||||
|
||||
private static final Operator EQUAL = new Operator("==", 2, true, PRECEDENCE_EQUAL) {
|
||||
@Override
|
||||
public double apply(double... args) {
|
||||
return (args[0] > args[1] - EPSILON && args[0] < args[1] + EPSILON) ? 1.0 : 0.0;
|
||||
}
|
||||
};
|
||||
|
||||
private static final Operator NOT_EQUAL = new Operator("!=", 2, true, PRECEDENCE_EQUAL) {
|
||||
@Override
|
||||
public double apply(double... args) {
|
||||
return (args[0] < args[1] - EPSILON || args[0] > args[1] + EPSILON) ? 1.0 : 0.0;
|
||||
}
|
||||
};
|
||||
|
||||
private static final Function ROUND = new Function("round", 1) {
|
||||
@Override
|
||||
public double apply(double... args) {
|
||||
return Math.rint(args[0]);
|
||||
}
|
||||
};
|
||||
|
||||
private final Formattable delegate = new RequiredArg(argEquation -> {
|
||||
try {
|
||||
double answer = new ExpressionBuilder(argEquation.toString())
|
||||
.operator(GREATER)
|
||||
.operator(LESSER)
|
||||
.operator(GREATER_EQUAL)
|
||||
.operator(LESSER_EQUAL)
|
||||
.operator(EQUAL)
|
||||
.operator(NOT_EQUAL)
|
||||
.operator(AND)
|
||||
.operator(OR)
|
||||
.operator(NOT)
|
||||
.function(ROUND)
|
||||
.build()
|
||||
.evaluate();
|
||||
return Message.of(BigDecimal.valueOf(answer).stripTrailingZeros().toPlainString());
|
||||
} catch (Exception e) {
|
||||
return Message.error("{" + argEquation + "}");
|
||||
}
|
||||
});
|
||||
|
||||
@Override
|
||||
public Formattable applyModifier(Message arg) {
|
||||
return delegate.applyModifier(arg);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Message format(Context context) {
|
||||
return delegate.format(context);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,24 @@
|
||||
package io.github.mviper.bomberman.messaging;
|
||||
|
||||
import org.bukkit.Bukkit;
|
||||
|
||||
public class Execute implements Formattable {
|
||||
private final Formattable delegate = new RequiredArg(command ->
|
||||
new ContextArg(context -> {
|
||||
if (context.elevated) {
|
||||
Bukkit.getServer().dispatchCommand(Bukkit.getConsoleSender(), command.toString());
|
||||
}
|
||||
return Message.empty;
|
||||
})
|
||||
);
|
||||
|
||||
@Override
|
||||
public Formattable applyModifier(Message arg) {
|
||||
return delegate.applyModifier(arg);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Message format(Context context) {
|
||||
return delegate.format(context);
|
||||
}
|
||||
}
|
||||
173
src/main/java/io/github/mviper/bomberman/messaging/Expander.java
Normal file
173
src/main/java/io/github/mviper/bomberman/messaging/Expander.java
Normal file
@@ -0,0 +1,173 @@
|
||||
package io.github.mviper.bomberman.messaging;
|
||||
|
||||
import org.bukkit.ChatColor;
|
||||
|
||||
import java.util.Collections;
|
||||
import java.util.HashMap;
|
||||
import java.util.Locale;
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.atomic.AtomicInteger;
|
||||
|
||||
public final class Expander {
|
||||
private static final Map<String, Formattable> FUNCTIONS;
|
||||
|
||||
static {
|
||||
Map<String, Formattable> functions = new HashMap<>();
|
||||
functions.put("=", new Equation());
|
||||
functions.put("#switch", new Switch());
|
||||
functions.put("#title", new TitleExpander());
|
||||
functions.put("#raw", Message.rawFlag);
|
||||
functions.put("#", new CustomPath());
|
||||
functions.put("#regex", new RegexExpander());
|
||||
functions.put("#len", new LengthExpander());
|
||||
functions.put("#sub", new SubstringExpander());
|
||||
functions.put("#padl", new PadLeftExpander());
|
||||
functions.put("#padr", new PadRightExpander());
|
||||
functions.put("#exec", new Execute());
|
||||
functions.put("#rand", new RandomExpander());
|
||||
for (ChatColor color : ChatColor.values()) {
|
||||
functions.put("#" + color.name().toLowerCase(Locale.ROOT), new ColorWrapper(color));
|
||||
}
|
||||
FUNCTIONS = Collections.unmodifiableMap(functions);
|
||||
}
|
||||
|
||||
private Expander() {
|
||||
}
|
||||
|
||||
/**
|
||||
* Expands all braces in text. The number of open braces must be balanced with close braces.
|
||||
*
|
||||
* @param text the text to expand
|
||||
* @param context Reference-able things
|
||||
* @return the expanded text
|
||||
*/
|
||||
public static Message expand(String text, Context context) {
|
||||
Message expanded = Message.empty;
|
||||
StringBuilder building = new StringBuilder();
|
||||
boolean ignoreNextSpecial = false;
|
||||
int i = 0;
|
||||
while (i < text.length()) {
|
||||
char c = text.charAt(i);
|
||||
if (c == '{' && !ignoreNextSpecial) {
|
||||
if (building.length() > 0) {
|
||||
expanded = expanded.append(Message.of(building.toString()));
|
||||
building.setLength(0);
|
||||
}
|
||||
|
||||
String subtext = toNext(text, '}', i);
|
||||
if (subtext == null) {
|
||||
throw new IllegalArgumentException("Braces unmatched: '" + text + "'");
|
||||
}
|
||||
Message expandedBrace;
|
||||
if (subtext.startsWith("{!")) {
|
||||
expandedBrace = Message.of(subtext.replaceFirst("!", ""));
|
||||
} else {
|
||||
expandedBrace = expandBrace(subtext, context);
|
||||
}
|
||||
expanded = expanded.append(expandedBrace);
|
||||
i += subtext.length() - 1;
|
||||
} else if (c == '\\' && !ignoreNextSpecial) {
|
||||
ignoreNextSpecial = true;
|
||||
} else {
|
||||
building.append(c);
|
||||
ignoreNextSpecial = false;
|
||||
}
|
||||
i++;
|
||||
}
|
||||
if (building.length() > 0) {
|
||||
expanded = expanded.append(Message.of(building.toString()));
|
||||
}
|
||||
return expanded;
|
||||
}
|
||||
|
||||
/**
|
||||
* Expands a brace in a message.
|
||||
* Each sub brace will be expanded with the same arguments as this message.
|
||||
*
|
||||
* @param text the text to expands formatted as "{ key | arg1 | ... | argN }"
|
||||
* @return the expanded string
|
||||
*/
|
||||
private static Message expandBrace(String text, Context context) {
|
||||
if (text.charAt(0) != '{' || text.charAt(text.length() - 1) != '}') {
|
||||
throw new RuntimeException("expandBrace() must start and end with a brace");
|
||||
}
|
||||
|
||||
String keyString = toNext(text, '|', 1);
|
||||
if (keyString == null) {
|
||||
throw new RuntimeException("Text bad: '" + text + "'");
|
||||
}
|
||||
|
||||
AtomicInteger index = new AtomicInteger(keyString.length());
|
||||
java.util.List<Message> args = new java.util.ArrayList<>();
|
||||
while (true) {
|
||||
String subArg = toNext(text, '|', index.get());
|
||||
if (subArg == null) {
|
||||
break;
|
||||
}
|
||||
args.add(Message.lazyExpand(subArg.substring(1, subArg.length() - 1), context));
|
||||
index.addAndGet(subArg.length() - 1);
|
||||
}
|
||||
|
||||
keyString = keyString.substring(0, keyString.length() - 1).trim().toLowerCase(Locale.ROOT);
|
||||
|
||||
if ("#exec".equals(keyString) && !context.elevated) {
|
||||
return Message.empty;
|
||||
}
|
||||
|
||||
boolean reference = keyString.startsWith("@");
|
||||
if (reference) {
|
||||
keyString = keyString.substring(1);
|
||||
}
|
||||
|
||||
Formattable thing = context.get(keyString);
|
||||
if (thing == null) {
|
||||
thing = FUNCTIONS.get(keyString);
|
||||
}
|
||||
if (thing == null) {
|
||||
return Message.error(text);
|
||||
}
|
||||
|
||||
Formattable modified = thing;
|
||||
for (Message arg : args) {
|
||||
modified = modified.applyModifier(arg);
|
||||
}
|
||||
|
||||
if (reference) {
|
||||
return Message.reference(modified, context);
|
||||
}
|
||||
return modified.format(context);
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the substring of sequence from index to the next endingChar but takes into account brace skipping.
|
||||
* The returned string will include both the start and end characters. If a closing brace
|
||||
* is found before the wanted character, then the remaining to that brace is returned. If the end of sequence is
|
||||
* reached, then null is returned.
|
||||
*/
|
||||
private static String toNext(String sequence, char endingChar, int startIndex) {
|
||||
int size = sequence.length();
|
||||
int openBracesFound = 0;
|
||||
boolean ignoreNextSpecial = false;
|
||||
for (int i = startIndex + 1; i < size; i++) {
|
||||
if (ignoreNextSpecial) {
|
||||
ignoreNextSpecial = false;
|
||||
} else {
|
||||
char c = sequence.charAt(i);
|
||||
if (c == endingChar && openBracesFound == 0) {
|
||||
return sequence.substring(startIndex, i + 1);
|
||||
}
|
||||
if (c == '{') {
|
||||
openBracesFound++;
|
||||
} else if (c == '}') {
|
||||
openBracesFound--;
|
||||
if (openBracesFound < 0) {
|
||||
return sequence.substring(startIndex, i + 1);
|
||||
}
|
||||
} else if (c == '\\') {
|
||||
ignoreNextSpecial = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,28 @@
|
||||
package io.github.mviper.bomberman.messaging;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
import java.util.function.BiFunction;
|
||||
|
||||
class ExtraArgsHolder implements Formattable {
|
||||
private final BiFunction<Context, List<Message>, Formattable> callback;
|
||||
private final List<Message> extraArgs;
|
||||
|
||||
ExtraArgsHolder(BiFunction<Context, List<Message>, Formattable> callback, Message... args) {
|
||||
this.callback = callback;
|
||||
this.extraArgs = new ArrayList<>(Arrays.asList(args));
|
||||
}
|
||||
|
||||
@Override
|
||||
public Formattable applyModifier(Message arg) {
|
||||
List<Message> next = new ArrayList<>(extraArgs);
|
||||
next.add(arg);
|
||||
return new ExtraArgsHolder(callback, next.toArray(new Message[0]));
|
||||
}
|
||||
|
||||
@Override
|
||||
public Message format(Context context) {
|
||||
return callback.apply(context, extraArgs).format(context);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
package io.github.mviper.bomberman.messaging;
|
||||
|
||||
public interface Formattable {
|
||||
/**
|
||||
* Applies a modifier to this object.
|
||||
*
|
||||
* @param arg the value of the modifier
|
||||
*/
|
||||
Formattable applyModifier(Message arg);
|
||||
|
||||
default Formattable applyModifier(String arg) {
|
||||
return applyModifier(Message.of(arg));
|
||||
}
|
||||
|
||||
/**
|
||||
* Formats this custom object into "simple" text.
|
||||
*
|
||||
* @param context additional variables in scope
|
||||
*/
|
||||
Message format(Context context);
|
||||
}
|
||||
@@ -0,0 +1,29 @@
|
||||
package io.github.mviper.bomberman.messaging;
|
||||
|
||||
import org.bukkit.inventory.ItemStack;
|
||||
|
||||
public class ItemWrapper implements Formattable {
|
||||
private final ItemStack item;
|
||||
|
||||
public ItemWrapper(ItemStack item) {
|
||||
this.item = item;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Message format(Context context) {
|
||||
return Text.ITEM_FORMAT.format(context.newScope().plus("item", this));
|
||||
}
|
||||
|
||||
@Override
|
||||
public Formattable applyModifier(Message arg) {
|
||||
String option = arg.toString().toLowerCase();
|
||||
switch (option) {
|
||||
case "amount":
|
||||
return Message.of(item.getAmount());
|
||||
case "type":
|
||||
return Message.of(item.getType().getKey().toString());
|
||||
default:
|
||||
return Message.empty;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,15 @@
|
||||
package io.github.mviper.bomberman.messaging;
|
||||
|
||||
public class LengthExpander implements Formattable {
|
||||
private final Formattable delegate = new RequiredArg(arg -> Message.of(arg.toString().length()));
|
||||
|
||||
@Override
|
||||
public Formattable applyModifier(Message arg) {
|
||||
return delegate.applyModifier(arg);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Message format(Context context) {
|
||||
return delegate.format(context);
|
||||
}
|
||||
}
|
||||
475
src/main/java/io/github/mviper/bomberman/messaging/Message.java
Normal file
475
src/main/java/io/github/mviper/bomberman/messaging/Message.java
Normal file
@@ -0,0 +1,475 @@
|
||||
package io.github.mviper.bomberman.messaging;
|
||||
|
||||
import org.bukkit.ChatColor;
|
||||
import org.bukkit.command.CommandSender;
|
||||
import org.bukkit.entity.Player;
|
||||
|
||||
import javax.annotation.CheckReturnValue;
|
||||
import java.util.ArrayDeque;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Deque;
|
||||
import java.util.LinkedHashSet;
|
||||
import java.util.List;
|
||||
import java.util.Objects;
|
||||
import java.util.Set;
|
||||
|
||||
/**
|
||||
* A message is an immutable coloured/formatted expanded string.
|
||||
*/
|
||||
@CheckReturnValue
|
||||
public class Message implements Formattable {
|
||||
private final TreeNode contents;
|
||||
|
||||
private Message(TreeNode contents) {
|
||||
this.contents = contents;
|
||||
}
|
||||
|
||||
public static Message of(String text) {
|
||||
return new Message(new StringNode(text));
|
||||
}
|
||||
|
||||
public static Message of(Number num) {
|
||||
return of(num.toString());
|
||||
}
|
||||
|
||||
public static final Message empty = of("");
|
||||
|
||||
public static Message title(Message text, Message subtitle, int fadeIn, int duration, int fadeOut) {
|
||||
return new Message(new TitleNode(text, subtitle, fadeIn, duration, fadeOut));
|
||||
}
|
||||
|
||||
public static final Message rawFlag = new Message(new RawNode());
|
||||
|
||||
public static Message error(String text) {
|
||||
return of(text).color(ChatColor.RED);
|
||||
}
|
||||
|
||||
public static Message lazyExpand(String text, Context context) {
|
||||
return new Message(new LazyNode(text, context));
|
||||
}
|
||||
|
||||
public static Message reference(Formattable item, Context context) {
|
||||
return new Message(new ReferenceNode(item, context));
|
||||
}
|
||||
|
||||
private static final class Style {
|
||||
private final ChatColor color;
|
||||
private final Set<ChatColor> formats;
|
||||
|
||||
private Style(ChatColor color, Set<ChatColor> formats) {
|
||||
this.color = color;
|
||||
this.formats = formats;
|
||||
}
|
||||
}
|
||||
|
||||
private static final class Cursor {
|
||||
private final StringBuilder content = new StringBuilder();
|
||||
private final Deque<Style> colorStack = new ArrayDeque<>();
|
||||
|
||||
private Cursor() {
|
||||
colorStack.add(new Style(ChatColor.RESET, Set.of()));
|
||||
}
|
||||
|
||||
private void addStyle(ChatColor color) {
|
||||
Style currentStyle = colorStack.getLast();
|
||||
Style newStyle;
|
||||
if (color.isColor()) {
|
||||
newStyle = new Style(color, currentStyle.formats);
|
||||
} else if (color.isFormat()) {
|
||||
Set<ChatColor> newFormats = new LinkedHashSet<>(currentStyle.formats);
|
||||
newFormats.add(color);
|
||||
newStyle = new Style(currentStyle.color, newFormats);
|
||||
} else {
|
||||
newStyle = new Style(ChatColor.RESET, Set.of());
|
||||
}
|
||||
colorStack.addLast(newStyle);
|
||||
appendConversionString(currentStyle, newStyle);
|
||||
}
|
||||
|
||||
private void appendConversionString(Style from, Style to) {
|
||||
Set<ChatColor> formatsRemoved = new LinkedHashSet<>(from.formats);
|
||||
formatsRemoved.removeAll(to.formats);
|
||||
|
||||
Set<ChatColor> formatsAdded = new LinkedHashSet<>(to.formats);
|
||||
formatsAdded.removeAll(from.formats);
|
||||
|
||||
if (!Objects.equals(from.color, to.color) || !formatsRemoved.isEmpty()) {
|
||||
content.append(to.color);
|
||||
for (ChatColor format : to.formats) {
|
||||
content.append(format);
|
||||
}
|
||||
} else if (!formatsAdded.isEmpty()) {
|
||||
for (ChatColor format : formatsAdded) {
|
||||
content.append(format);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void write(String text) {
|
||||
content.append(text);
|
||||
}
|
||||
|
||||
private void popColor() {
|
||||
Style from = colorStack.removeLast();
|
||||
Style to = colorStack.getLast();
|
||||
appendConversionString(from, to);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return content.toString();
|
||||
}
|
||||
}
|
||||
|
||||
private static final class Title {
|
||||
private final String title;
|
||||
private final String subtitle;
|
||||
private final int fadeIn;
|
||||
private final int stay;
|
||||
private final int fadeOut;
|
||||
|
||||
private Title(String title, String subtitle, int fadeIn, int stay, int fadeOut) {
|
||||
this.title = title;
|
||||
this.subtitle = subtitle;
|
||||
this.fadeIn = fadeIn;
|
||||
this.stay = stay;
|
||||
this.fadeOut = fadeOut;
|
||||
}
|
||||
}
|
||||
|
||||
private interface TreeNode {
|
||||
void expand(Cursor cursor);
|
||||
|
||||
boolean isRaw();
|
||||
|
||||
Title expandTitle();
|
||||
|
||||
Formattable applyModifier(Message arg);
|
||||
}
|
||||
|
||||
private static final class StringNode implements TreeNode {
|
||||
private final String text;
|
||||
|
||||
private StringNode(String text) {
|
||||
this.text = text;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void expand(Cursor cursor) {
|
||||
cursor.write(text);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isRaw() {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Title expandTitle() {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Formattable applyModifier(Message arg) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
private static final class Joined implements TreeNode {
|
||||
private final List<TreeNode> parts;
|
||||
|
||||
private Joined(List<TreeNode> parts) {
|
||||
List<TreeNode> flattened = new ArrayList<>();
|
||||
for (TreeNode part : parts) {
|
||||
if (part instanceof Joined) {
|
||||
flattened.addAll(((Joined) part).parts);
|
||||
} else if (part != empty.contents) {
|
||||
flattened.add(part);
|
||||
}
|
||||
}
|
||||
this.parts = flattened;
|
||||
}
|
||||
|
||||
private Joined(TreeNode a, TreeNode b) {
|
||||
this(List.of(a, b));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void expand(Cursor cursor) {
|
||||
for (TreeNode part : parts) {
|
||||
part.expand(cursor);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isRaw() {
|
||||
for (TreeNode part : parts) {
|
||||
if (part.isRaw()) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Title expandTitle() {
|
||||
for (TreeNode part : parts) {
|
||||
Title title = part.expandTitle();
|
||||
if (title != null) {
|
||||
return title;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Formattable applyModifier(Message arg) {
|
||||
for (TreeNode part : parts) {
|
||||
Formattable result = part.applyModifier(arg);
|
||||
if (result != null) {
|
||||
return result;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
private static final class Colored implements TreeNode {
|
||||
private final TreeNode content;
|
||||
private final ChatColor color;
|
||||
|
||||
private Colored(TreeNode content, ChatColor color) {
|
||||
this.content = content;
|
||||
this.color = color;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void expand(Cursor cursor) {
|
||||
cursor.addStyle(color);
|
||||
content.expand(cursor);
|
||||
cursor.popColor();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isRaw() {
|
||||
return content.isRaw();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Title expandTitle() {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Formattable applyModifier(Message arg) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
private static final class RawNode implements TreeNode {
|
||||
@Override
|
||||
public void expand(Cursor cursor) {
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isRaw() {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Title expandTitle() {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Formattable applyModifier(Message arg) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
private static final class TitleNode implements TreeNode {
|
||||
private final Message title;
|
||||
private final Message subtitle;
|
||||
private final int fadeIn;
|
||||
private final int stay;
|
||||
private final int fadeOut;
|
||||
|
||||
private TitleNode(Message title, Message subtitle, int fadeIn, int stay, int fadeOut) {
|
||||
this.title = title;
|
||||
this.subtitle = subtitle;
|
||||
this.fadeIn = fadeIn;
|
||||
this.stay = stay;
|
||||
this.fadeOut = fadeOut;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void expand(Cursor cursor) {
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isRaw() {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Title expandTitle() {
|
||||
Cursor titleCursor = new Cursor();
|
||||
title.contents.expand(titleCursor);
|
||||
String titleString = titleCursor.toString();
|
||||
Cursor subtitleCursor = new Cursor();
|
||||
subtitle.contents.expand(subtitleCursor);
|
||||
String subtitleString = subtitleCursor.toString();
|
||||
return new Title(titleString, subtitleString, fadeIn, stay, fadeOut);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Formattable applyModifier(Message arg) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
private static final class LazyNode implements TreeNode {
|
||||
private final String text;
|
||||
private final Context context;
|
||||
private TreeNode content;
|
||||
|
||||
private LazyNode(String text, Context context) {
|
||||
this.text = text;
|
||||
this.context = context;
|
||||
}
|
||||
|
||||
private TreeNode content() {
|
||||
if (content == null) {
|
||||
content = Expander.expand(text, context).contents;
|
||||
}
|
||||
return content;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void expand(Cursor cursor) {
|
||||
content().expand(cursor);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isRaw() {
|
||||
return content().isRaw();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Title expandTitle() {
|
||||
return content().expandTitle();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Formattable applyModifier(Message arg) {
|
||||
return content().applyModifier(arg);
|
||||
}
|
||||
}
|
||||
|
||||
private static final class ReferenceNode implements TreeNode {
|
||||
private final Formattable item;
|
||||
private final Context context;
|
||||
private TreeNode content;
|
||||
|
||||
private ReferenceNode(Formattable item, Context context) {
|
||||
this.item = item;
|
||||
this.context = context;
|
||||
}
|
||||
|
||||
private TreeNode content() {
|
||||
if (content == null) {
|
||||
content = item.format(context).contents;
|
||||
}
|
||||
return content;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void expand(Cursor cursor) {
|
||||
content().expand(cursor);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isRaw() {
|
||||
return content().isRaw();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Title expandTitle() {
|
||||
return content().expandTitle();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Formattable applyModifier(Message arg) {
|
||||
return item.applyModifier(arg);
|
||||
}
|
||||
}
|
||||
|
||||
public Message color(ChatColor color) {
|
||||
return new Message(new Colored(contents, color));
|
||||
}
|
||||
|
||||
public Message append(Message text) {
|
||||
return new Message(new Joined(contents, text.contents));
|
||||
}
|
||||
|
||||
@Override
|
||||
public Message format(Context context) {
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Formattable applyModifier(Message arg) {
|
||||
Formattable result = contents.applyModifier(arg);
|
||||
if (result == null) {
|
||||
throw new IllegalArgumentException("Cannot accept extra arguments");
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
public void sendTo(CommandSender sender) {
|
||||
try {
|
||||
TreeNode sendContents;
|
||||
if (contents.isRaw()) {
|
||||
sendContents = contents;
|
||||
} else {
|
||||
sendContents = new Switch()
|
||||
.applyModifier(this)
|
||||
.applyModifier(empty)
|
||||
.applyModifier(empty)
|
||||
.applyModifier(Text.MESSAGE_FORMAT.format(new Context(false).plus("message", this)))
|
||||
.format(new Context(false))
|
||||
.contents;
|
||||
}
|
||||
Cursor cursor = new Cursor();
|
||||
sendContents.expand(cursor);
|
||||
if (!cursor.toString().isBlank()) {
|
||||
String[] lines = cursor.toString().split("\n|\r");
|
||||
StringBuilder text = new StringBuilder();
|
||||
for (String line : lines) {
|
||||
if (!line.isBlank()) {
|
||||
if (text.length() > 0) {
|
||||
text.append("\n");
|
||||
}
|
||||
text.append(line);
|
||||
}
|
||||
}
|
||||
sender.sendMessage(text.toString());
|
||||
}
|
||||
if (sender instanceof Player) {
|
||||
Title title = contents.expandTitle();
|
||||
if (title != null) {
|
||||
((Player) sender).sendTitle(title.title, title.subtitle, title.fadeIn, title.stay, title.fadeOut);
|
||||
}
|
||||
}
|
||||
} catch (RuntimeException e) {
|
||||
sender.sendMessage(ChatColor.RED + "Message format invalid");
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
Cursor cursor = new Cursor();
|
||||
contents.expand(cursor);
|
||||
return cursor.toString();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,48 @@
|
||||
package io.github.mviper.bomberman.messaging;
|
||||
|
||||
import java.util.Iterator;
|
||||
import java.util.function.Function;
|
||||
|
||||
public class PadExpander implements Formattable {
|
||||
private final Function<String, String> startText;
|
||||
private final Function<String, String> endText;
|
||||
private final Formattable delegate;
|
||||
|
||||
public PadExpander(Function<String, String> startText, Function<String, String> endText) {
|
||||
this.startText = startText;
|
||||
this.endText = endText;
|
||||
this.delegate = new RequiredArg(argText ->
|
||||
new RequiredArg(argLength ->
|
||||
new DefaultArg(" ", argPadText -> {
|
||||
int length = Integer.parseInt(argLength.toString());
|
||||
String padText = argPadText.toString();
|
||||
if (padText.isEmpty()) {
|
||||
padText = " ";
|
||||
}
|
||||
|
||||
StringBuilder result = new StringBuilder(startText.apply(argText.toString()));
|
||||
String endString = endText.apply(argText.toString());
|
||||
Iterator<Character> iterator = padText.chars().mapToObj(c -> (char) c).iterator();
|
||||
while (result.length() < length - endString.length()) {
|
||||
if (!iterator.hasNext()) {
|
||||
iterator = padText.chars().mapToObj(c -> (char) c).iterator();
|
||||
}
|
||||
result.append(iterator.next());
|
||||
}
|
||||
result.append(endString);
|
||||
return Message.of(result.toString());
|
||||
})
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Formattable applyModifier(Message arg) {
|
||||
return delegate.applyModifier(arg);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Message format(Context context) {
|
||||
return delegate.format(context);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
package io.github.mviper.bomberman.messaging;
|
||||
|
||||
public class PadLeftExpander extends PadExpander {
|
||||
public PadLeftExpander() {
|
||||
super(text -> "", text -> text);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
package io.github.mviper.bomberman.messaging;
|
||||
|
||||
public class PadRightExpander extends PadExpander {
|
||||
public PadRightExpander() {
|
||||
super(text -> text, text -> "");
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,23 @@
|
||||
package io.github.mviper.bomberman.messaging;
|
||||
|
||||
import java.util.concurrent.ThreadLocalRandom;
|
||||
|
||||
public class RandomExpander implements Formattable {
|
||||
private final Formattable delegate = new DefaultArg(Message.of(1), max ->
|
||||
new DefaultArg(Message.of(0), min -> {
|
||||
double minValue = Double.parseDouble(min.toString());
|
||||
double maxValue = Double.parseDouble(max.toString());
|
||||
return Message.of(Double.toString(ThreadLocalRandom.current().nextDouble(minValue, maxValue)));
|
||||
})
|
||||
);
|
||||
|
||||
@Override
|
||||
public Formattable applyModifier(Message arg) {
|
||||
return delegate.applyModifier(arg);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Message format(Context context) {
|
||||
return delegate.format(context);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,24 @@
|
||||
package io.github.mviper.bomberman.messaging;
|
||||
|
||||
public class RegexExpander implements Formattable {
|
||||
private final Formattable delegate = new RequiredArg(argText ->
|
||||
new RequiredArg(argPattern ->
|
||||
new RequiredArg(argReplace -> {
|
||||
String text = argText.toString();
|
||||
String pattern = argPattern.toString();
|
||||
String replace = argReplace.toString();
|
||||
return Message.of(text.replaceAll(pattern, replace));
|
||||
})
|
||||
)
|
||||
);
|
||||
|
||||
@Override
|
||||
public Formattable applyModifier(Message arg) {
|
||||
return delegate.applyModifier(arg);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Message format(Context context) {
|
||||
return delegate.format(context);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
package io.github.mviper.bomberman.messaging;
|
||||
|
||||
import java.util.function.Function;
|
||||
|
||||
public class RequiredArg implements Formattable {
|
||||
private final Function<Message, Formattable> function;
|
||||
|
||||
public RequiredArg(Function<Message, Formattable> function) {
|
||||
this.function = function;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Formattable applyModifier(Message arg) {
|
||||
return function.apply(arg);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Message format(Context context) {
|
||||
throw new IllegalArgumentException("Extra argument required");
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,47 @@
|
||||
package io.github.mviper.bomberman.messaging;
|
||||
|
||||
import org.bukkit.Bukkit;
|
||||
import org.bukkit.command.CommandSender;
|
||||
|
||||
public class SenderWrapper implements Formattable {
|
||||
private final CommandSender sender;
|
||||
private final Formattable delegate;
|
||||
|
||||
public SenderWrapper(CommandSender sender) {
|
||||
this.sender = sender;
|
||||
this.delegate = new DefaultArg("name", arg -> {
|
||||
String option = arg.toString().toLowerCase();
|
||||
switch (option) {
|
||||
case "name":
|
||||
return Message.of(sender.getName());
|
||||
case "msg":
|
||||
return new RequiredArg(arg2 -> {
|
||||
arg2.sendTo(sender);
|
||||
return Message.empty;
|
||||
});
|
||||
case "exec":
|
||||
return new RequiredArg(arg2 -> new ContextArg(context -> {
|
||||
if (context.elevated) {
|
||||
String cmd = arg2.toString();
|
||||
Bukkit.getServer().dispatchCommand(sender, cmd);
|
||||
}
|
||||
return Message.empty;
|
||||
}));
|
||||
case "haspermission":
|
||||
return new RequiredArg(arg2 -> Message.of(sender.hasPermission(arg2.toString()) ? 1 : 0));
|
||||
default:
|
||||
throw new IllegalArgumentException("Unknown CommandSender option: " + arg);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
public Formattable applyModifier(Message arg) {
|
||||
return delegate.applyModifier(arg);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Message format(Context context) {
|
||||
return delegate.format(context);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,50 @@
|
||||
package io.github.mviper.bomberman.messaging;
|
||||
|
||||
public class SubstringExpander implements Formattable {
|
||||
private final Formattable delegate = new RequiredArg(argText ->
|
||||
new RequiredArg(argStart ->
|
||||
new DefaultArg(Message.of(Integer.MAX_VALUE), argLength -> {
|
||||
String text = argText.toString();
|
||||
int start = Integer.parseInt(argStart.toString());
|
||||
if (start < 0) {
|
||||
start = text.length() - Math.abs(start);
|
||||
}
|
||||
int length = Integer.parseInt(argLength.toString());
|
||||
int end;
|
||||
if (length < 0) {
|
||||
end = text.length() - Math.abs(length);
|
||||
} else {
|
||||
end = start + Math.min(length, text.length());
|
||||
}
|
||||
|
||||
if (start < 0) {
|
||||
start = 0;
|
||||
}
|
||||
if (start > text.length()) {
|
||||
start = text.length();
|
||||
}
|
||||
if (end < 0) {
|
||||
end = 0;
|
||||
}
|
||||
if (end > text.length()) {
|
||||
end = text.length();
|
||||
}
|
||||
if (end < start) {
|
||||
end = start;
|
||||
}
|
||||
|
||||
return Message.of(text.substring(start, end));
|
||||
})
|
||||
)
|
||||
);
|
||||
|
||||
@Override
|
||||
public Formattable applyModifier(Message arg) {
|
||||
return delegate.applyModifier(arg);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Message format(Context context) {
|
||||
return delegate.format(context);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,35 @@
|
||||
package io.github.mviper.bomberman.messaging;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
public class Switch implements Formattable {
|
||||
private final Formattable delegate = new RequiredArg(valueArg ->
|
||||
new AdditionalArgs(args -> {
|
||||
String value = valueArg.toString();
|
||||
int size = args.size();
|
||||
int i = 0;
|
||||
while (i < size) {
|
||||
Message test = args.get(i);
|
||||
if (i + 1 < args.size()) {
|
||||
if (value.equals(test.toString())) {
|
||||
return args.get(i + 1);
|
||||
}
|
||||
} else {
|
||||
return test; // default
|
||||
}
|
||||
i += 2;
|
||||
}
|
||||
return Message.empty;
|
||||
})
|
||||
);
|
||||
|
||||
@Override
|
||||
public Formattable applyModifier(Message arg) {
|
||||
return delegate.applyModifier(arg);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Message format(Context context) {
|
||||
return delegate.format(context);
|
||||
}
|
||||
}
|
||||
215
src/main/java/io/github/mviper/bomberman/messaging/Text.java
Normal file
215
src/main/java/io/github/mviper/bomberman/messaging/Text.java
Normal file
@@ -0,0 +1,215 @@
|
||||
package io.github.mviper.bomberman.messaging;
|
||||
|
||||
import io.github.mviper.bomberman.Bomberman;
|
||||
import org.bukkit.configuration.InvalidConfigurationException;
|
||||
import org.bukkit.configuration.file.YamlConfiguration;
|
||||
|
||||
import java.io.BufferedReader;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStreamReader;
|
||||
import java.io.Reader;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
|
||||
/**
|
||||
* A list of Contexteds that are defined in messages.yml. If messages.yml has not been created, then it will default to
|
||||
* default_messages.yml in the resource folder.
|
||||
*/
|
||||
public enum Text implements Formattable {
|
||||
MESSAGE_FORMAT("format.message"),
|
||||
ITEM_FORMAT("format.item"),
|
||||
PLAYER_WON("game-play.player-won"),
|
||||
GAME_COUNT("game-play.count"),
|
||||
GAME_STARTED("game-play.started"),
|
||||
DENY_PERMISSION("command.deny-permission"),
|
||||
INCORRECT_USAGE("command.incorrect-usage"),
|
||||
UNKNOWN_COMMAND("command.unknown-command"),
|
||||
MUST_BE_PLAYER("command.must-be-player"),
|
||||
INVALID_NUMBER("command.invalid-number"),
|
||||
INVALID_TARGET_SELECTOR("command.invalid-target-selector"),
|
||||
COMMAND_GROUP_HELP("command.group.help"),
|
||||
COMMAND_GROUP_USAGE("command.group.usage"),
|
||||
COMMAND_GROUP_EXAMPLE("command.group.example"),
|
||||
COMMAND_GROUP_EXTRA("command.group.extra"),
|
||||
COMMAND_HELP("command.help"),
|
||||
COMMAND_CANCELLED("command.cancelled"),
|
||||
BOMBERMAN_DESCRIPTION("command.bomberman.description"),
|
||||
START_NAME("command.start.name"),
|
||||
START_DESCRIPTION("command.start.description"),
|
||||
START_USAGE("command.start.usage"),
|
||||
START_EXAMPLE("command.start.example"),
|
||||
START_EXTRA("command.start.extra"),
|
||||
START_FLAG_OVERRIDE_DESC("command.start.flags.o.description"),
|
||||
START_FLAG_DELAY_DESC("command.start.flags.d.description"),
|
||||
START_FLAG_DELAY_EXT("command.start.flags.d.ext"),
|
||||
GAME_ALREADY_STARTED("command.start.already-started"),
|
||||
GAME_ALREADY_COUNTING("command.start.already-counting"),
|
||||
GAME_NO_PLAYERS("command.start.no-players"),
|
||||
GAME_START_SUCCESS("command.start.success"),
|
||||
STOP_NAME("command.stop.name"),
|
||||
STOP_DESCRIPTION("command.stop.description"),
|
||||
STOP_USAGE("command.stop.usage"),
|
||||
STOP_EXAMPLE("command.stop.example"),
|
||||
STOP_EXTRA("command.stop.extra"),
|
||||
STOP_NOT_STARTED("command.stop.not-started"),
|
||||
STOP_SUCCESS("command.stop.success"),
|
||||
STOP_TIMER_STOPPED("command.stop.timer-stopped"),
|
||||
RELOAD_NAME("command.reload.name"),
|
||||
RELOAD_DESCRIPTION("command.reload.description"),
|
||||
RELOAD_USAGE("command.reload.usage"),
|
||||
RELOAD_EXAMPLE("command.reload.example"),
|
||||
RELOAD_EXTRA("command.reload.extra"),
|
||||
RELOAD_SUCCESS("command.reload.success"),
|
||||
RELOAD_CANNOT_LOAD("command.reload.cannot-load"),
|
||||
CONFIGURE_NAME("command.configure.name"),
|
||||
CONFIGURE_DESCRIPTION("command.configure.description"),
|
||||
CONFIGURE_USAGE("command.configure.usage"),
|
||||
CONFIGURE_EXAMPLE("command.configure.example"),
|
||||
CONFIGURE_EXTRA("command.configure.extra"),
|
||||
CONFIGURE_PROMPT_CREATIVE("command.configure.prompt-creative"),
|
||||
CONFIGURE_TITLE_MAIN("command.configure.title.main"),
|
||||
CONFIGURE_TITLE_GENERAL("command.configure.title.general"),
|
||||
CONFIGURE_TITLE_BLOCKS("command.configure.title.blocks"),
|
||||
CONFIGURE_TITLE_LOOT("command.configure.title.loot"),
|
||||
CONFIGURE_TITLE_INVENTORY("command.configure.title.inventory"),
|
||||
CONFIGURE_BACK("command.configure.back"),
|
||||
CONFIGURE_DESTRUCTIBLE("command.configure.blocks.destructible.name"),
|
||||
CONFIGURE_DESTRUCTIBLE_DESC("command.configure.blocks.destructible.description"),
|
||||
CONFIGURE_INDESTRUCTIBLE("command.configure.blocks.indestructible.name"),
|
||||
CONFIGURE_INDESTRUCTIBLE_DESC("command.configure.blocks.indestructible.description"),
|
||||
CONFIGURE_PASS_KEEP("command.configure.blocks.pass-keep.name"),
|
||||
CONFIGURE_PASS_KEEP_DESC("command.configure.blocks.pass-keep.description"),
|
||||
CONFIGURE_PASS_DESTROY("command.configure.blocks.pass-destroy.name"),
|
||||
CONFIGURE_PASS_DESTROY_DESC("command.configure.blocks.pass-destroy.description"),
|
||||
CONFIGURE_LIVES("command.configure.general.lives"),
|
||||
CONFIGURE_FUSE_TICKS("command.configure.general.fuse-ticks"),
|
||||
CONFIGURE_FIRE_TICKS("command.configure.general.fire-ticks"),
|
||||
CONFIGURE_IMMUNITY_TICKS("command.configure.general.immunity-ticks"),
|
||||
CONFIGURE_TNT_BLOCK("command.configure.general.tnt-block"),
|
||||
CONFIGURE_FIRE_ITEM("command.configure.general.fire-item"),
|
||||
CONFIGURE_LOOT_SLOT("command.configure.loot.slot"),
|
||||
CONFIGURE_LOOT_BLOCK("command.configure.loot.block"),
|
||||
CONFIGURE_LOOT_ITEM("command.configure.loot.item"),
|
||||
CONFIGURE_LOOT_WEIGHT("command.configure.loot.weight"),
|
||||
CREATE_NAME("command.create.name"),
|
||||
CREATE_DESCRIPTION("command.create.description"),
|
||||
CREATE_USAGE("command.create.usage"),
|
||||
CREATE_EXAMPLE("command.create.example"),
|
||||
CREATE_EXTRA("command.create.extra"),
|
||||
CREATE_GAME_EXISTS("command.create.game-exists"),
|
||||
CREATE_GAME_FILE_CONFLICT("command.create.file-conflict"),
|
||||
CREATE_GAME_FILE_NOT_FOUND("command.create.file-not-found"),
|
||||
CREATE_NEED_SELECTION("command.create.need-selection"),
|
||||
CREATE_SUCCESS("command.create.success"),
|
||||
CREATE_ERROR("command.create.error"),
|
||||
CREATE_SCHEMA_NOT_FOUND("command.create.schema-not-found"),
|
||||
CREATE_FLAG_SCHEMA("command.create.flags.s.description"),
|
||||
CREATE_FLAG_GAME("command.create.flags.g.description"),
|
||||
CREATE_FLAG_TEMPLATE("command.create.flags.t.description"),
|
||||
CREATE_FLAG_WAND("command.create.flags.w.description"),
|
||||
CREATE_FLAG_SCHEMA_EXT("command.create.flags.s.ext"),
|
||||
CREATE_FLAG_GAME_EXT("command.create.flags.t.ext"),
|
||||
CREATE_FLAG_TEMPLATE_EXT("command.create.flags.g.ext"),
|
||||
DELETE_NAME("command.delete.name"),
|
||||
DELETE_DESCRIPTION("command.delete.description"),
|
||||
DELETE_USAGE("command.delete.usage"),
|
||||
DELETE_EXAMPLE("command.delete.example"),
|
||||
DELETE_EXTRA("command.delete.extra"),
|
||||
DELETE_SUCCESS("command.delete.success"),
|
||||
UNDO_NAME("command.undo.name"),
|
||||
UNDO_DESCRIPTION("command.undo.description"),
|
||||
UNDO_USAGE("command.undo.usage"),
|
||||
UNDO_EXAMPLE("command.undo.example"),
|
||||
UNDO_EXTRA("command.undo.extra"),
|
||||
UNDO_DELETED("command.undo.deleted"),
|
||||
UNDO_SUCCESS("command.undo.success"),
|
||||
UNDO_UNKNOWN_GAME("command.undo.unknown-game"),
|
||||
GAMELIST_NAME("command.list.name"),
|
||||
GAMELIST_DESCRIPTION("command.list.description"),
|
||||
GAMELIST_USAGE("command.list.usage"),
|
||||
GAMELIST_EXAMPLE("command.list.example"),
|
||||
GAMELIST_EXTRA("command.list.extra"),
|
||||
GAMELIST_GAMES("command.list.games"),
|
||||
INFO_NAME("command.info.name"),
|
||||
INFO_DESCRIPTION("command.info.description"),
|
||||
INFO_USAGE("command.info.usage"),
|
||||
INFO_EXAMPLE("command.info.example"),
|
||||
INFO_EXTRA("command.info.extra"),
|
||||
INFO_DETAILS("command.info.details"),
|
||||
JOIN_NAME("command.join.name"),
|
||||
JOIN_DESCRIPTION("command.join.description"),
|
||||
JOIN_USAGE("command.join.usage"),
|
||||
JOIN_EXAMPLE("command.join.example"),
|
||||
JOIN_EXTRA("command.join.extra"),
|
||||
JOIN_FLAG_TARGET("command.join.flags.t.description"),
|
||||
JOIN_FLAG_TARGET_EXT("command.join.flags.t.ext"),
|
||||
JOIN_GAME_STARTED("command.join.game-started"),
|
||||
JOIN_ALREADY_JOINED("command.join.already-joined"),
|
||||
JOIN_GAME_FULL("command.join.game-full"),
|
||||
JOIN_SUCCESS("command.join.success"),
|
||||
LEAVE_NAME("command.leave.name"),
|
||||
LEAVE_DESCRIPTION("command.leave.description"),
|
||||
LEAVE_USAGE("command.leave.usage"),
|
||||
LEAVE_EXAMPLE("command.leave.example"),
|
||||
LEAVE_EXTRA("command.leave.extra"),
|
||||
LEAVE_FLAG_TARGET("command.leave.flags.t.description"),
|
||||
LEAVE_FLAG_TARGET_EXT("command.leave.flags.t.ext"),
|
||||
LEAVE_SUCCESS("command.leave.success"),
|
||||
LEAVE_NOT_JOINED("command.leave.not-joined");
|
||||
|
||||
private final String text;
|
||||
|
||||
Text(String path) {
|
||||
String serverValue = YAMLLanguage.server.getString(path);
|
||||
if (serverValue != null) {
|
||||
this.text = serverValue;
|
||||
return;
|
||||
}
|
||||
String fallback = YAMLLanguage.builtin.getString(path);
|
||||
if (fallback == null) {
|
||||
throw new RuntimeException("No default message for text: " + path);
|
||||
}
|
||||
this.text = fallback;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Formattable applyModifier(Message arg) {
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Message format(Context context) {
|
||||
return Expander.expand(text, context.plus(YAMLLanguage.textContext));
|
||||
}
|
||||
|
||||
private static final class YAMLLanguage {
|
||||
private static final YamlConfiguration builtin;
|
||||
private static final YamlConfiguration server;
|
||||
private static final Context textContext;
|
||||
|
||||
static {
|
||||
Reader reader = new BufferedReader(new InputStreamReader(
|
||||
Text.class.getClassLoader().getResourceAsStream("default_messages.yml")
|
||||
));
|
||||
builtin = new YamlConfiguration();
|
||||
server = new YamlConfiguration();
|
||||
try {
|
||||
builtin.load(reader);
|
||||
if (Bomberman.instance != null) {
|
||||
Path custom = Bomberman.instance.language();
|
||||
if (Files.exists(custom)) {
|
||||
try (Reader fileReader = Files.newBufferedReader(custom)) {
|
||||
server.load(fileReader);
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch (IOException | InvalidConfigurationException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
textContext = new Context(true).addFunctions(key -> {
|
||||
String result = server.getString(key);
|
||||
return result != null ? result : builtin.getString(key);
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,28 @@
|
||||
package io.github.mviper.bomberman.messaging;
|
||||
|
||||
public class TitleExpander implements Formattable {
|
||||
private final Formattable delegate = new RequiredArg(title ->
|
||||
new DefaultArg(Message.empty, subtitle ->
|
||||
new DefaultArg(Message.of(0), argFadeIn ->
|
||||
new DefaultArg(Message.of(20), argDuration ->
|
||||
new DefaultArg(Message.of(0), argFadeOut -> {
|
||||
int fadeIn = Integer.parseInt(argFadeIn.toString());
|
||||
int duration = Integer.parseInt(argDuration.toString());
|
||||
int fadeOut = Integer.parseInt(argFadeOut.toString());
|
||||
return Message.title(title, subtitle, fadeIn, duration, fadeOut);
|
||||
})
|
||||
)
|
||||
)
|
||||
)
|
||||
);
|
||||
|
||||
@Override
|
||||
public Formattable applyModifier(Message arg) {
|
||||
return delegate.applyModifier(arg);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Message format(Context context) {
|
||||
return delegate.format(context);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,4 @@
|
||||
@ParametersAreNonnullByDefault
|
||||
package io.github.mviper.bomberman;
|
||||
|
||||
import javax.annotation.ParametersAreNonnullByDefault;
|
||||
104
src/main/java/io/github/mviper/bomberman/utils/Box.java
Normal file
104
src/main/java/io/github/mviper/bomberman/utils/Box.java
Normal file
@@ -0,0 +1,104 @@
|
||||
package io.github.mviper.bomberman.utils;
|
||||
|
||||
import org.bukkit.Location;
|
||||
import org.bukkit.World;
|
||||
import org.bukkit.entity.Entity;
|
||||
|
||||
import javax.annotation.CheckReturnValue;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
@CheckReturnValue
|
||||
public class Box {
|
||||
public final World world;
|
||||
public final Dim p1;
|
||||
public final Dim p2;
|
||||
|
||||
public Box(World world, Dim p1, Dim p2) {
|
||||
this.world = world;
|
||||
this.p1 = p1;
|
||||
this.p2 = p2;
|
||||
}
|
||||
|
||||
public Box(World world, int x, int y, int z, int xSize, int ySize, int zSize) {
|
||||
this(world, new Dim(x, y, z), new Dim(x + xSize - 1, y + ySize - 1, z + zSize - 1));
|
||||
}
|
||||
|
||||
public Box(Location location, int xSize, int ySize, int zSize) {
|
||||
this(location.getWorld(), location.getBlockX(), location.getBlockY(), location.getBlockZ(), xSize, ySize, zSize);
|
||||
}
|
||||
|
||||
public Dim getSize() {
|
||||
return new Dim(p2.x - p1.x + 1, p2.y - p1.y + 1, p2.z - p1.z + 1);
|
||||
}
|
||||
|
||||
public boolean contains(Location location) {
|
||||
return contains(location.getWorld(), location.getBlockX(), location.getBlockY(), location.getBlockZ());
|
||||
}
|
||||
|
||||
public boolean contains(World world, int x, int y, int z) {
|
||||
if (world != this.world) {
|
||||
return false;
|
||||
}
|
||||
return x >= p1.x && x <= p2.x
|
||||
&& y >= p1.y && y <= p2.y
|
||||
&& z >= p1.z && z <= p2.z;
|
||||
}
|
||||
|
||||
public List<Entity> getEntities() {
|
||||
List<Entity> entities = new ArrayList<>();
|
||||
Dim size = getSize();
|
||||
// the "+ 16" is to make sure the chunks at the edge are also included
|
||||
int i = p1.x;
|
||||
while (i < p1.x + size.x + 16) {
|
||||
int k = p1.z;
|
||||
while (k < p1.z + size.z + 16) {
|
||||
var chunk = world.getBlockAt(i, 1, k).getChunk();
|
||||
for (Entity entity : chunk.getEntities()) {
|
||||
if (contains(entity.getLocation())) {
|
||||
entities.add(entity);
|
||||
}
|
||||
}
|
||||
k += 16;
|
||||
}
|
||||
i += 16;
|
||||
}
|
||||
return entities;
|
||||
}
|
||||
|
||||
public Stream<Location> stream() {
|
||||
Stream.Builder<Location> builder = Stream.builder();
|
||||
for (int x = p1.x; x <= p2.x; x++) {
|
||||
for (int y = p1.y; y <= p2.y; y++) {
|
||||
for (int z = p1.z; z <= p2.z; z++) {
|
||||
builder.add(new Location(world, x, y, z));
|
||||
}
|
||||
}
|
||||
}
|
||||
return builder.build();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "Box " + p1 + " - " + p2 + " : (" + getSize().volume() + " blocks)";
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
int hash = 31;
|
||||
hash = hash * 31 + world.hashCode();
|
||||
hash = hash * 31 + p1.hashCode();
|
||||
hash = hash * 31 + p2.hashCode();
|
||||
return hash;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object other) {
|
||||
if (!(other instanceof Box)) {
|
||||
return false;
|
||||
}
|
||||
Box box = (Box) other;
|
||||
return box.world == world && box.p1.equals(p1) && box.p2.equals(p2);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,65 @@
|
||||
package io.github.mviper.bomberman.utils;
|
||||
|
||||
import org.bukkit.Location;
|
||||
import org.bukkit.Material;
|
||||
import org.bukkit.World;
|
||||
import org.bukkit.inventory.ItemStack;
|
||||
import org.bukkit.inventory.meta.PotionMeta;
|
||||
import org.bukkit.potion.PotionData;
|
||||
import org.bukkit.potion.PotionType;
|
||||
import org.bukkit.util.BoundingBox;
|
||||
|
||||
import javax.annotation.CheckReturnValue;
|
||||
|
||||
public final class BukkitUtils {
|
||||
private BukkitUtils() {
|
||||
}
|
||||
|
||||
@CheckReturnValue
|
||||
public static Location boxLoc1(Box box) {
|
||||
return new Location(box.world, box.p1.x, box.p1.y, box.p1.z);
|
||||
}
|
||||
|
||||
@CheckReturnValue
|
||||
public static Location boxLoc2(Box box) {
|
||||
return new Location(box.world, box.p2.x, box.p2.y, box.p2.z);
|
||||
}
|
||||
|
||||
@CheckReturnValue
|
||||
public static Location asLoc(World world, Dim dim) {
|
||||
return new Location(world, dim.x, dim.y, dim.z);
|
||||
}
|
||||
|
||||
public static BoundingBox convert(Box box) {
|
||||
return new BoundingBox(
|
||||
box.p1.x, box.p1.y, box.p1.z,
|
||||
box.p2.x + 1, box.p2.y + 1, box.p2.z + 1
|
||||
);
|
||||
}
|
||||
|
||||
@CheckReturnValue
|
||||
public static ItemStack makePotion(PotionType type, int qty, boolean extend, boolean upgraded) {
|
||||
ItemStack stack = new ItemStack(Material.POTION, qty);
|
||||
PotionMeta meta = (PotionMeta) stack.getItemMeta();
|
||||
if (meta != null) {
|
||||
meta.setBasePotionData(new PotionData(type, extend, upgraded));
|
||||
stack.setItemMeta(meta);
|
||||
}
|
||||
return stack;
|
||||
}
|
||||
|
||||
@CheckReturnValue
|
||||
public static ItemStack makePotion(PotionType type) {
|
||||
return makePotion(type, 1, false, false);
|
||||
}
|
||||
|
||||
@CheckReturnValue
|
||||
public static ItemStack makePotion(PotionType type, int qty) {
|
||||
return makePotion(type, qty, false, false);
|
||||
}
|
||||
|
||||
@CheckReturnValue
|
||||
public static Location blockLoc(Location loc) {
|
||||
return new Location(loc.getWorld(), loc.getBlockX(), loc.getBlockY(), loc.getBlockZ());
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,28 @@
|
||||
package io.github.mviper.bomberman.utils;
|
||||
|
||||
import org.bukkit.configuration.serialization.ConfigurationSerializable;
|
||||
|
||||
import java.util.Collections;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* Stores the data passed into it from a configuration section.
|
||||
* Useful for retrieving data from classes that do not exist anymore.
|
||||
*/
|
||||
public class DataRestorer implements ConfigurationSerializable {
|
||||
private final Map<String, Object> data;
|
||||
|
||||
@RefectAccess
|
||||
public DataRestorer(Map<String, Object> data) {
|
||||
this.data = data;
|
||||
}
|
||||
|
||||
public Map<String, Object> getData() {
|
||||
return data;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Map<String, Object> serialize() {
|
||||
return Collections.emptyMap();
|
||||
}
|
||||
}
|
||||
43
src/main/java/io/github/mviper/bomberman/utils/Dim.java
Normal file
43
src/main/java/io/github/mviper/bomberman/utils/Dim.java
Normal file
@@ -0,0 +1,43 @@
|
||||
package io.github.mviper.bomberman.utils;
|
||||
|
||||
import javax.annotation.CheckReturnValue;
|
||||
|
||||
@CheckReturnValue
|
||||
public class Dim {
|
||||
public final int x;
|
||||
public final int y;
|
||||
public final int z;
|
||||
|
||||
public Dim(int x, int y, int z) {
|
||||
this.x = x;
|
||||
this.y = y;
|
||||
this.z = z;
|
||||
}
|
||||
|
||||
public int volume() {
|
||||
return x * y * z;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
int hash = 31;
|
||||
hash = hash * 31 + x;
|
||||
hash = hash * 31 + y;
|
||||
hash = hash * 31 + z;
|
||||
return hash;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object other) {
|
||||
if (!(other instanceof Dim)) {
|
||||
return false;
|
||||
}
|
||||
Dim dim = (Dim) other;
|
||||
return dim.x == x && dim.y == y && dim.z == z;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "Dim{" + x + ", " + y + ", " + z + "}";
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
package io.github.mviper.bomberman.utils;
|
||||
|
||||
import java.lang.annotation.Retention;
|
||||
import java.lang.annotation.RetentionPolicy;
|
||||
|
||||
/**
|
||||
* States that the item is designed to be accessed by reflection. Can be used to tell the IDE warnings to shut up
|
||||
*/
|
||||
@Retention(value = RetentionPolicy.SOURCE)
|
||||
public @interface RefectAccess {
|
||||
}
|
||||
@@ -0,0 +1,47 @@
|
||||
package io.github.mviper.bomberman.utils;
|
||||
|
||||
import com.sk89q.worldedit.bukkit.BukkitAdapter;
|
||||
import com.sk89q.worldedit.extent.clipboard.Clipboard;
|
||||
import com.sk89q.worldedit.math.BlockVector3;
|
||||
import com.sk89q.worldedit.regions.CuboidRegion;
|
||||
import com.sk89q.worldedit.regions.Region;
|
||||
import org.bukkit.Location;
|
||||
|
||||
public final class WorldEditUtils {
|
||||
private WorldEditUtils() {
|
||||
}
|
||||
|
||||
public static CuboidRegion convert(Box box) {
|
||||
return new CuboidRegion(
|
||||
BukkitAdapter.adapt(box.world),
|
||||
BlockVector3.at(box.p1.x, box.p1.y, box.p1.z),
|
||||
BlockVector3.at(box.p2.x, box.p2.y, box.p2.z)
|
||||
);
|
||||
}
|
||||
|
||||
public static BlockVector3 convert(Dim dim) {
|
||||
return BlockVector3.at(dim.x, dim.y, dim.z);
|
||||
}
|
||||
|
||||
public static Dim convert(BlockVector3 vec) {
|
||||
return new Dim(vec.getBlockX(), vec.getBlockY(), vec.getBlockZ());
|
||||
}
|
||||
|
||||
public static Box pastedBounds(Location pasteLocation, Clipboard clipboard) {
|
||||
BlockVector3 pasteVec = BlockVector3.at(
|
||||
pasteLocation.getBlockX(),
|
||||
pasteLocation.getBlockY(),
|
||||
pasteLocation.getBlockZ()
|
||||
);
|
||||
BlockVector3 delta = pasteVec.subtract(clipboard.getOrigin());
|
||||
BlockVector3 min = clipboard.getMinimumPoint().add(delta);
|
||||
BlockVector3 max = clipboard.getMaximumPoint().add(delta);
|
||||
return new Box(pasteLocation.getWorld(), convert(min), convert(max));
|
||||
}
|
||||
|
||||
public static Box selectionBounds(Region region) {
|
||||
BlockVector3 min = region.getMinimumPoint();
|
||||
BlockVector3 max = region.getMaximumPoint();
|
||||
return new Box(BukkitAdapter.adapt(region.getWorld()), convert(min), convert(max));
|
||||
}
|
||||
}
|
||||
14
src/main/resources/config.yml
Normal file
14
src/main/resources/config.yml
Normal file
@@ -0,0 +1,14 @@
|
||||
# -------------------------------------------------------------------- #
|
||||
# #
|
||||
# BOMBERMAN CONFIG #
|
||||
# #
|
||||
# Refer to the Bomberman Wiki for help on configuration: #
|
||||
# https://github.com/mviper/bomberman/wiki #
|
||||
# #
|
||||
# There is nothing to configure in this file. All configuration #
|
||||
# is done on a per-game basis. #
|
||||
# #
|
||||
# This file is automatically generated and will be reset on next #
|
||||
# server reload. #
|
||||
# #
|
||||
# -------------------------------------------------------------------- #
|
||||
356
src/main/resources/default_messages.yml
Normal file
356
src/main/resources/default_messages.yml
Normal file
@@ -0,0 +1,356 @@
|
||||
# -------------------------------------------------------------------- #
|
||||
# #
|
||||
# BOMBERMAN DEFAULT MESSAGES #
|
||||
# #
|
||||
# This file contains the default messages. #
|
||||
# You can overwrite anything by setting the value in messages.yml #
|
||||
# #
|
||||
# Refer to the Bomberman wiki for help on configuring messages: #
|
||||
# https://github.com/mviper/bomberman/wiki/Localisation #
|
||||
# #
|
||||
# This file is automatically generated and will be reset on next #
|
||||
# server reload. #
|
||||
# #
|
||||
# -------------------------------------------------------------------- #
|
||||
|
||||
format:
|
||||
# How a basic message looks
|
||||
# {message}: the message being sent
|
||||
message: '{#green|[Bomberman]} {message}'
|
||||
# How to format an item by default
|
||||
# {item}: the item to format
|
||||
item: '{item|amount}x{item|type}'
|
||||
# How to format a heading
|
||||
# {arg0}: The title
|
||||
heading: '{#raw}{#yellow|--------} {arg0} {#yellow|---------------}'
|
||||
# How to format a list of key-value pairs
|
||||
# {arg0}: the maps key
|
||||
# {arg1} the map's value
|
||||
map: "{#raw}{#switch|{arg1}||| {#gold|{arg0}:} {arg1}}"
|
||||
# How to display a list
|
||||
# {arg0}: the value of the list
|
||||
list: "{#raw}{#switch|{arg0}||| {#gold|*} {arg0}}"
|
||||
# How code/commands are formatted
|
||||
# {arg0}: the code
|
||||
code: '{#gray|{#italic|{arg0}}}'
|
||||
# How errors are displayed
|
||||
# {arg0}: the error
|
||||
error: '{#red|{arg0}}'
|
||||
# How names of things are displayed
|
||||
# {arg0}: the name
|
||||
name: '{#italic|{arg0}}'
|
||||
# How quotes should be formatted
|
||||
# {arg0}: the code
|
||||
quote: "'{arg0}'"
|
||||
|
||||
# Format shortcodes
|
||||
heading: '{#|format.heading|{arg0}}'
|
||||
map: '{#|format.map|{arg0}|{arg1}}'
|
||||
list: '{#|format.list|{arg0}}'
|
||||
code: '{#|format.code|{arg0}}'
|
||||
error: '{#|format.error|{arg0}}'
|
||||
name: '{#|format.name|{arg0}}'
|
||||
quote: '{#|format.quote|{arg0}}'
|
||||
|
||||
game-play:
|
||||
# Informs all the other players not involved that a player died
|
||||
# {player}: the player who died
|
||||
player-killed: '{#|name|{player}} is out!'
|
||||
# The winning text message
|
||||
# {player}: the winner
|
||||
player-won: '{#title|{#gold|WIN!}||0|60|20}'
|
||||
# Sent as the game timer counts down. Called every second
|
||||
# {time}: the time till the game begins
|
||||
count: '{#title|{#yellow|{time}}||0|17|2}'
|
||||
# Sent when the game starts
|
||||
started: '{#title|{#white|GO!}||0|20|2}'
|
||||
# All messages that can be displayed when executing a command.
|
||||
# Most messages have a {command} argument (TODO: which ones?)
|
||||
command:
|
||||
# How to display command help
|
||||
# {command}: The command
|
||||
help: |-
|
||||
{#|heading|Help: {command|name}}
|
||||
{command|description}
|
||||
{#|map|Usage|{#|code|{command|usage}}}
|
||||
{#|map|Extra|{command|extra}}
|
||||
{#|map|Example|{command|example}}
|
||||
{#|map|Flags|{#switch|{command|flags|length}|0|| }}
|
||||
{command|flags|map|{!#|map| -{it|name}{#switch|{it|ext}|||={it|ext}}|{it|description}}|join|
|
||||
}
|
||||
# Shown when a player tries to run a command they do not have permission to use
|
||||
deny-permission: '{#|error|You do not have permission to use {#|code|/{command|path}}}'
|
||||
# Shown when the player uses a command incorrectly
|
||||
# {attempt}: the list of arguments that the player typed
|
||||
incorrect-usage: '{#|error|Incorrect usage. For help, type: {#|code|/{command|path} -?}}'
|
||||
# Shown when a player types a command that does not exist
|
||||
# {attempt}: the cmd that the player typed
|
||||
unknown-command: '{#|error|Unknown command: {#|quote|{attempt}}}'
|
||||
# Shown to the console when it tries to run a command that needs to be done as a player
|
||||
must-be-player: You must be a player
|
||||
# Shown when a command uses a target selector, but the selecting is invalid
|
||||
# {selector}: the string of what they typed
|
||||
invalid-target-selector: '{#|error|{#|quote|{selector}} is invalid}'
|
||||
# Shown when a command needs a number but they typed something else
|
||||
# {number}: the string of what they typed (this will _not_ be a number)
|
||||
invalid-number: '{#|error|{#|quote|{number}} is not a valid number}'
|
||||
# Called when a command is cancelled for an unknown reason
|
||||
cancelled: Operation cancelled
|
||||
# The base command details
|
||||
bomberman:
|
||||
description: Main command for Bomberman
|
||||
# Details for commands for the base Bomberman command
|
||||
group:
|
||||
# Help instructions
|
||||
# {sender}: the player (or console) requesting help
|
||||
help: |-
|
||||
{#|heading|Help: {command|name}}
|
||||
{command|children|sort|{!it|name}|filter|{!sender|haspermission|{it|permission}}|map|{!#|map|{it|name}|{#switch|{=|{#len|{it|description}}>40}|1|{#sub|{it|description}|0|40}...|{it|description}}}|join|
|
||||
}
|
||||
usage: '/{command|path} <subcommand>'
|
||||
example: ''
|
||||
extra: ''
|
||||
# Commands relating to '/bm start'
|
||||
start:
|
||||
name: start
|
||||
description: Start a game
|
||||
usage: /{command|path} <game>
|
||||
example: ''
|
||||
extra: ''
|
||||
flags:
|
||||
d:
|
||||
description: 'The delay in seconds (default=3)'
|
||||
ext: '<delay>'
|
||||
o:
|
||||
description: 'Override existing timers (default=false)'
|
||||
# Message when a player starts an already started arena
|
||||
# {game}: the game attempted to be started
|
||||
already-started: Game {#|name|{game}} already started
|
||||
# Called if game start was called when a timer was already running
|
||||
# {game}: the game attempted to be started
|
||||
# {time}: the time remaining
|
||||
already-counting: Game {#|name|{game}} already counting
|
||||
# Called if game start was called when no players had joined
|
||||
# {game}: the game attempted to be started
|
||||
no-players: Game {#|name|{game}} has no players
|
||||
# The game has been started
|
||||
# {game}: the game that has been started
|
||||
success: Game {#|name|{game}} starting
|
||||
# Messages relating to '/bm stop'
|
||||
stop:
|
||||
name: stop
|
||||
description: Stop a game
|
||||
usage: /{command|path} <game>
|
||||
example: ''
|
||||
extra: ''
|
||||
# The game hasn't been started so the game cannot be stopped
|
||||
# {game}: the game
|
||||
not-started: Game {#|name|{game}} hasn't started
|
||||
# The game has stopped
|
||||
# {game}: the game
|
||||
success: Game {#|name|{game}} stopped
|
||||
# The timer stopped
|
||||
# {game}: the game
|
||||
# {time}: time before it would have started
|
||||
timer-stopped: Timer cancelled
|
||||
# Commands Relating to /bm reload
|
||||
reload:
|
||||
name: reload
|
||||
description: Reloads the config file and resets the arena
|
||||
usage: /{command|path} <game>
|
||||
example: ''
|
||||
extra: ''
|
||||
# The game has been reloaded
|
||||
# {game}: the game
|
||||
success: Game {#|name|{game}} reloaded
|
||||
# Error while loading
|
||||
# {game}: the old game's name (not an actual game)
|
||||
cannot-load: Error reloading the game's save file
|
||||
configure:
|
||||
name: configure
|
||||
description: Configure game's settings
|
||||
usage: /{command|path} <game>
|
||||
example: ''
|
||||
extra: '{#gray|{#italic|Creative users only}}'
|
||||
# If player tries to edit when not in creative
|
||||
# {player}: the player
|
||||
prompt-creative: '{#|error|You must be in creative}'
|
||||
# Various labels in the pop-up menu (no args)
|
||||
title:
|
||||
main: 'Game Settings'
|
||||
general: 'General Settings'
|
||||
blocks: 'Block Settings'
|
||||
loot: 'Block Loot Table'
|
||||
inventory: 'Starting Kit'
|
||||
back: 'Menu'
|
||||
blocks:
|
||||
destructible:
|
||||
name: 'Destructible'
|
||||
description: 'Semi-solid blocks'
|
||||
indestructible:
|
||||
name: 'Indestructible'
|
||||
description: 'Solid blocks default'
|
||||
pass-destroy:
|
||||
name: 'Pass Destroy'
|
||||
description: 'Non solid blocks default'
|
||||
pass-keep:
|
||||
name: 'Pass keep'
|
||||
description: 'Fire passes through without changing'
|
||||
general:
|
||||
lives: Lives
|
||||
fuse-ticks: Fuse ticks
|
||||
fire-ticks: Fire ticks
|
||||
immunity-ticks: Immunity ticks
|
||||
tnt-block: TNT Block
|
||||
fire-item: Fire Item
|
||||
loot:
|
||||
slot: "Block Group {=|{slot}+1}"
|
||||
block: Block Types
|
||||
item: Drop
|
||||
weight: Weighting
|
||||
# Messages relating to '/bm create'
|
||||
create:
|
||||
name: create
|
||||
description: |-
|
||||
Builds a Bomberman game. Use one of {#|code|-g}, {#|code|-t}, {#|code|-s} or {#|code|-w} to select what to build.
|
||||
usage: /{command|path} <name> [options]
|
||||
example: /{command|path} mygame -t=purple
|
||||
extra: 'Custom schematics need all spawns marked by writing {#|code|[spawn]} on signs.'
|
||||
flags:
|
||||
s:
|
||||
description: Build using a World Edit schematic
|
||||
ext: '<file>'
|
||||
t:
|
||||
description: Specify a template game to duplicate
|
||||
ext: '<file>'
|
||||
g:
|
||||
description: Specify an existing game to copy
|
||||
ext: '<game>'
|
||||
w:
|
||||
description: Uses the current World Edit wand selection.
|
||||
# The game's name is already used
|
||||
# {game}: the game that has the shared name
|
||||
game-exists: Game {#|name|{game}} already exists
|
||||
# The game's file name conflicts with an already existing game
|
||||
# {game}: the name of the game that tried to be created
|
||||
# {file}: the filename in conflict
|
||||
file-conflict: Game {#|name|{game}} conflicts with file {#|quote|{file}}
|
||||
# The referenced file name could not be found
|
||||
# {file}: the file (including path) that does not exist
|
||||
# {filename}: just the filename
|
||||
file-not-found: Can not find {#|quote|{file}}
|
||||
# The game was built successfully
|
||||
# {game}: the game that was built
|
||||
success: Game {#|name|{game}} created{#switch|{game|spawns}|0| but has no spawns. Did you mark spawns with "[spawn]"?}
|
||||
# An exception was thrown while building
|
||||
# {error}: the error message
|
||||
error: |-
|
||||
{#|error|Build Error: {#|quote|{error}}
|
||||
See console for details. Game creation may be incomplete.
|
||||
Was the schematic made in a different WorldEdit version?}
|
||||
# Player needs to use WorldEdit to select something to create the game from
|
||||
need-selection: Use wooden axe to select a region first
|
||||
# The requested schematic was not found
|
||||
# {schema}: the file name that was attempted
|
||||
schema-not-found: '{#red|Schematic not found: {#italic|{schema}}}'
|
||||
# Messages relating to '/bm delete'
|
||||
delete:
|
||||
name: delete
|
||||
description: Deletes a game
|
||||
usage: /{command|path} <game>
|
||||
example: ''
|
||||
extra: ''
|
||||
# The game has been deleted
|
||||
# {game}: the game that was deleted (this is still a game object)
|
||||
success: Game {#|name|{game}} destroyed
|
||||
# Messages relating to '/bm undo'
|
||||
undo:
|
||||
name: undo
|
||||
description: Reverts the schematic to its pre-create state and deletes the game (if not already deleted)
|
||||
usage: /{command|path} <game>
|
||||
example: ''
|
||||
extra: Can only undo games built in the last 10 minutes. Does not work for games built with a wand.
|
||||
# The game arena was restored to previous state
|
||||
# {game}: the game name that was reset (NOT a valid object; it is just the name)
|
||||
success: Game {#|name|{game}} schematic reverted
|
||||
# The game was deleted
|
||||
# {game}: the game that was deleted (this is a game object)
|
||||
deleted: Game {#|name|{game}} deleted
|
||||
# Game was not in the memory
|
||||
# {game}: the game name requested (NOT a valid game)
|
||||
unknown-game: No previous state of {#|name|{game}} stored - was it created more than 10 minutes ago or by using a wand?
|
||||
# Messages relating to '/bm list'
|
||||
list:
|
||||
name: list
|
||||
description: Shows all existing games
|
||||
usage: /{command|path}
|
||||
example: ''
|
||||
extra: ''
|
||||
# The displayed message
|
||||
# {games}: a list of games
|
||||
games: |-
|
||||
{#|heading|Games}
|
||||
{#switch|{games|length}|0|{#red|No games present}|{games|sort|{!it|name}|map|{!#|map|{it|name}|{it|players}/{it|spawns} ({#switch|{it|running}|true|Running|false|Waiting|{#red|?}})}|join|
|
||||
}}
|
||||
# Messages relating to '/bm info'
|
||||
info:
|
||||
name: info
|
||||
description: Show information about a game
|
||||
usage: /{command|path} <game>
|
||||
example: ''
|
||||
extra: ''
|
||||
# What info to display about a game
|
||||
# {game}: the game
|
||||
details: |-
|
||||
{#|heading|Info: {game|name}}
|
||||
{#|map|Status|{#switch|{game|running}|true|Playing|false|Waiting|{#red|?}}}
|
||||
{#|map|Players|{game|players}/{game|spawns}}
|
||||
{#|map|Init lives|{game|lives}}
|
||||
{#|map|Init bombs|{game|bombs}}
|
||||
{#|map|Init power|{game|power}}
|
||||
{#|map|Location|({game|x}, {game|y}, {game|z})}
|
||||
{#|map|Schema|{game|schema|name}}
|
||||
{#|map|Size|[{game|schema|xsize}x{game|schema|ysize}x{game|schema|zsize}]}
|
||||
|
||||
# Commands relating to '/bm join'. Also see 'join.xxx' messages
|
||||
join:
|
||||
name: join
|
||||
description: Join a game
|
||||
usage: /{command|path} <game>
|
||||
example: ''
|
||||
extra: ''
|
||||
flags:
|
||||
t:
|
||||
description: Target selector to apply command to. All standard minecraft selectors work, but only players can be selected. This flag requires additional permissions.
|
||||
ext: "<selector>"
|
||||
# The game has no spare spawn points
|
||||
# {game}: the game attempted to be joined
|
||||
game-full: Game {#|name|{game}} is full
|
||||
# Cannot join because the game has already started
|
||||
# {game}: the game that couldn't be joined
|
||||
game-started: 'Game has already started'
|
||||
# Player tried to join a game when they are already playing a game
|
||||
# {game}: the game that couldn't be joined
|
||||
# {player}: the player joining
|
||||
already-joined: 'You are already part of a game'
|
||||
# A player has joined the game. Sent to all observers of a game
|
||||
# {game}: the game joined
|
||||
# {player}: the player who joined
|
||||
success: '{#|name|{player}} joined game {#|name|{game}}{#switch|{=|{game|players}=={game|spawns}}|1|{#exec|bm start {game} -d=10}}'
|
||||
# Messages relating to '/bm leave'
|
||||
leave:
|
||||
name: leave
|
||||
description: Leave the game
|
||||
usage: /{command|path}
|
||||
example: ''
|
||||
extra: ''
|
||||
flags:
|
||||
t:
|
||||
description: Target selector to apply command to. All standard minecraft selectors work, but only players can be selected. This flag requires additional permissions.
|
||||
ext: "<selector>"
|
||||
# The player is not part of any game
|
||||
# {player}: player who isn't joined
|
||||
not-joined: You're not part of a game
|
||||
# Player left successfully
|
||||
# {player}: the player that left
|
||||
success: '{#gray|{#italic|{#|name|{player}} left game}}{#switch|{=|{game|players}==0}|1|{#exec|bm stop {game}}}'
|
||||
203
src/main/resources/games/README.yml
Normal file
203
src/main/resources/games/README.yml
Normal file
@@ -0,0 +1,203 @@
|
||||
# -------------------------------------------------------------------- #
|
||||
# #
|
||||
# BOMBERMAN GAMES DIRECTORY #
|
||||
# #
|
||||
# This directory contains all games that will be loaded. Each #
|
||||
# game has a single zip file called <name>.game.zip. The game #
|
||||
# file contains the schema and all configuration. #
|
||||
# #
|
||||
# DO NOT UNZIP GAME FILES - Unzipped files will not be loaded. #
|
||||
# #
|
||||
# To share a game, simple share the game's zip file. Others can #
|
||||
# download the file into `games/templates` and create copies with #
|
||||
# \bm create <name> -schem=t:<file>.game.zip #
|
||||
# #
|
||||
# Most configuration of a game can be done using: #
|
||||
# \bm configure <name> #
|
||||
# Advanced configuration can be done by opening .game.zip, and #
|
||||
# editing settings.yml. This file contains the default settings #
|
||||
# and explains each setting. #
|
||||
# #
|
||||
# This file is automatically generated and will be reset on next #
|
||||
# server reload. #
|
||||
# #
|
||||
# -------------------------------------------------------------------- #
|
||||
|
||||
|
||||
settings:
|
||||
==: io.github.mviper.bomberman.game.GameSettings
|
||||
|
||||
# How many lives each player gets
|
||||
lives: 3
|
||||
# The item type to use for bomb size calculations
|
||||
power: minecraft:gunpowder
|
||||
# The block type which explodes
|
||||
bomb: minecraft:tnt
|
||||
# The block type used for flames from tnt
|
||||
fire: minecraft:fire
|
||||
|
||||
# What each player spawns in the game with
|
||||
# The item order is preserved in players inventory; pad with nulls
|
||||
initial-items:
|
||||
- ==: org.bukkit.inventory.ItemStack
|
||||
v: 2230
|
||||
type: TNT
|
||||
amount: 3
|
||||
|
||||
# How long before tnt explodes
|
||||
fuse-ticks: 40
|
||||
# The duration that tnt flames should remain for
|
||||
fire-ticks: 20
|
||||
# How long before a player can take a second hit
|
||||
immunity-ticks: 21
|
||||
|
||||
# What each block type should drop.
|
||||
# Each set of materials has a list of Item stacks mapped to a weighting.
|
||||
# Materials without an entry will not drop anything.
|
||||
loot-table:
|
||||
- blocks:
|
||||
- minecraft:snow_block
|
||||
- minecraft:dirt
|
||||
- minecraft:sand
|
||||
loot:
|
||||
- weight: 4 # The relative weighting of choosing this item
|
||||
item: # What item to drop
|
||||
==: org.bukkit.inventory.ItemStack
|
||||
v: 2230
|
||||
type: TNT
|
||||
- weight: 100
|
||||
item:
|
||||
==: org.bukkit.inventory.ItemStack
|
||||
v: 2230
|
||||
type: AIR
|
||||
amount: 0 # amount 0 means "no drop"
|
||||
- weight: 3
|
||||
item:
|
||||
==: org.bukkit.inventory.ItemStack
|
||||
v: 2230
|
||||
type: GUNPOWDER
|
||||
- weight: 1
|
||||
item:
|
||||
==: org.bukkit.inventory.ItemStack
|
||||
v: 2230
|
||||
type: POTION
|
||||
meta:
|
||||
==: ItemMeta
|
||||
meta-type: POTION
|
||||
potion-type: minecraft:healing
|
||||
- weight: 1
|
||||
item:
|
||||
==: org.bukkit.inventory.ItemStack
|
||||
v: 2230
|
||||
type: POTION
|
||||
meta:
|
||||
==: ItemMeta
|
||||
meta-type: POTION
|
||||
potion-type: minecraft:strong_swiftness
|
||||
- weight: 1
|
||||
item:
|
||||
==: org.bukkit.inventory.ItemStack
|
||||
v: 2230
|
||||
type: POTION
|
||||
meta:
|
||||
==: ItemMeta
|
||||
meta-type: POTION
|
||||
potion-type: minecraft:invisibility
|
||||
|
||||
# Below is a second set of blocks with different loot.
|
||||
# Add as many sets as you want
|
||||
- blocks:
|
||||
- minecraft:gravel
|
||||
loot:
|
||||
- item:
|
||||
==: org.bukkit.inventory.ItemStack
|
||||
v: 2230
|
||||
type: TNT
|
||||
weight: 1.0
|
||||
- item:
|
||||
==: org.bukkit.inventory.ItemStack
|
||||
v: 2230
|
||||
type: AIR
|
||||
amount: 0
|
||||
weight: 10.0
|
||||
|
||||
# By default, bomberman players cannot be hurt by anything except TNT
|
||||
# By configuring the below, specific damage causes can be allowed
|
||||
# The possible damage causes are listed here https://hub.spigotmc.org/javadocs/spigot/org/bukkit/event/entity/EntityDamageEvent.DamageCause.html
|
||||
# Values are specified as a regex and not case-sensitive.
|
||||
#
|
||||
# Each damage cause can be altered with different modifiers listed here: https://hub.spigotmc.org/javadocs/bukkit/org/bukkit/event/entity/EntityDamageEvent.DamageModifier.html
|
||||
# Modifiers can be matched by regex (case-insensitive). Modifiers not matched will not be altered.
|
||||
# Modifiers that reduce damage taken will be negative.
|
||||
# If the value is specified on the damage cause, only the 'base' modifier will be altered.
|
||||
#
|
||||
# A special 'cancel' value can be specified that will cancel the event if a non-zero number is given.
|
||||
#
|
||||
# The damage specified is evaluated as a localisation message with the following arguments:
|
||||
# - game: the current game data
|
||||
# - player: the player affected
|
||||
# - cause: the damage cause
|
||||
# - final: the total damage that will be dealt
|
||||
# - base: the damage done by the base modifier
|
||||
# - damage: the modifiers damage to do (not applicable for 'cancel')
|
||||
# - modifier: the modifier being altered (not applicable for 'cancel')
|
||||
damage-source:
|
||||
CUSTOM: {} # damage from bomberman explosions. 'Custom' is the only cause enabled by default
|
||||
# SUICIDE: {} # Allow use of the /kill command
|
||||
# HOT_FLOOR|CONTACT: 1 # Take one heart base damage from magma blocks and cactus and such
|
||||
# ENTITY_ATTACK: # Allow players to attack each other with tools only (hands deal 1.5 max). Each hit will deal 1 heart
|
||||
# base: '{#=|{damage}>=2}'
|
||||
# (?!base).+: 0 # Disable other modifiers (e.g. armour)
|
||||
# cancel: '{=|!{final}}' # Cancel the event when not damaged
|
||||
# '*': {} # Allow everything
|
||||
|
||||
|
||||
# Materials listed here will not be pasted when building the schematic.
|
||||
# This is useful in irregular shaped buildings to keep e.g. the ground material.
|
||||
# It could also be used to keep some blocks from being reset each game.
|
||||
source-mask: []
|
||||
|
||||
# Sets the block that surrounds spawn points
|
||||
cage-block: minecraft:white_stained_glass
|
||||
|
||||
# Bomberman has four types of blocks:
|
||||
# 1. indestructible: tnt will not affect the block
|
||||
# 2. destructible: tnt will destroy the block, but progress no further
|
||||
# 3. pass-keep: tnt will pass through the block without changing the blocks state.
|
||||
# Players inside pass-keep blocks will still be hit
|
||||
# 4. pass-destroy: tnt will pass through the block, ignite the block, then change into air
|
||||
#
|
||||
# Unless set otherwise:
|
||||
# * All solid blocks are indestructible
|
||||
# * All non-solid blocks are pass-destroy.
|
||||
# A solid block is defined as a block that has any hitbox that blocks the players movement
|
||||
#
|
||||
# All types will drop loot when destroyed if they are assigned in loot-table
|
||||
#
|
||||
# The assigned tnt fire type and air types are always pass-destroy and cannot be altered
|
||||
#
|
||||
# Note: The in-game configuration gui will "smart add" similar material ids (e.g wall_sign and sign).
|
||||
# You have to add similar ids manually here
|
||||
|
||||
# Blocks tnt will explode the first block of
|
||||
# Generally, blocks that are in the loot-table should be destructible
|
||||
# Removing tnt from destructible means tnt explosions will not chain together
|
||||
destructible:
|
||||
- minecraft:tnt
|
||||
- minecraft:snow_block
|
||||
- minecraft:dirt
|
||||
- minecraft:sand
|
||||
- minecraft:gravel
|
||||
|
||||
# Blocks tnt cannot touch.
|
||||
# All solid blocks included by default
|
||||
indestructible: []
|
||||
|
||||
# Blocks tnt will pass through, but not alter.
|
||||
# You might like to put your sensitive redstone, tripwires, signs, etc here
|
||||
pass-keep: []
|
||||
|
||||
# Blocks tnt will pass through, ignite, and destroy
|
||||
# All non-solid blocks included by default
|
||||
# Will always include air types and the tnt fire type
|
||||
pass-destroy: []
|
||||
19
src/main/resources/games/templates/README.txt
Normal file
19
src/main/resources/games/templates/README.txt
Normal file
@@ -0,0 +1,19 @@
|
||||
# -------------------------------------------------------------------- #
|
||||
# #
|
||||
# BOMBERMAN TEMPLATE DIRECTORY #
|
||||
# #
|
||||
# This directory contains all games that can be used as a #
|
||||
# template in `/bm create`. Games copied into this directory will #
|
||||
# not be loaded. Template files should end in `.game.zip` #
|
||||
# #
|
||||
# To make a copy of a templated game, run: #
|
||||
# \bm create <name> -schem=t:<file>.game.zip #
|
||||
# #
|
||||
# A templated game is the exact same file as a normal game, #
|
||||
# except that it is stored in this folder. Thus, to share a game #
|
||||
# you've created, simply copy the game's zip file. #
|
||||
# #
|
||||
# This file is automatically generated and will be reset on next #
|
||||
# server reload. #
|
||||
# #
|
||||
# -------------------------------------------------------------------- #
|
||||
BIN
src/main/resources/games/templates/experimental.game.zip
Normal file
BIN
src/main/resources/games/templates/experimental.game.zip
Normal file
Binary file not shown.
BIN
src/main/resources/games/templates/purple.game.zip
Normal file
BIN
src/main/resources/games/templates/purple.game.zip
Normal file
Binary file not shown.
14
src/main/resources/messages.yml
Normal file
14
src/main/resources/messages.yml
Normal file
@@ -0,0 +1,14 @@
|
||||
# -------------------------------------------------------------------- #
|
||||
# #
|
||||
# BOMBERMAN MESSAGES #
|
||||
# #
|
||||
# This file is where overrides to messages are placed. #
|
||||
# #
|
||||
# A list of default messages and parameters are specified in #
|
||||
# default_messages.yml. #
|
||||
# #
|
||||
# For changes to take effect, the server must be reloaded. #
|
||||
# #
|
||||
# -------------------------------------------------------------------- #
|
||||
|
||||
# Add overrides here
|
||||
101
src/main/resources/plugin.yml
Normal file
101
src/main/resources/plugin.yml
Normal file
@@ -0,0 +1,101 @@
|
||||
name: Bomberman
|
||||
main: io.github.mviper.bomberman.Bomberman
|
||||
version: ${version}
|
||||
api-version: 1.21
|
||||
load: POSTWORLD
|
||||
|
||||
author: M_Viper
|
||||
website: https://github.com/mviper/bomberman
|
||||
|
||||
commands:
|
||||
bomberman:
|
||||
description: Main command for Bomberman
|
||||
usage: bomberman <more commands>
|
||||
aliases: bm
|
||||
permission: bomberman.bm
|
||||
|
||||
depend: [WorldEdit]
|
||||
# SoftDepend Multiverse so Bomberman is loaded after the Multiverse worlds get loaded
|
||||
softdepend: [Multiverse-Core, PlaceholderAPI]
|
||||
|
||||
permissions:
|
||||
bomberman.*:
|
||||
description: Access to all Bomberman commands
|
||||
children:
|
||||
bomberman.bm: true
|
||||
bomberman.dictator: true
|
||||
bomberman.operator: true
|
||||
bomberman.player: true
|
||||
bomberman.remote: true
|
||||
default: op
|
||||
bomberman.player:
|
||||
description: Allows join/leave/info
|
||||
children:
|
||||
bomberman.join: true
|
||||
bomberman.leave: true
|
||||
bomberman.info: true
|
||||
bomberman.list: true
|
||||
default: true
|
||||
bomberman.operator:
|
||||
description: Allows control of games (start/stop)
|
||||
children:
|
||||
bomberman.start: true
|
||||
bomberman.stop: true
|
||||
bomberman.reload: true
|
||||
default: op
|
||||
bomberman.dictator:
|
||||
description: Allows building/configuring games
|
||||
children:
|
||||
bomberman.create: true
|
||||
bomberman.delete: true
|
||||
bomberman.undo: true
|
||||
bomberman.configure: true
|
||||
default: op
|
||||
bomberman.remote:
|
||||
description: Allows executing commands on behalf of other players
|
||||
children:
|
||||
bomberman.join.remote: true
|
||||
bomberman.leave.remote: true
|
||||
default: op
|
||||
bomberman.bm:
|
||||
description: Root bomberman command (/bm)
|
||||
default: true
|
||||
bomberman.join:
|
||||
description: /bm join
|
||||
default: false
|
||||
bomberman.join.remote:
|
||||
description: /bm join -t=...
|
||||
default: false
|
||||
bomberman.leave:
|
||||
description: /bm join
|
||||
default: false
|
||||
bomberman.leave.remote:
|
||||
description: /bm leave -t=...
|
||||
default: false
|
||||
bomberman.info:
|
||||
description: /bm info
|
||||
default: false
|
||||
bomberman.list:
|
||||
description: /bm list
|
||||
default: false
|
||||
bomberman.start:
|
||||
description: /bm start
|
||||
default: false
|
||||
bomberman.stop:
|
||||
description: /bm stop
|
||||
default: false
|
||||
bomberman.reload:
|
||||
description: /bm reload
|
||||
default: false
|
||||
bomberman.create:
|
||||
description: /bm create
|
||||
default: false
|
||||
bomberman.delete:
|
||||
description: /bm delete
|
||||
default: false
|
||||
bomberman.undo:
|
||||
description: /bm undo
|
||||
default: false
|
||||
bomberman.configure:
|
||||
description: /bm configure
|
||||
default: false
|
||||
21
src/main/resources/temp/README.txt
Normal file
21
src/main/resources/temp/README.txt
Normal file
@@ -0,0 +1,21 @@
|
||||
# -------------------------------------------------------------------- #
|
||||
# #
|
||||
# BOMBERMAN TEMPORARY DIRECTORY #
|
||||
# #
|
||||
# This directory contains temporary data for Bomberman's #
|
||||
# operation. #
|
||||
# #
|
||||
# Two directories exist: #
|
||||
# - game: #
|
||||
# Data cached for a game. This decreases the server #
|
||||
# restart time as less data needs to be read and processed #
|
||||
# - players: #
|
||||
# Data about the status of a player before they joined a #
|
||||
# Bomberman game. This stores e.g. inventory, health, #
|
||||
# location, etc. The file is used in-case the server #
|
||||
# crashes or shutdowns un-expectantly. #
|
||||
# #
|
||||
# This file is automatically generated and will be reset on next #
|
||||
# server reload. #
|
||||
# #
|
||||
# -------------------------------------------------------------------- #
|
||||
@@ -0,0 +1,102 @@
|
||||
package io.github.mviper.bomberman.commands;
|
||||
|
||||
import io.github.mviper.bomberman.messaging.Message;
|
||||
import org.bukkit.command.Command;
|
||||
import org.bukkit.command.CommandSender;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.assertArrayEquals;
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
import static org.mockito.ArgumentMatchers.any;
|
||||
import static org.mockito.ArgumentMatchers.anyString;
|
||||
import static org.mockito.ArgumentMatchers.eq;
|
||||
import static org.mockito.Mockito.*;
|
||||
|
||||
class BaseCommandTest {
|
||||
@Test
|
||||
void testBaseCommandFindsCorrectChildAndGivesCorrectArguments() {
|
||||
BaseCommand group = new BaseCommand(false);
|
||||
|
||||
Cmd child = mock(Cmd.class, "child");
|
||||
group.addChildren(child);
|
||||
when(child.name()).thenReturn(Message.of("Child"));
|
||||
when(child.permission()).thenReturn(Permissions.CREATE);
|
||||
when(child.run(any(), eq(List.of("hello", "world")), eq(Map.of()))).thenReturn(true);
|
||||
|
||||
Cmd child2 = mock(Cmd.class, "child2");
|
||||
group.addChildren(child2);
|
||||
when(child2.name()).thenReturn(Message.of("child2"));
|
||||
when(child2.permission()).thenReturn(Permissions.CREATE);
|
||||
|
||||
CommandSender sender = mock(CommandSender.class, "sender");
|
||||
when(sender.hasPermission(anyString())).thenReturn(true);
|
||||
|
||||
group.run(sender, List.of("chIlD", "hello", "world"), Map.of());
|
||||
|
||||
verify(child).run(sender, List.of("hello", "world"), Map.of());
|
||||
|
||||
verify(child2, atMostOnce()).name();
|
||||
verifyNoMoreInteractions(child2);
|
||||
|
||||
verify(sender, never()).sendMessage(anyString());
|
||||
}
|
||||
|
||||
@Test
|
||||
void testBaseCommandTabCompletesCommands() {
|
||||
BaseCommand group = new BaseCommand(false);
|
||||
|
||||
CommandSender sender = mock(CommandSender.class, "sender");
|
||||
when(sender.hasPermission(anyString())).thenReturn(true);
|
||||
Permission permission = mock(Permission.class);
|
||||
when(permission.isAllowedBy(any())).thenReturn(true);
|
||||
|
||||
Cmd child = mock(Cmd.class, "child");
|
||||
group.addChildren(child);
|
||||
when(child.name()).thenReturn(Message.of("child"));
|
||||
when(child.permission()).thenReturn(permission);
|
||||
when(child.options(sender, List.of("hello", "world", ""))).thenReturn(List.of("all", "good"));
|
||||
|
||||
Cmd child2 = mock(Cmd.class, "child2");
|
||||
group.addChildren(child2);
|
||||
when(child2.name()).thenReturn(Message.of("child2"));
|
||||
when(child2.permission()).thenReturn(permission);
|
||||
|
||||
List<String> result = group.onTabComplete(sender, mock(Command.class), "", new String[]{"chIlD", "hello", "world", ""});
|
||||
|
||||
verify(child, atLeastOnce()).name();
|
||||
verify(child).permission();
|
||||
verify(child).options(sender, List.of("hello", "world", ""));
|
||||
|
||||
verify(child2, atMostOnce()).name();
|
||||
verify(child2, atMostOnce()).permission();
|
||||
verifyNoMoreInteractions(child2);
|
||||
|
||||
assertEquals(List.of("all", "good"), result);
|
||||
|
||||
verify(sender, never()).sendMessage(anyString());
|
||||
}
|
||||
|
||||
@Test
|
||||
void testBaseCommandTabCompletesFlagOptions() {
|
||||
CommandSender sender = mock(CommandSender.class, "sender");
|
||||
when(sender.hasPermission(anyString())).thenReturn(true);
|
||||
Permission permission = mock(Permission.class);
|
||||
when(permission.isAllowedBy(any())).thenReturn(true);
|
||||
|
||||
Cmd subCommand = mock(Cmd.class);
|
||||
when(subCommand.name()).thenReturn(Message.of("dummy"));
|
||||
when(subCommand.permission()).thenReturn(permission);
|
||||
when(subCommand.flags(any())).thenReturn(List.of("a", "b").stream().collect(java.util.stream.Collectors.toSet()));
|
||||
when(subCommand.flagExtension("a")).thenReturn(Message.of("<something>"));
|
||||
when(subCommand.flagExtension("b")).thenReturn(Message.of(""));
|
||||
|
||||
BaseCommand base = new BaseCommand(false);
|
||||
base.addChildren(subCommand);
|
||||
List<String> result = base.onTabComplete(sender, mock(Command.class), "", new String[]{"dummy", "-"});
|
||||
|
||||
assertArrayEquals(new String[]{"-?", "-a", "-b"}, result.stream().sorted().toArray(String[]::new));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,50 @@
|
||||
package io.github.mviper.bomberman.game;
|
||||
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
import java.util.concurrent.atomic.AtomicInteger;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
import static org.junit.jupiter.api.Assertions.assertTrue;
|
||||
|
||||
class ExplosionTest {
|
||||
@Test
|
||||
void lootSelectWithSingleItemAlwaysGetThatItem() {
|
||||
for (int i = 0; i <= 99; i++) {
|
||||
Map<String, Integer> loot = Map.of("diamond", 1);
|
||||
Set<String> got = Explosion.lootSelect(loot);
|
||||
assertEquals(Set.of("diamond"), got);
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
void lootSelectWithNoLootGetsNothing() {
|
||||
for (int i = 0; i <= 99; i++) {
|
||||
Map<String, Integer> loot = Map.of();
|
||||
Set<String> got = Explosion.lootSelect(loot);
|
||||
assertEquals(Set.of(), got);
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
void lootSelectIsWeightedUnbiased() {
|
||||
AtomicInteger diamonds = new AtomicInteger();
|
||||
AtomicInteger dirt = new AtomicInteger();
|
||||
Map<AtomicInteger, Integer> loot = Map.of(
|
||||
diamonds, 1,
|
||||
dirt, 3
|
||||
);
|
||||
|
||||
for (int i = 0; i <= 999; i++) {
|
||||
Set<AtomicInteger> got = Explosion.lootSelect(loot);
|
||||
got.forEach(AtomicInteger::incrementAndGet);
|
||||
}
|
||||
|
||||
int diamondsCount = diamonds.get();
|
||||
int dirtCount = dirt.get();
|
||||
assertTrue(diamondsCount >= 150 && diamondsCount <= 350, "Diamonds count out of expected range: " + diamondsCount);
|
||||
assertTrue(dirtCount >= 650 && dirtCount <= 850, "Dirt count out of expected range: " + dirtCount);
|
||||
}
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user