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