Add support for join, leave, and info signs.

The ability to execute commands by hitting signs is already implemented by other plugins, but by creating built-in support for such signs, it's possible to leverage information about the plugin and its current state. This implementation allows for displaying live information about player counts, waves, etc. on the signs in addition to tying actions to them.

Customizable templates defined in the new signs.yml config-file can be bound to signs during the in-game sign creation, and users can define state-specific templates that change based on whether an arena is completely idle, has players in the lobby, or is running and in full swing.

Sign data is stored in data/signs.data as a YAML-formatted file that shouldn't be modified directly, effectively separating configuration (templates in signs.yml) and data (coordinates and parameters in signs.data).

Closes #385
This commit is contained in:
Andreas Troelsen
2018-06-14 00:29:11 +02:00
parent 42416cab2f
commit 84249640d1
32 changed files with 2102 additions and 0 deletions
@@ -674,6 +674,7 @@ public class ArenaMasterImpl implements ArenaMaster
plugin.reloadConfig();
config = plugin.getConfig();
initialize();
plugin.reloadSigns();
if (wasEnabled) setEnabled(true);
}
@@ -8,6 +8,9 @@ import com.garbagemule.MobArena.listeners.MagicSpellsListener;
import com.garbagemule.MobArena.metrics.ArenaCountChart;
import com.garbagemule.MobArena.metrics.ClassCountChart;
import com.garbagemule.MobArena.metrics.VaultChart;
import com.garbagemule.MobArena.signs.ArenaSign;
import com.garbagemule.MobArena.signs.SignBootstrap;
import com.garbagemule.MobArena.signs.SignListeners;
import com.garbagemule.MobArena.things.ThingManager;
import com.garbagemule.MobArena.util.VersionChecker;
import com.garbagemule.MobArena.util.config.ConfigUtils;
@@ -18,6 +21,7 @@ import org.bukkit.ChatColor;
import org.bukkit.configuration.InvalidConfigurationException;
import org.bukkit.configuration.file.FileConfiguration;
import org.bukkit.configuration.file.YamlConfiguration;
import org.bukkit.configuration.serialization.ConfigurationSerialization;
import org.bukkit.plugin.Plugin;
import org.bukkit.plugin.PluginManager;
import org.bukkit.plugin.RegisteredServiceProvider;
@@ -52,6 +56,8 @@ public class MobArena extends JavaPlugin
private Messenger messenger;
private ThingManager thingman;
private SignListeners signListeners;
@Override
public void onLoad() {
thingman = new ThingManager(this);
@@ -61,6 +67,7 @@ public class MobArena extends JavaPlugin
// Initialize config-file
configFile = new File(getDataFolder(), "config.yml");
config = new YamlConfiguration();
ConfigurationSerialization.registerClass(ArenaSign.class);
reloadConfig();
// Initialize global messenger
@@ -88,6 +95,9 @@ public class MobArena extends JavaPlugin
arenaMaster = new ArenaMasterImpl(this);
arenaMaster.initialize();
// Load signs after Messenger and ArenaMaster
reloadSigns();
// Register event listeners
registerListeners();
@@ -111,6 +121,7 @@ public class MobArena extends JavaPlugin
}
arenaMaster.resetArenaMap();
VersionChecker.shutdown();
ConfigurationSerialization.unregisterClass(ArenaSign.class);
getLogger().info("disabled.");
}
@@ -126,6 +137,16 @@ public class MobArena extends JavaPlugin
@Override
public void reloadConfig() {
// Make sure the data folder exists
File data = new File(getDataFolder(), "data");
if (!data.exists()) {
boolean created = data.mkdir();
if (!created) {
throw new IllegalStateException("Failed to create data folder!");
}
getLogger().info("Created data folder.");
}
// Check if the config-file exists
if (!configFile.exists()) {
getLogger().info("No config-file found, creating default...");
@@ -162,6 +183,15 @@ public class MobArena extends JavaPlugin
}
}
void reloadSigns() {
if (signListeners != null) {
signListeners.unregister();
}
SignBootstrap bootstrap = SignBootstrap.create(this);
signListeners = new SignListeners();
signListeners.register(bootstrap);
}
@Override
public void saveConfig() {
try {
@@ -0,0 +1,47 @@
package com.garbagemule.MobArena.signs;
import org.bukkit.Location;
import org.bukkit.configuration.serialization.ConfigurationSerializable;
import java.util.HashMap;
import java.util.Map;
public class ArenaSign implements ConfigurationSerializable {
final Location location;
final String templateId;
final String arenaId;
final String type;
ArenaSign(Location location, String templateId, String arenaId, String type) {
this.location = location;
this.templateId = templateId;
this.arenaId = arenaId;
this.type = type;
}
@Override
public Map<String, Object> serialize() {
Map<String, Object> result = new HashMap<>();
result.put("location", location);
result.put("templateId", templateId);
result.put("arenaId", arenaId);
result.put("type", type);
return result;
}
@SuppressWarnings("WeakerAccess")
public static ArenaSign deserialize(Map<String, Object> map) {
try {
Location location = (Location) map.get("location");
String templateId = (String) map.get("templateId");
String arenaId = (String) map.get("arenaId");
String type = (String) map.get("type");
return new ArenaSign(location, templateId, arenaId, type);
} catch (ClassCastException e) {
String msg = "An arena sign in " + SignStore.FILENAME + " is invalid! You may have to delete the file.";
throw new IllegalStateException(msg);
}
}
}
@@ -0,0 +1,52 @@
package com.garbagemule.MobArena.signs;
import org.bukkit.block.Block;
import org.bukkit.block.Sign;
import org.bukkit.entity.Player;
import org.bukkit.event.EventHandler;
import org.bukkit.event.EventPriority;
import org.bukkit.event.Listener;
import org.bukkit.event.player.PlayerInteractEvent;
import java.util.HashMap;
import java.util.Map;
import java.util.UUID;
class HandlesSignClicks implements Listener {
private static final long COOLDOWN_TIME = 500;
private final SignStore signStore;
private final InvokesSignAction invokesSignAction;
private final Map<UUID, Long> cooldowns;
HandlesSignClicks(SignStore signStore, InvokesSignAction invokesSignAction) {
this.signStore = signStore;
this.invokesSignAction = invokesSignAction;
this.cooldowns = new HashMap<>();
}
@EventHandler(priority = EventPriority.MONITOR)
public void on(PlayerInteractEvent event) {
Block block = event.getClickedBlock();
if (block == null) {
return;
}
if (!(block.getState() instanceof Sign)) {
return;
}
signStore.findByLocation(block.getLocation())
.ifPresent(sign -> purgeAndInvoke(sign, event.getPlayer()));
}
private void purgeAndInvoke(ArenaSign sign, Player player) {
long now = System.currentTimeMillis();
cooldowns.values().removeIf(time -> time < now);
cooldowns.computeIfAbsent(player.getUniqueId(), id -> {
invokesSignAction.invoke(sign, player);
return now + COOLDOWN_TIME;
});
}
}
@@ -0,0 +1,65 @@
package com.garbagemule.MobArena.signs;
import com.garbagemule.MobArena.Messenger;
import org.bukkit.Location;
import org.bukkit.entity.Player;
import org.bukkit.event.EventHandler;
import org.bukkit.event.EventPriority;
import org.bukkit.event.Listener;
import org.bukkit.event.block.SignChangeEvent;
import java.util.stream.IntStream;
class HandlesSignCreation implements Listener {
private final StoresNewSign storesNewSign;
private final RendersTemplateById rendersTemplate;
private final Messenger messenger;
HandlesSignCreation(
StoresNewSign storesNewSign,
RendersTemplateById rendersTemplate,
Messenger messenger
) {
this.storesNewSign = storesNewSign;
this.rendersTemplate = rendersTemplate;
this.messenger = messenger;
}
@EventHandler(priority = EventPriority.HIGHEST, ignoreCancelled = true)
public void on(SignChangeEvent event) {
if (!trim(event, 0).equalsIgnoreCase("[MA]")) {
return;
}
Location location = event.getBlock().getLocation();
String arenaId = trim(event, 1);
String signType = trim(event, 2).toLowerCase();
String templateId = trim(event, 3).toLowerCase();
if (templateId.isEmpty()) {
templateId = signType;
}
Player player = event.getPlayer();
try {
storesNewSign.store(location, arenaId, templateId, signType);
messenger.tell(player, "New " + signType + " sign created for arena " + arenaId);
String[] lines = rendersTemplate.render(templateId, arenaId);
IntStream.range(0, 4)
.forEach(i -> event.setLine(i, lines[i]));
} catch (IllegalArgumentException e) {
messenger.tell(player, e.getMessage());
}
}
private String trim(SignChangeEvent event, int index) {
String line = event.getLine(index);
if (line == null) {
return "";
}
return line.trim();
}
}
@@ -0,0 +1,33 @@
package com.garbagemule.MobArena.signs;
import com.garbagemule.MobArena.Messenger;
import org.bukkit.Location;
import org.bukkit.event.EventHandler;
import org.bukkit.event.EventPriority;
import org.bukkit.event.Listener;
import org.bukkit.event.block.BlockBreakEvent;
class HandlesSignDestruction implements Listener {
private final RemovesSignAtLocation removesSignAtLocation;
private final Messenger messenger;
HandlesSignDestruction(
RemovesSignAtLocation removesSignAtLocation,
Messenger messenger
) {
this.removesSignAtLocation = removesSignAtLocation;
this.messenger = messenger;
}
@EventHandler(priority = EventPriority.MONITOR, ignoreCancelled = true)
public void on(BlockBreakEvent event) {
Location location = event.getBlock().getLocation();
removesSignAtLocation.remove(location)
.ifPresent(sign -> messenger.tell(
event.getPlayer(),
"Removed " + sign.type + " sign for arena " + sign.arenaId
));
}
}
@@ -0,0 +1,50 @@
package com.garbagemule.MobArena.signs;
import com.garbagemule.MobArena.Messenger;
import com.garbagemule.MobArena.Msg;
import com.garbagemule.MobArena.framework.Arena;
import com.garbagemule.MobArena.framework.ArenaMaster;
import org.bukkit.entity.Player;
import java.util.function.Consumer;
class InvokesSignAction {
private final ArenaMaster arenaMaster;
private final Messenger messenger;
InvokesSignAction(ArenaMaster arenaMaster, Messenger messenger) {
this.arenaMaster = arenaMaster;
this.messenger = messenger;
}
void invoke(ArenaSign sign, Player player) {
if (sign.type.equals("join")) {
withArena(sign, player, arena -> {
if (arena.canJoin(player)) {
// Join message is sent in playerJoin
arena.playerJoin(player, player.getLocation());
}
});
} else if (sign.type.equals("leave")) {
withArena(sign, player, arena -> {
if (arena.inArena(player) || arena.inLobby(player) || arena.inSpec(player)) {
// Leave message is not sent in playerLeave
if (arena.playerLeave(player)) {
arena.getMessenger().tell(player, Msg.LEAVE_PLAYER_LEFT);
}
}
});
}
}
private void withArena(ArenaSign sign, Player player, Consumer<Arena> action) {
Arena arena = arenaMaster.getArenaWithName(sign.arenaId);
if (arena == null) {
messenger.tell(player, "Arena " + sign.arenaId + " not found");
return;
}
action.accept(arena);
}
}
@@ -0,0 +1,46 @@
package com.garbagemule.MobArena.signs;
import com.garbagemule.MobArena.MobArena;
import org.bukkit.configuration.InvalidConfigurationException;
import org.bukkit.configuration.file.YamlConfiguration;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.util.Collections;
import java.util.List;
import java.util.stream.Collectors;
class LoadsSignStore {
private final MobArena plugin;
LoadsSignStore(MobArena plugin) {
this.plugin = plugin;
}
SignStore load() {
YamlConfiguration yaml = new YamlConfiguration();
try {
File data = new File(plugin.getDataFolder(), "data");
yaml.load(new File(data, SignStore.FILENAME));
} catch (FileNotFoundException e) {
return new SignStore(Collections.emptyList());
} catch (InvalidConfigurationException e) {
String msg = SignStore.FILENAME + " is invalid! You may have to delete it.";
throw new IllegalStateException(msg, e);
} catch (IOException e) {
throw new IllegalStateException(e);
}
List<ArenaSign> signs = yaml.getList("signs").stream()
.filter(raw -> raw instanceof ArenaSign)
.map(raw -> (ArenaSign) raw)
.collect(Collectors.toList());
plugin.getLogger().info("Loaded " + signs.size() + " arena signs.");
return new SignStore(signs);
}
}
@@ -0,0 +1,106 @@
package com.garbagemule.MobArena.signs;
import com.garbagemule.MobArena.MobArena;
import org.bukkit.configuration.InvalidConfigurationException;
import org.bukkit.configuration.file.YamlConfiguration;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
class LoadsTemplateStore {
private final MobArena plugin;
LoadsTemplateStore(MobArena plugin) {
this.plugin = plugin;
}
TemplateStore read() {
YamlConfiguration yaml = new YamlConfiguration();
try {
File file = new File(plugin.getDataFolder(), TemplateStore.FILENAME);
if (!file.exists()) {
plugin.getLogger().info(TemplateStore.FILENAME + " not found, creating default...");
plugin.saveResource(TemplateStore.FILENAME, false);
}
yaml.load(file);
} catch (InvalidConfigurationException e) {
throw new IllegalStateException(TemplateStore.FILENAME + " is invalid!", e);
} catch (FileNotFoundException e) {
throw new IllegalStateException(TemplateStore.FILENAME + " is missing!", e);
} catch (IOException e) {
throw new IllegalStateException(e);
}
Map<String, Template> map = new HashMap<>();
for (String key : yaml.getKeys(false)) {
validateTemplateNode(key, yaml);
String templateId = stripStateSuffix(key);
map.computeIfAbsent(templateId, id -> loadTemplate(id, yaml));
}
plugin.getLogger().info("Loaded " + map.size() + " sign templates.");
return new TemplateStore(map);
}
private void validateTemplateNode(String key, YamlConfiguration yaml) {
List<?> list = yaml.getList(key);
if (list == null) {
String msg = "Template " + key + " in " + TemplateStore.FILENAME + " is not a list!";
throw new IllegalStateException(msg);
}
list.forEach(element -> {
if (!(element instanceof String)) {
String msg = "Template " + key + " in " + TemplateStore.FILENAME + " is not a valid list of strings!";
throw new IllegalStateException(msg);
}
});
}
private String stripStateSuffix(String id) {
if (hasStateSuffix(id)) {
return id.split("-", -1)[0];
}
return id;
}
private boolean hasStateSuffix(String id) {
return id.endsWith("-idle")
|| id.endsWith("-joining")
|| id.endsWith("-running");
}
private Template loadTemplate(String id, YamlConfiguration yaml) {
String[] base = getLines(yaml, id);
String[] idle = getLines(yaml, id + "-idle");
String[] joining = getLines(yaml, id + "-joining");
String[] running = getLines(yaml, id + "-running");
return new Template.Builder(id)
.withBase(base)
.withIdle(idle)
.withJoining(joining)
.withRunning(running)
.build();
}
private String[] getLines(YamlConfiguration config, String id) {
List<String> list = config.getStringList(id);
if (list.isEmpty()) {
return null;
}
while (list.size() < 4) {
list.add("");
}
if (list.size() > 4) {
list = list.subList(0, 4);
}
return list.toArray(new String[0]);
}
}
@@ -0,0 +1,61 @@
package com.garbagemule.MobArena.signs;
import com.garbagemule.MobArena.framework.Arena;
import org.bukkit.ChatColor;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;
class RedrawsArenaSigns {
private final SignStore signStore;
private final TemplateStore templateStore;
private final RendersTemplate rendersTemplate;
private final SetsLines setsSignLines;
RedrawsArenaSigns(
SignStore signStore,
TemplateStore templateStore,
RendersTemplate rendersTemplate,
SetsLines setsSignLines
) {
this.signStore = signStore;
this.templateStore = templateStore;
this.rendersTemplate = rendersTemplate;
this.setsSignLines = setsSignLines;
}
void redraw(Arena arena) {
List<ArenaSign> signs = signStore.findByArenaId(arena.configName());
Map<String, String[]> rendered = signs.stream()
.map(sign -> sign.templateId)
.distinct()
.collect(Collectors.toMap(
templateId -> templateId,
templateId -> render(templateId, arena)
));
signs.forEach(sign -> setsSignLines.set(
sign.location,
rendered.get(sign.templateId)
));
}
private String[] render(String templateId, Arena arena) {
return templateStore.findById(templateId)
.map(template -> rendersTemplate.render(template, arena))
.orElseGet(() -> notFound(templateId));
}
private static String[] notFound(String templateId) {
return new String[]{
String.join(ChatColor.MAGIC + "b" + ChatColor.RESET, "BROKEN".split("")),
"Template",
ChatColor.BOLD + templateId,
"not found! :("
};
}
}
@@ -0,0 +1,65 @@
package com.garbagemule.MobArena.signs;
import com.garbagemule.MobArena.MobArena;
import com.garbagemule.MobArena.events.*;
import com.garbagemule.MobArena.framework.Arena;
import org.bukkit.event.EventHandler;
import org.bukkit.event.EventPriority;
import org.bukkit.event.Listener;
import org.bukkit.scheduler.BukkitScheduler;
class RedrawsSignsOnUpdates implements Listener {
private final RedrawsArenaSigns redrawsArenaSigns;
private final BukkitScheduler scheduler;
private final MobArena plugin;
RedrawsSignsOnUpdates(
RedrawsArenaSigns redrawsArenaSigns,
MobArena plugin
) {
this.redrawsArenaSigns = redrawsArenaSigns;
this.scheduler = plugin.getServer().getScheduler();
this.plugin = plugin;
}
@EventHandler(priority = EventPriority.MONITOR, ignoreCancelled = true)
public void on(ArenaPlayerJoinEvent event) {
handle(event.getArena());
}
@EventHandler(priority = EventPriority.MONITOR, ignoreCancelled = true)
public void on(ArenaPlayerLeaveEvent event) {
handle(event.getArena());
}
@EventHandler(priority = EventPriority.MONITOR, ignoreCancelled = true)
public void on(ArenaPlayerReadyEvent event) {
handle(event.getArena());
}
@EventHandler(priority = EventPriority.MONITOR, ignoreCancelled = true)
public void on(ArenaStartEvent event) {
handle(event.getArena());
}
@EventHandler(priority = EventPriority.MONITOR, ignoreCancelled = true)
public void on(NewWaveEvent event) {
handle(event.getArena());
}
@EventHandler(priority = EventPriority.MONITOR, ignoreCancelled = true)
public void on(ArenaPlayerDeathEvent event) {
handle(event.getArena());
}
@EventHandler(priority = EventPriority.MONITOR, ignoreCancelled = true)
public void on(ArenaEndEvent event) {
handle(event.getArena());
}
private void handle(Arena arena) {
scheduler.runTask(plugin, () -> redrawsArenaSigns.redraw(arena));
}
}
@@ -0,0 +1,26 @@
package com.garbagemule.MobArena.signs;
import org.bukkit.Location;
import java.util.Optional;
class RemovesSignAtLocation {
private final SignStore signStore;
private final SavesSignStore savesSignStore;
RemovesSignAtLocation(
SignStore signStore,
SavesSignStore savesSignStore
) {
this.signStore = signStore;
this.savesSignStore = savesSignStore;
}
Optional<ArenaSign> remove(Location location) {
Optional<ArenaSign> sign = signStore.remove(location);
sign.ifPresent(s -> savesSignStore.save(signStore));
return sign;
}
}
@@ -0,0 +1,76 @@
package com.garbagemule.MobArena.signs;
import static java.lang.String.valueOf;
import com.garbagemule.MobArena.framework.Arena;
import org.bukkit.ChatColor;
class RendersTemplate {
String[] render(Template template, Arena arena) {
String[] lines = getTemplateByState(template, arena);
String[] result = new String[lines.length];
for (int i = 0; i < lines.length; i++) {
String rendered = render(lines[i], arena);
result[i] = truncate(rendered);
}
return result;
}
private String[] getTemplateByState(Template template, Arena arena) {
if (arena.isRunning()) {
return template.running;
}
if (arena.getPlayersInLobby().size() > 0) {
return template.joining;
}
return template.idle;
}
private String render(String line, Arena arena) {
String result = generic(line, arena);
if (arena.isRunning()) {
result = running(result, arena);
} else {
result = joining(result, arena);
}
return ChatColor.translateAlternateColorCodes('&', result);
}
private String generic(String line, Arena arena) {
return line
.replace("<arena-name>", arena.configName())
.replace("<min-players>", valueOf(arena.getMinPlayers()))
.replace("<max-players>", valueOf(arena.getMaxPlayers()));
}
private String running(String line, Arena arena) {
return line
.replace("<initial-players>", valueOf(arena.getPlayerCount()))
.replace("<live-players>", valueOf(arena.getPlayersInArena().size()))
.replace("<dead-players>", valueOf(arena.getPlayerCount() - arena.getPlayersInArena().size()))
.replace("<current-wave>", valueOf(arena.getWaveManager().getWaveNumber()))
.replace("<final-wave>", valueOf(arena.getWaveManager().getFinalWave()))
.replace("<lobby-players>", "-")
.replace("<ready-players>", "-");
}
private String joining(String line, Arena arena) {
return line
.replace("<initial-players>", valueOf(arena.getPlayersInLobby().size()))
.replace("<live-players>", valueOf(arena.getPlayersInLobby().size()))
.replace("<dead-players>", "-")
.replace("<current-wave>", "-")
.replace("<lobby-players>", valueOf(arena.getPlayersInLobby().size()))
.replace("<ready-players>", valueOf(arena.getReadyPlayersInLobby().size()));
}
private String truncate(String rendered) {
if (rendered.length() <= 15) {
return rendered;
}
return rendered.substring(0, 15);
}
}
@@ -0,0 +1,36 @@
package com.garbagemule.MobArena.signs;
import com.garbagemule.MobArena.framework.Arena;
import com.garbagemule.MobArena.framework.ArenaMaster;
class RendersTemplateById {
private final ArenaMaster arenaMaster;
private final TemplateStore templateStore;
private final RendersTemplate rendersTemplate;
RendersTemplateById(
ArenaMaster arenaMaster,
TemplateStore templateStore,
RendersTemplate rendersTemplate
) {
this.arenaMaster = arenaMaster;
this.templateStore = templateStore;
this.rendersTemplate = rendersTemplate;
}
String[] render(String templateId, String arenaId) {
Template template = templateStore.findById(templateId)
.orElseThrow(() -> new IllegalArgumentException(
"Template " + templateId + " not found"
));
Arena arena = arenaMaster.getArenaWithName(arenaId);
if (arena == null) {
throw new IllegalStateException("Arena " + arenaId + " not found");
}
return rendersTemplate.render(template, arena);
}
}
@@ -0,0 +1,32 @@
package com.garbagemule.MobArena.signs;
import com.garbagemule.MobArena.MobArena;
import org.bukkit.configuration.file.YamlConfiguration;
import java.io.File;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
class SavesSignStore {
private final MobArena plugin;
SavesSignStore(MobArena plugin) {
this.plugin = plugin;
}
void save(SignStore signStore) {
YamlConfiguration yaml = new YamlConfiguration();
List<ArenaSign> values = new ArrayList<>(signStore.findAll());
yaml.set("signs", values);
try {
File data = new File(plugin.getDataFolder(), "data");
yaml.options().header("MobArena Sign Store\n\nPlease DON'T edit this file by hand!\n");
yaml.save(new File(data, SignStore.FILENAME));
} catch (IOException e) {
throw new IllegalStateException(e);
}
}
}
@@ -0,0 +1,22 @@
package com.garbagemule.MobArena.signs;
import org.bukkit.Location;
import org.bukkit.block.BlockState;
import org.bukkit.block.Sign;
class SetsLines {
void set(Location location, String[] lines) {
BlockState state = location.getBlock().getState();
if (!(state instanceof Sign)) {
return;
}
Sign sign = (Sign) state;
for (int i = 0; i < lines.length; i++) {
sign.setLine(i, lines[i]);
}
sign.update();
}
}
@@ -0,0 +1,130 @@
package com.garbagemule.MobArena.signs;
import com.garbagemule.MobArena.MobArena;
@SuppressWarnings("WeakerAccess")
public class SignBootstrap {
private final MobArena plugin;
private final SignStore signStore;
private final TemplateStore templateStore;
private InvokesSignAction invokesSignAction;
private RedrawsArenaSigns redrawsArenaSigns;
private RemovesSignAtLocation removesSignAtLocation;
private RendersTemplate rendersTemplate;
private RendersTemplateById rendersTemplateById;
private SetsLines setsLines;
private StoresNewSign storesNewSign;
private SavesSignStore savesSignStore;
private SignBootstrap(
MobArena plugin,
SignStore signStore,
TemplateStore templateStore
) {
this.plugin = plugin;
this.signStore = signStore;
this.templateStore = templateStore;
}
MobArena getPlugin() {
return plugin;
}
SignStore getSignStore() {
return signStore;
}
TemplateStore getTemplateStore() {
return templateStore;
}
InvokesSignAction getInvokesSignAction() {
if (invokesSignAction == null) {
invokesSignAction = new InvokesSignAction(
plugin.getArenaMaster(),
plugin.getGlobalMessenger()
);
}
return invokesSignAction;
}
RedrawsArenaSigns getRedrawsArenaSigns() {
if (redrawsArenaSigns == null) {
redrawsArenaSigns = new RedrawsArenaSigns(
getSignStore(),
getTemplateStore(),
getRendersTemplate(),
getSetsLines()
);
}
return redrawsArenaSigns;
}
RemovesSignAtLocation getRemovesSignAtLocation() {
if (removesSignAtLocation == null) {
removesSignAtLocation = new RemovesSignAtLocation(
getSignStore(),
getSavesSignStore()
);
}
return removesSignAtLocation;
}
RendersTemplate getRendersTemplate() {
if (rendersTemplate == null) {
rendersTemplate = new RendersTemplate();
}
return rendersTemplate;
}
RendersTemplateById getRendersTemplateById() {
if (rendersTemplateById == null) {
rendersTemplateById = new RendersTemplateById(
plugin.getArenaMaster(),
getTemplateStore(),
getRendersTemplate()
);
}
return rendersTemplateById;
}
SetsLines getSetsLines() {
if (setsLines == null) {
setsLines = new SetsLines();
}
return setsLines;
}
StoresNewSign getStoresNewSign() {
if (storesNewSign == null) {
storesNewSign = new StoresNewSign(
plugin.getArenaMaster(),
getTemplateStore(),
getSignStore(),
getSavesSignStore()
);
}
return storesNewSign;
}
SavesSignStore getSavesSignStore() {
if (savesSignStore == null) {
savesSignStore = new SavesSignStore(plugin);
}
return savesSignStore;
}
public static SignBootstrap create(MobArena plugin) {
TemplateStore templateStore = new LoadsTemplateStore(plugin).read();
SignStore signStore = new LoadsSignStore(plugin).load();
SignBootstrap bootstrap = new SignBootstrap(plugin, signStore, templateStore);
plugin.getArenaMaster().getArenas()
.forEach(bootstrap.getRedrawsArenaSigns()::redraw);
return bootstrap;
}
}
@@ -0,0 +1,66 @@
package com.garbagemule.MobArena.signs;
import com.garbagemule.MobArena.MobArena;
import org.bukkit.event.HandlerList;
import org.bukkit.event.Listener;
import java.util.ArrayList;
import java.util.List;
public class SignListeners {
private final List<Listener> listeners;
public SignListeners() {
this.listeners = new ArrayList<>();
}
public void register(SignBootstrap bootstrap) {
listeners.add(clicks(bootstrap));
listeners.add(creation(bootstrap));
listeners.add(destruction(bootstrap));
listeners.add(redraw(bootstrap));
listeners.forEach(listener -> register(listener, bootstrap));
}
private HandlesSignClicks clicks(SignBootstrap bootstrap) {
return new HandlesSignClicks(
bootstrap.getSignStore(),
bootstrap.getInvokesSignAction()
);
}
private HandlesSignCreation creation(SignBootstrap bootstrap) {
return new HandlesSignCreation(
bootstrap.getStoresNewSign(),
bootstrap.getRendersTemplateById(),
bootstrap.getPlugin().getGlobalMessenger()
);
}
private HandlesSignDestruction destruction(SignBootstrap bootstrap) {
return new HandlesSignDestruction(
bootstrap.getRemovesSignAtLocation(),
bootstrap.getPlugin().getGlobalMessenger()
);
}
private RedrawsSignsOnUpdates redraw(SignBootstrap bootstrap) {
return new RedrawsSignsOnUpdates(
bootstrap.getRedrawsArenaSigns(),
bootstrap.getPlugin()
);
}
private void register(Listener listener, SignBootstrap bootstrap) {
MobArena plugin = bootstrap.getPlugin();
plugin.getServer().getPluginManager().registerEvents(listener, plugin);
}
public void unregister() {
listeners.forEach(HandlerList::unregisterAll);
listeners.clear();
}
}
@@ -0,0 +1,49 @@
package com.garbagemule.MobArena.signs;
import org.bukkit.Location;
import java.util.Collection;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.stream.Collectors;
class SignStore {
static final String FILENAME = "signs.data";
private final Map<Location, ArenaSign> signs;
SignStore(List<ArenaSign> signs) {
this.signs = signs.stream()
.collect(Collectors.toMap(
sign -> sign.location,
sign -> sign
));
}
void store(ArenaSign sign) {
signs.put(sign.location, sign);
}
Optional<ArenaSign> remove(Location location) {
ArenaSign sign = signs.remove(location);
return Optional.ofNullable(sign);
}
Collection<ArenaSign> findAll() {
return signs.values();
}
Optional<ArenaSign> findByLocation(Location location) {
ArenaSign sign = signs.get(location);
return Optional.ofNullable(sign);
}
List<ArenaSign> findByArenaId(String arenaId) {
return signs.values().stream()
.filter(sign -> sign.arenaId.equals(arenaId))
.collect(Collectors.toList());
}
}
@@ -0,0 +1,56 @@
package com.garbagemule.MobArena.signs;
import com.garbagemule.MobArena.framework.Arena;
import com.garbagemule.MobArena.framework.ArenaMaster;
import org.bukkit.Location;
class StoresNewSign {
private final ArenaMaster arenaMaster;
private final TemplateStore templateStore;
private final SignStore signStore;
private final SavesSignStore savesSignStore;
StoresNewSign(
ArenaMaster arenaMaster,
TemplateStore templateStore,
SignStore signStore,
SavesSignStore savesSignStore
) {
this.arenaMaster = arenaMaster;
this.templateStore = templateStore;
this.signStore = signStore;
this.savesSignStore = savesSignStore;
}
void store(
Location location,
String arenaId,
String templateId,
String signType
) {
Arena arena = arenaMaster.getArenaWithName(arenaId);
if (arena == null) {
throw new IllegalArgumentException("Arena " + arenaId + " not found");
}
templateStore.findById(templateId)
.orElseThrow(() -> new IllegalArgumentException(
"Template " + templateId + " not found"
));
switch (signType) {
case "info":
case "join":
case "leave":
break;
default:
throw new IllegalArgumentException("Invalid sign type: " + signType);
}
signStore.store(new ArenaSign(location, templateId, arenaId, signType));
savesSignStore.save(signStore);
}
}
@@ -0,0 +1,78 @@
package com.garbagemule.MobArena.signs;
class Template {
final String[] idle;
final String[] joining;
final String[] running;
private Template(String[] idle, String[] joining, String[] running) {
this.idle = idle;
this.joining = joining;
this.running = running;
}
static class Builder {
private String id;
private String[] base;
private String[] idle;
private String[] joining;
private String[] running;
Builder(String id) {
this.id = id;
}
Builder withBase(String[] lines) {
this.base = lines;
return this;
}
Builder withIdle(String[] lines) {
this.idle = lines;
return this;
}
Builder withJoining(String[] lines) {
this.joining = lines;
return this;
}
Builder withRunning(String[] lines) {
this.running = lines;
return this;
}
Template build() {
if (base == null) {
if (idle == null) {
missing("idle");
}
if (joining == null) {
missing("joining");
}
if (running == null) {
missing("running");
}
}
if (idle == null) {
idle = base;
}
if (joining == null) {
joining = base;
}
if (running == null) {
running = base;
}
return new Template(idle, joining, running);
}
private void missing(String state) {
String msg = "Missing either base template '" + id + "' or state template '" + id + "-" + state + "'";
throw new IllegalArgumentException(msg);
}
}
}
@@ -0,0 +1,21 @@
package com.garbagemule.MobArena.signs;
import java.util.Map;
import java.util.Optional;
class TemplateStore {
static final String FILENAME = "signs.yml";
private final Map<String, Template> templates;
TemplateStore(Map<String, Template> templates) {
this.templates = templates;
}
Optional<Template> findById(String templateId) {
Template template = templates.get(templateId);
return Optional.ofNullable(template);
}
}