Use Things API for class armor.

Swaps out the hard dependecy on ItemStack for class armor, such that the ThingManager - and thus every ThingParser registered within - has a chance at providing "armor" for a class.

To distinguish armor from items, ArenaMasterImpl prepends "armor:" or slot-specific prefixes to these values before passing them to the ThingManager. This makes it possible to distinguish between items and armor on the thing parser end if applicable - it doesn't make sense for money and command parsers, but they will just end up skipping these values.

The ItemStackThingParser is given a bit of an overhaul. It now depends internally on ItemStack parsers, which can be registered via the ThingManager. This allows custom plugins to hook into the ItemStack-specific part of the ItemStackThingParser to avoid having to duplicate code. Plugins that just want to provide ItemStacks can use this parser approach, and plugins that want to provide more abstract Things can use the Thing parser approach.
This commit is contained in:
Andreas Troelsen 2018-04-22 13:18:38 +02:00
parent 8014420c1b
commit 37de1e66e9
13 changed files with 507 additions and 75 deletions

View File

@ -25,8 +25,8 @@ import java.util.stream.IntStream;
public class ArenaClass
{
private String configName, lowercaseName;
private ItemStack helmet, chestplate, leggings, boots, offhand;
private List<ItemStack> armor;
private Thing helmet, chestplate, leggings, boots, offhand;
private List<Thing> armor;
private List<Thing> items;
private Map<String,Boolean> perms;
private Map<String,Boolean> lobbyperms;
@ -71,42 +71,42 @@ public class ArenaClass
/**
* Set the helmet slot for the class.
* @param helmet an item
* @param helmet a Thing
*/
public void setHelmet(ItemStack helmet) {
public void setHelmet(Thing helmet) {
this.helmet = helmet;
}
/**
* Set the chestplate slot for the class.
* @param chestplate an item
* @param chestplate a Thing
*/
public void setChestplate(ItemStack chestplate) {
public void setChestplate(Thing chestplate) {
this.chestplate = chestplate;
}
/**
* Set the leggings slot for the class.
* @param leggings an item
* @param leggings a Thing
*/
public void setLeggings(ItemStack leggings) {
public void setLeggings(Thing leggings) {
this.leggings = leggings;
}
/**
* Set the boots slot for the class.
* @param boots an item
* @param boots a Thing
*/
public void setBoots(ItemStack boots) {
public void setBoots(Thing boots) {
this.boots = boots;
}
/**
* Set the off-hand slot for the class.
* @param offHand
* @param offhand a Thing
*/
public void setOffHand(ItemStack offHand) {
this.offhand = offHand;
public void setOffHand(Thing offhand) {
this.offhand = offhand;
}
/**
@ -131,9 +131,9 @@ public class ArenaClass
/**
* Replace the current armor list with the given list.
* @param armor a list of items
* @param armor a list of Things
*/
public void setArmor(List<ItemStack> armor) {
public void setArmor(List<Thing> armor) {
this.armor = armor;
}
@ -152,36 +152,14 @@ public class ArenaClass
items.forEach(item -> item.giveTo(p));
// Check for legacy armor-node items
if (!armor.isEmpty()) {
for (ItemStack piece : armor) {
ArmorType type = ArmorType.getType(piece);
if (type == null) continue;
switch (type) {
case HELMET:
inv.setHelmet(piece);
break;
case CHESTPLATE:
inv.setChestplate(piece);
break;
case LEGGINGS:
inv.setLeggings(piece);
break;
case BOOTS:
inv.setBoots(piece);
break;
default:
break;
}
}
}
armor.forEach(thing -> thing.giveTo(p));
// Check type specifics.
if (helmet != null) inv.setHelmet(helmet);
if (chestplate != null) inv.setChestplate(chestplate);
if (leggings != null) inv.setLeggings(leggings);
if (boots != null) inv.setBoots(boots);
if (offhand != null) inv.setItemInOffHand(offhand);
if (helmet != null) helmet.giveTo(p);
if (chestplate != null) chestplate.giveTo(p);
if (leggings != null) leggings.giveTo(p);
if (boots != null) boots.giveTo(p);
if (offhand != null) offhand.giveTo(p);
}
/**

View File

@ -32,6 +32,7 @@ import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Set;
import java.util.function.Consumer;
import java.util.stream.Collectors;
public class ArenaMasterImpl implements ArenaMaster
@ -330,33 +331,8 @@ public class ArenaMasterImpl implements ArenaMaster
// Load items
loadClassItems(section, arenaClass);
// And the legacy armor-node
String armor = section.getString("armor", "");
if (!armor.equals("")) {
List<ItemStack> stacks = ItemParser.parseItems(armor);
arenaClass.setArmor(stacks);
}
// Get armor strings
String head = section.getString("helmet", null);
String chest = section.getString("chestplate", null);
String legs = section.getString("leggings", null);
String feet = section.getString("boots", null);
String off = section.getString("offhand", null);
// Parse to ItemStacks
ItemStack helmet = ItemParser.parseItem(head);
ItemStack chestplate = ItemParser.parseItem(chest);
ItemStack leggings = ItemParser.parseItem(legs);
ItemStack boots = ItemParser.parseItem(feet);
ItemStack offhand = ItemParser.parseItem(off);
// Set in ArenaClass
arenaClass.setHelmet(helmet);
arenaClass.setChestplate(chestplate);
arenaClass.setLeggings(leggings);
arenaClass.setBoots(boots);
arenaClass.setOffHand(offhand);
// Load armor
loadClassArmor(section, arenaClass);
// Per-class permissions
loadClassPermissions(arenaClass, section);
@ -390,6 +366,49 @@ public class ArenaMasterImpl implements ArenaMaster
arenaClass.setItems(things);
}
private void loadClassArmor(ConfigurationSection section, ArenaClass arenaClass) {
// Legacy armor node
loadClassArmorLegacyNode(section, arenaClass);
// Specific armor pieces
loadClassArmorPiece(section, "helmet", arenaClass::setHelmet);
loadClassArmorPiece(section, "chestplate", arenaClass::setChestplate);
loadClassArmorPiece(section, "leggings", arenaClass::setLeggings);
loadClassArmorPiece(section, "boots", arenaClass::setBoots);
loadClassArmorPiece(section, "offhand", arenaClass::setOffHand);
}
private void loadClassArmorLegacyNode(ConfigurationSection section, ArenaClass arenaClass) {
List<String> armor = section.getStringList("armor");
if (armor == null || armor.isEmpty()) {
String value = section.getString("armor", "");
armor = Arrays.asList(value.split(","));
}
// Prepend "armor:" for the armor thing parser
List<Thing> things = armor.stream()
.map(String::trim)
.map(s -> "armor:" + s)
.map(plugin.getThingManager()::parse)
.filter(Objects::nonNull)
.collect(Collectors.toList());
arenaClass.setArmor(things);
}
private void loadClassArmorPiece(ConfigurationSection section, String slot, Consumer<Thing> setter) {
String value = section.getString(slot, null);
if (value == null) {
return;
}
// Prepend the slot name for the item parser
Thing thing = plugin.getThingManager().parse(slot + ":" + value);
if (thing == null) {
return;
}
setter.accept(thing);
}
private void loadClassPermissions(ArenaClass arenaClass, ConfigurationSection section) {
List<String> perms = section.getStringList("permissions");
if (perms.isEmpty()) return;

View File

@ -0,0 +1,27 @@
package com.garbagemule.MobArena.things;
import org.bukkit.entity.Player;
import org.bukkit.inventory.ItemStack;
public class BootsThing extends ItemStackThing {
public BootsThing(ItemStack stack) {
super(stack);
}
@Override
public boolean giveTo(Player player) {
player.getInventory().setBoots(super.getItemStack());
return true;
}
@Override
public boolean takeFrom(Player player) {
player.getInventory().setBoots(null);
return true;
}
@Override
public boolean heldBy(Player player) {
return super.getItemStack().equals(player.getInventory().getBoots());
}
}

View File

@ -0,0 +1,27 @@
package com.garbagemule.MobArena.things;
import org.bukkit.entity.Player;
import org.bukkit.inventory.ItemStack;
public class ChestplateThing extends ItemStackThing {
public ChestplateThing(ItemStack stack) {
super(stack);
}
@Override
public boolean giveTo(Player player) {
player.getInventory().setChestplate(super.getItemStack());
return true;
}
@Override
public boolean takeFrom(Player player) {
player.getInventory().setChestplate(null);
return true;
}
@Override
public boolean heldBy(Player player) {
return super.getItemStack().equals(player.getInventory().getChestplate());
}
}

View File

@ -0,0 +1,78 @@
package com.garbagemule.MobArena.things;
import org.bukkit.Material;
import org.bukkit.inventory.ItemStack;
import java.util.EnumSet;
class Equippable {
@FunctionalInterface
interface Wrapper {
ItemStackThing wrap(ItemStack stack);
}
private static EnumSet<Material> helmets = EnumSet.of(
Material.LEATHER_HELMET,
Material.IRON_HELMET,
Material.CHAINMAIL_HELMET,
Material.GOLD_HELMET,
Material.DIAMOND_HELMET
);
private static EnumSet<Material> chestplates = EnumSet.of(
Material.LEATHER_CHESTPLATE,
Material.IRON_CHESTPLATE,
Material.CHAINMAIL_CHESTPLATE,
Material.GOLD_CHESTPLATE,
Material.DIAMOND_CHESTPLATE
);
private static EnumSet<Material> leggings = EnumSet.of(
Material.LEATHER_LEGGINGS,
Material.IRON_LEGGINGS,
Material.CHAINMAIL_LEGGINGS,
Material.GOLD_LEGGINGS,
Material.DIAMOND_LEGGINGS
);
private static EnumSet<Material> boots = EnumSet.of(
Material.LEATHER_BOOTS,
Material.IRON_BOOTS,
Material.CHAINMAIL_BOOTS,
Material.GOLD_BOOTS,
Material.DIAMOND_BOOTS
);
static Wrapper getWrapperByPrefix(String prefix) {
if (prefix.equals("helmet")) {
return HelmetThing::new;
}
if (prefix.equals("chestplate")) {
return ChestplateThing::new;
}
if (prefix.equals("leggings")) {
return LeggingsThing::new;
}
if (prefix.equals("boots")) {
return BootsThing::new;
}
if (prefix.equals("offhand")) {
return OffHandThing::new;
}
return null;
}
static Wrapper guessWrapperFromItemStack(ItemStack stack) {
Material type = stack.getType();
if (helmets.contains(type)) {
return HelmetThing::new;
}
if (chestplates.contains(type)) {
return ChestplateThing::new;
}
if (leggings.contains(type)) {
return LeggingsThing::new;
}
if (boots.contains(type)) {
return BootsThing::new;
}
return null;
}
}

View File

@ -0,0 +1,27 @@
package com.garbagemule.MobArena.things;
import org.bukkit.entity.Player;
import org.bukkit.inventory.ItemStack;
public class HelmetThing extends ItemStackThing {
public HelmetThing(ItemStack stack) {
super(stack);
}
@Override
public boolean giveTo(Player player) {
player.getInventory().setHelmet(super.getItemStack());
return true;
}
@Override
public boolean takeFrom(Player player) {
player.getInventory().setHelmet(null);
return true;
}
@Override
public boolean heldBy(Player player) {
return super.getItemStack().equals(player.getInventory().getHelmet());
}
}

View File

@ -0,0 +1,19 @@
package com.garbagemule.MobArena.things;
import org.bukkit.inventory.ItemStack;
/**
* An ItemStack parser takes a string as input and returns either an instance
* of {@link ItemStack} or null.
*/
@FunctionalInterface
public interface ItemStackParser {
/**
* Parse the given string, returning an {@link ItemStack} instance on
* success, otherwise null.
*
* @param s a string to parse
* @return an instance of {@link ItemStack}, or null
*/
ItemStack parse(String s);
}

View File

@ -26,6 +26,10 @@ public class ItemStackThing implements Thing {
return player.getInventory().containsAtLeast(stack, stack.getAmount());
}
ItemStack getItemStack() {
return stack;
}
@Override
public String toString() {
String name = getName();

View File

@ -1,15 +1,78 @@
package com.garbagemule.MobArena.things;
import com.garbagemule.MobArena.things.Equippable.Wrapper;
import com.garbagemule.MobArena.util.ItemParser;
import org.bukkit.inventory.ItemStack;
import java.util.ArrayList;
import java.util.List;
import java.util.Objects;
class ItemStackThingParser implements ThingParser {
private List<ItemStackParser> parsers;
ItemStackThingParser() {
this.parsers = new ArrayList<>();
}
public void register(ItemStackParser parser) {
parsers.add(parser);
}
@Override
public ItemStackThing parse(String s) {
ItemStack stack = ItemParser.parseItem(s, false);
String[] parts = s.split(":", 2);
if (parts.length == 1) {
return genericItem(s);
}
String prefix = parts[0];
String rest = parts[1];
if (prefix.equals("armor")) {
return genericArmor(rest);
}
return specificSlot(s, prefix, rest);
}
private ItemStackThing genericItem(String s) {
ItemStack stack = parseItemStack(s);
if (stack == null) {
return null;
}
return new ItemStackThing(stack);
}
private ItemStackThing genericArmor(String s) {
ItemStack stack = parseItemStack(s);
if (stack == null) {
return null;
}
Wrapper wrapper = Equippable.guessWrapperFromItemStack(stack);
if (wrapper == null) {
return new ItemStackThing(stack);
}
return wrapper.wrap(stack);
}
private ItemStackThing specificSlot(String s, String prefix, String rest) {
Wrapper wrapper = Equippable.getWrapperByPrefix(prefix);
if (wrapper == null) {
return genericItem(s);
}
ItemStack stack = parseItemStack(rest);
if (stack == null) {
return null;
}
return wrapper.wrap(stack);
}
private ItemStack parseItemStack(String s) {
return parsers.stream()
.map(p -> p.parse(s))
.filter(Objects::nonNull)
.findFirst()
.orElseGet(() -> ItemParser.parseItem(s, false));
}
}

View File

@ -0,0 +1,27 @@
package com.garbagemule.MobArena.things;
import org.bukkit.entity.Player;
import org.bukkit.inventory.ItemStack;
public class LeggingsThing extends ItemStackThing {
public LeggingsThing(ItemStack stack) {
super(stack);
}
@Override
public boolean giveTo(Player player) {
player.getInventory().setLeggings(super.getItemStack());
return true;
}
@Override
public boolean takeFrom(Player player) {
player.getInventory().setLeggings(null);
return true;
}
@Override
public boolean heldBy(Player player) {
return super.getItemStack().equals(player.getInventory().getLeggings());
}
}

View File

@ -0,0 +1,27 @@
package com.garbagemule.MobArena.things;
import org.bukkit.entity.Player;
import org.bukkit.inventory.ItemStack;
public class OffHandThing extends ItemStackThing {
public OffHandThing(ItemStack stack) {
super(stack);
}
@Override
public boolean giveTo(Player player) {
player.getInventory().setItemInOffHand(super.getItemStack());
return true;
}
@Override
public boolean takeFrom(Player player) {
player.getInventory().setItemInOffHand(null);
return true;
}
@Override
public boolean heldBy(Player player) {
return super.getItemStack().equals(player.getInventory().getItemInOffHand());
}
}

View File

@ -7,12 +7,17 @@ import java.util.List;
public class ThingManager implements ThingParser {
private List<ThingParser> parsers;
private ItemStackThingParser items;
public ThingManager(MobArena plugin) {
public ThingManager(MobArena plugin, ItemStackThingParser parser) {
parsers = new ArrayList<>();
parsers.add(new CommandThingParser());
parsers.add(new MoneyThingParser(plugin));
parsers.add(new ItemStackThingParser());
items = parser;
}
public ThingManager(MobArena plugin) {
this(plugin, new ItemStackThingParser());
}
/**
@ -43,6 +48,18 @@ public class ThingManager implements ThingParser {
register(parser, false);
}
/**
* Register a new ItemStack parser in the manager.
* <p>
* The provided parser will be invoked if no other thing parsers return a
* non-null result, and if all other ItemStack parsers also return null.
*
* @param parser an ItemStack parser, non-null
*/
public void register(ItemStackParser parser) {
items.register(parser);
}
@Override
public Thing parse(String s) {
for (ThingParser parser : parsers) {
@ -51,6 +68,6 @@ public class ThingManager implements ThingParser {
return thing;
}
}
return null;
return items.parse(s);
}
}

View File

@ -0,0 +1,119 @@
package com.garbagemule.MobArena.things;
import static org.hamcrest.CoreMatchers.equalTo;
import static org.hamcrest.CoreMatchers.is;
import static org.hamcrest.CoreMatchers.nullValue;
import static org.hamcrest.MatcherAssert.assertThat;
import static org.mockito.Mockito.inOrder;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.verifyZeroInteractions;
import static org.mockito.Mockito.when;
import org.bukkit.Material;
import org.bukkit.inventory.ItemStack;
import org.junit.Before;
import org.junit.Test;
import org.mockito.InOrder;
public class ItemStackThingParserTest {
private ItemStackThingParser subject;
@Before
public void setup() {
subject = new ItemStackThingParser();
}
@Test
public void emptyStringReturnsNull() {
ItemStackThing result = subject.parse("");
assertThat(result, is(nullValue()));
}
@Test
public void gibberishReturnsNull() {
ItemStackThing result = subject.parse("I can't believe you've done this");
assertThat(result, is(nullValue()));
}
@Test
public void fiveDirt() {
ItemStackThing result = subject.parse("dirt:5");
// ItemStack's equals() method does naughty things, so verify manually
ItemStack stack = result.getItemStack();
assertThat(stack.getType(), equalTo(Material.DIRT));
assertThat(stack.getAmount(), equalTo(5));
}
@Test
public void bareLederhosenReturnsGenericItemStackThing() {
ItemStackThing result = subject.parse("leather_leggings");
assertThat(result.getClass(), equalTo(ItemStackThing.class));
}
@Test
public void prefixedIronChestplateReturnsLeggingsThing() {
ItemStackThing result = subject.parse("leggings:leather_leggings");
assertThat(result.getClass(), equalTo(LeggingsThing.class));
}
@Test
public void genericArmorPrefixGuessesChestplateThing() {
ItemStackThing result = subject.parse("armor:iron_chestplate");
assertThat(result.getClass(), equalTo(ChestplateThing.class));
}
@Test
public void returnsNonNullCustomParserResult() {
String input = "something:soft";
ItemStack stack = new ItemStack(Material.SPONGE, 3);
ItemStackParser parser = mock(ItemStackParser.class);
when(parser.parse(input)).thenReturn(stack);
subject.register(parser);
ItemStackThing result = subject.parse(input);
assertThat(result.getItemStack(), equalTo(stack));
}
@Test
public void customParsersInvokedInOrder() {
String input = "unicorns and rainbows";
ItemStackParser first = mock(ItemStackParser.class);
ItemStackParser second = mock(ItemStackParser.class);
subject.register(first);
subject.register(second);
subject.parse(input);
InOrder order = inOrder(first, second);
order.verify(first).parse(input);
order.verify(second).parse(input);
}
@Test
public void customParsersInvokedUntilNonNullResult() {
String input = "unicorns and rainbows";
ItemStackParser first = mock(ItemStackParser.class);
ItemStackParser second = mock(ItemStackParser.class);
ItemStackParser third = mock(ItemStackParser.class);
ItemStack stack = new ItemStack(Material.GLOWSTONE, 9);
when(second.parse(input)).thenReturn(stack);
subject.register(first);
subject.register(second);
subject.register(third);
subject.parse(input);
verify(first).parse(input);
verifyZeroInteractions(third);
}
}