diff options
Diffstat (limited to 'src/scriptapi_item.cpp')
-rw-r--r-- | src/scriptapi_item.cpp | 718 |
1 files changed, 718 insertions, 0 deletions
diff --git a/src/scriptapi_item.cpp b/src/scriptapi_item.cpp new file mode 100644 index 000000000..b266d856d --- /dev/null +++ b/src/scriptapi_item.cpp @@ -0,0 +1,718 @@ +/* +Minetest +Copyright (C) 2013 celeron55, Perttu Ahola <celeron55@gmail.com> + +This program is free software; you can redistribute it and/or modify +it under the terms of the GNU Lesser General Public License as published by +the Free Software Foundation; either version 2.1 of the License, or +(at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU Lesser General Public License for more details. + +You should have received a copy of the GNU Lesser General Public License along +with this program; if not, write to the Free Software Foundation, Inc., +51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. +*/ + +#include "scriptapi.h" +#include "server.h" +#include "script.h" +#include "tool.h" +#include "nodedef.h" +#include "util/pointedthing.h" +#include "scriptapi_item.h" +#include "scriptapi_types.h" +#include "scriptapi_common.h" +#include "scriptapi_object.h" +#include "scriptapi_content.h" + + +struct EnumString es_ItemType[] = +{ + {ITEM_NONE, "none"}, + {ITEM_NODE, "node"}, + {ITEM_CRAFT, "craft"}, + {ITEM_TOOL, "tool"}, + {0, NULL}, +}; + + +/* + ItemDefinition +*/ + +ItemDefinition read_item_definition(lua_State *L, int index, + ItemDefinition default_def) +{ + if(index < 0) + index = lua_gettop(L) + 1 + index; + + // Read the item definition + ItemDefinition def = default_def; + + def.type = (ItemType)getenumfield(L, index, "type", + es_ItemType, ITEM_NONE); + getstringfield(L, index, "name", def.name); + getstringfield(L, index, "description", def.description); + getstringfield(L, index, "inventory_image", def.inventory_image); + getstringfield(L, index, "wield_image", def.wield_image); + + lua_getfield(L, index, "wield_scale"); + if(lua_istable(L, -1)){ + def.wield_scale = check_v3f(L, -1); + } + lua_pop(L, 1); + + def.stack_max = getintfield_default(L, index, "stack_max", def.stack_max); + if(def.stack_max == 0) + def.stack_max = 1; + + lua_getfield(L, index, "on_use"); + def.usable = lua_isfunction(L, -1); + lua_pop(L, 1); + + getboolfield(L, index, "liquids_pointable", def.liquids_pointable); + + warn_if_field_exists(L, index, "tool_digging_properties", + "deprecated: use tool_capabilities"); + + lua_getfield(L, index, "tool_capabilities"); + if(lua_istable(L, -1)){ + def.tool_capabilities = new ToolCapabilities( + read_tool_capabilities(L, -1)); + } + + // If name is "" (hand), ensure there are ToolCapabilities + // because it will be looked up there whenever any other item has + // no ToolCapabilities + if(def.name == "" && def.tool_capabilities == NULL){ + def.tool_capabilities = new ToolCapabilities(); + } + + lua_getfield(L, index, "groups"); + read_groups(L, -1, def.groups); + lua_pop(L, 1); + + lua_getfield(L, index, "sounds"); + if(lua_istable(L, -1)){ + lua_getfield(L, -1, "place"); + read_soundspec(L, -1, def.sound_place); + lua_pop(L, 1); + } + lua_pop(L, 1); + + // Client shall immediately place this node when player places the item. + // Server will update the precise end result a moment later. + // "" = no prediction + getstringfield(L, index, "node_placement_prediction", + def.node_placement_prediction); + + return def; +} + +// register_item_raw({lots of stuff}) +int l_register_item_raw(lua_State *L) +{ + luaL_checktype(L, 1, LUA_TTABLE); + int table = 1; + + // Get the writable item and node definition managers from the server + IWritableItemDefManager *idef = + get_server(L)->getWritableItemDefManager(); + IWritableNodeDefManager *ndef = + get_server(L)->getWritableNodeDefManager(); + + // Check if name is defined + std::string name; + lua_getfield(L, table, "name"); + if(lua_isstring(L, -1)){ + name = lua_tostring(L, -1); + verbosestream<<"register_item_raw: "<<name<<std::endl; + } else { + throw LuaError(L, "register_item_raw: name is not defined or not a string"); + } + + // Check if on_use is defined + + ItemDefinition def; + // Set a distinctive default value to check if this is set + def.node_placement_prediction = "__default"; + + // Read the item definition + def = read_item_definition(L, table, def); + + // Default to having client-side placement prediction for nodes + // ("" in item definition sets it off) + if(def.node_placement_prediction == "__default"){ + if(def.type == ITEM_NODE) + def.node_placement_prediction = name; + else + def.node_placement_prediction = ""; + } + + // Register item definition + idef->registerItem(def); + + // Read the node definition (content features) and register it + if(def.type == ITEM_NODE) + { + ContentFeatures f = read_content_features(L, table); + ndef->set(f.name, f); + } + + return 0; /* number of results */ +} + +// register_alias_raw(name, convert_to_name) +int l_register_alias_raw(lua_State *L) +{ + std::string name = luaL_checkstring(L, 1); + std::string convert_to = luaL_checkstring(L, 2); + + // Get the writable item definition manager from the server + IWritableItemDefManager *idef = + get_server(L)->getWritableItemDefManager(); + + idef->registerAlias(name, convert_to); + + return 0; /* number of results */ +} + +// Retrieves minetest.registered_items[name][callbackname] +// If that is nil or on error, return false and stack is unchanged +// If that is a function, returns true and pushes the +// function onto the stack +// If minetest.registered_items[name] doesn't exist, minetest.nodedef_default +// is tried instead so unknown items can still be manipulated to some degree +bool get_item_callback(lua_State *L, + const char *name, const char *callbackname) +{ + lua_getglobal(L, "minetest"); + lua_getfield(L, -1, "registered_items"); + lua_remove(L, -2); + luaL_checktype(L, -1, LUA_TTABLE); + lua_getfield(L, -1, name); + lua_remove(L, -2); + // Should be a table + if(lua_type(L, -1) != LUA_TTABLE) + { + // Report error and clean up + errorstream<<"Item \""<<name<<"\" not defined"<<std::endl; + lua_pop(L, 1); + + // Try minetest.nodedef_default instead + lua_getglobal(L, "minetest"); + lua_getfield(L, -1, "nodedef_default"); + lua_remove(L, -2); + luaL_checktype(L, -1, LUA_TTABLE); + } + lua_getfield(L, -1, callbackname); + lua_remove(L, -2); + // Should be a function or nil + if(lua_type(L, -1) == LUA_TFUNCTION) + { + return true; + } + else if(lua_isnil(L, -1)) + { + lua_pop(L, 1); + return false; + } + else + { + errorstream<<"Item \""<<name<<"\" callback \"" + <<callbackname<<" is not a function"<<std::endl; + lua_pop(L, 1); + return false; + } +} + +bool scriptapi_item_on_drop(lua_State *L, ItemStack &item, + ServerActiveObject *dropper, v3f pos) +{ + realitycheck(L); + assert(lua_checkstack(L, 20)); + StackUnroller stack_unroller(L); + + // Push callback function on stack + if(!get_item_callback(L, item.name.c_str(), "on_drop")) + return false; + + // Call function + LuaItemStack::create(L, item); + objectref_get_or_create(L, dropper); + pushFloatPos(L, pos); + if(lua_pcall(L, 3, 1, 0)) + script_error(L, "error: %s", lua_tostring(L, -1)); + if(!lua_isnil(L, -1)) + item = read_item(L, -1); + return true; +} + +bool scriptapi_item_on_place(lua_State *L, ItemStack &item, + ServerActiveObject *placer, const PointedThing &pointed) +{ + realitycheck(L); + assert(lua_checkstack(L, 20)); + StackUnroller stack_unroller(L); + + // Push callback function on stack + if(!get_item_callback(L, item.name.c_str(), "on_place")) + return false; + + // Call function + LuaItemStack::create(L, item); + objectref_get_or_create(L, placer); + push_pointed_thing(L, pointed); + if(lua_pcall(L, 3, 1, 0)) + script_error(L, "error: %s", lua_tostring(L, -1)); + if(!lua_isnil(L, -1)) + item = read_item(L, -1); + return true; +} + +bool scriptapi_item_on_use(lua_State *L, ItemStack &item, + ServerActiveObject *user, const PointedThing &pointed) +{ + realitycheck(L); + assert(lua_checkstack(L, 20)); + StackUnroller stack_unroller(L); + + // Push callback function on stack + if(!get_item_callback(L, item.name.c_str(), "on_use")) + return false; + + // Call function + LuaItemStack::create(L, item); + objectref_get_or_create(L, user); + push_pointed_thing(L, pointed); + if(lua_pcall(L, 3, 1, 0)) + script_error(L, "error: %s", lua_tostring(L, -1)); + if(!lua_isnil(L, -1)) + item = read_item(L, -1); + return true; +} + +// garbage collector +int LuaItemStack::gc_object(lua_State *L) +{ + LuaItemStack *o = *(LuaItemStack **)(lua_touserdata(L, 1)); + delete o; + return 0; +} + +// is_empty(self) -> true/false +int LuaItemStack::l_is_empty(lua_State *L) +{ + LuaItemStack *o = checkobject(L, 1); + ItemStack &item = o->m_stack; + lua_pushboolean(L, item.empty()); + return 1; +} + +// get_name(self) -> string +int LuaItemStack::l_get_name(lua_State *L) +{ + LuaItemStack *o = checkobject(L, 1); + ItemStack &item = o->m_stack; + lua_pushstring(L, item.name.c_str()); + return 1; +} + +// get_count(self) -> number +int LuaItemStack::l_get_count(lua_State *L) +{ + LuaItemStack *o = checkobject(L, 1); + ItemStack &item = o->m_stack; + lua_pushinteger(L, item.count); + return 1; +} + +// get_wear(self) -> number +int LuaItemStack::l_get_wear(lua_State *L) +{ + LuaItemStack *o = checkobject(L, 1); + ItemStack &item = o->m_stack; + lua_pushinteger(L, item.wear); + return 1; +} + +// get_metadata(self) -> string +int LuaItemStack::l_get_metadata(lua_State *L) +{ + LuaItemStack *o = checkobject(L, 1); + ItemStack &item = o->m_stack; + lua_pushlstring(L, item.metadata.c_str(), item.metadata.size()); + return 1; +} + +// clear(self) -> true +int LuaItemStack::l_clear(lua_State *L) +{ + LuaItemStack *o = checkobject(L, 1); + o->m_stack.clear(); + lua_pushboolean(L, true); + return 1; +} + +// replace(self, itemstack or itemstring or table or nil) -> true +int LuaItemStack::l_replace(lua_State *L) +{ + LuaItemStack *o = checkobject(L, 1); + o->m_stack = read_item(L, 2); + lua_pushboolean(L, true); + return 1; +} + +// to_string(self) -> string +int LuaItemStack::l_to_string(lua_State *L) +{ + LuaItemStack *o = checkobject(L, 1); + std::string itemstring = o->m_stack.getItemString(); + lua_pushstring(L, itemstring.c_str()); + return 1; +} + +// to_table(self) -> table or nil +int LuaItemStack::l_to_table(lua_State *L) +{ + LuaItemStack *o = checkobject(L, 1); + const ItemStack &item = o->m_stack; + if(item.empty()) + { + lua_pushnil(L); + } + else + { + lua_newtable(L); + lua_pushstring(L, item.name.c_str()); + lua_setfield(L, -2, "name"); + lua_pushinteger(L, item.count); + lua_setfield(L, -2, "count"); + lua_pushinteger(L, item.wear); + lua_setfield(L, -2, "wear"); + lua_pushlstring(L, item.metadata.c_str(), item.metadata.size()); + lua_setfield(L, -2, "metadata"); + } + return 1; +} + +// get_stack_max(self) -> number +int LuaItemStack::l_get_stack_max(lua_State *L) +{ + LuaItemStack *o = checkobject(L, 1); + ItemStack &item = o->m_stack; + lua_pushinteger(L, item.getStackMax(get_server(L)->idef())); + return 1; +} + +// get_free_space(self) -> number +int LuaItemStack::l_get_free_space(lua_State *L) +{ + LuaItemStack *o = checkobject(L, 1); + ItemStack &item = o->m_stack; + lua_pushinteger(L, item.freeSpace(get_server(L)->idef())); + return 1; +} + +// is_known(self) -> true/false +// Checks if the item is defined. +int LuaItemStack::l_is_known(lua_State *L) +{ + LuaItemStack *o = checkobject(L, 1); + ItemStack &item = o->m_stack; + bool is_known = item.isKnown(get_server(L)->idef()); + lua_pushboolean(L, is_known); + return 1; +} + +// get_definition(self) -> table +// Returns the item definition table from minetest.registered_items, +// or a fallback one (name="unknown") +int LuaItemStack::l_get_definition(lua_State *L) +{ + LuaItemStack *o = checkobject(L, 1); + ItemStack &item = o->m_stack; + + // Get minetest.registered_items[name] + lua_getglobal(L, "minetest"); + lua_getfield(L, -1, "registered_items"); + luaL_checktype(L, -1, LUA_TTABLE); + lua_getfield(L, -1, item.name.c_str()); + if(lua_isnil(L, -1)) + { + lua_pop(L, 1); + lua_getfield(L, -1, "unknown"); + } + return 1; +} + +// get_tool_capabilities(self) -> table +// Returns the effective tool digging properties. +// Returns those of the hand ("") if this item has none associated. +int LuaItemStack::l_get_tool_capabilities(lua_State *L) +{ + LuaItemStack *o = checkobject(L, 1); + ItemStack &item = o->m_stack; + const ToolCapabilities &prop = + item.getToolCapabilities(get_server(L)->idef()); + push_tool_capabilities(L, prop); + return 1; +} + +// add_wear(self, amount) -> true/false +// The range for "amount" is [0,65535]. Wear is only added if the item +// is a tool. Adding wear might destroy the item. +// Returns true if the item is (or was) a tool. +int LuaItemStack::l_add_wear(lua_State *L) +{ + LuaItemStack *o = checkobject(L, 1); + ItemStack &item = o->m_stack; + int amount = lua_tointeger(L, 2); + bool result = item.addWear(amount, get_server(L)->idef()); + lua_pushboolean(L, result); + return 1; +} + +// add_item(self, itemstack or itemstring or table or nil) -> itemstack +// Returns leftover item stack +int LuaItemStack::l_add_item(lua_State *L) +{ + LuaItemStack *o = checkobject(L, 1); + ItemStack &item = o->m_stack; + ItemStack newitem = read_item(L, 2); + ItemStack leftover = item.addItem(newitem, get_server(L)->idef()); + create(L, leftover); + return 1; +} + +// item_fits(self, itemstack or itemstring or table or nil) -> true/false, itemstack +// First return value is true iff the new item fits fully into the stack +// Second return value is the would-be-left-over item stack +int LuaItemStack::l_item_fits(lua_State *L) +{ + LuaItemStack *o = checkobject(L, 1); + ItemStack &item = o->m_stack; + ItemStack newitem = read_item(L, 2); + ItemStack restitem; + bool fits = item.itemFits(newitem, &restitem, get_server(L)->idef()); + lua_pushboolean(L, fits); // first return value + create(L, restitem); // second return value + return 2; +} + +// take_item(self, takecount=1) -> itemstack +int LuaItemStack::l_take_item(lua_State *L) +{ + LuaItemStack *o = checkobject(L, 1); + ItemStack &item = o->m_stack; + u32 takecount = 1; + if(!lua_isnone(L, 2)) + takecount = luaL_checkinteger(L, 2); + ItemStack taken = item.takeItem(takecount); + create(L, taken); + return 1; +} + +// peek_item(self, peekcount=1) -> itemstack +int LuaItemStack::l_peek_item(lua_State *L) +{ + LuaItemStack *o = checkobject(L, 1); + ItemStack &item = o->m_stack; + u32 peekcount = 1; + if(!lua_isnone(L, 2)) + peekcount = lua_tointeger(L, 2); + ItemStack peekaboo = item.peekItem(peekcount); + create(L, peekaboo); + return 1; +} + +LuaItemStack::LuaItemStack(const ItemStack &item): + m_stack(item) +{ +} + +LuaItemStack::~LuaItemStack() +{ +} + +const ItemStack& LuaItemStack::getItem() const +{ + return m_stack; +} +ItemStack& LuaItemStack::getItem() +{ + return m_stack; +} + +// LuaItemStack(itemstack or itemstring or table or nil) +// Creates an LuaItemStack and leaves it on top of stack +int LuaItemStack::create_object(lua_State *L) +{ + ItemStack item = read_item(L, 1); + LuaItemStack *o = new LuaItemStack(item); + *(void **)(lua_newuserdata(L, sizeof(void *))) = o; + luaL_getmetatable(L, className); + lua_setmetatable(L, -2); + return 1; +} +// Not callable from Lua +int LuaItemStack::create(lua_State *L, const ItemStack &item) +{ + LuaItemStack *o = new LuaItemStack(item); + *(void **)(lua_newuserdata(L, sizeof(void *))) = o; + luaL_getmetatable(L, className); + lua_setmetatable(L, -2); + return 1; +} + +LuaItemStack* LuaItemStack::checkobject(lua_State *L, int narg) +{ + luaL_checktype(L, narg, LUA_TUSERDATA); + void *ud = luaL_checkudata(L, narg, className); + if(!ud) luaL_typerror(L, narg, className); + return *(LuaItemStack**)ud; // unbox pointer +} + +void LuaItemStack::Register(lua_State *L) +{ + lua_newtable(L); + int methodtable = lua_gettop(L); + luaL_newmetatable(L, className); + int metatable = lua_gettop(L); + + lua_pushliteral(L, "__metatable"); + lua_pushvalue(L, methodtable); + lua_settable(L, metatable); // hide metatable from Lua getmetatable() + + lua_pushliteral(L, "__index"); + lua_pushvalue(L, methodtable); + lua_settable(L, metatable); + + lua_pushliteral(L, "__gc"); + lua_pushcfunction(L, gc_object); + lua_settable(L, metatable); + + lua_pop(L, 1); // drop metatable + + luaL_openlib(L, 0, methods, 0); // fill methodtable + lua_pop(L, 1); // drop methodtable + + // Can be created from Lua (LuaItemStack(itemstack or itemstring or table or nil)) + lua_register(L, className, create_object); +} + +const char LuaItemStack::className[] = "ItemStack"; +const luaL_reg LuaItemStack::methods[] = { + luamethod(LuaItemStack, is_empty), + luamethod(LuaItemStack, get_name), + luamethod(LuaItemStack, get_count), + luamethod(LuaItemStack, get_wear), + luamethod(LuaItemStack, get_metadata), + luamethod(LuaItemStack, clear), + luamethod(LuaItemStack, replace), + luamethod(LuaItemStack, to_string), + luamethod(LuaItemStack, to_table), + luamethod(LuaItemStack, get_stack_max), + luamethod(LuaItemStack, get_free_space), + luamethod(LuaItemStack, is_known), + luamethod(LuaItemStack, get_definition), + luamethod(LuaItemStack, get_tool_capabilities), + luamethod(LuaItemStack, add_wear), + luamethod(LuaItemStack, add_item), + luamethod(LuaItemStack, item_fits), + luamethod(LuaItemStack, take_item), + luamethod(LuaItemStack, peek_item), + {0,0} +}; + +ItemStack read_item(lua_State *L, int index) +{ + if(index < 0) + index = lua_gettop(L) + 1 + index; + + if(lua_isnil(L, index)) + { + return ItemStack(); + } + else if(lua_isuserdata(L, index)) + { + // Convert from LuaItemStack + LuaItemStack *o = LuaItemStack::checkobject(L, index); + return o->getItem(); + } + else if(lua_isstring(L, index)) + { + // Convert from itemstring + std::string itemstring = lua_tostring(L, index); + IItemDefManager *idef = get_server(L)->idef(); + try + { + ItemStack item; + item.deSerialize(itemstring, idef); + return item; + } + catch(SerializationError &e) + { + infostream<<"WARNING: unable to create item from itemstring" + <<": "<<itemstring<<std::endl; + return ItemStack(); + } + } + else if(lua_istable(L, index)) + { + // Convert from table + IItemDefManager *idef = get_server(L)->idef(); + std::string name = getstringfield_default(L, index, "name", ""); + int count = getintfield_default(L, index, "count", 1); + int wear = getintfield_default(L, index, "wear", 0); + std::string metadata = getstringfield_default(L, index, "metadata", ""); + return ItemStack(name, count, wear, metadata, idef); + } + else + { + throw LuaError(L, "Expecting itemstack, itemstring, table or nil"); + } +} + +std::vector<ItemStack> read_items(lua_State *L, int index) +{ + if(index < 0) + index = lua_gettop(L) + 1 + index; + + std::vector<ItemStack> items; + luaL_checktype(L, index, LUA_TTABLE); + lua_pushnil(L); + while(lua_next(L, index) != 0){ + // key at index -2 and value at index -1 + items.push_back(read_item(L, -1)); + // removes value, keeps key for next iteration + lua_pop(L, 1); + } + return items; +} + +// creates a table of ItemStacks +void push_items(lua_State *L, const std::vector<ItemStack> &items) +{ + // Get the table insert function + lua_getglobal(L, "table"); + lua_getfield(L, -1, "insert"); + int table_insert = lua_gettop(L); + // Create and fill table + lua_newtable(L); + int table = lua_gettop(L); + for(u32 i=0; i<items.size(); i++){ + ItemStack item = items[i]; + lua_pushvalue(L, table_insert); + lua_pushvalue(L, table); + LuaItemStack::create(L, item); + if(lua_pcall(L, 2, 0, 0)) + script_error(L, "error: %s", lua_tostring(L, -1)); + } + lua_remove(L, -2); // Remove table + lua_remove(L, -2); // Remove insert +} |