{"action":"created","issue":{"url":"https://api.github.com/repos/SpongePowered/SpongeAPI/issues/495","labels_url":"https://api.github.com/repos/SpongePowered/SpongeAPI/issues/495/labels{/name}","comments_url":"https://api.github.com/repos/SpongePowered/SpongeAPI/issues/495/comments","events_url":"https://api.github.com/repos/SpongePowered/SpongeAPI/issues/495/events","html_url":"https://github.com/SpongePowered/SpongeAPI/pull/495","id":60307836,"number":495,"title":"A More Robust Items API","user":{"login":"gabizou","id":1203877,"avatar_url":"https://avatars.githubusercontent.com/u/1203877?v=3","gravatar_id":"","url":"https://api.github.com/users/gabizou","html_url":"https://github.com/gabizou","followers_url":"https://api.github.com/users/gabizou/followers","following_url":"https://api.github.com/users/gabizou/following{/other_user}","gists_url":"https://api.github.com/users/gabizou/gists{/gist_id}","starred_url":"https://api.github.com/users/gabizou/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/gabizou/subscriptions","organizations_url":"https://api.github.com/users/gabizou/orgs","repos_url":"https://api.github.com/users/gabizou/repos","events_url":"https://api.github.com/users/gabizou/events{/privacy}","received_events_url":"https://api.github.com/users/gabizou/received_events","type":"User","site_admin":false},"labels":[{"url":"https://api.github.com/repos/SpongePowered/SpongeAPI/labels/enhancement","name":"enhancement","color":"84b6eb"},{"url":"https://api.github.com/repos/SpongePowered/SpongeAPI/labels/input%20wanted","name":"input wanted","color":"fbca04"},{"url":"https://api.github.com/repos/SpongePowered/SpongeAPI/labels/major%20feature","name":"major feature","color":"bfd4f2"},{"url":"https://api.github.com/repos/SpongePowered/SpongeAPI/labels/needs%20team%20review","name":"needs team review","color":"eb6420"}],"state":"open","locked":false,"assignee":null,"milestone":{"url":"https://api.github.com/repos/SpongePowered/SpongeAPI/milestones/2","html_url":"https://github.com/SpongePowered/SpongeAPI/milestones/2.0-Release","labels_url":"https://api.github.com/repos/SpongePowered/SpongeAPI/milestones/2/labels","id":882230,"number":2,"title":"2.0-Release","description":"This is the second stable release for the Sponge API, version 2.0 ","creator":{"login":"gratimax","id":1261779,"avatar_url":"https://avatars.githubusercontent.com/u/1261779?v=3","gravatar_id":"","url":"https://api.github.com/users/gratimax","html_url":"https://github.com/gratimax","followers_url":"https://api.github.com/users/gratimax/followers","following_url":"https://api.github.com/users/gratimax/following{/other_user}","gists_url":"https://api.github.com/users/gratimax/gists{/gist_id}","starred_url":"https://api.github.com/users/gratimax/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/gratimax/subscriptions","organizations_url":"https://api.github.com/users/gratimax/orgs","repos_url":"https://api.github.com/users/gratimax/repos","events_url":"https://api.github.com/users/gratimax/events{/privacy}","received_events_url":"https://api.github.com/users/gratimax/received_events","type":"User","site_admin":false},"open_issues":14,"closed_issues":69,"state":"open","created_at":"2014-11-27T05:08:55Z","updated_at":"2015-03-19T05:42:39Z","due_on":"2015-03-13T05:00:00Z","closed_at":null},"comments":18,"created_at":"2015-03-09T07:32:36Z","updated_at":"2015-03-20T00:37:54Z","closed_at":null,"pull_request":{"url":"https://api.github.com/repos/SpongePowered/SpongeAPI/pulls/495","html_url":"https://github.com/SpongePowered/SpongeAPI/pull/495","diff_url":"https://github.com/SpongePowered/SpongeAPI/pull/495.diff","patch_url":"https://github.com/SpongePowered/SpongeAPI/pull/495.patch"},"body":"Hello again! After a week of designing, rewriting, thinking, and patience, I bring my rather unique Items API!\r\n\r\nDesign Proposal for a usable Items API\r\n----------------\r\n\r\nItems have always been the goto object for adding extra data for various reasons. And they worked. ItemStack internally is backed by an `NBTTagCompound` with no limitations since the data stored is persisted until the ItemStack no longer exists. Most data on an ItemStack is dependent on the [`ItemType`](https://github.com/SpongePowered/SpongeAPI/blob/master/src/main/java/org/spongepowered/api/item/ItemType.java) to define the behaviors of that ItemStack when interacted with. ItemStacks can even contain [`AttributeModifiers`](http://minecraft.gamepedia.com/Attribute#Modifiers) that are applied upon equipping the item stack.\r\n\r\nSo, yes, Items are a huge bag of spiders that very few people really safely understand since bad practices have led to more bad practices that have lead to more bad practices. These bad practices in the past lead to some unmaintainable APIs for some projects, including possibly leaking the entire custom data backed by an `NBTTagCompound`. For those that are familiar with my work with SpongeAPI in the past know that I absolutely hate mediocre API design, let alone API that becomes so obtuse about following an implementation to the letter.\r\n\r\nSo, what are we going to have?\r\n\r\n**A well thought out Items API**\r\n\r\n### Design Goals\r\n\r\nWe want to address the shortcomings of past APIs, they include:\r\n\r\n- Representation of a mutable data structure directly linking to the underlying `NBTTagCompound`. This leaking of mutable data prevents certain behaviors from being monitored and managed. Not to mention, passing around the underlying data has the potential to break the itemstack entirely.\r\n\r\n- Many developers believe that all behaviors with ItemStacks can be customized by granting access to the underlying `NBTTagCompound` without issue, but really, they can't verify that their changes are being stored, let alone valid.\r\n\r\n- Limitations on custom data from mods and plugins are sanitized or unstable for use in previous API's.\r\n\r\n### Let's get Started!\r\n\r\nTo begin, we have to start with the basics.\r\n\r\n##### What is an ItemStack?\r\n\r\nTraditionally, [`ItemStack`](https://github.com/SpongePowered/SpongeAPI/blob/master/src/main/java/org/spongepowered/api/item/inventory/ItemStack.java)s are an inventory item with some additional data, including some of the following:\r\n- The amount of the item\r\n- The type of item\r\n- The durability of the item if it is damageable\r\n- And some extra data like enchantments etc.\r\n\r\n##### Ok... So what?\r\n\r\nWell, now I have to focus on [`Item`](https://github.com/SpongePowered/SpongeAPI/blob/master/src/main/java/org/spongepowered/api/item/ItemType.java#L32) for a minute. What makes an `ItemType` so special? Does it have any special behaviors? Special data related to what it does? *ABSOLUTELY!* But, what are we really trying to achieve? I mean, `ItemType.STONE_PICKAXE` and `ItemType.DIAMOND_PICKAXE` are similar in that they're both \"pickaxes\", but what do we know about the type of the item?\r\n\r\nLet's break it down even further...\r\n\r\nA pickaxe is just an item that has extra information pertaining to breaking certain kinds of blocks. It also has certain information regarding the efficiency of breaking certain blocks, whether it can harvest\r\nother types of blocks, etc.\r\n\r\n##### So.... represent tools with ToolData?\r\n\r\n*Not so faaaaaaast...* Seriously, remember I said we were going to start from the basics?\r\n\r\nOk, we have items that can be considered \"tools\" that have certain.... *properties*. Why *property*? Well, let's see, tools can harvest certain blocks, they are efficient at mining a certain other set of blocks, and, well, they have some defined maximum amount of uses. This is the basis of `ItemProperty`. Something as simple as defining a `ItemType.DIAMOND_PICKAXE` being able to break `BlockType.OBSIDIAN` can not be changed. It's literally a `property` of the item and there's nothing you can do to change it!\r\n\r\n##### What's the point of `ItemProperty`?\r\n\r\nHold on, I haven't finished explaining this system yet.\r\n\r\nTo really understand `ItemProperty`, let's see a code example of getting one:\r\n\r\n``` java\r\n\r\n@Subscribe\r\npublic void onBreak(PlayerBreakBlockEvent event) {\r\n   Optional<ItemStack> option = event.getPlayer().getItemInHand();\r\n   if (!option.isPresent() {\r\n     return;\r\n   }\r\n   ItemStack myItemStack = option.get();\r\n   Optional<HarvestingProperty> harvestOptional = myItemStack.get(HarvestingProperty.class);\r\n   if (harvestOptional.isPresent()) {\r\n     // Now we can actually use the itemproperty \r\n     // since the item stack does have it\r\n     HarvestingProperty harvest = harvestOptional.get();\r\n     // And the code goes on doing whatever it was you wanted to \r\n     // do with that information...\r\n     if (harvest.getValue().contains(event.getBlock().getType()) {\r\n       // We want to just make the player exhausted. Because we're mean >:]\r\n       event.getPlayer().setExhaustion(event.getPlayer().getExhaustion() + 10);\r\n     }\r\n```\r\n\r\nSo, we've determined that the `ItemStack` has a `HarvestingProperty` and determined that the item could actually harvest the block broken... **All without having to check `ItemType`!**\r\n\r\nBut now we have to understand some properties of an `ItemStack`, like the color of dye.\r\n\r\n##### OH! I KNOW THOSE! THEY'RE NUMBERS FROM 0 TO 15!\r\n\r\nNOPE! NOPE! NOPE! You're not listening! Again! We are starting at the basics!\r\n\r\nJokes aside, how do we really represent *mutable* data? Your instinct was to think of dyes by \"damage\" values. Sure, we can be lazy and say that a `DyeColor`'s damage value is say, 10. But what does that get us? Some archaic \"*you-have-to-use-this-table-of-damage-values-to-types-table*\"? That's not at all a good way to represent data, especially if we can expect more than the vanilla types of data. \r\n\r\nSo, let's really try to avoid going with \"what we know\" and try to think with the same mentality as we used for `ItemProperty`.\r\n\r\nData on the ItemStack that can be changed is going to be called `ItemData`.\r\n\r\nLet's take a look at a code example to explain the basis.\r\n\r\nWith code, we have our `ItemStack` as so:\r\n\r\n```java\r\nItemStack myItemStack = someMethodToGetItemStack();\r\n```\r\n\r\nWe discovered we can get `ItemData` objects from some method in `ItemStack`, looking something like this:\r\n\r\n```java\r\npublic interface ItemStack {\r\n    /*\r\n     * ....\r\n     */\r\n    <T extends ItemData> Optional<T> getItemData(Class<T> itemDataClass);\r\n\r\n}\r\n```\r\n\r\nAnd for our particular example, `DyeableItemData` can look like this:\r\n\r\n```java\r\n\r\npublic interface DyeableItemData extends ItemData {\r\n\r\n    DyeColor getColor();\r\n\r\n    void setColor(DyeColor color);\r\n\r\n}\r\n```\r\n\r\nAnd now we have our plugin code like so:\r\n\r\n```java\r\npublic class MyClass {\r\n\r\n    public void myMethod(ItemStack itemStack) {\r\n        Optional<DyeableItemData> dye = itemStack.getItemData(DyeableItemData.class);\r\n        if (dye.isPresent()) {\r\n            dye.get().setColor(DyeColors.CYAN);\r\n        }\r\n    }\r\n}\r\n```\r\nBUT WAIT! THAT'S NOT ALL!\r\n\r\nTo avoid leaking references everywhere, `ItemStack` doesn't actually return it's own references of `ItemData`, we have now *set* the `ItemData` back onto the `ItemStack`!\r\n\r\n```java\r\npublic class MyClass {\r\n\r\n    public void myMethod(ItemStack itemStack) {\r\n        Optional<DyeableItemData> dye = itemStack.getItemData(DyeableItemData.class);\r\n        if (dye.isPresent()) {\r\n            dye.get().setColor(DyeColors.CYAN);\r\n            itemstack.setItemData(dye);\r\n        }\r\n    }\r\n}\r\n```\r\n\r\n##### Fine, but how do I create a new `ItemData` for something like *Lore*?\r\n\r\nWell, that's actually solved for you! Here, look!\r\n\r\n```java\r\npublic interface ItemStack extends DataSerializable {\r\n\r\n   <T extends ItemData> Optional<T> getOrCreateData(Class<T> clazz);\r\n\r\n}\r\n```\r\n\r\nAnd thankfully, we can rely on our handy dandy `LoreItemData` interface:\r\n\r\n```java\r\npublic interface LoreItemData extends ItemData {\r\n    \r\n    List<Message> getLines();\r\n    //... and so on\r\n}\r\n```\r\nSo, now, we end up with a UML diagram something like this:\r\n![Rough Diagram](http://i.imgur.com/ivLVLJJ.png)\r\nSo? What do you think?\r\n\r\n##### Wait, if you said earlier that you have something for `DyeColor`, what is the point of `ItemStack#getDurability()`?\r\n\r\n***CLAPS*** Now you're thinking! There isn't one! In fact, this PR removes the method entirely. If we really think about it, the only items that worry about \"durability\" are.... tools, armor, and weapons!\r\n\r\nSo, now we somewhat understand the basis of this massive API change is going to have us do.\r\n\r\nFortunately, our `ItemStack` interface can be trimmed down a little bit by extending `DataHolder`.\r\n\r\nAfter all is said and done, the API itself is pretty extensible.\r\n\r\n##### What's the point of overcomplicating this if you can just create something like `ToolProperty` and `FoodProperty` and `WoolData` or `WrittenBookData`?\r\n\r\nI'm actually very happy you asked that! I'll show you something pretty awesome instead of answering directly:\r\n\r\n```java\r\npublic void givePlayerTreasure(Player player) {\r\n\tItemStack diamondSword = MyItemBuilder.builder()\r\n\t\t\t\t\t\t\t\t\t\t.itemType(ItemTypes.DIAMOND_SWORD)\r\n\t\t\t\t\t\t\t\t\t\t.build();\r\n    // Let's add some enchantments\r\n    EnchantmentData ench = diamondSword.getData(EnchantmentData.class).get();\r\n    ench.setUnsafeEnchantment(Enchantments.UNBREAKING, 10);\r\n    ench.setUnsafeEnchantment(Enchantments.SHARPNESS, 5);\r\n    diamondSword.setData(ench);\r\n\r\n\t// Can we name this sword? SURE!\r\n\tDisplayNameData disp = diamondSword.getData(DisplayNameData.class).get();\r\n\tdisp.setDisplayName(Messages.of(\"Slayer of Plebians\"));\r\n\t// Ok, you get the point, that's how you access some basic data...\r\n\tdiamondSword.setData(disp);\r\n\t\r\n\t// But wait! what's this? A chest? pssh can't do anything with that...\r\n\tItemStack chestItem = MyItemBuilder.builder().itemType(ItemTypes.CHEST).build();\r\n\t\r\n\t// Wait what? There's InventoryItemData? Is that like an array of ItemStacks?\r\n\tInventoryItemData invDat = chestItem.getOrCreate(InventoryItemData.class).get();\r\n        // .........\r\n\tCarriedInventory chestInventory = invDat.getInventory();\r\n        // ....................................\r\n\tchestInventory.set(diamondSword);\r\n\tchestItem.setData(invDat);\r\n\r\n\tplayer.setItemInHand(chestItem);\r\n}\r\n```\r\n\r\n##### ***WHAAAAAAAAAT??!?!?!?!*** **YOU CAN SET INVENTORIES IN ITEMS?!**\r\n\r\nI told you this API was going to be impressive! Notice that we didn't even have to check anything! A few assumptions were made of course about the `ItemStack`, such as the fact that the `diamondSword` would allow `EnchantmentData` and `DisplayNameData`, but let's be real here: You can create a fully filled chested inventory in a chest with all sorts of special data assigned to the individual items! AND YOU CAN DO THIS ON AN `ItemStack`!!\r\n\r\n\r\n### Wrapping up\r\n\r\nWith this Items API, we can:\r\n\r\n- Check for various `ItemProperty` information without having to know the `ItemType`\r\n- Get mutable `ItemData` objects representing various information of different types of items, again without checking `ItemType`\r\n- Use the Persistence API for serializing and deserializing `ItemStack`\r\n- Forget useless key tables of \"magic\" values to various types of items\r\n- Have a wonderful day\r\n\r\n\r\n##### Well wait! I want to access data in the `NBTTagCompound`!\r\n\r\n***Sigh....*** I thought we just agreed that we didn't care about NBT or things like that...\r\n\r\nSeriously, we don't remotely need this direct access to a mutable compound (or [`DataView`](https://github.com/SpongePowered/SpongeAPI/blob/master/src/main/java/org/spongepowered/api/service/persistence/data/DataView.java))\r\nbecause it doesn't make sense, and it's all based on implementation details.\r\n\r\nWhat do we really need for custom data then? Did you want to add something else that isn't covered by the API? Did you just want to use a `DataContainer` to access the data directly? \r\n\r\nWell, you have two options now:\r\n1) You create your own `MyData extends ItemData<MyData>` which handles your custom data (or mod data to provide usability to SpongeAPI plugins wanting to modify custom mod data) and handle serialization and deserialization accordingly\r\n2) You use the unsavory `toContainer()` which you can modify the existing data of and then `setRawData(DataContainer)` to set that modified data, assuming the data is valid, otherwise an `InvalidDataException` is thrown.\r\n\r\nI'll show you how to go about the first option:\r\n\r\n```java\r\npublic class MyData implements ItemData<MyData> {\r\n    private int myInt;\r\n\r\n    public MyData(int myInt, double myDouble) {\r\n        this.myInt = myInt;\r\n    }\r\n\r\n    @Override\r\n    public int compareTo(MyData o) {\r\n        return this.myInt > o.myInt ? 1 : this.myInt < o.myInt ? -1 : 0;\r\n    }\r\n\r\n    @Override\r\n    public DataContainer toContainer() {\r\n        DataContainer container = new MemoryDataContainer();\r\n        container.set(new DataQuery(\"com.gabizou.myData\"), this.myInt);\r\n        return container;\r\n    }\r\n}\r\n```\r\n\r\nNow, at the moment, you've yet to see how this will be created with `ItemStack#getOrCreate(MyData.class)` because, essentially, as `ItemData` itself is [`DataSerializable`](https://github.com/SpongePowered/SpongeAPI/blob/master/src/main/java/org/spongepowered/api/service/persistence/DataSerializable.java), we essentially know that all `DataSerializable`s must have registered a [`DataSerializableBuilder`](https://github.com/SpongePowered/SpongeAPI/blob/master/src/main/java/org/spongepowered/api/service/persistence/DataSerializableBuilder.java)! So, we can safely then assume that as long as we have our `MyDataBuilder implements DataSerializableBuilder<MyData>` and it is registered with [`SerializationService`](https://github.com/SpongePowered/SpongeAPI/blob/master/src/main/java/org/spongepowered/api/service/persistence/SerializationService.java), we don't have to worry about constructing our data without the serialization service! Pretty ingenious, isn't it?\r\n\r\nNow let's stop asking for an NBT API and call it a day."},"comment":{"url":"https://api.github.com/repos/SpongePowered/SpongeAPI/issues/comments/83827588","html_url":"https://github.com/SpongePowered/SpongeAPI/pull/495#issuecomment-83827588","issue_url":"https://api.github.com/repos/SpongePowered/SpongeAPI/issues/495","id":83827588,"user":{"login":"Zidane","id":744482,"avatar_url":"https://avatars.githubusercontent.com/u/744482?v=3","gravatar_id":"","url":"https://api.github.com/users/Zidane","html_url":"https://github.com/Zidane","followers_url":"https://api.github.com/users/Zidane/followers","following_url":"https://api.github.com/users/Zidane/following{/other_user}","gists_url":"https://api.github.com/users/Zidane/gists{/gist_id}","starred_url":"https://api.github.com/users/Zidane/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/Zidane/subscriptions","organizations_url":"https://api.github.com/users/Zidane/orgs","repos_url":"https://api.github.com/users/Zidane/repos","events_url":"https://api.github.com/users/Zidane/events{/privacy}","received_events_url":"https://api.github.com/users/Zidane/received_events","type":"User","site_admin":false},"created_at":"2015-03-20T00:37:54Z","updated_at":"2015-03-20T00:37:54Z","body":"@frogocomics \r\n\r\nThis API is based on Mumfrey's for a different part of the API...."}}