From d6b30dd3a5df4b84a287305f807442064f4cf68d Mon Sep 17 00:00:00 2001 From: Kahrl Date: Tue, 29 Nov 2011 17:15:18 +0200 Subject: [PATCH] CraftItem rework and Lua interface --- data/mods/default/init.lua | 203 ++++- src/CMakeLists.txt | 1 + src/client.cpp | 134 ++-- src/client.h | 17 +- src/clientserver.h | 37 +- src/content_sao.cpp | 18 - src/content_sao.h | 1 - src/game.cpp | 604 ++++++++------- src/gamedef.h | 5 +- src/inventory.cpp | 269 ++++--- src/inventory.h | 69 +- src/player.cpp | 57 +- src/player.h | 7 +- src/scriptapi.cpp | 335 +++++++++ src/scriptapi.h | 13 + src/server.cpp | 1457 ++++++++++++++++++------------------ src/server.h | 8 + src/servercommand.cpp | 2 +- src/serverobject.h | 2 + src/utility.cpp | 100 +++ src/utility.h | 73 +- 21 files changed, 2162 insertions(+), 1250 deletions(-) diff --git a/data/mods/default/init.lua b/data/mods/default/init.lua index 58ac72242..a8e3b7ba7 100644 --- a/data/mods/default/init.lua +++ b/data/mods/default/init.lua @@ -10,6 +10,7 @@ -- minetest.register_entity(name, prototype_table) -- minetest.register_tool(name, {lots of stuff}) -- minetest.register_node(name, {lots of stuff}) +-- minetest.register_craftitem(name, {lots of stuff}) -- minetest.register_craft({output=item, recipe={...}) -- minetest.register_globalstep(func) -- minetest.register_on_placenode(func(pos, newnode, placer)) @@ -30,7 +31,9 @@ -- -- Global tables: -- minetest.registered_nodes --- ^ List of registed node definitions, indexed by name +-- ^ List of registered node definitions, indexed by name +-- minetest.registered_craftitems +-- ^ List of registered craft item definitions, indexed by name -- minetest.registered_entities -- ^ List of registered entity prototypes, indexed by name -- minetest.object_refs @@ -44,6 +47,9 @@ -- - remove_node(pos) -- - get_node(pos) -- - add_luaentity(pos, name) +-- - add_item(pos, itemstring) +-- - add_rat(pos) +-- - add_firefly(pos) -- - get_meta(pos) -- Get a NodeMetaRef at that position -- -- NodeMetaRef @@ -73,6 +79,9 @@ -- - setpos(pos); pos={x=num, y=num, z=num} -- - moveto(pos, continuous=false): interpolated move -- - add_to_inventory(itemstring): add an item to object inventory +-- - add_to_inventory_later(itemstring): like above, but after callback returns (only allowed for craftitem callbacks) +-- - get_hp(): returns number of hitpoints (2 * number of hearts) +-- - set_hp(hp): set number of hitpoints (2 * number of hearts) -- - settexturemod(mod) -- - setsprite(p={x=0,y=0}, num_frames=1, framelength=0.2, -- - select_horiz_by_yawpitch=false) @@ -1159,6 +1168,118 @@ minetest.register_node("TNT", { }, }) +-- +-- Crafting items +-- + +local craftitem_place_item = function(item, placer, pos) + --print("craftitem_place_item") + print("item: " .. dump(item)) + print("placer: " .. dump(placer)) + print("pos: " .. dump(pos)) + minetest.env:add_item(pos, 'CraftItem "' .. item .. '" 1') + return true +end + +local craftitem_eat = function(hp_change) + return function(item, user, pointed_thing) -- closure + --print("craftitem_eat(" .. hp_change .. ")") + --print("item: " .. dump(item)) + --print("user: " .. dump(user)) + --print("pointed_thing: " .. dump(pointed_thing)) + user:set_hp(user:get_hp() + hp_change) + return true + end +end + +minetest.register_craftitem("Stick", { + image = "stick.png", + --furnace_burntime = ..., + on_place_on_ground = craftitem_place_item, +}) + +minetest.register_craftitem("paper", { + image = "paper.png", + on_place_on_ground = craftitem_place_item, +}) + +minetest.register_craftitem("book", { + image = "book.png", + on_place_on_ground = craftitem_place_item, +}) + +minetest.register_craftitem("lump_of_coal", { + image = "lump_of_coal.png", + furnace_burntime = 40; + on_place_on_ground = craftitem_place_item, +}) + +minetest.register_craftitem("lump_of_iron", { + image = "lump_of_iron.png", + cookresult_item = 'CraftItem "steel_ingot" 1', + on_place_on_ground = craftitem_place_item, +}) + +minetest.register_craftitem("lump_of_clay", { + image = "lump_of_clay.png", + cookresult_item = 'CraftItem "clay_brick" 1', + on_place_on_ground = craftitem_place_item, +}) + +minetest.register_craftitem("steel_ingot", { + image = "steel_ingot.png", + on_place_on_ground = craftitem_place_item, +}) + +minetest.register_craftitem("clay_brick", { + image = "clay_brick.png", + on_place_on_ground = craftitem_place_item, +}) + +minetest.register_craftitem("rat", { + image = "rat.png", + cookresult_item = 'CraftItem "cooked_rat" 1', + on_drop = function(item, dropper, pos) + minetest.env:add_rat(pos) + return true + end, +}) + +minetest.register_craftitem("cooked_rat", { + image = "cooked_rat.png", + cookresult_item = 'CraftItem "scorched_stuff" 1', + on_place_on_ground = craftitem_place_item, + on_use = craftitem_eat(6), +}) + +minetest.register_craftitem("scorched_stuff", { + image = "scorched_stuff.png", + on_place_on_ground = craftitem_place_item, +}) + +minetest.register_craftitem("firefly", { + image = "firefly.png", + on_drop = function(item, dropper, pos) + minetest.env:add_firefly(pos) + return true + end, +}) + +minetest.register_craftitem("apple", { + image = "apple.png", + on_place_on_ground = craftitem_place_item, + on_use = craftitem_eat(4), +}) + +minetest.register_craftitem("apple_iron", { + image = "apple_iron.png", + on_place_on_ground = craftitem_place_item, + on_use = craftitem_eat(8), +}) + +print(dump(minetest.registered_craftitems)) + + -- -- Some common functions -- @@ -1237,6 +1358,7 @@ function TNT:on_punch(hitter) if self.health <= 0 then self.object:remove() hitter:add_to_inventory("NodeItem TNT 1") + hitter:set_hp(hitter:get_hp() - 1) end end @@ -1344,6 +1466,8 @@ function on_punchnode(p, node) if node.name == "TNT" then minetest.env:remove_node(p) minetest.env:add_luaentity(p, "TNT") + --minetest.env:add_luaentity(p, "testentity") + --minetest.env:add_firefly(p) nodeupdate(p) end end @@ -1381,7 +1505,7 @@ minetest.register_on_chat_message(function(name, message) end) -- Grow papyrus on TNT every 10 seconds ---[[minetest.register_abm({ +minetest.register_abm({ nodenames = {"TNT"}, interval = 10.0, chance = 1, @@ -1390,7 +1514,7 @@ end) pos.y = pos.y + 1 minetest.env:add_node(pos, {name="papyrus"}) end, -})]] +}) -- Replace texts of alls signs with "foo" every 10 seconds --[[minetest.register_abm({ @@ -1404,6 +1528,79 @@ end) end, })]] +--[[local ncpos = nil +local ncq = 1 +local ncstuff = { + {2, 1, 0, 3}, {3, 0, 1, 2}, {4, -1, 0, 1}, {5, -1, 0, 1}, {6, 0, -1, 0}, + {7, 0, -1, 0}, {8, 1, 0, 3}, {9, 1, 0, 3}, {10, 1, 0, 3}, {11, 0, 1, 2}, + {12, 0, 1, 2}, {13, 0, 1, 2}, {14, -1, 0, 1}, {15, -1, 0, 1}, {16, -1, 0, 1}, + {17, -1, 0, 1}, {18, 0, -1, 0}, {19, 0, -1, 0}, {20, 0, -1, 0}, {21, 0, -1, 0}, + {22, 1, 0, 3}, {23, 1, 0, 3}, {24, 1, 0, 3}, {25, 1, 0, 3}, {10, 0, 1, 2} +} +local ncold = {} +local nctime = nil + +minetest.register_abm({ + nodenames = {"dirt_with_grass"}, + interval = 100000.0, + chance = 1, + action = function(pos, node, active_object_count, active_object_count_wider) + if ncpos ~= nil then + return + end + + if pos.x % 16 ~= 8 or pos.z % 16 ~= 8 then + return + end + + pos.y = pos.y + 1 + n = minetest.env:get_node(pos) + print(dump(n)) + if n.name ~= "air" then + return + end + + pos.y = pos.y + 2 + ncpos = pos + nctime = os.clock() + minetest.env:add_node(ncpos, {name="nyancat"}) + end +}) + +minetest.register_abm({ + nodenames = {"nyancat"}, + interval = 1.0, + chance = 1, + action = function(pos, node, active_object_count, active_object_count_wider) + if ncpos == nil then + return + end + if pos.x == ncpos.x and pos.y == ncpos.y and pos.z == ncpos.z then + clock = os.clock() + if clock - nctime < 0.1 then + return + end + nctime = clock + + s0 = ncstuff[ncq] + ncq = s0[1] + s1 = ncstuff[ncq] + p0 = pos + p1 = {x = p0.x + s0[2], y = p0.y, z = p0.z + s0[3]} + p2 = {x = p1.x + s1[2], y = p1.y, z = p1.z + s1[3]} + table.insert(ncold, 1, p0) + while #ncold >= 10 do + minetest.env:add_node(ncold[#ncold], {name="air"}) + table.remove(ncold, #ncold) + end + minetest.env:add_node(p0, {name="nyancat_rainbow"}) + minetest.env:add_node(p1, {name="nyancat", param1=s0[4]}) + minetest.env:add_node(p2, {name="air"}) + ncpos = p1 + end + end, +})--]] + -- LuaNodeMetadata should support something like this --meta.setvar("somevariable", {x=0, y=0, z=0}) --meta.getvar("somevariable") -> {x=0, y=0, z=0} diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index a7805faea..153faf7ac 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -99,6 +99,7 @@ set(common_SRCS nameidmapping.cpp tooldef.cpp nodedef.cpp + craftitemdef.cpp luaentity_common.cpp scriptapi.cpp script.cpp diff --git a/src/client.cpp b/src/client.cpp index 2326ff35d..0f28087d4 100644 --- a/src/client.cpp +++ b/src/client.cpp @@ -34,6 +34,7 @@ with this program; if not, write to the Free Software Foundation, Inc., #include "nodemetadata.h" #include "nodedef.h" #include "tooldef.h" +#include "craftitemdef.h" #include /* @@ -191,11 +192,13 @@ Client::Client( MapDrawControl &control, IWritableTextureSource *tsrc, IWritableToolDefManager *tooldef, - IWritableNodeDefManager *nodedef + IWritableNodeDefManager *nodedef, + IWritableCraftItemDefManager *craftitemdef ): m_tsrc(tsrc), m_tooldef(tooldef), m_nodedef(nodedef), + m_craftitemdef(craftitemdef), m_mesh_update_thread(this), m_env( new ClientMap(this, this, control, @@ -207,6 +210,7 @@ Client::Client( m_con(PROTOCOL_ID, 512, CONNECTION_TIMEOUT, this), m_device(device), m_server_ser_ver(SER_FMT_VER_INVALID), + m_playeritem(0), m_inventory_updated(false), m_time_of_day(0), m_map_seed(0), @@ -215,7 +219,8 @@ Client::Client( m_texture_receive_progress(0), m_textures_received(false), m_tooldef_received(false), - m_nodedef_received(false) + m_nodedef_received(false), + m_craftitemdef_received(false) { m_packetcounter_timer = 0.0; //m_delete_unused_sectors_timer = 0.0; @@ -1628,6 +1633,26 @@ void Client::ProcessData(u8 *data, u32 datasize, u16 sender_peer_id) m_mesh_update_thread.setRun(true); m_mesh_update_thread.Start(); } + else if(command == TOCLIENT_CRAFTITEMDEF) + { + infostream<<"Client: Received CraftItem definitions: packet size: " + <deSerialize(tmp_is); + + // Resume threads + m_mesh_update_thread.setRun(true); + m_mesh_update_thread.Start(); + } else { infostream<<"Client: Ignoring unknown command " @@ -1641,93 +1666,41 @@ void Client::Send(u16 channelnum, SharedBuffer data, bool reliable) m_con.Send(PEER_ID_SERVER, channelnum, data, reliable); } -void Client::groundAction(u8 action, v3s16 nodepos_undersurface, - v3s16 nodepos_oversurface, u16 item) +void Client::interact(u8 action, const PointedThing& pointed) { if(connectedAndInitialized() == false){ - infostream<<"Client::groundAction() " + infostream<<"Client::interact() " "cancelled (not connected)" < data(datasize); - writeU16(&data[0], TOSERVER_GROUND_ACTION); - writeU8(&data[2], action); - writeV3S16(&data[3], nodepos_undersurface); - writeV3S16(&data[9], nodepos_oversurface); - writeU16(&data[15], item); - Send(0, data, true); -} + writeU16(os, TOSERVER_INTERACT); + writeU8(os, action); + writeU16(os, getPlayerItem()); + std::ostringstream tmp_os(std::ios::binary); + pointed.serialize(tmp_os); + os< data((u8*)s.c_str(), s.size()); - Player *player = m_env.getLocalPlayer(); - if(player == NULL) - return; - - ClientActiveObject *obj = m_env.getActiveObject(id); - if(obj){ - if(button == 0){ - ToolItem *titem = NULL; - std::string toolname = ""; - - InventoryList *mlist = player->inventory.getList("main"); - if(mlist != NULL) - { - InventoryItem *item = mlist->getItem(item_i); - if(item && (std::string)item->getName() == "ToolItem") - { - titem = (ToolItem*)item; - toolname = titem->getToolName(); - } - } - - v3f playerpos = player->getPosition(); - v3f objpos = obj->getPosition(); - v3f dir = (objpos - playerpos).normalize(); - - bool disable_send = obj->directReportPunch(toolname, dir); - - if(disable_send) - return; - } - } - - /* - length: 7 - [0] u16 command - [2] u8 button (0=left, 1=right) - [3] u16 id - [5] u16 item - */ - u8 datasize = 2 + 1 + 6 + 2 + 2; - SharedBuffer data(datasize); - writeU16(&data[0], TOSERVER_CLICK_ACTIVEOBJECT); - writeU8(&data[2], button); - writeU16(&data[3], id); - writeU16(&data[5], item_i); + // Send as reliable Send(0, data, true); } @@ -2036,9 +2009,12 @@ void Client::setPlayerControl(PlayerControl &control) void Client::selectPlayerItem(u16 item) { + //JMutexAutoLock envlock(m_env_mutex); //bulk comment-out + m_playeritem = item; + m_inventory_updated = true; + LocalPlayer *player = m_env.getLocalPlayer(); assert(player != NULL); - player->wieldItem(item); sendPlayerItem(item); @@ -2322,6 +2298,10 @@ ICraftDefManager* Client::getCraftDefManager() return NULL; //return m_craftdef; } +ICraftItemDefManager* Client::getCraftItemDefManager() +{ + return m_craftitemdef; +} ITextureSource* Client::getTextureSource() { return m_tsrc; diff --git a/src/client.h b/src/client.h index e5368b17a..2f212dad8 100644 --- a/src/client.h +++ b/src/client.h @@ -37,6 +37,7 @@ class IWritableTextureSource; class IWritableToolDefManager; class IWritableNodeDefManager; //class IWritableCraftDefManager; +class IWritableCraftItemDefManager; class ClientNotReadyException : public BaseException { @@ -165,7 +166,8 @@ public: MapDrawControl &control, IWritableTextureSource *tsrc, IWritableToolDefManager *tooldef, - IWritableNodeDefManager *nodedef + IWritableNodeDefManager *nodedef, + IWritableCraftItemDefManager *craftitemdef ); ~Client(); @@ -203,9 +205,7 @@ public: // Pops out a packet from the packet queue //IncomingPacket getPacket(); - void groundAction(u8 action, v3s16 nodepos_undersurface, - v3s16 nodepos_oversurface, u16 item); - void clickActiveObject(u8 button, u16 id, u16 item_i); + void interact(u8 action, const PointedThing& pointed); void sendSignNodeText(v3s16 p, std::string text); void sendInventoryAction(InventoryAction *a); @@ -234,6 +234,8 @@ public: void setPlayerControl(PlayerControl &control); void selectPlayerItem(u16 item); + u16 getPlayerItem() const + { return m_playeritem; } // Returns true if the inventory of the local player has been // updated from the server. If it is true, it is set to false. @@ -321,6 +323,8 @@ public: { return m_tooldef_received; } bool nodedefReceived() { return m_nodedef_received; } + bool craftitemdefReceived() + { return m_craftitemdef_received; } float getRTT(void); @@ -328,6 +332,7 @@ public: virtual IToolDefManager* getToolDefManager(); virtual INodeDefManager* getNodeDefManager(); virtual ICraftDefManager* getCraftDefManager(); + virtual ICraftItemDefManager* getCraftItemDefManager(); virtual ITextureSource* getTextureSource(); virtual u16 allocateUnknownNodeId(const std::string &name); @@ -356,13 +361,14 @@ private: IWritableTextureSource *m_tsrc; IWritableToolDefManager *m_tooldef; IWritableNodeDefManager *m_nodedef; + IWritableCraftItemDefManager *m_craftitemdef; MeshUpdateThread m_mesh_update_thread; ClientEnvironment m_env; con::Connection m_con; IrrlichtDevice *m_device; // Server serialization version u8 m_server_ser_ver; - // This is behind m_env_mutex. + u16 m_playeritem; bool m_inventory_updated; core::map m_active_blocks; PacketCounter m_packetcounter; @@ -383,6 +389,7 @@ private: bool m_textures_received; bool m_tooldef_received; bool m_nodedef_received; + bool m_craftitemdef_received; friend class FarMesh; }; diff --git a/src/clientserver.h b/src/clientserver.h index 148f99cc3..ff9fc31f9 100644 --- a/src/clientserver.h +++ b/src/clientserver.h @@ -31,6 +31,10 @@ with this program; if not, write to the Free Software Foundation, Inc., Add TOCLIENT_TEXTURES Add TOCLIENT_TOOLDEF Add TOCLIENT_NODEDEF + Add TOCLIENT_CRAFTITEMDEF + Add TOSERVER_INTERACT + Obsolete TOSERVER_CLICK_ACTIVEOBJECT + Obsolete TOSERVER_GROUND_ACTION */ #define PROTOCOL_VERSION 4 @@ -222,6 +226,13 @@ enum ToClientCommand u32 length of the next item serialized NodeDefManager */ + + TOCLIENT_CRAFTITEMDEF = 0x3b, + /* + u16 command + u32 length of the next item + serialized CraftiItemDefManager + */ }; enum ToServerCommand @@ -283,7 +294,7 @@ enum ToServerCommand [8] u16 i */ - TOSERVER_CLICK_OBJECT = 0x27, + TOSERVER_CLICK_OBJECT = 0x27, // Obsolete /* length: 13 [0] u16 command @@ -293,7 +304,7 @@ enum ToServerCommand [11] u16 item */ - TOSERVER_GROUND_ACTION = 0x28, + TOSERVER_GROUND_ACTION = 0x28, // Obsolete /* length: 17 [0] u16 command @@ -312,7 +323,7 @@ enum ToServerCommand // (oops, there is some gap here) - TOSERVER_SIGNTEXT = 0x30, // Old signs + TOSERVER_SIGNTEXT = 0x30, // Old signs, obsolete /* u16 command v3s16 blockpos @@ -341,7 +352,7 @@ enum ToServerCommand textdata */ - TOSERVER_CLICK_ACTIVEOBJECT = 0x34, + TOSERVER_CLICK_ACTIVEOBJECT = 0x34, // Obsolete /* length: 7 [0] u16 command @@ -377,6 +388,24 @@ enum ToServerCommand /* u16 TOSERVER_RESPAWN */ + + TOSERVER_INTERACT = 0x39, + /* + [0] u16 command + [2] u8 action + [3] u16 item + [5] u32 length of the next item + [9] serialized PointedThing + actions: + 0: start digging (from undersurface) or use + 1: stop digging (all parameters ignored) + 2: digging completed + 3: place block or item (to abovesurface) + 4: use item + + (Obsoletes TOSERVER_GROUND_ACTION and TOSERVER_CLICK_ACTIVEOBJECT.) + */ + }; inline SharedBuffer makePacket_TOCLIENT_TIME_OF_DAY(u16 time) diff --git a/src/content_sao.cpp b/src/content_sao.cpp index b013069aa..414d63f2d 100644 --- a/src/content_sao.cpp +++ b/src/content_sao.cpp @@ -251,24 +251,6 @@ void ItemSAO::punch(ServerActiveObject *puncher) delete item; } -void ItemSAO::rightClick(ServerActiveObject *clicker) -{ - infostream<<__FUNCTION_NAME<use(m_env, clicker); - - if(to_be_deleted) - m_removed = true; - else - // Reflect changes to the item here - m_inventorystring = item->getItemString(); - - delete item; // Delete temporary item -} - /* RatSAO */ diff --git a/src/content_sao.h b/src/content_sao.h index c5e1471bc..428998799 100644 --- a/src/content_sao.h +++ b/src/content_sao.h @@ -51,7 +51,6 @@ public: std::string getStaticData(); InventoryItem* createInventoryItem(); void punch(ServerActiveObject *puncher); - void rightClick(ServerActiveObject *clicker); float getMinimumSavedMovement(){ return 0.1*BS; } private: std::string m_inventorystring; diff --git a/src/game.cpp b/src/game.cpp index 5bbd92d55..abeceae0b 100644 --- a/src/game.cpp +++ b/src/game.cpp @@ -40,6 +40,7 @@ with this program; if not, write to the Free Software Foundation, Inc., #include "settings.h" #include "profiler.h" #include "mainmenumanager.h" +#include "craftitemdef.h" #include "gettext.h" #include "log.h" #include "filesys.h" @@ -87,8 +88,6 @@ Queue inventory_action_queue; // This is a copy of the inventory that the client's environment has Inventory local_inventory; -u16 g_selected_item = 0; - /* Text input system */ @@ -159,7 +158,7 @@ private: void draw_hotbar(video::IVideoDriver *driver, gui::IGUIFont *font, ITextureSource *tsrc, v2s32 centerlowerpos, s32 imgsize, s32 itemcount, - Inventory *inventory, s32 halfheartcount) + Inventory *inventory, s32 halfheartcount, u16 playeritem) { InventoryList *mainlist = inventory->getList("main"); if(mainlist == NULL) @@ -190,7 +189,7 @@ void draw_hotbar(video::IVideoDriver *driver, gui::IGUIFont *font, core::rect rect = imgrect + pos + v2s32(padding+i*(imgsize+padding*2), padding); - if(g_selected_item == i) + if(playeritem == i) { video::SColor c_outside(255,255,0,0); //video::SColor c_outside(255,0,0,0); @@ -288,16 +287,65 @@ void draw_hotbar(video::IVideoDriver *driver, gui::IGUIFont *font, } } +/* + Check if a node is pointable +*/ +inline bool isPointableNode(const MapNode& n, + Client *client, bool liquids_pointable) +{ + const ContentFeatures &features = client->getNodeDefManager()->get(n); + return features.pointable || + (liquids_pointable && features.isLiquid()); +} + /* Find what the player is pointing at */ -void getPointedNode(Client *client, v3f player_position, +PointedThing getPointedThing(Client *client, v3f player_position, v3f camera_direction, v3f camera_position, - bool &nodefound, core::line3d shootline, - v3s16 &nodepos, v3s16 &neighbourpos, - core::aabbox3d &nodehilightbox, - f32 d) + core::line3d shootline, f32 d, + bool liquids_pointable, + bool look_for_object, + core::aabbox3d &hilightbox, + bool &should_show_hilightbox, + ClientActiveObject *&selected_object) { + PointedThing result; + + hilightbox = core::aabbox3d(0,0,0,0,0,0); + should_show_hilightbox = false; + selected_object = NULL; + + // First try to find a pointed at active object + if(look_for_object) + { + selected_object = client->getSelectedActiveObject(d*BS, + camera_position, shootline); + } + if(selected_object != NULL) + { + core::aabbox3d *selection_box + = selected_object->getSelectionBox(); + // Box should exist because object was returned in the + // first place + assert(selection_box); + + v3f pos = selected_object->getPosition(); + + hilightbox = core::aabbox3d( + selection_box->MinEdge + pos, + selection_box->MaxEdge + pos + ); + + should_show_hilightbox = selected_object->doShowSelectionBox(); + + result.type = POINTEDTHING_OBJECT; + result.object_id = selected_object->getId(); + return result; + } + + // That didn't work, try to find a pointed at node + f32 mindistance = BS * 1001; v3s16 pos_i = floatToInt(player_position, BS); @@ -321,13 +369,13 @@ void getPointedNode(Client *client, v3f player_position, try { n = client->getNode(v3s16(x,y,z)); - if(client->getNodeDefManager()->get(n).pointable == false) - continue; } catch(InvalidPositionException &e) { continue; } + if(!isPointableNode(n, client, liquids_pointable)) + continue; v3s16 np(x,y,z); v3f npf = intToFloat(np, BS); @@ -402,11 +450,12 @@ void getPointedNode(Client *client, v3f player_position, continue; if(!faceboxes[i].intersectsWithLine(shootline)) continue; - nodefound = true; - nodepos = np; - neighbourpos = np+facedirs[i]; + result.type = POINTEDTHING_NODE; + result.node_undersurface = np; + result.node_abovesurface = np+facedirs[i]; mindistance = distance; - nodehilightbox = box; + hilightbox = box; + should_show_hilightbox = true; } } else if(f.selection_box.type == NODEBOX_WALLMOUNTED) @@ -458,11 +507,12 @@ void getPointedNode(Client *client, v3f player_position, { if(box.intersectsWithLine(shootline)) { - nodefound = true; - nodepos = np; - neighbourpos = np; + result.type = POINTEDTHING_NODE; + result.node_undersurface = np; + result.node_abovesurface = np; mindistance = distance; - nodehilightbox = box; + hilightbox = box; + should_show_hilightbox = true; } } } @@ -498,25 +548,28 @@ void getPointedNode(Client *client, v3f player_position, if(facebox.intersectsWithLine(shootline)) { - nodefound = true; - nodepos = np; - neighbourpos = np + dirs[i]; + result.type = POINTEDTHING_NODE; + result.node_undersurface = np; + result.node_abovesurface = np + dirs[i]; mindistance = distance; - //nodehilightbox = facebox; + //hilightbox = facebox; const float d = 0.502; core::aabbox3d nodebox (-BS*d, -BS*d, -BS*d, BS*d, BS*d, BS*d); - v3f nodepos_f = intToFloat(nodepos, BS); + v3f nodepos_f = intToFloat(np, BS); nodebox.MinEdge += nodepos_f; nodebox.MaxEdge += nodepos_f; - nodehilightbox = nodebox; + hilightbox = nodebox; + should_show_hilightbox = true; } } // if distance < mindistance } // for dirs } // regular block } // for coords + + return result; } void update_skybox(video::IVideoDriver* driver, ITextureSource *tsrc, @@ -642,6 +695,8 @@ void the_game( IWritableToolDefManager *tooldef = createToolDefManager(); // Create node definition manager IWritableNodeDefManager *nodedef = createNodeDefManager(); + // Create CraftItem definition manager + IWritableCraftItemDefManager *craftitemdef = createCraftItemDefManager(); // Add chat log output for errors to be shown in chat LogOutputBuffer chat_log_error_buf(LMT_ERROR); @@ -670,7 +725,7 @@ void the_game( MapDrawControl draw_control; Client client(device, playername.c_str(), password, draw_control, - tsrc, tooldef, nodedef); + tsrc, tooldef, nodedef, craftitemdef); // Client acts as our GameDef IGameDef *gamedef = &client; @@ -781,7 +836,8 @@ void the_game( // End condition if(client.texturesReceived() && client.tooldefReceived() && - client.nodedefReceived()){ + client.nodedefReceived() && + client.craftitemdefReceived()){ got_content = true; break; } @@ -801,6 +857,8 @@ void the_game( ss< frametime_log; - float nodig_delay_counter = 0.0; + float nodig_delay_timer = 0.0; float dig_time = 0.0; u16 dig_index = 0; - v3s16 nodepos_old(-32768,-32768,-32768); + PointedThing pointed_old; + bool digging = false; bool ldown_for_dig = false; float damage_flash_timer = 0; @@ -1083,7 +1142,10 @@ void the_game( /* Run timers */ - object_hit_delay_timer -= dtime; + if(nodig_delay_timer >= 0) + nodig_delay_timer -= dtime; + if(object_hit_delay_timer >= 0) + object_hit_delay_timer -= dtime; g_profiler->add("Elapsed time", dtime); g_profiler->avg("FPS", 1./dtime); @@ -1228,7 +1290,7 @@ void the_game( a->count = 0; a->from_inv = "current_player"; a->from_list = "main"; - a->from_i = g_selected_item; + a->from_i = client.getPlayerItem(); client.inventoryAction(a); } else if(input->wasKeyDown(getKeySetting("keymap_inventory"))) @@ -1369,6 +1431,7 @@ void the_game( } // Item selection with mouse wheel + u16 new_playeritem = client.getPlayerItem(); { s32 wheel = input->getMouseWheel(); u16 max_item = MYMIN(PLAYER_INVENTORY_SIZE-1, @@ -1376,17 +1439,17 @@ void the_game( if(wheel < 0) { - if(g_selected_item < max_item) - g_selected_item++; + if(new_playeritem < max_item) + new_playeritem++; else - g_selected_item = 0; + new_playeritem = 0; } else if(wheel > 0) { - if(g_selected_item > 0) - g_selected_item--; + if(new_playeritem > 0) + new_playeritem--; else - g_selected_item = max_item; + new_playeritem = max_item; } } @@ -1398,10 +1461,10 @@ void the_game( { if(i < PLAYER_INVENTORY_SIZE && i < hotbar_itemcount) { - g_selected_item = i; + new_playeritem = i; infostream<<"Selected item: " - <getItem(client.getPlayerItem()); + if(item) + { + if((std::string)item->getName() == "ToolItem") + { + ToolItem *titem = (ToolItem*)item; + playeritem_toolname = titem->getToolName(); + } + + playeritem_usable = item->isUsable(); + + playeritem_liquids_pointable = + item->areLiquidsPointable(); + } + } + } + /* Calculate what block is the crosshair pointing to */ //u32 t1 = device->getTimer()->getRealTime(); - //f32 d = 4; // max. distance f32 d = 4; // max. distance core::line3d shootline(camera_position, camera_position + camera_direction * BS * (d+1)); - ClientActiveObject *selected_active_object - = client.getSelectedActiveObject - (d*BS, camera_position, shootline); - + core::aabbox3d hilightbox; + bool should_show_hilightbox = false; + ClientActiveObject *selected_object = NULL; + + PointedThing pointed = getPointedThing( + // input + &client, player_position, camera_direction, + camera_position, shootline, d, + playeritem_liquids_pointable, !ldown_for_dig, + // output + hilightbox, should_show_hilightbox, + selected_object); + + if(pointed != pointed_old) + { + infostream<<"Pointing at "<getLeftReleased()) + { + infostream<<"Left button released" + <<" (stopped digging)"<getLeftState()) + { + ldown_for_dig = false; + } + bool left_punch = false; bool left_punch_muted = false; - if(selected_active_object != NULL && !ldown_for_dig) + if(playeritem_usable && input->getLeftState()) { - /* Clear possible cracking animation */ - if(nodepos_old != v3s16(-32768,-32768,-32768)) - { - client.clearTempMod(nodepos_old); - dig_time = 0.0; - nodepos_old = v3s16(-32768,-32768,-32768); - } - - //infostream<<"Client returned selected_active_object != NULL"< *selection_box - = selected_active_object->getSelectionBox(); - // Box should exist because object was returned in the - // first place - assert(selection_box); - - v3f pos = selected_active_object->getPosition(); - - core::aabbox3d box_on_map( - selection_box->MinEdge + pos, - selection_box->MaxEdge + pos - ); - - if(selected_active_object->doShowSelectionBox()) - hilightboxes.push_back(box_on_map); - - //infotext = narrow_to_wide("A ClientActiveObject"); - infotext = narrow_to_wide(selected_active_object->infoText()); - - //if(input->getLeftClicked()) - if(input->getLeftState()) - { - bool do_punch = false; - bool do_punch_damage = false; - if(object_hit_delay_timer <= 0.0){ - do_punch = true; - do_punch_damage = true; - object_hit_delay_timer = object_hit_delay; - } - if(input->getLeftClicked()){ - do_punch = true; - } - if(do_punch){ - infostream<<"Left-clicked object"<getId(), g_selected_item); - } - } - else if(input->getRightClicked()) - { - infostream<<"Right-clicked object"<getId(), g_selected_item); - } + if(input->getLeftClicked()) + client.interact(4, pointed); } - else // selected_object == NULL + else if(pointed.type == POINTEDTHING_NODE) { - - /* - Find out which node we are pointing at - */ - - bool nodefound = false; - v3s16 nodepos; - v3s16 neighbourpos; - core::aabbox3d nodehilightbox; - - getPointedNode(&client, player_position, - camera_direction, camera_position, - nodefound, shootline, - nodepos, neighbourpos, - nodehilightbox, d); - - if(!nodefound){ - if(nodepos_old != v3s16(-32768,-32768,-32768)) - { - client.clearTempMod(nodepos_old); - dig_time = 0.0; - nodepos_old = v3s16(-32768,-32768,-32768); - ldown_for_dig = false; - } - } else { - /* - Visualize selection - */ - - hilightboxes.push_back(nodehilightbox); + v3s16 nodepos = pointed.node_undersurface; + v3s16 neighbourpos = pointed.node_abovesurface; /* Check information text of node @@ -1744,139 +1815,94 @@ void the_game( infotext = narrow_to_wide(meta->infoText()); } - //MapNode node = client.getNode(nodepos); - /* Handle digging */ - if(input->getLeftReleased()) - { - client.clearTempMod(nodepos); - dig_time = 0.0; - ldown_for_dig = false; - } - if(nodig_delay_counter > 0.0) + if(nodig_delay_timer <= 0.0 && input->getLeftState()) { - nodig_delay_counter -= dtime; - } - else - { - if(nodepos != nodepos_old) - { - infostream<<"Pointing at ("<getLeftClicked() || - (input->getLeftState() && nodepos != nodepos_old)) + if(!digging) { infostream<<"Started digging"<getLeftClicked()) - { - client.setTempMod(nodepos, NodeMod(NODEMOD_CRACK, 0)); + client.interact(0, pointed); + digging = true; ldown_for_dig = true; } - if(input->getLeftState()) + MapNode n = client.getNode(nodepos); + + // Get digging properties for material and tool + content_t material = n.getContent(); + ToolDiggingProperties tp = + tooldef->getDiggingProperties(playeritem_toolname); + DiggingProperties prop = + getDiggingProperties(material, &tp, nodedef); + + float dig_time_complete = 0.0; + + if(prop.diggable == false) { - MapNode n = client.getNode(nodepos); - - // Get tool name. Default is "" = bare hands - std::string toolname = ""; - InventoryList *mlist = local_inventory.getList("main"); - if(mlist != NULL) - { - InventoryItem *item = mlist->getItem(g_selected_item); - if(item && (std::string)item->getName() == "ToolItem") - { - ToolItem *titem = (ToolItem*)item; - toolname = titem->getToolName(); - } - } - - // Get digging properties for material and tool - content_t material = n.getContent(); - ToolDiggingProperties tp = - tooldef->getDiggingProperties(toolname); - DiggingProperties prop = - getDiggingProperties(material, &tp, nodedef); - - float dig_time_complete = 0.0; - - if(prop.diggable == false) - { - /*infostream<<"Material "<<(int)material - <<" not diggable with \"" - <= 0.001) - { - dig_index = (u16)((float)CRACK_ANIMATION_LENGTH - * dig_time/dig_time_complete); - } - // This is for torches - else - { - dig_index = CRACK_ANIMATION_LENGTH; - } - - if(dig_index < CRACK_ANIMATION_LENGTH) - { - //TimeTaker timer("client.setTempMod"); - //infostream<<"dig_index="< 0.5) - { - nodig_delay_counter = 0.5; - } - // We want a slight delay to very little - // time consuming nodes - float mindelay = 0.15; - if(nodig_delay_counter < mindelay) - { - nodig_delay_counter = mindelay; - } - } - - dig_time += dtime; - - camera.setDigging(0); // left click animation + /*infostream<<"Material "<<(int)material + <<" not diggable with \"" + <= 0.001) + { + dig_index = (u16)((float)CRACK_ANIMATION_LENGTH + * dig_time/dig_time_complete); + } + // This is for torches + else + { + dig_index = CRACK_ANIMATION_LENGTH; + } + + if(dig_index < CRACK_ANIMATION_LENGTH) + { + //TimeTaker timer("client.setTempMod"); + //infostream<<"dig_index="< 0.5) + { + nodig_delay_timer = 0.5; + } + // We want a slight delay to very little + // time consuming nodes + float mindelay = 0.15; + if(nodig_delay_timer < mindelay) + { + nodig_delay_timer = mindelay; + } + } + + dig_time += dtime; + + camera.setDigging(0); // left click animation } - - + if(input->getRightClicked()) { infostream<<"Ground right-clicked"<infoText()); + + //if(input->getLeftClicked()) + if(input->getLeftState()) + { + bool do_punch = false; + bool do_punch_damage = false; + if(object_hit_delay_timer <= 0.0){ + do_punch = true; + do_punch_damage = true; + object_hit_delay_timer = object_hit_delay; + } + if(input->getLeftClicked()){ + do_punch = true; + } + if(do_punch){ + infostream<<"Left-clicked object"<getPosition(); + v3f dir = (objpos - player_position).normalize(); + + bool disable_send = selected_object->directReportPunch(playeritem_toolname, dir); + if(!disable_send) + client.interact(0, pointed); + } + } + else if(input->getRightClicked()) + { + infostream<<"Right-clicked object"<getLeftClicked() && !left_punch_muted)) { @@ -1950,20 +2011,7 @@ void the_game( input->resetLeftClicked(); input->resetRightClicked(); - - if(input->getLeftReleased()) - { - infostream<<"Left button released (stopped digging)" - <getRightReleased()) - { - //inostream<resetLeftReleased(); input->resetRightReleased(); @@ -2210,12 +2258,12 @@ void the_game( Inventory */ - static u16 old_selected_item = 65535; - if(client.getLocalInventoryUpdated() - || g_selected_item != old_selected_item) + if(client.getPlayerItem() != new_playeritem) + { + client.selectPlayerItem(new_playeritem); + } + if(client.getLocalInventoryUpdated()) { - client.selectPlayerItem(g_selected_item); - old_selected_item = g_selected_item; //infostream<<"Updating local inventory"<getItem(g_selected_item); + item = mlist->getItem(client.getPlayerItem()); camera.wield(item, gamedef); } @@ -2350,7 +2398,7 @@ void the_game( draw_hotbar(driver, font, tsrc, v2s32(displaycenter.X, screensize.Y), hotbar_imagesize, hotbar_itemcount, &local_inventory, - client.getHP()); + client.getHP(), client.getPlayerItem()); } /* diff --git a/src/gamedef.h b/src/gamedef.h index bca27a21a..c450568b7 100644 --- a/src/gamedef.h +++ b/src/gamedef.h @@ -25,7 +25,7 @@ with this program; if not, write to the Free Software Foundation, Inc., class IToolDefManager; class INodeDefManager; class ICraftDefManager; -//class IItemDefManager; //TODO +class ICraftItemDefManager; // Mineral too? class ITextureSource; @@ -42,7 +42,7 @@ public: virtual IToolDefManager* getToolDefManager()=0; virtual INodeDefManager* getNodeDefManager()=0; virtual ICraftDefManager* getCraftDefManager()=0; - //virtual IItemDefManager* getItemDefManager()=0; + virtual ICraftItemDefManager* getCraftItemDefManager()=0; // This is always thread-safe, but referencing the irrlicht texture // pointers in other threads than main thread will make things explode. @@ -55,6 +55,7 @@ public: IToolDefManager* tdef(){return getToolDefManager();} INodeDefManager* ndef(){return getNodeDefManager();} ICraftDefManager* cdef(){return getCraftDefManager();} + ICraftItemDefManager* cidef(){return getCraftItemDefManager();} ITextureSource* tsrc(){return getTextureSource();} }; diff --git a/src/inventory.cpp b/src/inventory.cpp index 4e897d9ff..d276e61c9 100644 --- a/src/inventory.cpp +++ b/src/inventory.cpp @@ -33,7 +33,9 @@ with this program; if not, write to the Free Software Foundation, Inc., #include "log.h" #include "nodedef.h" #include "tooldef.h" +#include "craftitemdef.h" #include "gamedef.h" +#include "scriptapi.h" #include "strfnd.h" /* @@ -182,18 +184,52 @@ std::string InventoryItem::getItemString() { return os.str(); } -ServerActiveObject* InventoryItem::createSAO(ServerEnvironment *env, v3f pos) +bool InventoryItem::dropOrPlace(ServerEnvironment *env, + ServerActiveObject *dropper, + v3f pos, bool place, s16 count) { /* - Create an ItemSAO + Ensure that the block is loaded so that the item + can properly be added to the static list too */ - pos.Y -= BS*0.25; // let it drop a bit - // Randomize a bit - pos.X += BS*0.2*(float)myrand_range(-1000,1000)/1000.0; - pos.Z += BS*0.2*(float)myrand_range(-1000,1000)/1000.0; - // Create object - ServerActiveObject *obj = new ItemSAO(env, pos, getItemString()); - return obj; + v3s16 blockpos = getNodeBlockPos(floatToInt(pos, BS)); + MapBlock *block = env->getMap().emergeBlock(blockpos, false); + if(block==NULL) + { + infostream<<"InventoryItem::dropOrPlace(): FAIL: block not found: " + < dropcount) + count = dropcount; + if(count < 0 || count > getCount()); + count = getCount(); + if(count > 0) + { + /* + Create an ItemSAO + */ + pos.Y -= BS*0.25; // let it drop a bit + // Randomize a bit + //pos.X += BS*0.2*(float)myrand_range(-1000,1000)/1000.0; + //pos.Z += BS*0.2*(float)myrand_range(-1000,1000)/1000.0; + // Create object + ServerActiveObject *obj = new ItemSAO(env, pos, getItemString()); + // Add the object to the environment + env->addActiveObject(obj); + infostream<<"Dropped item"<cidef(); ITextureSource *tsrc = m_gamedef->tsrc(); - - std::string name = item_craft_get_image_name(m_subname, m_gamedef); - - // Get such a texture - return tsrc->getTextureRaw(name); + std::string imagename = cidef->getImagename(m_subname); + return tsrc->getTextureRaw(imagename); } #endif -ServerActiveObject* CraftItem::createSAO(ServerEnvironment *env, v3f pos) +u16 CraftItem::getStackMax() const { - // Special cases - ServerActiveObject *obj = item_craft_create_object(m_subname, env, pos); - if(obj) - return obj; - // Default - return InventoryItem::createSAO(env, pos); + ICraftItemDefManager *cidef = m_gamedef->cidef(); + const CraftItemDefinition *def = cidef->getCraftItemDefinition(m_subname); + if(def == NULL) + return InventoryItem::getStackMax(); + return def->stack_max; } -u16 CraftItem::getDropCount() const +bool CraftItem::isUsable() const { - // Special cases - s16 dc = item_craft_get_drop_count(m_subname, m_gamedef); - if(dc != -1) - return dc; - // Default - return InventoryItem::getDropCount(); + ICraftItemDefManager *cidef = m_gamedef->cidef(); + const CraftItemDefinition *def = cidef->getCraftItemDefinition(m_subname); + return def != NULL && def->usable; } bool CraftItem::isCookable() const { - return item_craft_is_cookable(m_subname, m_gamedef); + ICraftItemDefManager *cidef = m_gamedef->cidef(); + const CraftItemDefinition *def = cidef->getCraftItemDefinition(m_subname); + return def != NULL && def->cookresult_item != ""; } InventoryItem *CraftItem::createCookResult() const { - return item_craft_create_cook_result(m_subname, m_gamedef); + ICraftItemDefManager *cidef = m_gamedef->cidef(); + const CraftItemDefinition *def = cidef->getCraftItemDefinition(m_subname); + if(def == NULL) + return InventoryItem::createCookResult(); + std::istringstream is(def->cookresult_item, std::ios::binary); + return InventoryItem::deSerialize(is, m_gamedef); } float CraftItem::getCookTime() const { - return 3.0; + ICraftItemDefManager *cidef = m_gamedef->cidef(); + const CraftItemDefinition *def = cidef->getCraftItemDefinition(m_subname); + if (def == NULL) + return InventoryItem::getCookTime(); + return def->furnace_cooktime; } float CraftItem::getBurnTime() const { - if(m_subname == "lump_of_coal") - return 40; - return -1; + ICraftItemDefManager *cidef = m_gamedef->cidef(); + const CraftItemDefinition *def = cidef->getCraftItemDefinition(m_subname); + if (def == NULL) + return InventoryItem::getBurnTime(); + return def->furnace_burntime; } -bool CraftItem::use(ServerEnvironment *env, ServerActiveObject *user) +s16 CraftItem::getDropCount() const { - if(!item_craft_is_eatable(m_subname, m_gamedef)) + // Special cases + ICraftItemDefManager *cidef = m_gamedef->cidef(); + const CraftItemDefinition *def = cidef->getCraftItemDefinition(m_subname); + if(def != NULL && def->dropcount >= 0) + return def->dropcount; + // Default + return InventoryItem::getDropCount(); +} + +bool CraftItem::areLiquidsPointable() const +{ + ICraftItemDefManager *cidef = m_gamedef->cidef(); + const CraftItemDefinition *def = cidef->getCraftItemDefinition(m_subname); + return def != NULL && def->liquids_pointable; +} + +bool CraftItem::dropOrPlace(ServerEnvironment *env, + ServerActiveObject *dropper, + v3f pos, bool place, s16 count) +{ + if(count == 0) return false; - - u16 result_count = getCount() - 1; // Eat one at a time - s16 hp_change = item_craft_eat_hp_change(m_subname, m_gamedef); - s16 hp = user->getHP(); - hp += hp_change; - if(hp < 0) - hp = 0; - user->setHP(hp); - - if(result_count < 1) - return true; - - setCount(result_count); - return false; + + bool callback_exists = false; + bool result = false; + + if(place) + { + result = scriptapi_craftitem_on_place_on_ground( + env->getLua(), + m_subname.c_str(), dropper, pos, + callback_exists); + } + + // note: on_drop is fallback for on_place_on_ground + + if(!callback_exists) + { + result = scriptapi_craftitem_on_drop( + env->getLua(), + m_subname.c_str(), dropper, pos, + callback_exists); + } + + if(callback_exists) + { + // If the callback returned true, drop one item + if(result) + setCount(getCount() - 1); + return getCount() < 1; + } + else + { + // If neither on_place_on_ground (if place==true) + // nor on_drop exists, call the base implementation + return InventoryItem::dropOrPlace(env, dropper, pos, place, count); + } +} + +bool CraftItem::use(ServerEnvironment *env, + ServerActiveObject *user, + const PointedThing& pointed) +{ + bool callback_exists = false; + bool result = false; + + result = scriptapi_craftitem_on_use( + env->getLua(), + m_subname.c_str(), user, pointed, + callback_exists); + + if(callback_exists) + { + // If the callback returned true, drop one item + if(result) + setCount(getCount() - 1); + return getCount() < 1; + } + else + { + // If neither on_place_on_ground (if place==true) + // nor on_drop exists, call the base implementation + return InventoryItem::use(env, user, pointed); + } } /* @@ -1067,6 +1178,19 @@ IDropAction::IDropAction(std::istream &is) void IDropAction::apply(InventoryContext *c, InventoryManager *mgr, ServerEnvironment *env) { + if(c->current_player == NULL){ + infostream<<"IDropAction::apply(): FAIL: current_player is NULL"<( + static_cast( + c->current_player + )); + Inventory *inv_from = mgr->getInventory(c, from_inv); if(!inv_from){ @@ -1086,7 +1210,8 @@ void IDropAction::apply(InventoryContext *c, InventoryManager *mgr, <<", from_list=\""<getItem(from_i) == NULL) + InventoryItem *item = list_from->getItem(from_i); + if(item == NULL) { infostream<<"IDropAction::apply(): FAIL: source item not found: " <<"context=["<current_player->getPosition(); + v3f pos = dropper->getBasePosition(); pos.Y += 0.5*BS; - v3s16 blockpos = getNodeBlockPos(floatToInt(pos, BS)); + + s16 count2 = count; + if(count2 == 0) + count2 = -1; /* - Ensure that the block is loaded so that the item - can properly be added to the static list too + Drop the item */ - MapBlock *block = env->getMap().emergeBlock(blockpos, false); - if(block==NULL) - { - infostream<<"IDropAction::apply(): FAIL: block not found: " - <getItem(from_i)->getDropCount(); - InventoryItem *item1 = list_from->takeItem(from_i, count); - - // Create an active object - ServerActiveObject *obj = item1->createSAO(env, pos); - if(obj == NULL) - { - infostream<<"IDropAction::apply(): item resulted in NULL object, " - <<"not placing onto map" - <addActiveObject(obj); - - infostream<<"Dropped object"<dropOrPlace(env, dropper, pos, false, count2); + if(remove) + list_from->deleteItem(from_i); mgr->inventoryModified(c, from_inv); diff --git a/src/inventory.h b/src/inventory.h index 6253ea50e..cee81a21e 100644 --- a/src/inventory.h +++ b/src/inventory.h @@ -31,6 +31,7 @@ with this program; if not, write to the Free Software Foundation, Inc., class ServerActiveObject; class ServerEnvironment; +class PointedThing; class ITextureSource; class IGameDef; @@ -63,10 +64,6 @@ public: virtual std::string getText() { return ""; } // Returns the string used for inventory virtual std::string getItemString(); - // Creates an object from the item, to be placed in the world. - virtual ServerActiveObject* createSAO(ServerEnvironment *env, v3f pos); - // Gets amount of items that dropping one SAO will decrement - virtual u16 getDropCount() const { return getCount(); } /* Quantity methods @@ -88,13 +85,17 @@ public: void setCount(u16 count) { m_count = count; } - // This should return something else for stackable items - virtual u16 freeSpace() const - { return 0; } + u16 freeSpace() const + { + u16 max = getStackMax(); + if(m_count > max) + return 0; + return max - m_count; + } void add(u16 count) { - assert(m_count + count <= QUANTITY_ITEM_MAX_COUNT); + assert(m_count + count <= getStackMax()); m_count += count; } void remove(u16 count) @@ -107,6 +108,10 @@ public: Other properties */ + // Maximum size of a stack + virtual u16 getStackMax() const {return 1;} + // Whether it can be used + virtual bool isUsable() const {return false;} // Whether it can be cooked virtual bool isCookable() const {return false;} // Result of cooking (can randomize) @@ -115,12 +120,24 @@ public: virtual float getCookTime() const {return 3.0;} // Whether it can be burned (<0 = cannot be burned) virtual float getBurnTime() const {return -1;} - + // Gets amount of items that dropping one ItemSAO will decrement + // -1 means as many as possible + virtual s16 getDropCount() const { return -1; } + // Whether this item can point to liquids + virtual bool areLiquidsPointable() const { return false; } + + // Creates an object from the item and places it in the world. + // If return value is true, item should be removed. + virtual bool dropOrPlace(ServerEnvironment *env, + ServerActiveObject *dropper, + v3f pos, bool place, s16 count); + // Eat, press, activate, whatever. - // Called when item is right-clicked when lying on ground. + // Called when item is left-clicked while in hand. // If returns true, item shall be deleted. virtual bool use(ServerEnvironment *env, - ServerActiveObject *user){return false;} + ServerActiveObject *user, + const PointedThing& pointed){return false;} protected: IGameDef *m_gamedef; @@ -189,12 +206,11 @@ public: return true; } - u16 freeSpace() const + u16 getStackMax() const { - if(m_count > QUANTITY_ITEM_MAX_COUNT) - return 0; - return QUANTITY_ITEM_MAX_COUNT - m_count; + return QUANTITY_ITEM_MAX_COUNT; } + /* Other properties */ @@ -254,9 +270,6 @@ public: return os.str(); } - ServerActiveObject* createSAO(ServerEnvironment *env, v3f pos); - u16 getDropCount() const; - virtual bool addableTo(const InventoryItem *other) const { if(std::string(other->getName()) != "CraftItem") @@ -284,24 +297,26 @@ public: return true; } - u16 freeSpace() const - { - if(m_count > QUANTITY_ITEM_MAX_COUNT) - return 0; - return QUANTITY_ITEM_MAX_COUNT - m_count; - } - /* Other properties */ + u16 getStackMax() const; + bool isUsable() const; bool isCookable() const; InventoryItem *createCookResult() const; float getCookTime() const; float getBurnTime() const; + s16 getDropCount() const; + bool areLiquidsPointable() const; + + bool dropOrPlace(ServerEnvironment *env, + ServerActiveObject *dropper, + v3f pos, bool place, s16 count); + bool use(ServerEnvironment *env, + ServerActiveObject *user, + const PointedThing& pointed); - bool use(ServerEnvironment *env, ServerActiveObject *user); - /* Special methods */ diff --git a/src/player.cpp b/src/player.cpp index ce2e26945..353f44d8a 100644 --- a/src/player.cpp +++ b/src/player.cpp @@ -183,7 +183,8 @@ ServerRemotePlayer::ServerRemotePlayer(ServerEnvironment *env): Player(env->getGameDef()), ServerActiveObject(env, v3f(0,0,0)), m_last_good_position(0,0,0), - m_last_good_position_age(0) + m_last_good_position_age(0), + m_additional_items() { } ServerRemotePlayer::ServerRemotePlayer(ServerEnvironment *env, v3f pos_, u16 peer_id_, @@ -195,6 +196,10 @@ ServerRemotePlayer::ServerRemotePlayer(ServerEnvironment *env, v3f pos_, u16 pee peer_id = peer_id_; updateName(name_); } +ServerRemotePlayer::~ServerRemotePlayer() +{ + clearAddToInventoryLater(); +} /* ServerActiveObject interface */ @@ -247,9 +252,59 @@ bool ServerRemotePlayer::addToInventory(InventoryItem *item) return true; } +void ServerRemotePlayer::addToInventoryLater(InventoryItem *item) +{ + infostream<<"Adding (later) "<getName()<<" into "<::iterator + i = m_additional_items.begin(); + i != m_additional_items.end(); i++) + { + delete *i; + } + m_additional_items.clear(); +} +void ServerRemotePlayer::completeAddToInventoryLater(u16 preferred_index) +{ + InventoryList *ilist = inventory.getList("main"); + if(ilist == NULL) + { + clearAddToInventoryLater(); + return; + } + + // In creative mode, just delete the items + if(g_settings->getBool("creative_mode")) + { + clearAddToInventoryLater(); + return; + } + + for (std::vector::iterator + i = m_additional_items.begin(); + i != m_additional_items.end(); i++) + { + InventoryItem *item = *i; + InventoryItem *leftover = item; + leftover = ilist->addItem(preferred_index, leftover); + leftover = ilist->addItem(leftover); + delete leftover; + } + m_additional_items.clear(); +} void ServerRemotePlayer::setHP(s16 hp_) { hp = hp_; + + // FIXME: don't hardcode maximum HP, make configurable per object + if(hp < 0) + hp = 0; + else if(hp > 20) + hp = 20; } s16 ServerRemotePlayer::getHP() { diff --git a/src/player.h b/src/player.h index 9476a9be3..0f92ccc87 100644 --- a/src/player.h +++ b/src/player.h @@ -188,8 +188,7 @@ public: ServerRemotePlayer(ServerEnvironment *env, v3f pos_, u16 peer_id_, const char *name_); - virtual ~ServerRemotePlayer() - {} + virtual ~ServerRemotePlayer(); virtual bool isLocal() const { return false; } @@ -230,11 +229,15 @@ public: virtual void damageWieldedItem(u16 amount); // If all fits, eats item and returns true. Otherwise returns false. virtual bool addToInventory(InventoryItem *item); + virtual void addToInventoryLater(InventoryItem *item); + void clearAddToInventoryLater(); + void completeAddToInventoryLater(u16 preferred_index); virtual void setHP(s16 hp_); virtual s16 getHP(); v3f m_last_good_position; float m_last_good_position_age; + std::vector m_additional_items; private: }; diff --git a/src/scriptapi.cpp b/src/scriptapi.cpp index 3918bdfc3..83efef670 100644 --- a/src/scriptapi.cpp +++ b/src/scriptapi.cpp @@ -39,11 +39,13 @@ extern "C" { #include "tooldef.h" #include "nodedef.h" #include "craftdef.h" +#include "craftitemdef.h" #include "main.h" // For g_settings #include "settings.h" // For accessing g_settings #include "nodemetadata.h" #include "mapblock.h" // For getNodeBlockPos #include "content_nodemeta.h" +#include "utility.h" static void stackDump(lua_State *L, std::ostream &o) { @@ -615,6 +617,55 @@ static int l_register_tool(lua_State *L) return 0; /* number of results */ } +// register_craftitem(name, {lots of stuff}) +static int l_register_craftitem(lua_State *L) +{ + const char *name = luaL_checkstring(L, 1); + infostream<<"register_craftitem: "<getWritableCraftItemDefManager(); + + // Check if on_drop is defined + lua_getfield(L, table, "on_drop"); + bool got_on_drop = !lua_isnil(L, -1); + lua_pop(L, 1); + + // Check if on_use is defined + lua_getfield(L, table, "on_use"); + bool got_on_use = !lua_isnil(L, -1); + lua_pop(L, 1); + + CraftItemDefinition def; + + getstringfield(L, table, "image", def.imagename); + getstringfield(L, table, "cookresult_item", def.cookresult_item); + getfloatfield(L, table, "furnace_cooktime", def.furnace_cooktime); + getfloatfield(L, table, "furnace_burntime", def.furnace_burntime); + def.usable = getboolfield_default(L, table, "usable", got_on_use); + getboolfield(L, table, "liquids_pointable", def.liquids_pointable); + def.dropcount = getintfield_default(L, table, "dropcount", def.dropcount); + def.stack_max = getintfield_default(L, table, "stack_max", def.stack_max); + + // If an on_drop callback is defined, force dropcount to 1 + if (got_on_drop) + def.dropcount = 1; + + // Register it + craftitemdef->registerCraftItem(name, def); + + lua_pushvalue(L, table); + scriptapi_add_craftitem(L, name); + + return 0; /* number of results */ +} + // register_node(name, {lots of stuff}) static int l_register_node(lua_State *L) { @@ -976,6 +1027,7 @@ static const struct luaL_Reg minetest_f [] = { {"register_nodedef_defaults", l_register_nodedef_defaults}, {"register_entity", l_register_entity}, {"register_tool", l_register_tool}, + {"register_craftitem", l_register_craftitem}, {"register_node", l_register_node}, {"register_craft", l_register_craft}, {"register_abm", l_register_abm}, @@ -1541,6 +1593,56 @@ private: return 0; } + // EnvRef:add_item(pos, inventorystring) + // pos = {x=num, y=num, z=num} + static int l_add_item(lua_State *L) + { + infostream<<"EnvRef::l_add_item()"<m_env; + if(env == NULL) return 0; + // pos + v3f pos = readFloatPos(L, 2); + // inventorystring + const char *inventorystring = lua_tostring(L, 3); + // Do it + ServerActiveObject *obj = new ItemSAO(env, pos, inventorystring); + env->addActiveObject(obj); + return 0; + } + + // EnvRef:add_rat(pos) + // pos = {x=num, y=num, z=num} + static int l_add_rat(lua_State *L) + { + infostream<<"EnvRef::l_add_rat()"<m_env; + if(env == NULL) return 0; + // pos + v3f pos = readFloatPos(L, 2); + // Do it + ServerActiveObject *obj = new RatSAO(env, pos); + env->addActiveObject(obj); + return 0; + } + + // EnvRef:add_firefly(pos) + // pos = {x=num, y=num, z=num} + static int l_add_firefly(lua_State *L) + { + infostream<<"EnvRef::l_add_firefly()"<m_env; + if(env == NULL) return 0; + // pos + v3f pos = readFloatPos(L, 2); + // Do it + ServerActiveObject *obj = new FireflySAO(env, pos); + env->addActiveObject(obj); + return 0; + } + // EnvRef:get_meta(pos) static int l_get_meta(lua_State *L) { @@ -1623,6 +1725,9 @@ const luaL_reg EnvRef::methods[] = { method(EnvRef, remove_node), method(EnvRef, get_node), method(EnvRef, add_luaentity), + method(EnvRef, add_item), + method(EnvRef, add_rat), + method(EnvRef, add_firefly), method(EnvRef, get_meta), {0,0} }; @@ -1663,6 +1768,16 @@ private: return (LuaEntitySAO*)obj; } + static ServerRemotePlayer* getplayer(ObjectRef *ref) + { + ServerActiveObject *obj = getobject(ref); + if(obj == NULL) + return NULL; + if(obj->getType() != ACTIVEOBJECT_TYPE_PLAYER) + return NULL; + return static_cast(obj); + } + // Exported functions // garbage collector @@ -1795,6 +1910,64 @@ private: return 1; } + // add_to_inventory_later(self, itemstring) + // returns: nil + static int l_add_to_inventory_later(lua_State *L) + { + ObjectRef *ref = checkobject(L, 1); + luaL_checkstring(L, 2); + ServerActiveObject *co = getobject(ref); + if(co == NULL) return 0; + // itemstring + const char *itemstring = lua_tostring(L, 2); + infostream<<"ObjectRef::l_add_to_inventory_later(): id="<getId() + <<" itemstring=\""<getEnv(); + assert(env); + IGameDef *gamedef = env->getGameDef(); + InventoryItem *item = InventoryItem::deSerialize(is, gamedef); + infostream<<"item="<addToInventoryLater(item); + // Return + return 0; + } + + // get_hp(self) + // returns: number of hitpoints (2 * number of hearts) + // 0 if not applicable to this type of object + static int l_get_hp(lua_State *L) + { + ObjectRef *ref = checkobject(L, 1); + ServerActiveObject *co = getobject(ref); + if(co == NULL) return 0; + int hp = co->getHP(); + infostream<<"ObjectRef::l_get_hp(): id="<getId() + <<" hp="< 0); + + // Set minetest.registered_craftitems[name] = table on top of stack + lua_getglobal(L, "minetest"); + lua_getfield(L, -1, "registered_craftitems"); + luaL_checktype(L, -1, LUA_TTABLE); + lua_pushvalue(L, -3); // push another reference to the table to be registered + lua_setfield(L, -2, name); // set minetest.registered_craftitems[name] +} + +static bool get_craftitem_callback(lua_State *L, const char *name, + const char *callbackname) +{ + // Get minetest.registered_craftitems[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 + + lua_getglobal(L, "minetest"); + lua_getfield(L, -1, "registered_craftitems"); + 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) + { + errorstream<<"CraftItem name \""<getValue(); ServerRemotePlayer *player = - (ServerRemotePlayer*)m_env->getPlayer(client->peer_id); + static_cast + (m_env->getPlayer(client->peer_id)); if(player==NULL) continue; player->m_last_good_position_age += dtime; @@ -2240,6 +2245,9 @@ void Server::ProcessData(u8 *data, u32 datasize, u16 peer_id) // Send node definitions SendNodeDef(m_con, peer_id, m_nodedef); + // Send CraftItem definitions + SendCraftItemDef(m_con, peer_id, m_craftitemdef); + // Send textures SendTextures(peer_id); @@ -2420,745 +2428,23 @@ void Server::ProcessData(u8 *data, u32 datasize, u16 peer_id) } else if(command == TOSERVER_CLICK_ACTIVEOBJECT) { - if(datasize < 7) - return; - - if((getPlayerPrivs(player) & PRIV_BUILD) == 0) - return; - - /* - length: 7 - [0] u16 command - [2] u8 button (0=left, 1=right) - [3] u16 id - [5] u16 item - */ - u8 button = readU8(&data[2]); - u16 id = readS16(&data[3]); - u16 item_i = readU16(&data[5]); - - ServerActiveObject *obj = m_env->getActiveObject(id); - - if(obj == NULL) - { - infostream<<"Server: CLICK_ACTIVEOBJECT: object not found" - <m_removed) - return; - - //TODO: Check that object is reasonably close - - // Get ServerRemotePlayer - ServerRemotePlayer *srp = (ServerRemotePlayer*)player; - - // Update wielded item - srp->wieldItem(item_i); - - // Left click, pick/punch - if(button == 0) - { - actionstream<getName()<<" punches object " - <getId()<punch(srp); - -#if 0 - /* - Try creating inventory item - */ - InventoryItem *item = obj->createPickedUpItem(); - - if(item) - { - InventoryList *ilist = player->inventory.getList("main"); - if(ilist != NULL) - { - actionstream<getName()<<" picked up " - <getName()<getBool("creative_mode") == false) - { - // Skip if inventory has no free space - if(ilist->roomForItem(item) == false) - { - infostream<<"Player inventory has no free space"<addItem(item); - UpdateCrafting(player->peer_id); - SendInventory(player->peer_id); - } - - // Remove object from environment - obj->m_removed = true; - } - } - else - { - /* - Item cannot be picked up. Punch it instead. - */ - - actionstream<getName()<<" punches object " - <getId()<inventory.getList("main"); - if(mlist != NULL) - { - InventoryItem *item = mlist->getItem(item_i); - if(item && (std::string)item->getName() == "ToolItem") - { - titem = (ToolItem*)item; - toolname = titem->getToolName(); - } - } - - v3f playerpos = player->getPosition(); - v3f objpos = obj->getBasePosition(); - v3f dir = (objpos - playerpos).normalize(); - - u16 wear = obj->punch(toolname, dir, player->getName()); - - if(titem) - { - bool weared_out = titem->addWear(wear); - if(weared_out) - mlist->deleteItem(item_i); - SendInventory(player->peer_id); - } - } -#endif - } - // Right click, do something with object - if(button == 1) - { - actionstream<getName()<<" right clicks object " - <getId()<rightClick(srp); - } - - /* - Update player state to client - */ - SendPlayerHP(player); - UpdateCrafting(player->peer_id); - SendInventory(player->peer_id); + infostream<<"Server: CLICK_ACTIVEOBJECT not supported anymore"<m_last_good_position.getDistanceFrom(np_f); - if(d > max_d){ - actionstream<<"Player "<getName() - <<" tried to access node from too far: " - <<"d="<SetBlockNotSent(blockpos); - // Do nothing else - return; - } - } - - /* - 0: start digging - */ - if(action == 0) - { - /* - NOTE: This can be used in the future to check if - somebody is cheating, by checking the timing. - */ - bool cannot_punch_node = false; - - MapNode n(CONTENT_IGNORE); - - try - { - n = m_env->getMap().getNode(p_under); - } - catch(InvalidPositionException &e) - { - infostream<<"Server: Not punching: Node not found." - <<" Adding block to emerge queue." - <m_dig_mutex); - client->m_dig_tool_item = -1; -#endif - } - - /* - 3: Digging completed - */ - else if(action == 3) - { - // Mandatory parameter; actually used for nothing - core::map modified_blocks; - - content_t material = CONTENT_IGNORE; - u8 mineral = MINERAL_NONE; - - bool cannot_remove_node = false; - - MapNode n(CONTENT_IGNORE); - try - { - n = m_env->getMap().getNode(p_under); - // Get mineral - mineral = n.getMineral(m_nodedef); - // Get material at position - material = n.getContent(); - // If not yet cancelled - if(cannot_remove_node == false) - { - // If it's not diggable, do nothing - if(m_nodedef->get(material).diggable == false) - { - infostream<<"Server: Not finishing digging: " - <<"Node not diggable" - <getMap().getNodeMetadata(p_under); - if(meta && meta->nodeRemovalDisabled() == true) - { - infostream<<"Server: Not finishing digging: " - <<"Node metadata disables removal" - <getName()<<" cannot remove node" - <<" because privileges are "<SetBlockNotSent(blockpos); - - return; - } - - actionstream<getName()<<" digs "< far_players; - sendRemoveNode(p_under, peer_id, &far_players, 30); - - /* - Update and send inventory - */ - - if(g_settings->getBool("creative_mode") == false) - { - /* - Wear out tool - */ - InventoryList *mlist = player->inventory.getList("main"); - if(mlist != NULL) - { - InventoryItem *item = mlist->getItem(item_i); - if(item && (std::string)item->getName() == "ToolItem") - { - ToolItem *titem = (ToolItem*)item; - std::string toolname = titem->getToolName(); - - // Get digging properties for material and tool - ToolDiggingProperties tp = - m_toolmgr->getDiggingProperties(toolname); - DiggingProperties prop = - getDiggingProperties(material, &tp, m_nodedef); - - if(prop.diggable == false) - { - infostream<<"Server: WARNING: Player digged" - <<" with impossible material + tool" - <<" combination"<addWear(prop.wear); - - if(weared_out) - { - mlist->deleteItem(item_i); - } - } - } - - /* - Add dug item to inventory - */ - - InventoryItem *item = NULL; - - if(mineral != MINERAL_NONE) - item = getDiggedMineralItem(mineral, this); - - // If not mineral - if(item == NULL) - { - const std::string &dug_s = m_nodedef->get(material).dug_item; - if(dug_s != "") - { - std::istringstream is(dug_s, std::ios::binary); - item = InventoryItem::deSerialize(is, this); - } - } - - if(item != NULL) - { - // Add a item to inventory - player->inventory.addItem("main", item); - - // Send inventory - UpdateCrafting(player->peer_id); - SendInventory(player->peer_id); - } - - item = NULL; - - if(mineral != MINERAL_NONE) - item = getDiggedMineralItem(mineral, this); - - // If not mineral - if(item == NULL) - { - const std::string &extra_dug_s = m_nodedef->get(material).extra_dug_item; - s32 extra_rarity = m_nodedef->get(material).extra_dug_item_rarity; - if(extra_dug_s != "" && extra_rarity != 0 - && myrand() % extra_rarity == 0) - { - std::istringstream is(extra_dug_s, std::ios::binary); - item = InventoryItem::deSerialize(is, this); - } - } - - if(item != NULL) - { - // Add a item to inventory - player->inventory.addItem("main", item); - - // Send inventory - UpdateCrafting(player->peer_id); - SendInventory(player->peer_id); - } - } - - /* - Remove the node - (this takes some time so it is done after the quick stuff) - */ - { - MapEditEventIgnorer ign(&m_ignore_map_edit_events); - - m_env->getMap().removeNodeAndUpdate(p_under, modified_blocks); - } - /* - Set blocks not sent to far players - */ - for(core::list::Iterator - i = far_players.begin(); - i != far_players.end(); i++) - { - u16 peer_id = *i; - RemoteClient *client = getClient(peer_id); - if(client==NULL) - continue; - client->SetBlocksNotSent(modified_blocks); - } - - /* - Run script hook - */ - scriptapi_environment_on_dignode(m_lua, p_under, n, srp); - } - - /* - 1: place block - */ - else if(action == 1) - { - - InventoryList *ilist = player->inventory.getList("main"); - if(ilist == NULL) - return; - - // Get item - InventoryItem *item = ilist->getItem(item_i); - - // If there is no item, it is not possible to add it anywhere - if(item == NULL) - return; - - /* - Handle material items - */ - if(std::string("MaterialItem") == item->getName()) - { - try{ - // Don't add a node if this is not a free space - MapNode n2 = m_env->getMap().getNode(p_over); - bool no_enough_privs = - ((getPlayerPrivs(player) & PRIV_BUILD)==0); - if(no_enough_privs) - infostream<<"Player "<getName()<<" cannot add node" - <<" because privileges are "<get(n2).buildable_to == false - || no_enough_privs) - { - // Client probably has wrong data. - // Set block not sent, so that client will get - // a valid one. - infostream<<"Client "<SetBlockNotSent(blockpos); - return; - } - } - catch(InvalidPositionException &e) - { - infostream<<"Server: Ignoring ADDNODE: Node not found" - <<" Adding block to emerge queue." - <m_time_from_building = 0.0; - - // Create node data - MaterialItem *mitem = (MaterialItem*)item; - MapNode n; - n.setContent(mitem->getMaterial()); - - actionstream<getName()<<" places material " - <<(int)mitem->getMaterial() - <<" at "<get(n).wall_mounted) - n.param2 = packDir(p_under - p_over); - - // Calculate the direction for furnaces and chests and stuff - if(m_nodedef->get(n).param_type == CPT_FACEDIR_SIMPLE) - { - v3f playerpos = player->getPosition(); - v3f blockpos = intToFloat(p_over, BS) - playerpos; - blockpos = blockpos.normalize(); - n.param1 = 0; - if (fabs(blockpos.X) > fabs(blockpos.Z)) { - if (blockpos.X < 0) - n.param1 = 3; - else - n.param1 = 1; - } else { - if (blockpos.Z < 0) - n.param1 = 2; - else - n.param1 = 0; - } - } - - /* - Send to all close-by players - */ - core::list far_players; - sendAddNode(p_over, n, 0, &far_players, 30); - - /* - Handle inventory - */ - InventoryList *ilist = player->inventory.getList("main"); - if(g_settings->getBool("creative_mode") == false && ilist) - { - // Remove from inventory and send inventory - if(mitem->getCount() == 1) - ilist->deleteItem(item_i); - else - mitem->remove(1); - // Send inventory - UpdateCrafting(peer_id); - SendInventory(peer_id); - } - - /* - Add node. - - This takes some time so it is done after the quick stuff - */ - core::map modified_blocks; - { - MapEditEventIgnorer ign(&m_ignore_map_edit_events); - - std::string p_name = std::string(player->getName()); - m_env->getMap().addNodeAndUpdate(p_over, n, modified_blocks, p_name); - } - /* - Set blocks not sent to far players - */ - for(core::list::Iterator - i = far_players.begin(); - i != far_players.end(); i++) - { - u16 peer_id = *i; - RemoteClient *client = getClient(peer_id); - if(client==NULL) - continue; - client->SetBlocksNotSent(modified_blocks); - } - - /* - Run script hook - */ - scriptapi_environment_on_placenode(m_lua, p_over, n, srp); - - /* - Calculate special events - */ - - /*if(n.d == LEGN(m_nodedef, "CONTENT_MESE")) - { - u32 count = 0; - for(s16 z=-1; z<=1; z++) - for(s16 y=-1; y<=1; y++) - for(s16 x=-1; x<=1; x++) - { - - } - }*/ - } - /* - Place other item (not a block) - */ - else - { - v3s16 blockpos = getNodeBlockPos(p_over); - - /* - Check that the block is loaded so that the item - can properly be added to the static list too - */ - MapBlock *block = m_env->getMap().getBlockNoCreateNoEx(blockpos); - if(block==NULL) - { - infostream<<"Error while placing object: " - "block not found"<getBool("creative_mode") && - (getPlayerPrivs(player) & PRIV_BUILD) == 0) - { - infostream<<"Not allowing player to drop item: " - "creative mode and no build privs"<createSAO(m_env, pos); - - if(obj == NULL) - { - infostream<<"WARNING: item resulted in NULL object, " - <<"not placing onto map" - <getName()<<" places "<getName() - <<" at "<addActiveObject(obj); - - infostream<<"Placed object"<getBool("creative_mode") == false) - { - // Delete the right amount of items from the slot - u16 dropcount = item->getDropCount(); - - // Delete item if all gone - if(item->getCount() <= dropcount) - { - if(item->getCount() < dropcount) - infostream<<"WARNING: Server: dropped more items" - <<" than the slot contains"<inventory.getList("main"); - if(ilist) - // Remove from inventory and send inventory - ilist->deleteItem(item_i); - } - // Else decrement it - else - item->remove(dropcount); - - // Send inventory - UpdateCrafting(peer_id); - SendInventory(peer_id); - } - } - } - - } // action == 1 - - /* - Catch invalid actions - */ - else - { - infostream<<"WARNING: Server: Invalid action " - <apply(&c, this, m_env); } - else - { - // Send inventory - UpdateCrafting(player->peer_id); - SendInventory(player->peer_id); - } // Eat the action delete a; @@ -3605,6 +2885,674 @@ void Server::ProcessData(u8 *data, u32 datasize, u16 peer_id) actionstream<getName()<<" respawns at " <getPosition()/BS)<getBasePosition(); + } + + float d = player_pos.getDistanceFrom(pointed_pos); + float max_d = BS * 10; // Just some large enough value + if(d > max_d){ + actionstream<<"Player "<getName() + <<" tried to access "<SetBlockNotSent(blockpos); + // Do nothing else + return; + } + } + + /* + Make sure the player is allowed to do it + */ + bool build_priv = (getPlayerPrivs(player) & PRIV_BUILD) != 0; + if(!build_priv) + { + infostream<<"Ignoring interaction from player "<getName() + <<" because privileges are "<getMap().getNode(p_under); + } + catch(InvalidPositionException &e) + { + infostream<<"Server: Not punching: Node not found." + <<" Adding block to emerge queue." + <m_removed) + return; + + actionstream<getName()<<" punches object " + <punch(srp); + } + + } // action == 0 + + /* + 1: stop digging + */ + else if(action == 1) + { + } // action == 1 + + /* + 2: Digging completed + */ + else if(action == 2) + { + // Only complete digging of nodes + if(pointed.type != POINTEDTHING_NODE) + return; + + // Mandatory parameter; actually used for nothing + core::map modified_blocks; + + content_t material = CONTENT_IGNORE; + u8 mineral = MINERAL_NONE; + + bool cannot_remove_node = !build_priv; + + MapNode n(CONTENT_IGNORE); + try + { + n = m_env->getMap().getNode(p_under); + // Get mineral + mineral = n.getMineral(m_nodedef); + // Get material at position + material = n.getContent(); + // If not yet cancelled + if(cannot_remove_node == false) + { + // If it's not diggable, do nothing + if(m_nodedef->get(material).diggable == false) + { + infostream<<"Server: Not finishing digging: " + <<"Node not diggable" + <getMap().getNodeMetadata(p_under); + if(meta && meta->nodeRemovalDisabled() == true) + { + infostream<<"Server: Not finishing digging: " + <<"Node metadata disables removal" + <SetBlockNotSent(blockpos); + + return; + } + + actionstream<getName()<<" digs "< far_players; + sendRemoveNode(p_under, peer_id, &far_players, 30); + + /* + Update and send inventory + */ + + if(g_settings->getBool("creative_mode") == false) + { + /* + Wear out tool + */ + InventoryList *mlist = player->inventory.getList("main"); + if(mlist != NULL) + { + InventoryItem *item = mlist->getItem(item_i); + if(item && (std::string)item->getName() == "ToolItem") + { + ToolItem *titem = (ToolItem*)item; + std::string toolname = titem->getToolName(); + + // Get digging properties for material and tool + ToolDiggingProperties tp = + m_toolmgr->getDiggingProperties(toolname); + DiggingProperties prop = + getDiggingProperties(material, &tp, m_nodedef); + + if(prop.diggable == false) + { + infostream<<"Server: WARNING: Player digged" + <<" with impossible material + tool" + <<" combination"<addWear(prop.wear); + + if(weared_out) + { + mlist->deleteItem(item_i); + } + } + } + + /* + Add dug item to inventory + */ + + InventoryItem *item = NULL; + + if(mineral != MINERAL_NONE) + item = getDiggedMineralItem(mineral, this); + + // If not mineral + if(item == NULL) + { + const std::string &dug_s = m_nodedef->get(material).dug_item; + if(dug_s != "") + { + std::istringstream is(dug_s, std::ios::binary); + item = InventoryItem::deSerialize(is, this); + } + } + + if(item != NULL) + { + // Add a item to inventory + player->inventory.addItem("main", item); + } + + item = NULL; + + if(mineral != MINERAL_NONE) + item = getDiggedMineralItem(mineral, this); + + // If not mineral + if(item == NULL) + { + const std::string &extra_dug_s = m_nodedef->get(material).extra_dug_item; + s32 extra_rarity = m_nodedef->get(material).extra_dug_item_rarity; + if(extra_dug_s != "" && extra_rarity != 0 + && myrand() % extra_rarity == 0) + { + std::istringstream is(extra_dug_s, std::ios::binary); + item = InventoryItem::deSerialize(is, this); + } + } + + if(item != NULL) + { + // Add a item to inventory + player->inventory.addItem("main", item); + } + } + + /* + Remove the node + (this takes some time so it is done after the quick stuff) + */ + { + MapEditEventIgnorer ign(&m_ignore_map_edit_events); + + m_env->getMap().removeNodeAndUpdate(p_under, modified_blocks); + } + /* + Set blocks not sent to far players + */ + for(core::list::Iterator + i = far_players.begin(); + i != far_players.end(); i++) + { + u16 peer_id = *i; + RemoteClient *client = getClient(peer_id); + if(client==NULL) + continue; + client->SetBlocksNotSent(modified_blocks); + } + + /* + Run script hook + */ + scriptapi_environment_on_dignode(m_lua, p_under, n, srp); + } // action == 2 + + /* + 3: place block or right-click object + */ + else if(action == 3) + { + if(pointed.type == POINTEDTHING_NODE) + { + InventoryList *ilist = player->inventory.getList("main"); + if(ilist == NULL) + return; + + // Get item + InventoryItem *item = ilist->getItem(item_i); + + // If there is no item, it is not possible to add it anywhere + if(item == NULL) + return; + + /* + Handle material items + */ + if(std::string("MaterialItem") == item->getName()) + { + bool cannot_place_node = !build_priv; + + try{ + // Don't add a node if this is not a free space + MapNode n2 = m_env->getMap().getNode(p_above); + if(m_nodedef->get(n2).buildable_to == false) + { + infostream<<"Client "<SetBlockNotSent(blockpos); + return; + } + + // Reset build time counter + getClient(peer_id)->m_time_from_building = 0.0; + + // Create node data + MaterialItem *mitem = (MaterialItem*)item; + MapNode n; + n.setContent(mitem->getMaterial()); + + actionstream<getName()<<" places material " + <<(int)mitem->getMaterial() + <<" at "<get(n).wall_mounted) + n.param2 = packDir(p_under - p_above); + + // Calculate the direction for furnaces and chests and stuff + if(m_nodedef->get(n).param_type == CPT_FACEDIR_SIMPLE) + { + v3f playerpos = player->getPosition(); + v3f blockpos = intToFloat(p_above, BS) - playerpos; + blockpos = blockpos.normalize(); + n.param1 = 0; + if (fabs(blockpos.X) > fabs(blockpos.Z)) { + if (blockpos.X < 0) + n.param1 = 3; + else + n.param1 = 1; + } else { + if (blockpos.Z < 0) + n.param1 = 2; + else + n.param1 = 0; + } + } + + /* + Send to all close-by players + */ + core::list far_players; + sendAddNode(p_above, n, 0, &far_players, 30); + + /* + Handle inventory + */ + InventoryList *ilist = player->inventory.getList("main"); + if(g_settings->getBool("creative_mode") == false && ilist) + { + // Remove from inventory and send inventory + if(mitem->getCount() == 1) + ilist->deleteItem(item_i); + else + mitem->remove(1); + } + + /* + Add node. + + This takes some time so it is done after the quick stuff + */ + core::map modified_blocks; + { + MapEditEventIgnorer ign(&m_ignore_map_edit_events); + + std::string p_name = std::string(player->getName()); + m_env->getMap().addNodeAndUpdate(p_above, n, modified_blocks, p_name); + } + /* + Set blocks not sent to far players + */ + for(core::list::Iterator + i = far_players.begin(); + i != far_players.end(); i++) + { + u16 peer_id = *i; + RemoteClient *client = getClient(peer_id); + if(client==NULL) + continue; + client->SetBlocksNotSent(modified_blocks); + } + + /* + Run script hook + */ + scriptapi_environment_on_placenode(m_lua, p_above, n, srp); + + /* + Calculate special events + */ + + /*if(n.d == LEGN(m_nodedef, "CONTENT_MESE")) + { + u32 count = 0; + for(s16 z=-1; z<=1; z++) + for(s16 y=-1; y<=1; y++) + for(s16 x=-1; x<=1; x++) + { + + } + }*/ + } + /* + Place other item (not a block) + */ + else + { + if(!build_priv) + { + infostream<<"Not allowing player to place item: " + "no build privileges"<getBasePosition(); + + // Randomize a bit + pos.X += BS*0.2*(float)myrand_range(-1000,1000)/1000.0; + pos.Z += BS*0.2*(float)myrand_range(-1000,1000)/1000.0; + } + + //pos.Y -= BS*0.45; + //pos.Y -= BS*0.25; // let it drop a bit + + /* + Check that the block is loaded so that the item + can properly be added to the static list too + */ + v3s16 blockpos = getNodeBlockPos(floatToInt(pos, BS)); + MapBlock *block = m_env->getMap().getBlockNoCreateNoEx(blockpos); + if(block==NULL) + { + infostream<<"Error while placing item: " + "block not found"<getName()<<" places "<getName() + <<" at "<dropOrPlace(m_env, srp, pos, true, -1); + if(remove && g_settings->getBool("creative_mode") == false) + { + InventoryList *ilist = player->inventory.getList("main"); + if(ilist) + // Remove from inventory and send inventory + ilist->deleteItem(item_i); + } + } + } + else if(pointed.type == POINTEDTHING_OBJECT) + { + // Right click object + + if(!build_priv) + return; + + // Skip if object has been removed + if(pointed_object->m_removed) + return; + + actionstream<getName()<<" right-clicks object " + <rightClick(srp); + } + + } // action == 3 + + /* + 4: use + */ + else if(action == 4) + { + InventoryList *ilist = player->inventory.getList("main"); + if(ilist == NULL) + return; + + // Get item + InventoryItem *item = ilist->getItem(item_i); + + // If there is no item, it is not possible to add it anywhere + if(item == NULL) + return; + + // Requires build privs + if(!build_priv) + { + infostream<<"Not allowing player to use item: " + "no build privileges"<getName()<<" uses "<getName() + <<", pointing at "<use(m_env, srp, pointed); + if(remove && g_settings->getBool("creative_mode") == false) + { + InventoryList *ilist = player->inventory.getList("main"); + if(ilist) + // Remove from inventory and send inventory + ilist->deleteItem(item_i); + } + + } // action == 4 + + /* + Catch invalid actions + */ + else + { + infostream<<"WARNING: Server: Invalid action " + <completeAddToInventoryLater(item_i); + + // Send inventory + // FIXME: Shouldn't be done unless something changed. + UpdateCrafting(player->peer_id); + SendInventory(player->peer_id); + } else { infostream<<"Server::ProcessData(): Ignoring " @@ -3865,6 +3813,31 @@ void Server::SendNodeDef(con::Connection &con, u16 peer_id, con.Send(peer_id, 0, data, true); } +void Server::SendCraftItemDef(con::Connection &con, u16 peer_id, + ICraftItemDefManager *craftitemdef) +{ + DSTACK(__FUNCTION_NAME); + std::ostringstream os(std::ios_base::binary); + + /* + u16 command + u32 length of the next item + serialized CraftItemDefManager + */ + writeU16(os, TOCLIENT_CRAFTITEMDEF); + std::ostringstream tmp_os(std::ios::binary); + craftitemdef->serialize(tmp_os); + os<