diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md new file mode 100644 index 0000000..16a26e3 --- /dev/null +++ b/CONTRIBUTING.md @@ -0,0 +1,3 @@ +- The `master` branch should be stable to end-users at all times. It should target latest Minetest point release. +- Put rewrites and new features in branches. +- Conform with setting defaults so that end-user upgrades doesn't change expected in-game behavior. Discuss default changes in an issue if one really need to change. diff --git a/README.md b/README.md index d81a294..ddbfb01 100644 --- a/README.md +++ b/README.md @@ -1,5 +1,6 @@ # item_drop -By [PilzAdam](https://github.com/PilzAdam), [texmex](https://github.com/tacotexmex/). +By [PilzAdam](https://github.com/PilzAdam), +[texmex](https://github.com/tacotexmex/), [hybriddog](https://github.com/hybriddog/). ## Description This mod adds Minecraft like drop/pick up of items to Minetest. @@ -8,26 +9,46 @@ This mod adds Minecraft like drop/pick up of items to Minetest. LGPLv2.1/CC BY-SA 3.0. ## Notes -item_drop can be played with Minetest 0.4.16 or above. It was originally developed by [PilzAdam](https://github.com/PilzAdam/item_drop). +item_drop can be played with Minetest 0.4.16 or above. It was originally +developed by [PilzAdam](https://github.com/PilzAdam/item_drop). ## List of features -- All settings may be configured from within the game itself (Settings tab > Advanced settings > Mods > item_drop) -- Drops nodes as in-world items on dig if `enable_item_drop` is `true`. (true by default) -- Pulls items to the player's inventory if `enable_item_pickup` is `true`. (true by default) It uses a node radius set in `pickup_radius` (default 0.75) -- Plays a sound the items are picked up, with the gain level set it `pickup_gain` (default 0.4) -- Requires a key to be pressed in order to pull items if `enable_item_pickup_key` is `true`. (true by default). The keytypes to choose from by setting `item_pickup_keytype` are: - - Use key (`Use`) - - Sneak key (`Sneak`) - - Left and Right keys combined (`LeftAndRight`) - - Right mouse button (`RMB`) - - Sneak key and right mouse button combined (`SneakAndRMB`) +* All settings may be configured from within the game itself. + (Settings tab > Advanced settings > Mods > item_drop) +* Drops nodes as in-world items on dig if `item_drop.enable_item_drop` is + `true` (true by default) It does nothing in creative mode. +* Puts dropped items to the player's inventory if `item_drop.enable_item_pickup` + is `true` (true by default) + * It uses a node radius set in `item_drop.pickup_radius` (default 0.75), + if items are within this radius around the player's belt, they're picked. + * If `item_drop.pickup_age` is something positive, items dropped by players + are ignored for this time to avoid instantly picking up when dropping. + * If `item_drop.pickup_age` is `-1`, items are only picked when they don't + move, it's another fix for instant item picking. + * If `item_drop.magnet_radius` is bigger than `item_drop.pickup_radius`, + items between these radii are flying to the player for + `item_drop.magnet_time` seconds, after this time, they're picked or stop + flying. +* Plays a sound when the items are picked up with the gain level set to + `item_drop.pickup_sound_gain` (default 0.2) +* Requires a key to be pressed in order to pick items if + `item_drop.enable_pickup_key` is `true` (true by default) + * The keytypes to choose from by setting `item_pickup_keytype` are: + * Use key (`Use`) + * Sneak key (`Sneak`) + * Left and Right keys combined (`LeftAndRight`) + * Right mouse button (`RMB`) + * Sneak key and right mouse button combined (`SneakAndRMB`) + * If `item_drop.pickup_keyinvert` is `true` (false by default), items are + collected when the key is not pressed instead of when it's pressed. ## Known issues ## Bug reports and suggestions -You can report bugs or suggest ideas by [filing an issue](http://github.com/tacotexmex/item_drop/issues/new). +You can report bugs or suggest ideas by +[filing an issue](http://github.com/minetest-mods/item_drop/issues/new). ## Links -* [Download ZIP](https://github.com/tacotexmex/item_drop/archive/master.zip) -* [Source](https://github.com/tacotexmex/item_drop/) +* [Download ZIP](https://github.com/minetest-mods/item_drop/archive/master.zip) +* [Source](https://github.com/minetest-mods/item_drop/) * [Forum thread](https://forum.minetest.net/viewtopic.php?t=16913) diff --git a/init.lua b/init.lua index 66584f9..70b44d2 100644 --- a/init.lua +++ b/init.lua @@ -1,155 +1,249 @@ -local pickup = minetest.settings:get_bool("enable_item_pickup") ~= false -local drop = minetest.settings:get_bool("enable_item_drop") ~= false -local key = minetest.settings:get_bool("enable_item_pickup_key") ~= false -local keytype = minetest.settings:get("item_pickup_keytype") or "Use" -local pickup_gain = tonumber(minetest.settings:get("item_pickup_gain")) or 0.4 -local pickup_radius = tonumber(minetest.settings:get("item_pickup_radius")) or 0.75 +local load_time_start = minetest.get_us_time() -local damage_enabled = minetest.settings:get_bool("enable_damage") -local timer = 0 +if minetest.settings:get_bool("item_drop.enable_item_pickup") ~= false then + local pickup_gain = tonumber( + minetest.settings:get("item_drop.pickup_sound_gain")) or 0.2 + local pickup_radius = tonumber( + minetest.settings:get("item_drop.pickup_radius")) or 0.75 + local magnet_radius = tonumber( + minetest.settings:get("item_drop.magnet_radius")) or -1 + local magnet_time = tonumber( + minetest.settings:get("item_drop.magnet_time")) or 5.0 + local pickup_age = tonumber( + minetest.settings:get("item_drop.pickup_age")) or 0.5 + local key_triggered = minetest.settings:get_bool( + "item_drop.enable_pickup_key") ~= false + local key_invert = minetest.settings:get_bool( + "item_drop.pickup_keyinvert") or false + local keytype + if key_triggered then + keytype = minetest.settings:get("item_drop.pickup_keytype") or "Use" + -- disable pickup age if picking is explicitly enabled by the player + if not key_invert then + pickup_age = math.min(pickup_age, 0) + end + end -if pickup then - minetest.register_globalstep(function(dtime) + local magnet_mode = magnet_radius > pickup_radius + local zero_velocity_mode = pickup_age == -1 + if magnet_mode + and zero_velocity_mode then + error"zero velocity mode can't be used together with magnet mode" + end - timer = timer + dtime - if timer < 0.2 then return end - timer = 0 + -- adds the item to the inventory and removes the object + local function collect_item(ent, pos, player) + minetest.sound_play("item_drop_pickup", { + pos = pos, + gain = pickup_gain, + }) + ent:on_punch(player) + end - for _,player in ipairs(minetest.get_connected_players()) do - local keys_pressed = false + -- opt_get_ent gets the object's luaentity if it can be collected + local opt_get_ent + if zero_velocity_mode then + function opt_get_ent(object) + if object:is_player() + or not vector.equals(object:getvelocity(), {x=0, y=0, z=0}) then + return + end + local ent = object:get_luaentity() + if not ent + or ent.name ~= "__builtin:item" + or ent.itemstring == "" then + return + end + return ent + end + else + function opt_get_ent(object) + if object:is_player() then + return + end + local ent = object:get_luaentity() + if not ent + or ent.name ~= "__builtin:item" + or (ent.dropped_by and ent.age < pickup_age) + or ent.itemstring == "" then + return + end + return ent + end + end - local control = player:get_player_control() + local afterflight + if magnet_mode then + -- take item or reset velocity after flying a second + function afterflight(object, inv, player) + -- TODO: test what happens if player left the game + local ent = opt_get_ent(object) + if not ent then + return + end + local item = ItemStack(ent.itemstring) + if inv + and inv:room_for_item("main", item) then + collect_item(ent, object:get_pos(), player) + else + object:setvelocity({x=0,y=0,z=0}) + ent.physical_state = true + ent.object:set_properties({ + physical = true + }) + end + end + end - if keytype == "Use" then - keys_pressed = control.aux1 - elseif keytype == "Sneak" then - keys_pressed = control.sneak - elseif keytype == "LeftAndRight" then -- LeftAndRight combination - keys_pressed = control.left and control.right - elseif keytype == "RMB" then - keys_pressed = control.RMB - elseif keytype == "SneakAndRMB" then -- SneakAndRMB combination - keys_pressed = control.sneak and control.RMB - end + -- set keytype to the key name if possible + if keytype == "Use" then + keytype = "aux1" + elseif keytype == "Sneak" then + keytype = "sneak" + elseif keytype == "LeftAndRight" then -- LeftAndRight combination + keytype = 0 + elseif keytype == "SneakAndRMB" then -- SneakAndRMB combination + keytype = 1 + end - if not keys_pressed and key - or (damage_enabled and player:get_hp() <= 0) then - return - end - local pos = player:getpos() - pos.y = pos.y+0.5 - local inv = player:get_inventory() + -- tests if the player has the keys pressed to enable item picking + local function has_keys_pressed(player) + if not key_triggered then + return true + end - local objectlist = minetest.get_objects_inside_radius(pos, pickup_radius) - for _,object in ipairs(objectlist) do - if not object:is_player() - and object:get_luaentity() - and object:get_luaentity().name == "__builtin:item" then - local pos2 = object:getpos() - local distance = math.sqrt(((pos2.x - pos.x) * (pos2.x - pos.x)) + ((pos2.y - pos.y) * (pos2.y - pos.y)) - + ((pos2.z - pos.z) * (pos2.z - pos.z))) - if distance <= 1 then - if inv and inv:room_for_item("main", ItemStack(object:get_luaentity().itemstring)) then - inv:add_item("main", ItemStack(object:get_luaentity().itemstring)) - if object:get_luaentity().itemstring ~= "" then - minetest.sound_play("item_drop_pickup", { - to_player = player:get_player_name(), - gain = pickup_gain, - }) - end - object:get_luaentity().itemstring = "" - object:remove() - end - else - if object:get_luaentity().collect then - if inv and inv:room_for_item("main", ItemStack(object:get_luaentity().itemstring)) then - local pos1 = pos - pos1.y = pos1.y+0.2 - local vec = {x=pos1.x-pos2.x, y=pos1.y-pos2.y, z=pos1.z-pos2.z} - vec.x = vec.x*3 - vec.y = vec.y*3 - vec.z = vec.z*3 - object:setvelocity(vec) - object:get_luaentity().physical_state = false - object:get_luaentity().object:set_properties({ - physical = false - }) + local control = player:get_player_control() + local keys_pressed + if keytype == 0 then -- LeftAndRight combination + keys_pressed = control.left and control.right + elseif keytype == 1 then -- SneakAndRMB combination + keys_pressed = control.sneak and control.RMB + else + keys_pressed = control[keytype] + end - minetest.after(1, function() - local lua = object:get_luaentity() - if not lua or not lua.itemstring then - return - end - if inv:room_for_item("main", ItemStack(object:get_luaentity().itemstring)) then - inv:add_item("main", ItemStack(object:get_luaentity().itemstring)) - if object:get_luaentity().itemstring ~= "" then - minetest.sound_play("item_drop_pickup", { - to_player = player:get_player_name(), - gain = pickup_gain, - }) - end - object:get_luaentity().itemstring = "" - object:remove() - else - object:setvelocity({x=0,y=0,z=0}) - object:get_luaentity().physical_state = true - object:get_luaentity().object:set_properties({ - physical = true - }) - end - end, {player, object}) - end - end - end - end - end - end - end) + return keys_pressed ~= key_invert + end + + -- called for each player to possibly collect an item, returns true if so + local function pickupfunc(player) + if not has_keys_pressed(player) + or not minetest.get_player_privs(player:get_player_name()).interact + or player:get_hp() <= 0 then + return + end + + local pos = player:getpos() + pos.y = pos.y+0.5 + local inv + + local objectlist = minetest.get_objects_inside_radius(pos, + magnet_mode and magnet_radius or pickup_radius) + for i = 1,#objectlist do + local object = objectlist[i] + local ent = opt_get_ent(object) + if ent then + if not inv then + inv = player:get_inventory() + if not inv then + minetest.log("error", "[item_drop] Couldn't " .. + "get inventory") + return + end + end + local item = ItemStack(ent.itemstring) + if inv:room_for_item("main", item) then + local flying_item + local pos2 + if magnet_mode then + pos2 = object:getpos() + flying_item = vector.distance(pos, pos2) > pickup_radius + end + if not flying_item then + -- collect one item at a time to avoid the loud pop + collect_item(ent, pos, player) + return true + end + local vel = vector.multiply( + vector.subtract(pos, pos2), 3) + vel.y = vel.y + 0.6 + object:setvelocity(vel) + if ent.physical_state then + ent.physical_state = false + ent.object:set_properties({ + physical = false + }) + + minetest.after(magnet_time, afterflight, + object, inv, player) + end + end + end + end + end + + local function pickup_step() + local got_item + local players = minetest.get_connected_players() + for i = 1,#players do + got_item = got_item or pickupfunc(players[i]) + end + -- lower step if takeable item(s) were found + local time + if got_item then + time = 0.02 + else + time = 0.2 + end + minetest.after(time, pickup_step) + end + minetest.after(3.0, pickup_step) end -if drop then - function minetest.handle_node_drops(pos, drops, digger) +if minetest.settings:get_bool("item_drop.enable_item_drop") ~= false +and not minetest.settings:get_bool("creative_mode") then + function minetest.handle_node_drops(pos, drops) + for i = 1,#drops do + local item = drops[i] + local count, name + if type(item) == "string" then + count = 1 + name = item + else + count = item:get_count() + name = item:get_name() + end - local inv - local diggerPos = pos + for _ = 1,count do + local obj = minetest.add_item(pos, name) + if not obj then + error("Couldn't spawn item") + end - if minetest.settings:get_bool("creative_mode") and digger and digger:is_player() then - inv = digger:get_inventory() - diggerPos = digger:getpos() - end - - for _,item in ipairs(drops) do - local count, name - if type(item) == "string" then - count = 1 - name = item - else - count = item:get_count() - name = item:get_name() - end - - if not inv or not inv:contains_item("main", ItemStack(name)) then - for i=1,count do - - local adjustedPos = {x=diggerPos.x, y=diggerPos.y, z=diggerPos.z} - local obj = minetest.add_item(adjustedPos, name) - - if obj ~= nil then - obj:get_luaentity().collect = true - local x = math.random(1, 5) - if math.random(1,2) == 1 then x = -x end - - local z = math.random(1, 5) - if math.random(1,2) == 1 then z = -z end - - obj:setvelocity({x=1/x, y=obj:getvelocity().y, z=1/z}) - end - end - end - end - end + local vel = obj:getvelocity() + local x = math.random(-5, 4) + if x >= 0 then + x = x+1 + end + vel.x = 1 / x + local z = math.random(-5, 4) + if z >= 0 then + z = z+1 + end + vel.z = 1 / z + obj:setvelocity(vel) + end + end + end end -if minetest.settings:get("log_mods") then minetest.log("action", "[Mod] item_drop loaded") end + +local time = (minetest.get_us_time() - load_time_start) / 1000000 +local msg = "[item_drop] loaded after ca. " .. time .. " seconds." +if time > 0.01 then + print(msg) +else + minetest.log("info", msg) +end diff --git a/settingtypes.txt b/settingtypes.txt index ad5b4cb..1503e4b 100644 --- a/settingtypes.txt +++ b/settingtypes.txt @@ -1,17 +1,32 @@ #Pick up items automatically -enable_item_pickup (Enable item pickups) bool true +item_drop.enable_item_pickup (Enable item pickups) bool true -#Drop items in-world on dig -enable_item_drop (Enable item drops) bool true +#Drop items in-world on dig, does nothing in creative mode +item_drop.enable_item_drop (Enable item drops) bool true #Use a key to pick up items -enable_item_pickup_key (Use pickup key) bool true +item_drop.enable_pickup_key (Use pickup key) bool true + +#Collect items when the key is not pressed instead of when it is pressed +item_drop.pickup_keyinvert (Invert pickup key) bool false #What keytype to use as pickup key -item_pickup_keytype (Pickup keytype) enum Use Use,Sneak,LeftAndRight,RMB,SneakAndRMB +item_drop.pickup_keytype (Pickup keytype) enum Use Use,Sneak,LeftAndRight,RMB,SneakAndRMB #The volume of the pickup sound -item_pickup_gain (Pickup sound gain) float 0.4 +item_drop.pickup_sound_gain (Pickup sound gain) float 0.4 -#Player pickup radius -item_pickup_radius (Pickup radius) float 0.75 +#Player pickup radius, the maximum distance from which items can be collected +item_drop.pickup_radius (Pickup radius) float 0.75 + +#Magnet radius, items between pickup_radius and this begin flying to the player +#Set it to -1 (or something else smaller than pickup_radius) to disable item +#flying +item_drop.magnet_radius (Magnet radius) float -1 + +#Item flight duration, items flying for more than this time are added to the +#player's inventory +item_drop.magnet_time (Magnet time) float 5.0 + +#Time delay in seconds after autopicking an item if it's dropped by a player +item_drop.pickup_age (Pickup age) float 0.5