diff options
-rw-r--r-- | data/mods/default/init.lua | 203 | ||||
-rw-r--r-- | src/CMakeLists.txt | 1 | ||||
-rw-r--r-- | src/client.cpp | 134 | ||||
-rw-r--r-- | src/client.h | 17 | ||||
-rw-r--r-- | src/clientserver.h | 37 | ||||
-rw-r--r-- | src/content_sao.cpp | 18 | ||||
-rw-r--r-- | src/content_sao.h | 1 | ||||
-rw-r--r-- | src/game.cpp | 564 | ||||
-rw-r--r-- | src/gamedef.h | 5 | ||||
-rw-r--r-- | src/inventory.cpp | 269 | ||||
-rw-r--r-- | src/inventory.h | 69 | ||||
-rw-r--r-- | src/player.cpp | 57 | ||||
-rw-r--r-- | src/player.h | 7 | ||||
-rw-r--r-- | src/scriptapi.cpp | 335 | ||||
-rw-r--r-- | src/scriptapi.h | 13 | ||||
-rw-r--r-- | src/server.cpp | 1457 | ||||
-rw-r--r-- | src/server.h | 8 | ||||
-rw-r--r-- | src/servercommand.cpp | 2 | ||||
-rw-r--r-- | src/serverobject.h | 2 | ||||
-rw-r--r-- | src/utility.cpp | 100 | ||||
-rw-r--r-- | src/utility.h | 73 |
21 files changed, 2142 insertions, 1230 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) @@ -1160,6 +1169,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 <IFileSystem.h> /* @@ -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: " + <<datasize<<std::endl; + + std::string datastring((char*)&data[2], datasize-2); + std::istringstream is(datastring, std::ios_base::binary); + + m_craftitemdef_received = true; + + // Stop threads while updating content definitions + m_mesh_update_thread.stop(); + + std::istringstream tmp_is(deSerializeLongString(is), std::ios::binary); + m_craftitemdef->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<u8> 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)" <<std::endl; return; } - + + std::ostringstream os(std::ios_base::binary); + /* - length: 17 [0] u16 command [2] u8 action - [3] v3s16 nodepos_undersurface - [9] v3s16 nodepos_abovesurface - [15] u16 item + [3] u16 item + [5] u32 length of the next item + [9] serialized PointedThing actions: - 0: start digging - 1: place block - 2: stop digging (all parameters ignored) - 3: digging completed + 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 */ - u8 datasize = 2 + 1 + 6 + 6 + 2; - SharedBuffer<u8> 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); -} - -void Client::clickActiveObject(u8 button, u16 id, u16 item_i) -{ - if(connectedAndInitialized() == false){ - infostream<<"Client::clickActiveObject() " - "cancelled (not connected)" - <<std::endl; - return; - } - - 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 = ""; + writeU16(os, TOSERVER_INTERACT); + writeU8(os, action); + writeU16(os, getPlayerItem()); + std::ostringstream tmp_os(std::ios::binary); + pointed.serialize(tmp_os); + os<<serializeLongString(tmp_os.str()); - 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(); - } - } + std::string s = os.str(); + SharedBuffer<u8> data((u8*)s.c_str(), s.size()); - 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<u8> 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<v3s16, bool> 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<u8> 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<<std::endl; - InventoryItem *item = createInventoryItem(); - if(item == NULL) - return; - - bool to_be_deleted = item->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<InventoryAction*> 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<s32> 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); @@ -289,15 +288,64 @@ 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<f32> shootline, - v3s16 &nodepos, v3s16 &neighbourpos, - core::aabbox3d<f32> &nodehilightbox, - f32 d) + core::line3d<f32> shootline, f32 d, + bool liquids_pointable, + bool look_for_object, + core::aabbox3d<f32> &hilightbox, + bool &should_show_hilightbox, + ClientActiveObject *&selected_object) { + PointedThing result; + + hilightbox = core::aabbox3d<f32>(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<f32> *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<f32>( + 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<f32> 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<<L" Tool definitions\n"; ss<<(client.nodedefReceived()?L"[X]":L"[ ]"); ss<<L" Node definitions\n"; + ss<<(client.craftitemdefReceived()?L"[X]":L"[ ]"); + ss<<L" Item definitions\n"; //ss<<(client.texturesReceived()?L"[X]":L"[ ]"); ss<<L"["<<(int)(client.textureReceiveProgress()*100+0.5)<<L"%] "; ss<<L" Textures\n"; @@ -937,10 +995,11 @@ void the_game( core::list<float> 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: " - <<g_selected_item<<std::endl; + <<new_playeritem<<std::endl; } } } @@ -1624,115 +1687,123 @@ void the_game( //TimeTaker //timer3("//timer3"); /* + For interaction purposes, get info about the held item + - Is it a tool, and what is the toolname? + - Is it a usable item? + - Can it point to liquids? + */ + std::string playeritem_toolname = ""; + bool playeritem_usable = false; + bool playeritem_liquids_pointable = false; + { + InventoryList *mlist = local_inventory.getList("main"); + if(mlist != NULL) + { + InventoryItem *item = mlist->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<f32> shootline(camera_position, camera_position + camera_direction * BS * (d+1)); - ClientActiveObject *selected_active_object - = client.getSelectedActiveObject - (d*BS, camera_position, shootline); - - bool left_punch = false; - bool left_punch_muted = false; - - if(selected_active_object != NULL && !ldown_for_dig) - { - /* 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"<<std::endl; - - core::aabbox3d<f32> *selection_box - = selected_active_object->getSelectionBox(); - // Box should exist because object was returned in the - // first place - assert(selection_box); + core::aabbox3d<f32> hilightbox; + bool should_show_hilightbox = false; + ClientActiveObject *selected_object = NULL; - v3f pos = selected_active_object->getPosition(); + 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); - core::aabbox3d<f32> box_on_map( - selection_box->MinEdge + pos, - selection_box->MaxEdge + pos - ); - - if(selected_active_object->doShowSelectionBox()) - hilightboxes.push_back(box_on_map); + if(pointed != pointed_old) + { + infostream<<"Pointing at "<<pointed.dump()<<std::endl; + //dstream<<"Pointing at "<<pointed.dump()<<std::endl; + } - //infotext = narrow_to_wide("A ClientActiveObject"); - infotext = narrow_to_wide(selected_active_object->infoText()); + /* + Visualize selection + */ + if(should_show_hilightbox) + hilightboxes.push_back(hilightbox); - //if(input->getLeftClicked()) - if(input->getLeftState()) + /* + Stop digging when + - releasing left mouse button + - pointing away from node + */ + if(digging) + { + if(input->getLeftReleased()) { - 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"<<std::endl; - left_punch = true; + infostream<<"Left button released" + <<" (stopped digging)"<<std::endl; + digging = false; + } + else if(pointed != pointed_old) + { + if (pointed.type == POINTEDTHING_NODE + && pointed_old.type == POINTEDTHING_NODE + && pointed.node_undersurface == pointed_old.node_undersurface) + { + // Still pointing to the same node, + // but a different face. Don't reset. } - if(do_punch_damage){ - client.clickActiveObject(0, - selected_active_object->getId(), g_selected_item); + else + { + infostream<<"Pointing away from node" + <<" (stopped digging)"<<std::endl; + digging = false; } } - else if(input->getRightClicked()) + if(!digging) { - infostream<<"Right-clicked object"<<std::endl; - client.clickActiveObject(1, - selected_active_object->getId(), g_selected_item); + client.interact(1, pointed_old); + client.clearTempMod(pointed_old.node_undersurface); + dig_time = 0.0; } } - else // selected_object == NULL + if(!digging && ldown_for_dig && !input->getLeftState()) { + ldown_for_dig = false; + } - /* - Find out which node we are pointing at - */ - - bool nodefound = false; - v3s16 nodepos; - v3s16 neighbourpos; - core::aabbox3d<f32> 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 - */ + bool left_punch = false; + bool left_punch_muted = false; - hilightboxes.push_back(nodehilightbox); + if(playeritem_usable && input->getLeftState()) + { + if(input->getLeftClicked()) + client.interact(4, pointed); + } + else if(pointed.type == POINTEDTHING_NODE) + { + 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) + if(!digging) { - infostream<<"Pointing at ("<<nodepos.X<<"," - <<nodepos.Y<<","<<nodepos.Z<<")"<<std::endl; + infostream<<"Started digging"<<std::endl; + client.interact(0, pointed); + digging = true; + ldown_for_dig = true; + } + MapNode n = client.getNode(nodepos); - if(nodepos_old != v3s16(-32768,-32768,-32768)) - { - client.clearTempMod(nodepos_old); - dig_time = 0.0; - nodepos_old = v3s16(-32768,-32768,-32768); - } + // 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) + { + /*infostream<<"Material "<<(int)material + <<" not diggable with \"" + <<playeritem_toolname<<"\""<<std::endl;*/ + // I guess nobody will wait for this long + dig_time_complete = 10000000.0; + } + else + { + dig_time_complete = prop.time; } - if(input->getLeftClicked() || - (input->getLeftState() && nodepos != nodepos_old)) + if(dig_time_complete >= 0.001) { - infostream<<"Started digging"<<std::endl; - client.groundAction(0, nodepos, neighbourpos, g_selected_item); + dig_index = (u16)((float)CRACK_ANIMATION_LENGTH + * dig_time/dig_time_complete); } - if(input->getLeftClicked()) + // This is for torches + else { - client.setTempMod(nodepos, NodeMod(NODEMOD_CRACK, 0)); - ldown_for_dig = true; + dig_index = CRACK_ANIMATION_LENGTH; } - if(input->getLeftState()) + + if(dig_index < CRACK_ANIMATION_LENGTH) { - 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(); - } - } + //TimeTaker timer("client.setTempMod"); + //infostream<<"dig_index="<<dig_index<<std::endl; + client.setTempMod(nodepos, NodeMod(NODEMOD_CRACK, dig_index)); + } + else + { + infostream<<"Digging completed"<<std::endl; + client.interact(2, pointed); + client.clearTempMod(nodepos); + client.removeNode(nodepos); - // 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; + dig_time = 0; + digging = false; - if(prop.diggable == false) - { - /*infostream<<"Material "<<(int)material - <<" not diggable with \"" - <<toolname<<"\""<<std::endl;*/ - // I guess nobody will wait for this long - dig_time_complete = 10000000.0; - } - else - { - dig_time_complete = prop.time; - } - - if(dig_time_complete >= 0.001) - { - dig_index = (u16)((float)CRACK_ANIMATION_LENGTH - * dig_time/dig_time_complete); - } - // This is for torches - else - { - dig_index = CRACK_ANIMATION_LENGTH; - } + nodig_delay_timer = dig_time_complete + / (float)CRACK_ANIMATION_LENGTH; - if(dig_index < CRACK_ANIMATION_LENGTH) + // We don't want a corresponding delay to + // very time consuming nodes + if(nodig_delay_timer > 0.5) { - //TimeTaker timer("client.setTempMod"); - //infostream<<"dig_index="<<dig_index<<std::endl; - client.setTempMod(nodepos, NodeMod(NODEMOD_CRACK, dig_index)); + nodig_delay_timer = 0.5; } - else + // We want a slight delay to very little + // time consuming nodes + float mindelay = 0.15; + if(nodig_delay_timer < mindelay) { - infostream<<"Digging completed"<<std::endl; - client.groundAction(3, nodepos, neighbourpos, g_selected_item); - client.clearTempMod(nodepos); - client.removeNode(nodepos); - - dig_time = 0; - - nodig_delay_counter = dig_time_complete - / (float)CRACK_ANIMATION_LENGTH; - - // We don't want a corresponding delay to - // very time consuming nodes - if(nodig_delay_counter > 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; - } + nodig_delay_timer = mindelay; } + } - dig_time += dtime; + dig_time += dtime; - camera.setDigging(0); // left click animation - } + camera.setDigging(0); // left click animation } - - + if(input->getRightClicked()) { infostream<<"Ground right-clicked"<<std::endl; @@ -1933,15 +1959,50 @@ void the_game( // Otherwise report right click to server else { - client.groundAction(1, nodepos, neighbourpos, g_selected_item); + client.interact(3, pointed); camera.setDigging(1); // right click animation } } - - nodepos_old = nodepos; + } + else if(pointed.type == POINTEDTHING_OBJECT) + { + infotext = narrow_to_wide(selected_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"<<std::endl; + left_punch = true; + } + if(do_punch_damage){ + // Report direct punch + v3f objpos = selected_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"<<std::endl; + client.interact(3, pointed); // place + } } - } // selected_object == NULL + pointed_old = pointed; if(left_punch || (input->getLeftClicked() && !left_punch_muted)) { @@ -1950,20 +2011,7 @@ void the_game( input->resetLeftClicked(); input->resetRightClicked(); - - if(input->getLeftReleased()) - { - infostream<<"Left button released (stopped digging)" - <<std::endl; - client.groundAction(2, v3s16(0,0,0), v3s16(0,0,0), 0); - ldown_for_dig = false; - } - if(input->getRightReleased()) - { - //inostream<<DTIME<<"Right released"<<std::endl; - // Nothing here - } - + input->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"<<std::endl; client.getLocalInventory(local_inventory); @@ -2223,7 +2271,7 @@ void the_game( InventoryList *mlist = local_inventory.getList("main"); InventoryItem *item = NULL; if(mlist != NULL) - item = mlist->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 + */ + v3s16 blockpos = getNodeBlockPos(floatToInt(pos, BS)); + MapBlock *block = env->getMap().emergeBlock(blockpos, false); + if(block==NULL) + { + infostream<<"InventoryItem::dropOrPlace(): FAIL: block not found: " + <<blockpos.X<<","<<blockpos.Y<<","<<blockpos.Z + <<std::endl; + return false; + } + + /* + Take specified number of items, + but limit to getDropCount(). */ - 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; + s16 dropcount = getDropCount(); + if(count < 0 || count > 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"<<std::endl; + + setCount(getCount() - count); + } + + return getCount() < 1; // delete the item? } /* @@ -307,75 +343,150 @@ video::ITexture * ToolItem::getImageRaw() const #ifndef SERVER video::ITexture * CraftItem::getImage() const { + ICraftItemDefManager *cidef = m_gamedef->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; +} + +s16 CraftItem::getDropCount() const +{ + // 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::use(ServerEnvironment *env, ServerActiveObject *user) +bool CraftItem::dropOrPlace(ServerEnvironment *env, + ServerActiveObject *dropper, + v3f pos, bool place, s16 count) { - if(!item_craft_is_eatable(m_subname, m_gamedef)) + 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"<<std::endl; + return; + } + + // Do NOT cast directly to ServerActiveObject*, it breaks + // because of multiple inheritance. + ServerActiveObject *dropper = + static_cast<ServerActiveObject*>( + static_cast<ServerRemotePlayer*>( + 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=\""<<from_list<<"\""<<std::endl; return; } - if(list_from->getItem(from_i) == NULL) + InventoryItem *item = list_from->getItem(from_i); + if(item == NULL) { infostream<<"IDropAction::apply(): FAIL: source item not found: " <<"context=["<<describeC(c)<<"], from_inv=\""<<from_inv<<"\"" @@ -1095,43 +1220,19 @@ void IDropAction::apply(InventoryContext *c, InventoryManager *mgr, return; } - v3f pos = c->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: " - <<blockpos.X<<","<<blockpos.Y<<","<<blockpos.Z - <<std::endl; - return; - } - - // Take item from source list - if(count == 0) - count = list_from->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" - <<std::endl; - } - else - { - // Add the object to the environment - env->addActiveObject(obj); - - infostream<<"Dropped object"<<std::endl; - } + bool remove = item->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) "<<item->getName()<<" into "<<getName() + <<"'s inventory"<<std::endl; + m_additional_items.push_back(item); +} +void ServerRemotePlayer::clearAddToInventoryLater() +{ + for (std::vector<InventoryItem*>::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<InventoryItem*>::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<InventoryItem*> 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: "<<name<<std::endl; + luaL_checktype(L, 2, LUA_TTABLE); + int table = 2; + + // Get server from registry + lua_getfield(L, LUA_REGISTRYINDEX, "minetest_server"); + Server *server = (Server*)lua_touserdata(L, -1); + // And get the writable CraftItem definition manager from the server + IWritableCraftItemDefManager *craftitemdef = + server->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()"<<std::endl; + EnvRef *o = checkobject(L, 1); + ServerEnvironment *env = o->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()"<<std::endl; + EnvRef *o = checkobject(L, 1); + ServerEnvironment *env = o->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()"<<std::endl; + EnvRef *o = checkobject(L, 1); + ServerEnvironment *env = o->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<ServerRemotePlayer*>(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="<<co->getId() + <<" itemstring=\""<<itemstring<<"\""<<std::endl; + // Do it + std::istringstream is(itemstring, std::ios::binary); + ServerEnvironment *env = co->getEnv(); + assert(env); + IGameDef *gamedef = env->getGameDef(); + InventoryItem *item = InventoryItem::deSerialize(is, gamedef); + infostream<<"item="<<env<<std::endl; + co->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="<<co->getId() + <<" hp="<<hp<<std::endl; + // Return + lua_pushnumber(L, hp); + return 1; + } + + // set_hp(self, hp) + // hp = number of hitpoints (2 * number of hearts) + // returns: nil + static int l_set_hp(lua_State *L) + { + ObjectRef *ref = checkobject(L, 1); + luaL_checknumber(L, 2); + ServerActiveObject *co = getobject(ref); + if(co == NULL) return 0; + int hp = lua_tonumber(L, 2); + infostream<<"ObjectRef::l_set_hp(): id="<<co->getId() + <<" hp="<<hp<<std::endl; + // Do it + co->setHP(hp); + // Return + return 0; + } + // settexturemod(self, mod) static int l_settexturemod(lua_State *L) { @@ -1901,6 +2074,9 @@ const luaL_reg ObjectRef::methods[] = { method(ObjectRef, setvelocity), method(ObjectRef, setacceleration), method(ObjectRef, add_to_inventory), + method(ObjectRef, add_to_inventory_later), + method(ObjectRef, get_hp), + method(ObjectRef, set_hp), method(ObjectRef, settexturemod), method(ObjectRef, setsprite), {0,0} @@ -1951,6 +2127,8 @@ void scriptapi_export(lua_State *L, Server *server) lua_newtable(L); lua_setfield(L, -2, "registered_entities"); lua_newtable(L); + lua_setfield(L, -2, "registered_craftitems"); + lua_newtable(L); lua_setfield(L, -2, "registered_abms"); lua_newtable(L); @@ -2200,6 +2378,163 @@ bool scriptapi_on_respawnplayer(lua_State *L, ServerActiveObject *player) } /* + craftitem +*/ + +static void pushPointedThing(lua_State *L, const PointedThing& pointed) +{ + lua_newtable(L); + if(pointed.type == POINTEDTHING_NODE) + { + lua_pushstring(L, "node"); + lua_setfield(L, -2, "type"); + pushpos(L, pointed.node_undersurface); + lua_setfield(L, -2, "under"); + pushpos(L, pointed.node_abovesurface); + lua_setfield(L, -2, "above"); + } + else if(pointed.type == POINTEDTHING_OBJECT) + { + lua_pushstring(L, "object"); + lua_setfield(L, -2, "type"); + objectref_get(L, pointed.object_id); + lua_setfield(L, -2, "ref"); + } + else + { + lua_pushstring(L, "nothing"); + lua_setfield(L, -2, "type"); + } +} + +void scriptapi_add_craftitem(lua_State *L, const char *name) +{ + StackUnroller stack_unroller(L); + assert(lua_gettop(L) > 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 \""<<name<<"\" not defined"<<std::endl; + lua_pop(L, 1); + return false; + } + 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<<"CraftItem name \""<<name<<"\" callback \"" + <<callbackname<<" is not a function"<<std::endl; + lua_pop(L, 1); + return false; + } +} + +bool scriptapi_craftitem_on_drop(lua_State *L, const char *name, + ServerActiveObject *dropper, v3f pos, + bool &callback_exists) +{ + realitycheck(L); + assert(lua_checkstack(L, 20)); + //infostream<<"scriptapi_craftitem_on_drop"<<std::endl; + StackUnroller stack_unroller(L); + + bool result = false; + callback_exists = get_craftitem_callback(L, name, "on_drop"); + if(callback_exists) + { + // Call function + lua_pushstring(L, name); + objectref_get_or_create(L, dropper); + pushFloatPos(L, pos); + if(lua_pcall(L, 3, 1, 0)) + script_error(L, "error: %s\n", lua_tostring(L, -1)); + result = lua_toboolean(L, -1); + } + return result; +} + +bool scriptapi_craftitem_on_place_on_ground(lua_State *L, const char *name, + ServerActiveObject *placer, v3f pos, + bool &callback_exists) +{ + realitycheck(L); + assert(lua_checkstack(L, 20)); + //infostream<<"scriptapi_craftitem_on_place_on_ground"<<std::endl; + StackUnroller stack_unroller(L); + + bool result = false; + callback_exists = get_craftitem_callback(L, name, "on_place_on_ground"); + if(callback_exists) + { + // Call function + lua_pushstring(L, name); + objectref_get_or_create(L, placer); + pushFloatPos(L, pos); + if(lua_pcall(L, 3, 1, 0)) + script_error(L, "error: %s\n", lua_tostring(L, -1)); + result = lua_toboolean(L, -1); + } + return result; +} + +bool scriptapi_craftitem_on_use(lua_State *L, const char *name, + ServerActiveObject *user, const PointedThing& pointed, + bool &callback_exists) +{ + realitycheck(L); + assert(lua_checkstack(L, 20)); + //infostream<<"scriptapi_craftitem_on_use"<<std::endl; + StackUnroller stack_unroller(L); + + bool result = false; + callback_exists = get_craftitem_callback(L, name, "on_use"); + if(callback_exists) + { + // Call function + lua_pushstring(L, name); + objectref_get_or_create(L, user); + pushPointedThing(L, pointed); + if(lua_pcall(L, 3, 1, 0)) + script_error(L, "error: %s\n", lua_tostring(L, -1)); + result = lua_toboolean(L, -1); + } + return result; +} + +/* environment */ diff --git a/src/scriptapi.h b/src/scriptapi.h index e6570e764..fe9329d75 100644 --- a/src/scriptapi.h +++ b/src/scriptapi.h @@ -29,6 +29,7 @@ class ServerEnvironment; class ServerActiveObject; typedef struct lua_State lua_State; struct LuaEntityProperties; +struct PointedThing; //class IGameDef; void scriptapi_export(lua_State *L, Server *server); @@ -60,6 +61,18 @@ void scriptapi_environment_on_generated(lua_State *L, v3s16 minp, v3s16 maxp); void scriptapi_on_newplayer(lua_State *L, ServerActiveObject *player); bool scriptapi_on_respawnplayer(lua_State *L, ServerActiveObject *player); +/* craftitem */ +void scriptapi_add_craftitem(lua_State *L, const char *name); +bool scriptapi_craftitem_on_drop(lua_State *L, const char *name, + ServerActiveObject *dropper, v3f pos, + bool &callback_exists); +bool scriptapi_craftitem_on_place_on_ground(lua_State *L, const char *name, + ServerActiveObject *placer, v3f pos, + bool &callback_exists); +bool scriptapi_craftitem_on_use(lua_State *L, const char *name, + ServerActiveObject *user, const PointedThing& pointed, + bool &callback_exists); + /* luaentity */ // Returns true if succesfully added into Lua; false otherwise. bool scriptapi_luaentity_add(lua_State *L, u16 id, const char *name, diff --git a/src/server.cpp b/src/server.cpp index 63172e955..70638a0a6 100644 --- a/src/server.cpp +++ b/src/server.cpp @@ -45,6 +45,7 @@ with this program; if not, write to the Free Software Foundation, Inc., #include "nodedef.h" #include "tooldef.h" #include "craftdef.h" +#include "craftitemdef.h" #include "mapgen.h" #include "content_abm.h" @@ -1033,6 +1034,7 @@ Server::Server( m_toolmgr(createToolDefManager()), m_nodedef(createNodeDefManager()), m_craftdef(createCraftDefManager()), + m_craftitemdef(createCraftItemDefManager()), m_thread(this), m_emergethread(this), m_time_counter(0), @@ -1206,6 +1208,8 @@ Server::~Server() delete m_toolmgr; delete m_nodedef; + delete m_craftdef; + delete m_craftitemdef; // Deinitialize scripting infostream<<"Server: Deinitializing scripting"<<std::endl; @@ -1396,7 +1400,8 @@ void Server::AsyncRunStep() { RemoteClient *client = i.getNode()->getValue(); ServerRemotePlayer *player = - (ServerRemotePlayer*)m_env->getPlayer(client->peer_id); + static_cast<ServerRemotePlayer*> + (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" - <<std::endl; - return; - } - - // Skip if object has been removed - if(obj->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<<player->getName()<<" punches object " - <<obj->getId()<<std::endl; - - // Do stuff - obj->punch(srp); - -#if 0 - /* - Try creating inventory item - */ - InventoryItem *item = obj->createPickedUpItem(); - - if(item) - { - InventoryList *ilist = player->inventory.getList("main"); - if(ilist != NULL) - { - actionstream<<player->getName()<<" picked up " - <<item->getName()<<std::endl; - if(g_settings->getBool("creative_mode") == false) - { - // Skip if inventory has no free space - if(ilist->roomForItem(item) == false) - { - infostream<<"Player inventory has no free space"<<std::endl; - return; - } - - // Add to inventory and send inventory - ilist->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<<player->getName()<<" punches object " - <<obj->getId()<<std::endl; - - 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->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<<player->getName()<<" right clicks object " - <<obj->getId()<<std::endl; - - // Do stuff - obj->rightClick(srp); - } - - /* - Update player state to client - */ - SendPlayerHP(player); - UpdateCrafting(player->peer_id); - SendInventory(player->peer_id); + infostream<<"Server: CLICK_ACTIVEOBJECT not supported anymore"<<std::endl; + return; } else if(command == TOSERVER_GROUND_ACTION) { - if(datasize < 17) - return; - /* - length: 17 - [0] u16 command - [2] u8 action - [3] v3s16 nodepos_undersurface - [9] v3s16 nodepos_abovesurface - [15] u16 item - actions: - 0: start digging - 1: place block - 2: stop digging (all parameters ignored) - 3: digging completed - */ - u8 action = readU8(&data[2]); - v3s16 p_under; - p_under.X = readS16(&data[3]); - p_under.Y = readS16(&data[5]); - p_under.Z = readS16(&data[7]); - v3s16 p_over; - p_over.X = readS16(&data[9]); - p_over.Y = readS16(&data[11]); - p_over.Z = readS16(&data[13]); - u16 item_i = readU16(&data[15]); - - ServerRemotePlayer *srp = (ServerRemotePlayer*)player; - - /* - Check that target is reasonably close - */ - if(action != 2) // action 2 has always position (0,0,0) - { - v3f np_f = intToFloat(p_under, BS); - float max_d = BS * 10; // Just some large enough value - float d = srp->m_last_good_position.getDistanceFrom(np_f); - if(d > max_d){ - actionstream<<"Player "<<player->getName() - <<" tried to access node from too far: " - <<"d="<<d<<", max_d="<<max_d - <<". ignoring."<<std::endl; - // Re-send block to revert change on client-side - RemoteClient *client = getClient(peer_id); - v3s16 blockpos = getNodeBlockPos(p_under); - client->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." - <<std::endl; - m_emerge_queue.addBlock(peer_id, - getNodeBlockPos(p_over), BLOCK_EMERGE_FLAG_FROMDISK); - cannot_punch_node = true; - } - - if(cannot_punch_node) - return; - - /* - Run script hook - */ - scriptapi_environment_on_punchnode(m_lua, p_under, n, srp); - - } // action == 0 - - /* - 2: stop digging - */ - else if(action == 2) - { -#if 0 - RemoteClient *client = getClient(peer_id); - JMutexAutoLock digmutex(client->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<v3s16, MapBlock*> 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" - <<std::endl; - cannot_remove_node = true; - } - } - // If not yet cancelled - if(cannot_remove_node == false) - { - // Get node metadata - NodeMetadata *meta = m_env->getMap().getNodeMetadata(p_under); - if(meta && meta->nodeRemovalDisabled() == true) - { - infostream<<"Server: Not finishing digging: " - <<"Node metadata disables removal" - <<std::endl; - cannot_remove_node = true; - } - } - } - catch(InvalidPositionException &e) - { - infostream<<"Server: Not finishing digging: Node not found." - <<" Adding block to emerge queue." - <<std::endl; - m_emerge_queue.addBlock(peer_id, - getNodeBlockPos(p_over), BLOCK_EMERGE_FLAG_FROMDISK); - cannot_remove_node = true; - } - - // Make sure the player is allowed to do it - if((getPlayerPrivs(player) & PRIV_BUILD) == 0) - { - infostream<<"Player "<<player->getName()<<" cannot remove node" - <<" because privileges are "<<getPlayerPrivs(player) - <<std::endl; - cannot_remove_node = true; - } - - /* - If node can't be removed, set block to be re-sent to - client and quit. - */ - if(cannot_remove_node) - { - infostream<<"Server: Not finishing digging."<<std::endl; - - // Client probably has wrong data. - // Set block not sent, so that client will get - // a valid one. - infostream<<"Client "<<peer_id<<" tried to dig " - <<"node; but node cannot be removed." - <<" setting MapBlock not sent."<<std::endl; - RemoteClient *client = getClient(peer_id); - v3s16 blockpos = getNodeBlockPos(p_under); - client->SetBlockNotSent(blockpos); - - return; - } - - actionstream<<player->getName()<<" digs "<<PP(p_under) - <<", gets material "<<(int)material<<", mineral " - <<(int)mineral<<std::endl; - - /* - Send the removal to all close-by players. - - If other player is close, send REMOVENODE - - Otherwise set blocks not sent - */ - core::list<u16> 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"<<std::endl; - } - - bool weared_out = titem->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<u16>::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 "<<player->getName()<<" cannot add node" - <<" because privileges are "<<getPlayerPrivs(player) - <<std::endl; - - if(m_nodedef->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 "<<peer_id<<" tried to place" - <<" node in invalid position; setting" - <<" MapBlock not sent."<<std::endl; - RemoteClient *client = getClient(peer_id); - v3s16 blockpos = getNodeBlockPos(p_over); - client->SetBlockNotSent(blockpos); - return; - } - } - catch(InvalidPositionException &e) - { - infostream<<"Server: Ignoring ADDNODE: Node not found" - <<" Adding block to emerge queue." - <<std::endl; - m_emerge_queue.addBlock(peer_id, - getNodeBlockPos(p_over), BLOCK_EMERGE_FLAG_FROMDISK); - 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<<player->getName()<<" places material " - <<(int)mitem->getMaterial() - <<" at "<<PP(p_under)<<std::endl; - - // Calculate direction for wall mounted stuff - if(m_nodedef->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<u16> 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<v3s16, MapBlock*> 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<u16>::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"<<std::endl; - return; - } - - /* - If in creative mode, item dropping is disabled unless - player has build privileges - */ - if(g_settings->getBool("creative_mode") && - (getPlayerPrivs(player) & PRIV_BUILD) == 0) - { - infostream<<"Not allowing player to drop item: " - "creative mode and no build privs"<<std::endl; - return; - } - - // Calculate a position for it - v3f pos = intToFloat(p_over, BS); - //pos.Y -= BS*0.45; - /*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 the object - */ - ServerActiveObject *obj = item->createSAO(m_env, pos); - - if(obj == NULL) - { - infostream<<"WARNING: item resulted in NULL object, " - <<"not placing onto map" - <<std::endl; - } - else - { - actionstream<<player->getName()<<" places "<<item->getName() - <<" at "<<PP(p_over)<<std::endl; - - // Add the object to the environment - m_env->addActiveObject(obj); - - infostream<<"Placed object"<<std::endl; - - if(g_settings->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"<<std::endl; - - InventoryList *ilist = player->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 + infostream<<"Server: GROUND_ACTION not supported anymore"<<std::endl; + return; - /* - Catch invalid actions - */ - else - { - infostream<<"WARNING: Server: Invalid action " - <<action<<std::endl; - } } -#if 0 else if(command == TOSERVER_RELEASE) { - if(datasize < 3) - return; - /* - length: 3 - [0] u16 command - [2] u8 button - */ - infostream<<"TOSERVER_RELEASE ignored"<<std::endl; + infostream<<"Server: RELEASE not supported anymore"<<std::endl; + return; } -#endif else if(command == TOSERVER_SIGNTEXT) { - infostream<<"Server: TOSERVER_SIGNTEXT not supported anymore" + infostream<<"Server: SIGNTEXT not supported anymore" <<std::endl; return; } @@ -3367,12 +2653,6 @@ void Server::ProcessData(u8 *data, u32 datasize, u16 peer_id) // Feed action to player inventory a->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<<player->getName()<<" respawns at " <<PP(player->getPosition()/BS)<<std::endl; } + else if(command == TOSERVER_INTERACT) + { + std::string datastring((char*)&data[2], datasize-2); + std::istringstream is(datastring, std::ios_base::binary); + + /* + [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 + */ + u8 action = readU8(is); + u16 item_i = readU16(is); + std::istringstream tmp_is(deSerializeLongString(is), std::ios::binary); + PointedThing pointed; + pointed.deSerialize(tmp_is); + + infostream<<"TOSERVER_INTERACT: action="<<(int)action<<", item="<<item_i<<", pointed="<<pointed.dump()<<std::endl; + + ServerRemotePlayer *srp = static_cast<ServerRemotePlayer*>(player); + v3f player_pos = srp->m_last_good_position; + + // Update wielded item + srp->wieldItem(item_i); + + // Get pointed to node (undefined if not POINTEDTYPE_NODE) + v3s16 p_under = pointed.node_undersurface; + v3s16 p_above = pointed.node_abovesurface; + + // Get pointed to object (NULL if not POINTEDTYPE_OBJECT) + ServerActiveObject *pointed_object = NULL; + if(pointed.type == POINTEDTHING_OBJECT) + { + pointed_object = m_env->getActiveObject(pointed.object_id); + if(pointed_object == NULL) + { + infostream<<"TOSERVER_INTERACT: " + "pointed object is NULL"<<std::endl; + return; + } + + } + + /* + Check that target is reasonably close + (only when digging or placing things) + */ + if(action == 0 || action == 2 || action == 3) + { + v3f pointed_pos = player_pos; + if(pointed.type == POINTEDTHING_NODE) + { + pointed_pos = intToFloat(p_under, BS); + } + else if(pointed.type == POINTEDTHING_OBJECT) + { + pointed_pos = pointed_object->getBasePosition(); + } + + float d = player_pos.getDistanceFrom(pointed_pos); + float max_d = BS * 10; // Just some large enough value + if(d > max_d){ + actionstream<<"Player "<<player->getName() + <<" tried to access "<<pointed.dump() + <<" from too far: " + <<"d="<<d<<", max_d="<<max_d + <<". ignoring."<<std::endl; + // Re-send block to revert change on client-side + RemoteClient *client = getClient(peer_id); + v3s16 blockpos = getNodeBlockPos(floatToInt(pointed_pos, BS)); + client->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 "<<player->getName() + <<" because privileges are "<<getPlayerPrivs(player) + <<std::endl; + // NOTE: no return; here, fall through + } + + /* + 0: start digging or punch object + */ + if(action == 0) + { + if(pointed.type == POINTEDTHING_NODE) + { + /* + NOTE: This can be used in the future to check if + somebody is cheating, by checking the timing. + */ + bool cannot_punch_node = !build_priv; + + 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." + <<std::endl; + m_emerge_queue.addBlock(peer_id, + getNodeBlockPos(p_above), BLOCK_EMERGE_FLAG_FROMDISK); + cannot_punch_node = true; + } + + if(cannot_punch_node) + return; + + /* + Run script hook + */ + scriptapi_environment_on_punchnode(m_lua, p_under, n, srp); + } + else if(pointed.type == POINTEDTHING_OBJECT) + { + if(!build_priv) + return; + + // Skip if object has been removed + if(pointed_object->m_removed) + return; + + actionstream<<player->getName()<<" punches object " + <<pointed.object_id<<std::endl; + + // Do stuff + pointed_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<v3s16, MapBlock*> 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" + <<std::endl; + cannot_remove_node = true; + } + } + // If not yet cancelled + if(cannot_remove_node == false) + { + // Get node metadata + NodeMetadata *meta = m_env->getMap().getNodeMetadata(p_under); + if(meta && meta->nodeRemovalDisabled() == true) + { + infostream<<"Server: Not finishing digging: " + <<"Node metadata disables removal" + <<std::endl; + cannot_remove_node = true; + } + } + } + catch(InvalidPositionException &e) + { + infostream<<"Server: Not finishing digging: Node not found." + <<" Adding block to emerge queue." + <<std::endl; + m_emerge_queue.addBlock(peer_id, + getNodeBlockPos(p_above), BLOCK_EMERGE_FLAG_FROMDISK); + cannot_remove_node = true; + } + + /* + If node can't be removed, set block to be re-sent to + client and quit. + */ + if(cannot_remove_node) + { + infostream<<"Server: Not finishing digging."<<std::endl; + + // Client probably has wrong data. + // Set block not sent, so that client will get + // a valid one. + infostream<<"Client "<<peer_id<<" tried to dig " + <<"node; but node cannot be removed." + <<" setting MapBlock not sent."<<std::endl; + RemoteClient *client = getClient(peer_id); + v3s16 blockpos = getNodeBlockPos(p_under); + client->SetBlockNotSent(blockpos); + + return; + } + + actionstream<<player->getName()<<" digs "<<PP(p_under) + <<", gets material "<<(int)material<<", mineral " + <<(int)mineral<<std::endl; + + /* + Send the removal to all close-by players. + - If other player is close, send REMOVENODE + - Otherwise set blocks not sent + */ + core::list<u16> 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"<<std::endl; + } + + bool weared_out = titem->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<u16>::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 "<<peer_id<<" tried to place" + <<" node in invalid position."<<std::endl; + cannot_place_node = true; + } + } + catch(InvalidPositionException &e) + { + infostream<<"Server: Ignoring ADDNODE: Node not found" + <<" Adding block to emerge queue." + <<std::endl; + m_emerge_queue.addBlock(peer_id, + getNodeBlockPos(p_above), BLOCK_EMERGE_FLAG_FROMDISK); + cannot_place_node = true; + } + + if(cannot_place_node) + { + // Client probably has wrong data. + // Set block not sent, so that client will get + // a valid one. + RemoteClient *client = getClient(peer_id); + v3s16 blockpos = getNodeBlockPos(p_above); + 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<<player->getName()<<" places material " + <<(int)mitem->getMaterial() + <<" at "<<PP(p_under)<<std::endl; + + // Calculate direction for wall mounted stuff + if(m_nodedef->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<u16> 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<v3s16, MapBlock*> 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<u16>::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"<<std::endl; + return; + } + + // Calculate a position for it + v3f pos = player_pos; + if(pointed.type == POINTEDTHING_NOTHING) + { + infostream<<"Not allowing player to place item: " + "pointing to nothing"<<std::endl; + return; + } + else if(pointed.type == POINTEDTHING_NODE) + { + pos = intToFloat(p_above, BS); + } + else if(pointed.type == POINTEDTHING_OBJECT) + { + pos = pointed_object->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"<<std::endl; + return; + } + + actionstream<<player->getName()<<" places "<<item->getName() + <<" at "<<PP(pos)<<std::endl; + + /* + Place the item + */ + bool remove = item->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<<player->getName()<<" right-clicks object " + <<pointed.object_id<<std::endl; + + // Do stuff + pointed_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"<<std::endl; + return; + } + + actionstream<<player->getName()<<" uses "<<item->getName() + <<", pointing at "<<pointed.dump()<<std::endl; + + bool remove = item->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 " + <<action<<std::endl; + } + + // Complete add_to_inventory_later + srp->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<<serializeLongString(tmp_os.str()); + + // Make data buffer + std::string s = os.str(); + infostream<<"Server::SendCraftItemDef(): Sending craft item definitions: size=" + <<s.size()<<std::endl; + SharedBuffer<u8> data((u8*)s.c_str(), s.size()); + // Send as reliable + con.Send(peer_id, 0, data, true); +} + /* Non-static send methods */ @@ -4481,7 +4454,7 @@ void Server::HandlePlayerHP(Player *player, s16 damage) void Server::RespawnPlayer(Player *player) { player->hp = 20; - ServerRemotePlayer *srp = (ServerRemotePlayer*)player; + ServerRemotePlayer *srp = static_cast<ServerRemotePlayer*>(player); bool repositioned = scriptapi_on_respawnplayer(m_lua, srp); if(!repositioned){ v3f pos = findSpawnPos(m_env->getServerMap()); @@ -4626,6 +4599,10 @@ ICraftDefManager* Server::getCraftDefManager() { return m_craftdef; } +ICraftItemDefManager* Server::getCraftItemDefManager() +{ + return m_craftitemdef; +} ITextureSource* Server::getTextureSource() { return NULL; @@ -4647,6 +4624,10 @@ IWritableCraftDefManager* Server::getWritableCraftDefManager() { return m_craftdef; } +IWritableCraftItemDefManager* Server::getWritableCraftItemDefManager() +{ + return m_craftitemdef; +} v3f findSpawnPos(ServerMap &map) { @@ -4776,7 +4757,7 @@ Player *Server::emergePlayer(const char *name, const char *password, u16 peer_id m_env->addPlayer(player); /* Run scripts */ - ServerRemotePlayer *srp = (ServerRemotePlayer*)player; + ServerRemotePlayer *srp = static_cast<ServerRemotePlayer*>(player); scriptapi_on_newplayer(m_lua, srp); /* Add stuff to inventory */ diff --git a/src/server.h b/src/server.h index 673458882..98c4f65d6 100644 --- a/src/server.h +++ b/src/server.h @@ -36,6 +36,7 @@ typedef struct lua_State lua_State; class IWritableToolDefManager; class IWritableNodeDefManager; class IWritableCraftDefManager; +class IWritableCraftItemDefManager; /* Some random functions @@ -495,12 +496,14 @@ public: virtual IToolDefManager* getToolDefManager(); virtual INodeDefManager* getNodeDefManager(); virtual ICraftDefManager* getCraftDefManager(); + virtual ICraftItemDefManager* getCraftItemDefManager(); virtual ITextureSource* getTextureSource(); virtual u16 allocateUnknownNodeId(const std::string &name); IWritableToolDefManager* getWritableToolDefManager(); IWritableNodeDefManager* getWritableNodeDefManager(); IWritableCraftDefManager* getWritableCraftDefManager(); + IWritableCraftItemDefManager* getWritableCraftItemDefManager(); private: @@ -523,6 +526,8 @@ private: IToolDefManager *tooldef); static void SendNodeDef(con::Connection &con, u16 peer_id, INodeDefManager *nodedef); + static void SendCraftItemDef(con::Connection &con, u16 peer_id, + ICraftItemDefManager *nodedef); /* Non-static send methods. @@ -644,6 +649,9 @@ private: // Craft definition manager IWritableCraftDefManager *m_craftdef; + // CraftItem definition manager + IWritableCraftItemDefManager *m_craftitemdef; + /* Threads */ diff --git a/src/servercommand.cpp b/src/servercommand.cpp index f415c86ce..afd704fd3 100644 --- a/src/servercommand.cpp +++ b/src/servercommand.cpp @@ -225,7 +225,7 @@ void cmd_teleport(std::wostringstream &os, //ctx->player->setPosition(dest); // Use the ServerActiveObject interface of ServerRemotePlayer - ServerRemotePlayer *srp = (ServerRemotePlayer*)ctx->player; + ServerRemotePlayer *srp = static_cast<ServerRemotePlayer*>(ctx->player); srp->setPos(dest); ctx->server->SendMovePlayer(ctx->player); diff --git a/src/serverobject.h b/src/serverobject.h index 26dce007f..9e9513a7c 100644 --- a/src/serverobject.h +++ b/src/serverobject.h @@ -123,6 +123,8 @@ public: // If all fits, eats item and returns true. Otherwise returns false. virtual bool addToInventory(InventoryItem *item) {return false;} + virtual void addToInventoryLater(InventoryItem *item) + {} virtual void setHP(s16 hp) {} virtual s16 getHP() diff --git a/src/utility.cpp b/src/utility.cpp index 7ffbe7160..87c81cb2a 100644 --- a/src/utility.cpp +++ b/src/utility.cpp @@ -276,3 +276,103 @@ std::string translatePassword(std::string playername, std::wstring password) +PointedThing::PointedThing(): + type(POINTEDTHING_NOTHING), + node_undersurface(0,0,0), + node_abovesurface(0,0,0), + object_id(-1) +{} + +std::string PointedThing::dump() const +{ + std::ostringstream os(std::ios::binary); + if(type == POINTEDTHING_NOTHING) + { + os<<"[nothing]"; + } + else if(type == POINTEDTHING_NODE) + { + const v3s16 &u = node_undersurface; + const v3s16 &a = node_abovesurface; + os<<"[node under="<<u.X<<","<<u.Y<<","<<u.Z + << " above="<<a.X<<","<<a.Y<<","<<a.Z<<"]"; + } + else if(type == POINTEDTHING_OBJECT) + { + os<<"[object "<<object_id<<"]"; + } + else + { + os<<"[unknown PointedThing]"; + } + return os.str(); +} + +void PointedThing::serialize(std::ostream &os) const +{ + writeU8(os, 0); // version + writeU8(os, (u8)type); + if(type == POINTEDTHING_NOTHING) + { + // nothing + } + else if(type == POINTEDTHING_NODE) + { + writeV3S16(os, node_undersurface); + writeV3S16(os, node_abovesurface); + } + else if(type == POINTEDTHING_OBJECT) + { + writeS16(os, object_id); + } +} + +void PointedThing::deSerialize(std::istream &is) +{ + int version = readU8(is); + if(version != 0) throw SerializationError( + "unsupported PointedThing version"); + type = (PointedThingType) readU8(is); + if(type == POINTEDTHING_NOTHING) + { + // nothing + } + else if(type == POINTEDTHING_NODE) + { + node_undersurface = readV3S16(is); + node_abovesurface = readV3S16(is); + } + else if(type == POINTEDTHING_OBJECT) + { + object_id = readS16(is); + } + else + { + throw SerializationError( + "unsupported PointedThingType"); + } +} + +bool PointedThing::operator==(const PointedThing &pt2) const +{ + if(type != pt2.type) + return false; + if(type == POINTEDTHING_NODE) + { + if(node_undersurface != pt2.node_undersurface) + return false; + if(node_abovesurface != pt2.node_abovesurface) + return false; + } + else if(type == POINTEDTHING_OBJECT) + { + if(object_id != pt2.object_id) + return false; + } + return true; +} + +bool PointedThing::operator!=(const PointedThing &pt2) const +{ + return !(*this == pt2); +} diff --git a/src/utility.h b/src/utility.h index 4e469db89..47696cbf8 100644 --- a/src/utility.h +++ b/src/utility.h @@ -104,13 +104,6 @@ inline s32 readS32(u8 *data){ return (s32)readU32(data); } -inline void writeF1000(u8 *data, f32 i){ - writeS32(data, i*1000); -} -inline f32 readF1000(u8 *data){ - return (f32)readS32(data)/1000.; -} - inline void writeS16(u8 *data, s16 i){ writeU16(data, (u16)i); } @@ -118,6 +111,20 @@ inline s16 readS16(u8 *data){ return (s16)readU16(data); } +inline void writeS8(u8 *data, s8 i){ + writeU8(data, (u8)i); +} +inline s8 readS8(u8 *data){ + return (s8)readU8(data); +} + +inline void writeF1000(u8 *data, f32 i){ + writeS32(data, i*1000); +} +inline f32 readF1000(u8 *data){ + return (f32)readS32(data)/1000.; +} + inline void writeV3S32(u8 *data, v3s32 p) { writeS32(&data[0], p.X); @@ -248,19 +255,45 @@ inline u32 readU32(std::istream &is) return readU32((u8*)buf); } -inline void writeS32(std::ostream &os, u32 p) +inline void writeS32(std::ostream &os, s32 p) { char buf[4]; writeS32((u8*)buf, p); os.write(buf, 4); } -inline u32 readS32(std::istream &is) +inline s32 readS32(std::istream &is) { char buf[4]; is.read(buf, 4); return readS32((u8*)buf); } +inline void writeS16(std::ostream &os, s16 p) +{ + char buf[2]; + writeS16((u8*)buf, p); + os.write(buf, 2); +} +inline s16 readS16(std::istream &is) +{ + char buf[2]; + is.read(buf, 2); + return readS16((u8*)buf); +} + +inline void writeS8(std::ostream &os, s8 p) +{ + char buf[1]; + writeS8((u8*)buf, p); + os.write(buf, 1); +} +inline s8 readS8(std::istream &is) +{ + char buf[1]; + is.read(buf, 1); + return readS8((u8*)buf); +} + inline void writeF1000(std::ostream &os, f32 p) { char buf[4]; @@ -1725,5 +1758,27 @@ protected: std::string translatePassword(std::string playername, std::wstring password); +enum PointedThingType +{ + POINTEDTHING_NOTHING, + POINTEDTHING_NODE, + POINTEDTHING_OBJECT +}; + +struct PointedThing +{ + PointedThingType type; + v3s16 node_undersurface; + v3s16 node_abovesurface; + s16 object_id; + + PointedThing(); + std::string dump() const; + void serialize(std::ostream &os) const; + void deSerialize(std::istream &is); + bool operator==(const PointedThing &pt2) const; + bool operator!=(const PointedThing &pt2) const; +}; + #endif |