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-01 23:25:11 +02:00
parent 42416cab2f
commit 84249640d1
32 changed files with 2102 additions and 0 deletions

View File

@ -674,6 +674,7 @@ public class ArenaMasterImpl implements ArenaMaster
plugin.reloadConfig();
config = plugin.getConfig();
initialize();
plugin.reloadSigns();
if (wasEnabled) setEnabled(true);
}

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -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! :("
};
}
}

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -0,0 +1,55 @@
# MobArena Sign Templates
#
# Use this file to set up sign templates for use in MobArena. All of
# the templates in this file can be used for any of the three sign
# types (info, join, leave).
#
# Here is an example of a template:
#
# potato:
# - '<arena-name>'
# - '---------------'
# - 'Hit the sign to'
# - 'join the arena!'
#
# The template ID is 'potato', and the template uses the <arena-name>
# variable, which is translated into the name of the arena. To use the
# template in a join sign for an arena called 'castle', just place a
# sign in-game with the following lines:
#
# [MA] <-- needed by MobArena
# castle <-- arena name
# join <-- sign type
# potato <-- template (optional)
#
# For detailed information about the available variables and the state
# suffixes (-idle, -joining, -running), please visit the documentation.
info-idle:
- '&a<arena-name>'
- '---------------'
- 'Waiting for'
- 'players to join'
info-joining:
- '&a<arena-name>'
- '---------------'
- 'Ready: <ready-players>/<lobby-players>'
info-running:
- '&a<arena-name>'
- '---------------'
- 'Wave: <current-wave>'
- 'Alive: <live-players>/<initial-players>'
join:
- '&a<arena-name>'
- '---------------'
- 'Hit to join'
join-running:
- '&a<arena-name>'
- '---------------'
- 'In progress'
leave:
- '&a<arena-name>'
- '---------------'
- 'Hit to leave'

View File

@ -0,0 +1,57 @@
package com.garbagemule.MobArena.signs;
import static org.hamcrest.MatcherAssert.*;
import static org.hamcrest.Matchers.*;
import static org.mockito.Mockito.*;
import org.bukkit.Location;
import org.bukkit.World;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.junit.MockitoJUnitRunner;
import java.util.HashMap;
import java.util.Map;
@RunWith(MockitoJUnitRunner.StrictStubs.class)
public class ArenaSignTest {
@Test
public void serialize() {
World world = mock(World.class);
Location location = new Location(world, 1, 2, 3);
String templateId = "a good template";
String arenaId = "cool arena";
String type = "join";
ArenaSign sign = new ArenaSign(location, templateId, arenaId, type);
Map<String, Object> result = sign.serialize();
assertThat(result.get("location"), equalTo(location));
assertThat(result.get("templateId"), equalTo(templateId));
assertThat(result.get("arenaId"), equalTo(arenaId));
assertThat(result.get("type"), equalTo(type));
}
@Test
public void deserialize() {
World world = mock(World.class);
Location location = new Location(world, 1, 2, 3);
String templateId = "a good template";
String arenaId = "cool arena";
String type = "join";
Map<String, Object> map = new HashMap<>();
map.put("location", location);
map.put("templateId", templateId);
map.put("arenaId", arenaId);
map.put("type", type);
ArenaSign result = ArenaSign.deserialize(map);
assertThat(result.location, is(equalTo(location)));
assertThat(result.templateId, is(equalTo(templateId)));
assertThat(result.arenaId, is(equalTo(arenaId)));
assertThat(result.type, is(equalTo(type)));
}
}

View File

@ -0,0 +1,89 @@
package com.garbagemule.MobArena.signs;
import static org.mockito.Mockito.*;
import org.bukkit.Location;
import org.bukkit.block.Block;
import org.bukkit.block.Chest;
import org.bukkit.block.Sign;
import org.bukkit.entity.Player;
import org.bukkit.event.player.PlayerInteractEvent;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.junit.MockitoJUnitRunner;
import java.util.Optional;
@SuppressWarnings("WeakerAccess")
@RunWith(MockitoJUnitRunner.StrictStubs.class)
public class HandlesSignClicksTest {
SignStore signStore;
InvokesSignAction invokesSignAction;
HandlesSignClicks subject;
@Before
public void setup() {
signStore = mock(SignStore.class);
when(signStore.findByLocation(any()))
.thenReturn(Optional.empty());
invokesSignAction = mock(InvokesSignAction.class);
subject = new HandlesSignClicks(signStore, invokesSignAction);
}
@Test
public void noBlockNoFun() {
PlayerInteractEvent event = event(null, null);
subject.on(event);
verifyZeroInteractions(signStore);
}
@Test
public void noSignBlockNoFun() {
Block block = mock(Block.class);
when(block.getState()).thenReturn(mock(Chest.class));
PlayerInteractEvent event = event(null, block);
subject.on(event);
verifyZeroInteractions(signStore);
}
@Test
public void nonArenaSignNoFun() {
Block block = mock(Block.class);
when(block.getState()).thenReturn(mock(Sign.class));
PlayerInteractEvent event = event(null, block);
subject.on(event);
verifyZeroInteractions(signStore);
}
@Test
public void arenaSignInvokesAction() {
Location location = mock(Location.class);
Block block = mock(Block.class);
when(block.getLocation()).thenReturn(location);
when(block.getState()).thenReturn(mock(Sign.class));
ArenaSign sign = new ArenaSign(location, "", "", "");
when(signStore.findByLocation(location))
.thenReturn(Optional.of(sign));
Player player = mock(Player.class);
PlayerInteractEvent event = event(player, block);
subject.on(event);
verify(invokesSignAction).invoke(sign, player);
}
private PlayerInteractEvent event(Player player, Block block) {
return new PlayerInteractEvent(player, null, null, block, null);
}
}

View File

@ -0,0 +1,146 @@
package com.garbagemule.MobArena.signs;
import static org.mockito.Mockito.*;
import com.garbagemule.MobArena.Messenger;
import org.bukkit.Location;
import org.bukkit.block.Block;
import org.bukkit.entity.Player;
import org.bukkit.event.block.SignChangeEvent;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.junit.MockitoJUnitRunner;
@SuppressWarnings("WeakerAccess")
@RunWith(MockitoJUnitRunner.StrictStubs.class)
public class HandlesSignCreationTest {
StoresNewSign storesNewSign;
RendersTemplateById rendersTemplate;
Messenger messenger;
HandlesSignCreation subject;
@Before
public void setup() {
storesNewSign = mock(StoresNewSign.class);
rendersTemplate = mock(RendersTemplateById.class);
when(rendersTemplate.render(any(), any()))
.thenReturn(new String[]{"", "", "", ""});
messenger = mock(Messenger.class);
subject = new HandlesSignCreation(
storesNewSign,
rendersTemplate,
messenger
);
}
@Test
public void noHeaderNoAction() {
String[] lines = {"why", "so", "serious", "?"};
SignChangeEvent event = event(lines, null);
subject.on(event);
verifyZeroInteractions(storesNewSign, rendersTemplate, messenger);
}
@Test
public void nullLinesHandledGracefully() {
String[] lines = {"[MA]", null, null, null};
SignChangeEvent event = event(lines, null);
subject.on(event);
}
@Test
public void useSignTypeIfTemplateNotAvailable() {
String arenaId = "castle";
String type = "join";
String[] lines = {"[MA]", arenaId, type, null};
Location location = mock(Location.class);
SignChangeEvent event = event(lines, location);
subject.on(event);
verify(storesNewSign).store(location, arenaId, type, type);
}
@Test
public void useTemplateIfAvailable() {
String arenaId = "castle";
String type = "join";
String templateId = "potato";
String[] lines = {"[MA]", arenaId, type, templateId};
Location location = mock(Location.class);
SignChangeEvent event = event(lines, location);
subject.on(event);
verify(storesNewSign).store(location, arenaId, templateId, type);
}
@Test
public void typeIsLowercased() {
String type = "JOIN";
String[] lines = {"[MA]", "", type, ""};
SignChangeEvent event = event(lines, null);
subject.on(event);
String lower = type.toLowerCase();
verify(storesNewSign).store(any(), any(), any(), eq(lower));
}
@Test
public void templateIdIsLowercased() {
String templateId = "BEST-TEMPLATE";
String[] lines = {"[MA]", "", "", templateId};
SignChangeEvent event = event(lines, null);
subject.on(event);
String lower = templateId.toLowerCase();
verify(storesNewSign).store(any(), any(), eq(lower), any());
}
@Test
public void rendersTemplateAfterStoring() {
String arenaId = "castle";
String type = "join";
String templateId = "potato";
String[] lines = {"[MA]", arenaId, type, templateId};
Location location = mock(Location.class);
SignChangeEvent event = event(lines, location);
subject.on(event);
verify(rendersTemplate).render(templateId, arenaId);
}
@Test
public void errorPassedToMessenger() {
String msg = "you messed up";
doThrow(new IllegalArgumentException(msg))
.when(storesNewSign).store(any(), any(), any(), any());
String[] lines = {"[MA]", "", "", ""};
SignChangeEvent event = event(lines, null);
subject.on(event);
verifyZeroInteractions(rendersTemplate);
verify(messenger).tell(event.getPlayer(), msg);
}
private SignChangeEvent event(String[] lines, Location location) {
Block block = mock(Block.class);
when(block.getLocation()).thenReturn(location);
Player player = mock(Player.class);
return new SignChangeEvent(block, player, lines);
}
}

View File

@ -0,0 +1,63 @@
package com.garbagemule.MobArena.signs;
import static org.mockito.Mockito.*;
import com.garbagemule.MobArena.Messenger;
import org.bukkit.block.Block;
import org.bukkit.entity.Player;
import org.bukkit.event.block.BlockBreakEvent;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.junit.MockitoJUnitRunner;
import java.util.Optional;
@SuppressWarnings("WeakerAccess")
@RunWith(MockitoJUnitRunner.StrictStubs.class)
public class HandlesSignDestructionTest {
RemovesSignAtLocation removesSignAtLocation;
Messenger messenger;
HandlesSignDestruction subject;
@Before
public void setup() {
removesSignAtLocation = mock(RemovesSignAtLocation.class);
messenger = mock(Messenger.class);
subject = new HandlesSignDestruction(
removesSignAtLocation,
messenger
);
}
@Test
public void doesNothingWithNonArenaSign() {
Block block = mock(Block.class);
Player player = mock(Player.class);
when(removesSignAtLocation.remove(any()))
.thenReturn(Optional.empty());
BlockBreakEvent event = new BlockBreakEvent(block, player);
subject.on(event);
verifyZeroInteractions(messenger);
}
@Test
public void reportsBreakageWithArenaSign() {
Block block = mock(Block.class);
Player player = mock(Player.class);
ArenaSign sign = new ArenaSign(null, "", "", "");
when(removesSignAtLocation.remove(any()))
.thenReturn(Optional.of(sign));
BlockBreakEvent event = new BlockBreakEvent(block, player);
subject.on(event);
verify(messenger).tell(eq(player), anyString());
}
}

View File

@ -0,0 +1,115 @@
package com.garbagemule.MobArena.signs;
import static org.mockito.Mockito.*;
import com.garbagemule.MobArena.Messenger;
import com.garbagemule.MobArena.framework.Arena;
import com.garbagemule.MobArena.framework.ArenaMaster;
import org.bukkit.entity.Player;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.junit.MockitoJUnitRunner;
@SuppressWarnings("WeakerAccess")
@RunWith(MockitoJUnitRunner.StrictStubs.class)
public class InvokesSignActionTest {
ArenaMaster arenaMaster;
Messenger messenger;
InvokesSignAction subject;
@Before
public void setup() {
arenaMaster = mock(ArenaMaster.class);
messenger = mock(Messenger.class);
subject = new InvokesSignAction(arenaMaster, messenger);
}
@Test
public void infoSignDoesNothing() {
String arenaId = "castle";
ArenaSign sign = new ArenaSign(null, "", arenaId, "info");
Player player = mock(Player.class);
subject.invoke(sign, player);
verifyZeroInteractions(arenaMaster);
}
@Test
public void joinSignCallsCanJoin() {
String arenaId = "castle";
ArenaSign sign = new ArenaSign(null, "", arenaId, "join");
Player player = mock(Player.class);
Arena arena = mock(Arena.class);
when(arenaMaster.getArenaWithName(arenaId))
.thenReturn(arena);
subject.invoke(sign, player);
verify(arena).canJoin(player);
}
@Test
public void joinSignCallsPlayerJoin() {
String arenaId = "castle";
ArenaSign sign = new ArenaSign(null, "", arenaId, "join");
Player player = mock(Player.class);
Arena arena = mock(Arena.class);
when(arena.canJoin(player)).thenReturn(true);
when(arenaMaster.getArenaWithName(arenaId))
.thenReturn(arena);
subject.invoke(sign, player);
verify(arena).playerJoin(eq(player), any());
}
@Test
public void leaveSignCallsInChecks() {
String arenaId = "castle";
ArenaSign sign = new ArenaSign(null, "", arenaId, "leave");
Player player = mock(Player.class);
Arena arena = mock(Arena.class);
when(arenaMaster.getArenaWithName(arenaId))
.thenReturn(arena);
subject.invoke(sign, player);
verify(arena).inArena(player);
verify(arena).inLobby(player);
verify(arena).inSpec(player);
}
@Test
public void leaveSignCallsPlayerLeave() {
String arenaId = "castle";
ArenaSign sign = new ArenaSign(null, "", arenaId, "leave");
Player player = mock(Player.class);
Arena arena = mock(Arena.class);
when(arena.inArena(player)).thenReturn(true);
when(arenaMaster.getArenaWithName(arenaId))
.thenReturn(arena);
subject.invoke(sign, player);
verify(arena).playerLeave(player);
}
@Test
public void nonExistentArenaReportsToPlayer() {
String arenaId = "castle";
ArenaSign sign = new ArenaSign(null, "", arenaId, "join");
Player player = mock(Player.class);
when(arenaMaster.getArenaWithName(arenaId))
.thenReturn(null);
subject.invoke(sign, player);
verify(messenger).tell(eq(player), anyString());
}
}

View File

@ -0,0 +1,141 @@
package com.garbagemule.MobArena.signs;
import static org.mockito.Mockito.*;
import com.garbagemule.MobArena.framework.Arena;
import org.bukkit.Location;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.junit.MockitoJUnitRunner;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Optional;
@SuppressWarnings("WeakerAccess")
@RunWith(MockitoJUnitRunner.StrictStubs.class)
public class RedrawsArenaSignsTest {
SignStore signStore;
TemplateStore templateStore;
RendersTemplate rendersTemplate;
SetsLines setsSignLines;
RedrawsArenaSigns subject;
@Before
public void setup() {
signStore = mock(SignStore.class);
when(signStore.findByArenaId(any()))
.thenReturn(Collections.emptyList());
templateStore = mock(TemplateStore.class);
when(templateStore.findById(any()))
.thenReturn(Optional.empty());
rendersTemplate = mock(RendersTemplate.class);
when(rendersTemplate.render(any(), any()))
.thenReturn(new String[]{"a", "b", "c", "d"});
setsSignLines = mock(SetsLines.class);
subject = new RedrawsArenaSigns(
signStore,
templateStore,
rendersTemplate,
setsSignLines
);
}
@Test
public void noSignsMeansNoRenderingOrLineSetting() {
Arena arena = arena("castle");
subject.redraw(arena);
verifyZeroInteractions(rendersTemplate);
verifyZeroInteractions(setsSignLines);
}
@Test
public void renderFoundTemplate() {
String arenaId = "castle";
Arena arena = arena(arenaId);
ArenaSign sign = sign("join", arenaId);
when(signStore.findByArenaId(arenaId))
.thenReturn(Collections.singletonList(sign));
Template template = template("template", "some", "info", "about", "arena");
when(templateStore.findById(sign.templateId))
.thenReturn(Optional.of(template));
subject.redraw(arena);
verify(rendersTemplate).render(template, arena);
}
@Test
public void setRenderedTemplateOnSign() {
String arenaId = "castle";
Arena arena = arena(arenaId);
ArenaSign sign = sign("join", arenaId);
when(signStore.findByArenaId(sign.arenaId))
.thenReturn(Collections.singletonList(sign));
Template template = template("template", "try", "with", "more", "fireballs");
when(templateStore.findById(sign.templateId))
.thenReturn(Optional.of(template));
String[] lines = new String[]{"this", "is", "a", "sign"};
when(rendersTemplate.render(template, arena))
.thenReturn(lines);
subject.redraw(arena);
verify(setsSignLines).set(sign.location, lines);
}
@Test
public void renderEachTemplateOnlyOnce() {
String arenaId = "castle";
Arena arena = arena(arenaId);
String templateId1 = "join";
String templateId2 = "info";
List<ArenaSign> signs = new ArrayList<>();
signs.add(sign(templateId1, arenaId));
signs.add(sign(templateId1, arenaId));
signs.add(sign(templateId1, arenaId));
signs.add(sign(templateId2, arenaId));
when(signStore.findByArenaId(arenaId))
.thenReturn(signs);
Template template1 = template(templateId1, "join", "a", "MobArena", "today!");
Template template2 = template(templateId2, "join", "another", "MobArena", "tomorrow!");
when(templateStore.findById(templateId1))
.thenReturn(Optional.of(template1));
when(templateStore.findById(templateId2))
.thenReturn(Optional.of(template2));
subject.redraw(arena);
verify(rendersTemplate, times(1)).render(template1, arena);
verify(rendersTemplate, times(1)).render(template2, arena);
verify(setsSignLines, times(signs.size())).set(any(), any());
}
private Arena arena(String arenaId) {
Arena arena = mock(Arena.class);
when(arena.configName()).thenReturn(arenaId);
return arena;
}
private ArenaSign sign(String templateId, String arenaId) {
Location location = mock(Location.class);
return new ArenaSign(location, templateId, arenaId, "join");
}
private Template template(String id, String l1, String l2, String l3, String l4) {
return new Template.Builder(id)
.withBase(new String[]{l1, l2, l3, l4})
.build();
}
}

View File

@ -0,0 +1,56 @@
package com.garbagemule.MobArena.signs;
import static org.mockito.Mockito.*;
import org.bukkit.Location;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.junit.MockitoJUnitRunner;
import java.util.Optional;
@SuppressWarnings("WeakerAccess")
@RunWith(MockitoJUnitRunner.StrictStubs.class)
public class RemovesSignAtLocationTest {
SignStore signStore;
SavesSignStore savesSignStore;
RemovesSignAtLocation subject;
@Before
public void setup() {
signStore = mock(SignStore.class);
savesSignStore = mock(SavesSignStore.class);
subject = new RemovesSignAtLocation(
signStore,
savesSignStore
);
}
@Test
public void noSignMeansNoWrite() {
Location location = mock(Location.class);
when(signStore.remove(location))
.thenReturn(Optional.empty());
subject.remove(location);
verifyZeroInteractions(savesSignStore);
}
@Test
public void signRemovedWritesStore() {
Location location = mock(Location.class);
ArenaSign sign = new ArenaSign(location, "", "", "");
when(signStore.remove(location))
.thenReturn(Optional.of(sign));
subject.remove(location);
verify(savesSignStore).save(signStore);
}
}

View File

@ -0,0 +1,106 @@
package com.garbagemule.MobArena.signs;
import static org.hamcrest.MatcherAssert.*;
import static org.hamcrest.Matchers.*;
import static org.mockito.Mockito.*;
import com.garbagemule.MobArena.framework.Arena;
import com.garbagemule.MobArena.waves.WaveManager;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.junit.MockitoJUnitRunner;
import java.util.Collections;
@SuppressWarnings("WeakerAccess")
@RunWith(MockitoJUnitRunner.StrictStubs.class)
public class RendersTemplateTest {
RendersTemplate subject;
@Before
public void setup() {
subject = new RendersTemplate();
}
@Test
public void rendersArenaName() {
String name = "castle";
Arena arena = arena(name, false, false);
Template template = new Template.Builder("template")
.withBase(new String[]{"<arena-name>", "", "", ""})
.build();
String[] result = subject.render(template, arena);
String[] expected = new String[]{name, "", "", ""};
assertThat(result, equalTo(expected));
}
@Test
public void defaultsToBaseIfArenaIsNotRunning() {
Arena arena = arena("castle", false, false);
String[] base = {"this", "is", "the", "base"};
Template template = new Template.Builder("template")
.withBase(base)
.withRunning(new String[]{"here", "is", "running", "yo"})
.build();
String[] result = subject.render(template, arena);
assertThat(result, equalTo(base));
}
@Test
public void idleOverridesBaseIfNotRunning() {
Arena arena = arena("castle", false, false);
String[] idle = {"relax", "don't", "do", "it"};
Template template = new Template.Builder("template")
.withBase(new String[]{"this", "is", "the", "base"})
.withIdle(idle)
.build();
String[] result = subject.render(template, arena);
assertThat(result, equalTo(idle));
}
@Test
public void runningOverridesBaseIfArenaIsRunning() {
Arena arena = arena("castle", true, false);
String[] running = {"here", "is", "running", "yo"};
Template template = new Template.Builder("template")
.withBase(new String[]{"this", "is", "the", "base"})
.withRunning(running)
.build();
String[] result = subject.render(template, arena);
assertThat(result, equalTo(running));
}
@Test
public void lobbyOverridesBaseIfPlayersInLobby() {
Arena arena = arena("castle", false, true);
String[] joining = {"we", "in", "da", "lobby"};
Template template = new Template.Builder("template")
.withBase(new String[]{"this", "is", "the", "base"})
.withJoining(joining)
.build();
String[] result = subject.render(template, arena);
assertThat(result, equalTo(joining));
}
private Arena arena(String name, boolean running, boolean lobby) {
Arena arena = mock(Arena.class);
when(arena.configName()).thenReturn(name);
when(arena.isRunning()).thenReturn(running);
when(arena.getPlayersInLobby()).thenReturn(lobby ? Collections.singleton(null) : Collections.emptySet());
when(arena.getWaveManager()).thenReturn(mock(WaveManager.class));
return arena;
}
}

View File

@ -0,0 +1,126 @@
package com.garbagemule.MobArena.signs;
import static org.hamcrest.MatcherAssert.*;
import static org.hamcrest.Matchers.*;
import static org.mockito.Mockito.*;
import com.garbagemule.MobArena.framework.Arena;
import com.garbagemule.MobArena.framework.ArenaMaster;
import org.bukkit.Location;
import org.junit.Before;
import org.junit.Rule;
import org.junit.Test;
import org.junit.rules.ExpectedException;
import org.junit.runner.RunWith;
import org.mockito.ArgumentCaptor;
import org.mockito.junit.MockitoJUnitRunner;
import java.util.Optional;
@SuppressWarnings("WeakerAccess")
@RunWith(MockitoJUnitRunner.StrictStubs.class)
public class StoresNewSignTest {
ArenaMaster arenaMaster;
TemplateStore templateStore;
SignStore signStore;
SavesSignStore savesSignStore;
StoresNewSign subject;
@Rule
public ExpectedException exception = ExpectedException.none();
@Before
public void setup() {
arenaMaster = mock(ArenaMaster.class);
when(arenaMaster.getArenaWithName(any()))
.thenReturn(null);
templateStore = mock(TemplateStore.class);
when(templateStore.findById(any()))
.thenReturn(Optional.empty());
signStore = mock(SignStore.class);
savesSignStore = mock(SavesSignStore.class);
subject = new StoresNewSign(
arenaMaster,
templateStore,
signStore,
savesSignStore
);
}
@Test
public void throwOnNonExistentArena() {
Location location = mock(Location.class);
String arenaId = "castle";
exception.expect(IllegalArgumentException.class);
subject.store(location, arenaId, "", "");
}
@Test
public void throwOnNonExistentTemplate() {
Location location = mock(Location.class);
String arenaId = "castle";
Arena arena = mock(Arena.class);
when(arenaMaster.getArenaWithName(arenaId))
.thenReturn(arena);
String templateId = "template";
exception.expect(IllegalArgumentException.class);
subject.store(location, arenaId, templateId, "");
}
@Test
public void throwOnNonInvalidSignType() {
Location location = mock(Location.class);
String arenaId = "castle";
Arena arena = mock(Arena.class);
when(arenaMaster.getArenaWithName(arenaId))
.thenReturn(arena);
String templateId = "a very nice template";
Template template = template(templateId);
when(templateStore.findById(templateId))
.thenReturn(Optional.of(template));
String signType = "not a real sign type";
exception.expect(IllegalArgumentException.class);
subject.store(location, arenaId, templateId, signType);
}
@Test
public void storesSignAndWritesToDisk() {
Location location = mock(Location.class);
String arenaId = "castle";
Arena arena = mock(Arena.class);
when(arenaMaster.getArenaWithName(arenaId))
.thenReturn(arena);
String templateId = "a very nice template";
Template template = template(templateId);
when(templateStore.findById(templateId))
.thenReturn(Optional.of(template));
String signType = "join";
subject.store(location, arenaId, templateId, signType);
ArgumentCaptor<ArenaSign> captor = ArgumentCaptor.forClass(ArenaSign.class);
verify(signStore).store(captor.capture());
verify(savesSignStore).save(signStore);
ArenaSign sign = captor.getValue();
assertThat(sign.location, equalTo(location));
assertThat(sign.arenaId, equalTo(arenaId));
assertThat(sign.templateId, equalTo(templateId));
assertThat(sign.type, equalTo(signType));
}
private Template template(String id) {
return new Template.Builder(id)
.withBase(new String[]{"", "", "", ""})
.build();
}
}