Skip to content

Instantly share code, notes, and snippets.

@literalplus
Last active August 29, 2015 14:10
Show Gist options
  • Save literalplus/9f1188a6cc0d02a8a132 to your computer and use it in GitHub Desktop.
Save literalplus/9f1188a6cc0d02a8a132 to your computer and use it in GitHub Desktop.
Something I made with bitmasks. Sorry for the title. Feel free to use `After.java` and `ConventionalChestHelperTest.java` under the terms of the [MIT license](http://opensource.org/licenses/MIT). I'd appreciate if you credit me.
/**
* Calculates loot for clicking conventional chests.
*
* @author <a href="http://xxyy.github.io/">xxyy</a>
* @since 02/12/14
*/
public final class ConventionalChestHelper {
public static final String CHEST_NUMBER_METADATA = "chests_collected"; //Store metadata key as constant to avoid being deprecated by Bukkit
private ConventionalChestHelper() {
}
/**
* Calculates which loot kit a player may still receive, based on what they have already received.
* If multiple kits are eligible, a semi-random one is chosen.
* @param plugin the plugin to interface with
* @param wrp the wrapper of the target player
* @return a list of items to give to the target player or an empty list if all chests have been used up already
*/
public static List<ItemStack> getLoot(TMPlugin plugin, TMPlayerWrapper wrp) { //wrp is a custom wrapper for players, offering a ~~custom~~ metadata system, game-specific metadata (points, etc.) as well as network-global metadata such as UUID, name, global K/D, etc.
int chestMask = 0b000; //Okay so every bit here represents a chest/kit. We have three of them, so I put three zerors for readability. 1 means used, 0 means unused.
if (wrp.hasMetadata(CHEST_NUMBER_METADATA)) { //Check if this player had already used up a chest/kit
chestMask = wrp.getMetadata(CHEST_NUMBER_METADATA).get(0).asInt(); //If so, store that in our bitmask
wrp.removeMetadata(CHEST_NUMBER_METADATA, plugin); //Kill off the old metadata to avoid issues with .get(0) - That would always return the first-put value
} //Close the if statement for extra performance
List<ChestContentSet> eligibleContents = new ArrayList<>(ChestContentSet.values().length); //Create a list of eligible chests/kits - On second thought, this should be an EnumSet.
for(ChestContentSet contents : ChestContentSet.values()) { //Loop through our available chests/kits (see enum declaration at the end of this file)
//Now, we check if this chest/kit has already been used up, i.e. its corresponding bit has been set.
//For the first chest/kit (id 0), the bit is 0b001 - that's equivalent to moving 0b001 to the left by 0,
// hence `<< contents.ordinal()` - I'm abusing .ordinal() as counter var here.
//For the second chest, we need 0b010. That's equivalent to moving 0b1 once to the left, i.e. 0b001 << 1 = 0b010
//We're then doing a bitwise AND to check if that bit is set - if that bit (operator 1) is not set, 0 is
// the result, as opposed to 1 when the bit is present.
//Example: 0b001 & 0b100 = 0b000 -> true -> we can still use this chest!
//Example: 0b100 & 0b100 = 0b100 -> false -> this chest has already been used!
if(((0b001 << contents.ordinal()) & chestMask) == 0) { //Check if our awesome bit is set in the mask
eligibleContents.add(contents); //If so, add this kit to the list of eligible kits
}
}
if(eligibleContents.isEmpty()) { //If we don't have any kits left,
wrp.setMetadata(CHEST_NUMBER_METADATA, new FixedMetadataValue(plugin, 0b111)); //make sure the metadata is set accordingly
return ImmutableList.of(); //and return an empty list, as per the JavaDoc.
}
ChestContentSet choice = eligibleContents.get(RandomUtils.nextInt(eligibleContents.size())); //Select a random kit from the available ones
//Now, we need to write back that we chose this kit.
//For that, we do a bitwise OR on our bitmask and the chest mask.
//Example: 0b000 | 0b001 = 0b001 -> chest 0 used
//Example: 0b100 | 0b010 = 0b110 -> chest 2, 1 used
chestMask = chestMask | (0b1 << choice.ordinal());
wrp.setMetadata(CHEST_NUMBER_METADATA, new FixedMetadataValue(plugin, chestMask)); //Just gotta write it back
return choice.getContents(); //And now, finally, return the contents.
} //Phew.
private enum ChestContentSet { //I hope I don't have to explain this one
WOOD_TIER(Arrays.asList(new ItemStack(Material.WOOD_SWORD, 1))),
STONE_TIER(Arrays.asList(new ItemStack(Material.STONE_SWORD, 1))),
AIR_TIER(Arrays.asList(new ItemStack(Material.BOW, 1), new ItemStack(Material.ARROW, 32)));
private final List<ItemStack> contents;
ChestContentSet(List<ItemStack> contents) {
this.contents = contents;
}
public List<ItemStack> getContents() {
return InventoryHelper.cloneAll(contents); //This just clones all the item stacks to make sure we don't get any of those weird ~~ghost items~~
}
}
}
if (Data.chest1.contains(plr)) { //TODO: This is shit - use metadata and an integer
if ((Data.chest2.contains(plr)) && (!Data.chest3.contains(plr))) {
plr.getInventory().addItem(new ItemStack(Material.STONE_SWORD, 1));
// p.updateInventory();
block.setType(Material.AIR);
Data.chest3.add(plr);
} else if ((Data.chest3.contains(plr)) && (!Data.chest2.contains(plr))) {
plr.getInventory().addItem(new ItemStack(Material.BOW, 1));
plr.getInventory().addItem(new ItemStack(Material.ARROW, 32));
// p.updateInventory();
block.setType(Material.AIR);
Data.chest2.add(plr);
} else if ((!Data.chest2.contains(plr)) && (!Data.chest3.contains(plr))) {
if (new Random().nextBoolean()) {
plr.getInventory().addItem(new ItemStack(Material.BOW, 1));
plr.getInventory().addItem(new ItemStack(Material.ARROW, 32));
// p.updateInventory();
block.setType(Material.AIR);
Data.chest2.add(plr);
} else {
plr.getInventory().addItem(new ItemStack(Material.STONE_SWORD, 1));
// p.updateInventory();
block.setType(Material.AIR);
Data.chest3.add(plr);
}
}
} else if (Data.chest2.contains(plr)) {
if ((Data.chest1.contains(plr)) && (!Data.chest3.contains(plr))) {
plr.getInventory().addItem(new ItemStack(Material.STONE_SWORD, 1));
// p.updateInventory();
block.setType(Material.AIR);
Data.chest3.add(plr);
} else if ((Data.chest3.contains(plr)) && (!Data.chest1.contains(plr))) {
plr.getInventory().addItem(new ItemStack(Material.WOOD_SWORD, 1));
// p.updateInventory();
block.setType(Material.AIR);
Data.chest1.add(plr);
} else if ((!Data.chest3.contains(plr)) && (!Data.chest1.contains(plr))) {
if (new Random().nextBoolean()) {
plr.getInventory().addItem(new ItemStack(Material.WOOD_SWORD, 1));
// p.updateInventory();
block.setType(Material.AIR);
Data.chest1.add(plr);
} else {
plr.getInventory().addItem(new ItemStack(Material.STONE_SWORD, 1));
// p.updateInventory();
block.setType(Material.AIR);
Data.chest3.add(plr);
}
}
} else if (Data.chest3.contains(plr)) {
if ((Data.chest1.contains(plr)) && (!Data.chest2.contains(plr))) {
plr.getInventory().addItem(new ItemStack(Material.BOW, 1));
plr.getInventory().addItem(new ItemStack(Material.ARROW, 32));
// p.updateInventory();
block.setType(Material.AIR);
Data.chest2.add(plr);
} else if ((Data.chest2.contains(plr)) && (!Data.chest1.contains(plr))) {
plr.getInventory().addItem(new ItemStack(Material.WOOD_SWORD, 1));
// p.updateInventory();
block.setType(Material.AIR);
Data.chest1.add(plr);
} else if ((!Data.chest2.contains(plr)) && (!Data.chest1.contains(plr))) {
if (new Random().nextBoolean()) {
plr.getInventory().addItem(new ItemStack(Material.WOOD_SWORD, 1));
// p.updateInventory();
block.setType(Material.AIR);
Data.chest1.add(plr);
} else {
plr.getInventory().addItem(new ItemStack(Material.BOW, 1));
plr.getInventory().addItem(new ItemStack(Material.ARROW, 32));
// p.updateInventory();
block.setType(Material.AIR);
Data.chest2.add(plr);
}
}
} else {
switch (new Random().nextInt(2)) {
case 0:
plr.getInventory().addItem(new ItemStack(Material.WOOD_SWORD, 1));
// p.updateInventory();
block.setType(Material.AIR);
Data.chest1.add(plr);
break;
case 1:
plr.getInventory().addItem(new ItemStack(Material.BOW, 1));
plr.getInventory().addItem(new ItemStack(Material.ARROW, 32));
// p.updateInventory();
block.setType(Material.AIR);
Data.chest2.add(plr);
break;
case 2:
plr.getInventory().addItem(new ItemStack(Material.STONE_SWORD, 1));
// p.updateInventory();
block.setType(Material.AIR);
Data.chest3.add(plr);
}
}
public class ConventionalChestHelperTest {
@Test
@SuppressWarnings({"unchecked", "SuspiciousMethodCalls"})
public void testGetLoot() throws Exception {
TMPlugin plugin = mock(TMPlugin.class);
TMPlayerWrapper wrp = mock(TMPlayerWrapper.class);
Map<String, MetadataValue> metadata = new HashMap<>(); //Simple metadata impl
when(wrp.hasMetadata(any(String.class))).thenAnswer(i -> metadata.containsKey(i.getArguments()[0]));
when(wrp.getMetadata(any(String.class))).thenAnswer(i -> ImmutableList.of(metadata.get(i.getArguments()[0])));
doAnswer(i -> metadata.put((String) i.getArguments()[0], (MetadataValue) i.getArguments()[1]))
.when(wrp).setMetadata(any(String.class), any(MetadataValue.class));
BaseMatcher<List<ItemStack>> empty = new BaseMatcher<List<ItemStack>>() { //Hamcrest interstingly doesn't seem to provide such thing, so we gotta make one ourselves
@Override
public boolean matches(Object item) {
return ((List<ItemStack>) item).isEmpty();
}
@Override
public void describeTo(Description description) {
description.appendText("empty"); //e.g. "expected: is not empty"
}
@Override
public void describeMismatch(Object item, Description description) {
List<ItemStack> list = (List<ItemStack>) item; //Need to work around a NPE because ItemStack's hashCode calls Bukkit#getItemFactory(), which is obviously null (Future generations please stop and remember my debugging session here)
description.appendText("list with " + list.size() + " elements"); //Do something so that we look professional
}
};
assertThat("Didn't get anything for first chest", ConventionalChestHelper.getLoot(plugin, wrp), is(not(empty)));
assertThat("Didn't get anything for second chest", ConventionalChestHelper.getLoot(plugin, wrp), is(not(empty)));
assertThat("Didn't get anything for third chest", ConventionalChestHelper.getLoot(plugin, wrp), is(not(empty)));
assertThat("Got stuff for non-existent chest", ConventionalChestHelper.getLoot(plugin, wrp), is(empty));
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment