Last active
August 29, 2015 14:10
-
-
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.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/** | |
* 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~~ | |
} | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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); | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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