From b1c80d7bdee993185513581682f36ca71110cb31 Mon Sep 17 00:00:00 2001 From: BodgeMaster <> Date: Tue, 20 Dec 2022 00:50:11 +0100 Subject: [PATCH] A lot of changes. Sorry, didn't keep track. The next things to do are implementing the tile entity and getting the player's items upon death. --- .../lostcave/deathchests/DeathChests.java | 29 ++- .../java/lostcave/deathchests/EventHook.java | 48 ++++- .../deathchests/block/BlockDeathChest.java | 100 ++++++++- .../deathchests/item/ItemObituary.java | 21 ++ .../lostcave/deathchests/item/Obituary.java | 17 -- .../lostcave/deathchests/util/Config.java | 14 +- .../deathchests/util/DeathChestStorage.java | 100 +++++++++ .../deathchests/util/LookUpTable.java | 193 ++++++++++++++++++ src/test/TestLUT.java | 41 ++++ 9 files changed, 524 insertions(+), 39 deletions(-) create mode 100644 src/main/java/lostcave/deathchests/item/ItemObituary.java delete mode 100644 src/main/java/lostcave/deathchests/item/Obituary.java create mode 100644 src/main/java/lostcave/deathchests/util/DeathChestStorage.java create mode 100644 src/main/java/lostcave/deathchests/util/LookUpTable.java create mode 100644 src/test/TestLUT.java diff --git a/src/main/java/lostcave/deathchests/DeathChests.java b/src/main/java/lostcave/deathchests/DeathChests.java index 58bbba1..10a3403 100644 --- a/src/main/java/lostcave/deathchests/DeathChests.java +++ b/src/main/java/lostcave/deathchests/DeathChests.java @@ -1,15 +1,18 @@ package lostcave.deathchests; import lostcave.deathchests.block.BlockDeathChest; -import lostcave.deathchests.item.Obituary; +import lostcave.deathchests.item.ItemObituary; import lostcave.deathchests.util.Config; +import lostcave.deathchests.util.DeathChestStorage; import lostcave.deathchests.util.Debug; - +import net.minecraft.client.Minecraft; import net.minecraft.init.Blocks; +import net.minecraftforge.common.DimensionManager; import net.minecraftforge.common.MinecraftForge; import java.io.File; +import cpw.mods.fml.common.FMLCommonHandler; import cpw.mods.fml.common.Loader; import cpw.mods.fml.common.Mod; import cpw.mods.fml.common.Mod.EventHandler; @@ -17,18 +20,22 @@ import cpw.mods.fml.common.event.FMLConstructionEvent; import cpw.mods.fml.common.event.FMLInitializationEvent; import cpw.mods.fml.common.event.FMLPostInitializationEvent; import cpw.mods.fml.common.event.FMLPreInitializationEvent; +import cpw.mods.fml.common.event.FMLServerAboutToStartEvent; +import cpw.mods.fml.common.event.FMLServerStoppedEvent; import cpw.mods.fml.common.registry.GameRegistry; @Mod(modid = DeathChests.MODID, version = DeathChests.VERSION) public class DeathChests { public static final String MODID = "deathchests"; public static final String VERSION = "0-SNAPSHOT"; + + private static File worldSaveLocation; @EventHandler public void init(FMLConstructionEvent event) { Debug.out("Received FMLConstructionEvent"); Config.configDir = Loader.instance().getConfigDir(); - Debug.out("Config file is: " + Config.configDir.toString() + System.getProperty("file.separator") + Config.configFileName); + Debug.out("Config file is: " + Config.configDir.toString() + System.getProperty("file.separator") + Config.config_file_name); Config.loadConfig(); Debug.out("Loaded config."); @@ -49,10 +56,11 @@ public class DeathChests { Debug.out("Received FMLInitializationEvent"); GameRegistry.registerBlock(BlockDeathChest.getInstance(), "death_chest"); - GameRegistry.registerItem(Obituary.getInstance(), "obituary"); + GameRegistry.registerItem(ItemObituary.getInstance(), "obituary"); Debug.out("Registered block and item."); MinecraftForge.EVENT_BUS.register(new EventHook()); + FMLCommonHandler.instance().bus().register(new EventHook()); Debug.out("Registered event hook."); } @@ -60,5 +68,18 @@ public class DeathChests { public void init(FMLPostInitializationEvent event) { Debug.out("Received FMLPostInitializationEvent"); } + + @EventHandler + public void init(FMLServerAboutToStartEvent event) { + Debug.out("Received FMLServerAboutToStartEvent"); + //TODO: get world save location + worldSaveLocation = DimensionManager.getCurrentSaveRootDirectory(); + DeathChestStorage.load(worldSaveLocation); + } + + @EventHandler + public void init(FMLServerStoppedEvent event) { + DeathChestStorage.save(worldSaveLocation); + } } diff --git a/src/main/java/lostcave/deathchests/EventHook.java b/src/main/java/lostcave/deathchests/EventHook.java index 3472e57..32a9d45 100644 --- a/src/main/java/lostcave/deathchests/EventHook.java +++ b/src/main/java/lostcave/deathchests/EventHook.java @@ -1,17 +1,23 @@ package lostcave.deathchests; import cpw.mods.fml.common.eventhandler.SubscribeEvent; +import cpw.mods.fml.common.gameevent.PlayerEvent; import lostcave.deathchests.block.BlockDeathChest; +import lostcave.deathchests.item.ItemObituary; import lostcave.deathchests.util.Config; +import lostcave.deathchests.util.DeathChestStorage; import lostcave.deathchests.util.Debug; import net.minecraft.block.BlockAir; import net.minecraft.entity.player.EntityPlayer; +import net.minecraft.item.ItemStack; import net.minecraft.world.World; +import net.minecraftforge.event.entity.EntityEvent.EntityConstructing; +import net.minecraftforge.event.entity.player.PlayerDropsEvent; public class EventHook { @SubscribeEvent - public void PlayerDropsEvent(net.minecraftforge.event.entity.player.PlayerDropsEvent event) { + public void death(PlayerDropsEvent event) { // This is kinda misleadingly named. // The event fires only when a player entity produces drops (due to death), // not when a player drops an item. @@ -29,9 +35,9 @@ public class EventHook { if (y < 0) y = 0; if (y > 255) y = 255; - boolean foundSuitableBlock = false; - int[] suitableBlock = new int[3]; World world = event.entity.worldObj; + boolean foundSuitableBlock = false; + int[] suitableBlock = new int[4]; // x y z dimension // search for air block by checking the outside walls of an expanding cube if (world.getBlock(x, y, z) instanceof BlockAir) { suitableBlock[0] = x; @@ -151,16 +157,46 @@ public class EventHook { Debug.out("Suitable block X: " + Integer.toString(suitableBlock[0])); Debug.out("Suitable block Y: " + Integer.toString(suitableBlock[1])); Debug.out("Suitable block Z: " + Integer.toString(suitableBlock[2])); - //TODO: place death chest + + Debug.out("Dimension: " + Integer.toString(event.entityPlayer.dimension)); + suitableBlock[3] = event.entityPlayer.dimension; + world.setBlock(suitableBlock[0], suitableBlock[1], suitableBlock[2], BlockDeathChest.getInstance()); //TODO: Get the player's items and put them in the death chest + + String uuid = event.entityPlayer.getGameProfile().getId().toString(); + Debug.out(uuid); + DeathChestStorage.addNewDeathChestLocation(uuid, suitableBlock); + event.setCanceled(true); Debug.out("Canceled PlayerDropEvent"); - //TODO: Tell the player about death chest location upon respawn } else { Debug.out("No suitable location could be found."); - //TODO: tell player that a death chest couldn’t be placed and that the items have been dropped } } } + + @SubscribeEvent + public void respawn(PlayerEvent.PlayerRespawnEvent event) { + Debug.out(event.player.getDisplayName() + " respawned"); + + String uuid = event.player.getGameProfile().getId().toString(); + Debug.out("UUID: " + uuid); + + //TODO: check for stored chest location + if (DeathChestStorage.hasNewDeathChestLocation(uuid)) { + int[] chestLocation = DeathChestStorage.popNewDeathChestLocation(uuid); + Debug.out("Chest location X: " + Integer.toString(chestLocation[0])); + Debug.out("Chest location Y: " + Integer.toString(chestLocation[1])); + Debug.out("Chest location Z: " + Integer.toString(chestLocation[2])); + Debug.out("Chest location dimension: " + Integer.toString(chestLocation[3])); + + //TODO: NBT data + event.player.inventory.addItemStackToInventory(new ItemStack(ItemObituary.getInstance())); + } else { + Debug.out("No death chest location was stored."); + //TODO: tell player that a death chest couldn’t be placed and that the items have been dropped + } + } + } diff --git a/src/main/java/lostcave/deathchests/block/BlockDeathChest.java b/src/main/java/lostcave/deathchests/block/BlockDeathChest.java index ec4214b..bdf0563 100644 --- a/src/main/java/lostcave/deathchests/block/BlockDeathChest.java +++ b/src/main/java/lostcave/deathchests/block/BlockDeathChest.java @@ -1,8 +1,22 @@ package lostcave.deathchests.block; -import net.minecraft.block.BlockChest; +import java.util.Random; -public class BlockDeathChest extends BlockChest { +import cpw.mods.fml.relauncher.Side; +import cpw.mods.fml.relauncher.SideOnly; +import net.minecraft.block.Block; +import net.minecraft.block.BlockEnderChest; +import net.minecraft.client.renderer.texture.IIconRegister; +import net.minecraft.entity.player.EntityPlayer; +import net.minecraft.init.Blocks; +import net.minecraft.inventory.InventoryEnderChest; +import net.minecraft.item.Item; +import net.minecraft.tileentity.TileEntity; +import net.minecraft.tileentity.TileEntityEnderChest; +import net.minecraft.world.Explosion; +import net.minecraft.world.World; + +public class BlockDeathChest extends BlockEnderChest { private static final BlockDeathChest instance = new BlockDeathChest(); @@ -11,13 +25,89 @@ public class BlockDeathChest extends BlockChest { } private BlockDeathChest() { - super(0); + super(); this.setBlockName("death_chest"); + this.disableStats(); + this.setBlockUnbreakable(); + // same value as bedrock + this.setResistance(6000000.0F); + } + + @Override + protected boolean canSilkHarvest() { + return false; + } + + @Override + public boolean canDropFromExplosion(Explosion p_149659_1_) { + return false; + } + + @Override + public Item getItemDropped(int p_149650_1_, Random p_149650_2_, int p_149650_3_) { + return null; + } + + /** + * Called upon block activation (right click on the block.) + */ + @Override + public boolean onBlockActivated(World world, int x, int y, int z, EntityPlayer player, int p_149727_6_, float p_149727_7_, float p_149727_8_, float p_149727_9_) { + if (world.isRemote) return true; + + TileDeathChest tiledeathchest = (TileDeathChest) world.getTileEntity(x, y, z); + if (tiledeathchest != null) { + //TODO: check if appropriate player + //TODO: drop items + return true; + } else return true; + + } + + /** + * Called when a player hits the block. Args: world, x, y, z, player + */ + @Override + public void onBlockClicked(World p_149699_1_, int p_149699_2_, int p_149699_3_, int p_149699_4_, EntityPlayer p_149699_5_) { + //TODO + } + + /** + * Returns a new instance of a block's tile entity class. Called on placing the + * block. + */ + @Override + public TileEntity createNewTileEntity(World p_149915_1_, int p_149915_2_) { + return new TileDeathChest(); + } + + /** + * A randomly called display update to be able to add particles or other items + * for display + *
+ *
+ * This is here to disable the particles that would otherwise be emitted + */ + @Override + @SideOnly(Side.CLIENT) + public void randomDisplayTick(World p_149734_1_, int p_149734_2_, int p_149734_3_, int p_149734_4_, Random p_149734_5_) { + } + + @Override + @SideOnly(Side.CLIENT) + public void registerBlockIcons(IIconRegister p_149651_1_) { + this.blockIcon = p_149651_1_.registerIcon("cobblestone"); + } + + public static class TileDeathChest extends TileEntity { + //TODO } - //TODO: Make death chest unobtanium (don’t drop the chest when broken) - //TODO: Change capacity somehow? //TODO: Custom texture? (optional) + //TODO: add chest inventory + //TODO: drop chest contents + //TODO: Remove chest when empty + //TODO: make unable to break by explosion (configurable?) //TODO: Make it so items can only be taken out of the chest? // Custom GUI? Maybe remove GUI altogether and have it drop the items when broken? diff --git a/src/main/java/lostcave/deathchests/item/ItemObituary.java b/src/main/java/lostcave/deathchests/item/ItemObituary.java new file mode 100644 index 0000000..bcd5f1b --- /dev/null +++ b/src/main/java/lostcave/deathchests/item/ItemObituary.java @@ -0,0 +1,21 @@ +package lostcave.deathchests.item; + +import net.minecraft.item.Item; + +public class ItemObituary extends Item { + + private static final ItemObituary instance = new ItemObituary(); + + public static ItemObituary getInstance() { + return instance; + } + + private ItemObituary() { + this.setUnlocalizedName("obituary"); + this.setTextureName("paper"); + } + + //TODO: NBT data + //TODO: right click action + //TODO: item description +} diff --git a/src/main/java/lostcave/deathchests/item/Obituary.java b/src/main/java/lostcave/deathchests/item/Obituary.java deleted file mode 100644 index dad3d62..0000000 --- a/src/main/java/lostcave/deathchests/item/Obituary.java +++ /dev/null @@ -1,17 +0,0 @@ -package lostcave.deathchests.item; - -import net.minecraft.item.Item; - -public class Obituary extends Item { - - private static final Obituary instance = new Obituary(); - - public static Obituary getInstance() { - return instance; - } - - private Obituary() { - this.setUnlocalizedName("obituary"); - this.setTextureName("paper"); - } -} diff --git a/src/main/java/lostcave/deathchests/util/Config.java b/src/main/java/lostcave/deathchests/util/Config.java index c6157b2..354e0db 100644 --- a/src/main/java/lostcave/deathchests/util/Config.java +++ b/src/main/java/lostcave/deathchests/util/Config.java @@ -9,28 +9,28 @@ import cpw.mods.fml.common.FMLCommonHandler; public class Config { - public static final String configFileName = "deathchests.txt"; + public static final String config_file_name = "deathchests.txt"; public static File configDir = null; public static boolean debug = true; public static boolean fixLog4J = true; public static int maxDistance = 50; - private static final String configHeader = "# The format of this file is strictly `option=value` (no spaces).\n# Lines starting with # and empty lines are ignored.\n"; + private static final String config_header = "# The format of this file is strictly `option=value` (no spaces).\n# Lines starting with # and empty lines are ignored.\n"; public static void writeConfig() { - String configOut = configHeader; + String configOut = config_header; configOut += "\n\n# Enable debug output?\n"; configOut += "debug="; configOut += debug ? "true" : "false"; - configOut += "\n\n# Enable Log4Shell counter measures?\n"; + configOut += "\n\n# Enable Log4Shell counter measures? (this feature is currently not implemented)\n"; configOut += "fixLog4J="; configOut += fixLog4J ? "true" : "false"; configOut += "\n\n# The maximum search radius for finding a suitable location for placing the death chest\n"; configOut += "maxDistance="; configOut += Integer.toString(maxDistance); - - File configFile = new File(configDir, configFileName); + + File configFile = new File(configDir, config_file_name); try { Files.write(configFile.toPath(), configOut.getBytes()); } catch (IOException e) { @@ -42,7 +42,7 @@ public class Config { public static void loadConfig() { List configData = null; - File configFile = new File(configDir, configFileName); + File configFile = new File(configDir, config_file_name); if (configFile.exists()) { try { configData = Files.readAllLines(configFile.toPath()); diff --git a/src/main/java/lostcave/deathchests/util/DeathChestStorage.java b/src/main/java/lostcave/deathchests/util/DeathChestStorage.java new file mode 100644 index 0000000..815c10e --- /dev/null +++ b/src/main/java/lostcave/deathchests/util/DeathChestStorage.java @@ -0,0 +1,100 @@ +package lostcave.deathchests.util; + +import java.io.File; +import java.io.FileInputStream; +import java.io.FileOutputStream; +import java.io.IOException; +import java.util.Set; + +import net.minecraft.nbt.CompressedStreamTools; +import net.minecraft.nbt.NBTTagCompound; + +public class DeathChestStorage { + + private static LookUpTable deathChestLocations; + public static final String death_chests_dat_file = "deathchests.dat"; + + /** + * When a new death chest has been placed, add it to the LUT. This does not + * place the chest itself. + * + * @param uuid + * @param location + */ + public static void addNewDeathChestLocation(String uuid, int[] location) { + deathChestLocations.add(uuid, location); + } + + /** + * Get the location of a new death chest for a given player and remove it from + * the LUT of new death chests. + * + * @param uuid + * @return the location {x, y, z, dimension} + */ + public static int[] popNewDeathChestLocation(String uuid) { + int[] location = deathChestLocations.get(uuid); + deathChestLocations.remove(uuid); + return location; + } + + /** + * Checks if a given player has a new death chest that no obituary item has been + * created for. + * This is used in respawn events. + * + * @param uuid + * @return + */ + public static boolean hasNewDeathChestLocation(String uuid) { + return deathChestLocations.get(uuid) != null; + } + + public static void save(File worldSaveLocation) { + Debug.out("Putting death chest LUT into a compound"); + NBTTagCompound compound = new NBTTagCompound(); + for (int i = 0; i < deathChestLocations.storedElements(); i++) { + String uuid = deathChestLocations.getKey(i); + compound.setIntArray(uuid, deathChestLocations.get(uuid)); + } + + File file = new File(worldSaveLocation, death_chests_dat_file); + Debug.out("Saving to file: " + file.toPath().toAbsolutePath().toString()); + try { + CompressedStreamTools.writeCompressed(compound, new FileOutputStream(file)); + } catch (IOException e) { + e.printStackTrace(); + } + } + + public static void load(File worldSaveLocation) { + File file = new File(worldSaveLocation, death_chests_dat_file); + Debug.out("Loading death chest LUT from file: " + file.toPath().toAbsolutePath().toString()); + + // reset the LUT in case one still exists from quitting to main menu and loading a world + deathChestLocations = new LookUpTable(); + + if (file.exists()) { + try { + NBTTagCompound compound = CompressedStreamTools.readCompressed(new FileInputStream(file)); + // func_150296_c() should be named getKeySet() but didn’t deobfuscate properly + // This might be a problem with Forge, or it might be a problem with my Frankenstein setup. + for (String uuid : (Set) compound.func_150296_c()) { + deathChestLocations.add(uuid, compound.getIntArray(uuid)); + } + } catch (IOException e) { + e.printStackTrace(); + } + } + + Debug.out("The following entries have been loaded:"); + if (Config.debug) { + for (int i = 0; i < deathChestLocations.storedElements(); i++) { + String uuid = deathChestLocations.getKey(i); + int[] location = deathChestLocations.get(uuid); + Debug.out(uuid + " - x=" + Integer.toString(location[0]) + " y=" + Integer.toString(location[1]) + " z=" + Integer.toString(location[2]) + " dim=" + Integer.toString(location[3])); + } + } + Debug.out("--"); + } +} diff --git a/src/main/java/lostcave/deathchests/util/LookUpTable.java b/src/main/java/lostcave/deathchests/util/LookUpTable.java new file mode 100644 index 0000000..c5ef81f --- /dev/null +++ b/src/main/java/lostcave/deathchests/util/LookUpTable.java @@ -0,0 +1,193 @@ +package lostcave.deathchests.util; + +// The code below is licensed under the WTFPL. + +// Here is a copy for your convenience: + +// DO WHAT THE FUCK YOU WANT TO PUBLIC LICENSE +// Version 2, December 2004 +// +// Copyright (C) 2004 Sam Hocevar +// +// Everyone is permitted to copy and distribute verbatim or modified +// copies of this license document, and changing it is allowed as long +// as the name is changed. +// +// DO WHAT THE FUCK YOU WANT TO PUBLIC LICENSE +// TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION +// +// 0. You just DO WHAT THE FUCK YOU WANT TO. + +import java.util.ArrayList; +import java.util.List; + +/** + *

+ * This is a naive implementation of a look up table. Store and retrieve key + * value pairs. + *

+ * + *

+ * I had no reason to implement this other than not wanting to go down a rabbit + * hole of Java types rather than just using simple functionality. + *

+ * + * @author BodgeMaster + * + * @param The type to be stored + */ +public class LookUpTable { + private List values; + private String[] keys; + private boolean[] usedSlots; + private int storedElements; + + public LookUpTable() { + this.values = new ArrayList(); + for (int i = 0; i < 10; i++) { + values.add(null); + } + this.keys = new String[10]; + this.usedSlots = new boolean[] { false, false, false, false, false, false, false, false, false, false }; + this.storedElements = 0; + } + + /** + * Add an element + * + * @param key + * @param value + * @throws DuplicateKeyException + */ + public void add(String key, T value) { + // duplicate check + for (int i = 0; i < this.keys.length; i++) { + if (usedSlots[i]) { + if (keys[i].equals(key)) throw new DuplicateKeyException(key); + } + } + + // Make bigger arrays when running out of space + if (this.storedElements == this.keys.length) { + String[] keys = this.keys; + boolean[] usedSlots = this.usedSlots; + this.keys = new String[keys.length + 10]; + this.usedSlots = new boolean[usedSlots.length + 10]; + for (int i = 0; i < keys.length; i++) { + this.keys[i] = keys[i]; + this.usedSlots[i] = usedSlots[i]; + } + for (int i = 0; i < 10; i++) { + // ensure capacity + this.values.add(null); + } + } + + // Find next unused slot + int unusedSlot = 0; + for (int i = 0; i < this.usedSlots.length; i++) { + if (!this.usedSlots[i]) { + unusedSlot = i; + break; + } + } + + this.keys[unusedSlot] = key; + this.values.set(unusedSlot, value); + usedSlots[unusedSlot] = true; + this.storedElements++; + } + + /** + * Get a value by key + * + * @param key + * @return value (null if key not found) + */ + public T get(String key) { + for (int i = 0; i < usedSlots.length; i++) { + if (usedSlots[i]) { + if (keys[i].equals(key)) return values.get(i); + } + } + return null; + } + + public void set(String key, T value) { + for (int i = 0; i < usedSlots.length; i++) { + if (usedSlots[i]) { + if (keys[i].equals(key)) values.set(i, value); + } + } + } + + /** + *

+ * Remove an element from the LUT + *

+ * + *

+ * This function marks the position of key as unused. If you actually want to + * ensure that the object is removed from the LUT, use + * {@link LookUpTable#fullRemove(String)} instead. + *

+ * + * @param key + */ + public void remove(String key) { + for (int i = 0; i < usedSlots.length; i++) { + if (usedSlots[i]) { + if (keys[i].equals(key)) usedSlots[i] = false; + } + } + storedElements--; + } + + /** + * Remove an element from the LUT by actually setting it to null. + * + * @param key + */ + public void fullRemove(String key) { + for (int i = 0; i < usedSlots.length; i++) { + if (usedSlots[i]) { + if (keys[i].equals(key)) { + values.set(i, null); + usedSlots[i] = false; + } + } + } + storedElements--; + } + + public int storedElements() { + return this.storedElements; + } + + /** + * Gets a key by its position in the LUT. Unused slots are ignored and do not + * count towards the position. + * + * @param index + * @return key + */ + public String getKey(int index) { + int i = 0; + int j = 0; + while (i < index) { + j++; + if (usedSlots[j]) i++; + } + return keys[j]; + } + + public static class DuplicateKeyException extends RuntimeException { + public DuplicateKeyException() { + super("Tried adding an element with a duplicte key to a LookUpTable."); + } + + public DuplicateKeyException(String key) { + super("Tried adding an element with a duplicte key to a LookUpTable: " + key); + } + } +} diff --git a/src/test/TestLUT.java b/src/test/TestLUT.java new file mode 100644 index 0000000..59071d8 --- /dev/null +++ b/src/test/TestLUT.java @@ -0,0 +1,41 @@ +import lostcave.deathchests.util.LookUpTable; +import lostcave.deathchests.util.LookUpTable.DuplicateKeyException; + +public class TestLUT { + public static void main(String[] args) { + System.out.println("Testing LookUpTable..."); + + LookUpTable table = new LookUpTable<>(); + + table.add("one", 1); + table.add("two", 2); + table.add("three", 3); + + if (table.get("one") != 1 || table.get("two") != 2 || table.get("three") != 3) System.err.println("Failed add test."); + else System.out.println("Passed add test."); + + if (table.getKey(1).equals("two")) System.out.println("Passed getKey test (1/2)."); + else System.err.println("Failed getKey test (1/2)."); + + table.remove("two"); + if (table.get("two") == null) System.out.println("Passed remove test."); + else System.err.println("Failed remove test"); + + if (table.getKey(1).equals("three")) System.out.println("Passed getKey test (2/2)."); + else System.err.println("Failed getKey test (2/2): " + table.getKey(1)); + + if (table.storedElements()==2) System.out.println("Passes storedElements test."); + else System.out.println("Failed storedElements test."); + + table.set("three", 33); + if (table.get("three")==33) System.out.println("Passed set test."); + else System.out.println("Failed set test."); + + try { + table.add("three", 2); + System.err.println("Failed duplicate key test."); + } catch (DuplicateKeyException e) { + System.out.println("Passed duplicate key test."); + } + } +}