cItem: Store custom data
#1
I was considering writing a plugin that could bind any command to any cItem in the player's inventory, and execute that command whenever the player clicked with that tool in hand. Such a plugin would come in handy especially on the Gallery server, when applying the same command to multiple areas.

I found out about some interesting behaviors that I'd like to describe, for documentation purposes.

My first attempt was to "hide" the command into cItem.m_Lore, somehow tricking the client into not displaying it but still being accessible to the plugin. For this, I tried embedding a NUL character into the string. Unfortunately, this seems to trim the string in the API bindings code, so the hidden part never really came back. While experimenting with m_Lore more, I learned about a few interesting things: when multiple successive line-breaks are given, the parser removes all but one. When using non-printable characters (in the 1 - 31 range), the client would still print them as little boxes with abbreviations. When using a character in the range 128 - 255, the client would disconnect and is no longer able to connect, always failing with "unable to parse NBT" exception. I assume that is due to the string being invalid UTF-8.

My second attempt was to use the cItem.m_CustomName with the same trick of hiding the data. Again, this didn't succeed, the additional data disappeared on the API boundary. And again, the client would print the nonprintable characters and disconnect with invalid UTF-8.

The third attempt was an elaborat Lua trick: store a custom value directly in the cItem object that represents the item in Lua - since it is a table, it can store any value after all. This does seem to work, but has a terrible short-coming: The cItem returned by cInventory:GetEquippedItem() represents the slot, rather than the item in it. So when the player moves another item into the slot, it still retains the custom value, and the item moved out of the slot "loses' the custom value. Therefore, this approach is unusable for the purpose of this plugin as well.

In the end, I seem to be left with three solutions:
- A plugin can keep track of the item manually, by storing the item's current location and adjusting it whenever there's a UI click by the player. Quite elaborate, and prone to errors.
- Add a value storage in the C++ code and export it to Lua. This is the cleanest solution, but somewhat incompatible - there's no such storage in Vanilla and who knows what the client could do if it tried to read the Anvil world saved by Cuberite with this "feature" used.
- Store the data in m_Lore and don't care about the player seeing it. Ugly, but works.

Or can you see some other option that I forgot?
Reply
Thanks given by:
#2
Wouldn't something like this work?:
local g_ItemData = {}

function cItem:SaveData(key, data)
	local storage = g_ItemData[self] or {}
	storage[key] = data;
	g_ItemData[self] = storage
end


function cItem:GetData(key)
	return (g_ItemData[self] or {})[key]
end
Or would it have the same problem as the third option?
Reply
Thanks given by:
#3
It has the same problem as my third approach - the cItem instance represents the slot, rather than the item in it. So addressing a table by that cItem as a key has the same behavior, transitively.
Reply
Thanks given by:
#4
Another option is giving every cItem an ID like cEntity, but if we're adding another member variable anyway we could just as well add an unordered_map.
Reply
Thanks given by:
#5
MORE fun with this Smile
I decided to use m_Lore to store the commands; after all it is good for the player to see them. However, I found out that the lore gets maimed when it contains multiple consecutive line-ends. Setting the lore via plugin to "a``b" sets the lore correctly, but moving the item around the inventory while in Creative resets the lore to "a`b`". Also, the client doesn't display the extra empty line.

Reported as an issue: https://github.com/cuberite/cuberite/issues/3467
Reply
Thanks given by:
#6
Have something like that in mind. Easy to access and easy to add / get keys and values.

-- firework item
local item = cItem(E_ITEM_FIREWORK_ROCKET, 1)
item.m_ItemMeta["Explosions"] = {}
item.m_ItemMeta["Explosions"][1] = {}
item.m_ItemMeta["Explosions"][1]["Flicker"] = 1 -- Twinkle effect
item.m_ItemMeta["Explosions"][1]["Type"] = 2 -- Star-shaped

item.m_ItemMeta["Explosions"][2] = {}
item.m_ItemMeta["Explosions"][2]["Trail"] = 1  -- Trail effect
item.m_ItemMeta["Explosions"][2]["Type"] = 3 -- Creeper-shaped
item.m_ItemMeta["Fireworks"]["Flight"] = 60 -- 3s flight time, until it explodes


-- written book
local item = cItem(E_ITEM_WRITTEN_BOOK, 1)
item.m_ItemMeta["author"] = "author name"
item.m_ItemMeta["title"] = "A book"
item.m_ItemMeta["pages"][1] = "page 1"
item.m_ItemMeta["pages"][2] = "page 2"
item.m_ItemMeta["pages"][3] = "page 3"

I added additional nbt tags in a written book and the client had no problems with the book, it only shows the tags that are shown and maybe it only reads the keys and values that are required.

Currently we have no way to manipulate nbt tags from plugin side. I see that cItem has m_ItemColor and m_FireworkItem, both doesn't work and are not exported to lua.
Reply
Thanks given by:
#7
I'd be great of we could just do something like this:
item.m_ItemMeta = 
{
	author = "NiLSPACE",
	title = "Cuberite's darkest secrets",
	pages = 
	{
		"Cuberite was previously called MCServer",
		"While Cuberite is hosted on Github it used to be hosted on code.google.com",
		"These are not dark secrets"
	}
}
Reply
Thanks given by:
#8
Yes that would be great Smile
Reply
Thanks given by:
#9
I know it's gonna sound weird coming from me, but would it make sense to make the m_ItemMeta member contain a JSON object? It could be made transparent to Lua (so one could set "m_ItemMeta = LuaTable") and it would be much easier to work with on the C++-side.

One thing though - how will we decide what properties get parsed into their specific member variables, and what properties stay in the ItemMeta object?
Reply
Thanks given by:
#10
I would say if a cItem gets created and its'a a item that contains nbt tags, then on the c++ side the default properties and default values should be added.
Reply
Thanks given by:




Users browsing this thread: 5 Guest(s)