Initial content
parent
e57d59b20c
commit
1df24a2d22
|
@ -1,4 +0,0 @@
|
|||
minetest-mod-prestibags
|
||||
=======================
|
||||
|
||||
Prestibags are simple bags that act like chests you can pick up.
|
|
@ -0,0 +1,57 @@
|
|||
Prestibags Minetest Mod
|
||||
=======================
|
||||
|
||||
Pretibags are simple bags that act like chests you can pick up. They do not
|
||||
modify the player's normal inventory formspec, so they will not interfere with
|
||||
any mods that do. Here are some of their features:
|
||||
|
||||
* Retain their inventory when picked up or unloaded/reloaded by the server.
|
||||
* Can be stored in other bags, nested as deeply as you like.
|
||||
* Are NOT owned, so any player can open them and pick them up.
|
||||
* Are flammable, so don't put them near lava or fire!
|
||||
* Fit in spaces with non-solid nodes (e.g. water, torches), but don't build
|
||||
solid stuff on top of them!
|
||||
|
||||
Note that bags are "active entities", although they are non-physical and act in
|
||||
many ways like nodes. This means that the "/clearobjects" command will destroy
|
||||
them, and they can potentially take damage from sources other than fire. They
|
||||
will show wear like a tool when taken into inventory if they have taken any
|
||||
damage.
|
||||
|
||||
Dependencies: default
|
||||
|
||||
Soft Dependencies: wool (for crafting), fire
|
||||
|
||||
Craft Recipies (W = "group:wool"):
|
||||
— W —
|
||||
W — W
|
||||
W W W
|
||||
|
||||
Git Repo: https://github.com/prestidigitator/minetest-mod-prestibags
|
||||
|
||||
Change History
|
||||
--------------
|
||||
|
||||
Version 1.0
|
||||
|
||||
* Released 2013-03-07
|
||||
* First working version.
|
||||
|
||||
|
||||
Future Direction
|
||||
----------------
|
||||
|
||||
In the future I may enhance bags so they can be opened directly from inventory
|
||||
by wielding and "using" them, but I'm struggling with this because I kind of
|
||||
like the drawback that you have to risk placing them to interact with their
|
||||
inventories.
|
||||
|
||||
Copyright and Licensing
|
||||
-----------------------
|
||||
|
||||
All content, including source code, textures, models, and sounds, are 100%
|
||||
original content by the mod author (prestidigitator).
|
||||
|
||||
Author: prestidigitator (as registered on forum.minetest.net)
|
||||
License: WTFPL (all content)
|
||||
|
Binary file not shown.
After Width: | Height: | Size: 175 KiB |
Binary file not shown.
After Width: | Height: | Size: 159 KiB |
|
@ -0,0 +1,414 @@
|
|||
--- prestibags Minetest mod
|
||||
--
|
||||
-- @author prestidigitator
|
||||
-- @copyright 2013, licensed under WTFPL
|
||||
|
||||
|
||||
---- Configuration
|
||||
|
||||
--- Width and height of bag inventory (>0)
|
||||
local BAG_WIDTH = 3
|
||||
local BAG_HEIGHT = 3
|
||||
|
||||
--- Sound played when placing/dropping a bag on the ground
|
||||
local DROP_BAG_SOUND = "prestibags_drop_bag"
|
||||
local DROP_BAG_SOUND_GAIN = 1.0
|
||||
local DROP_BAG_SOUND_DIST = 5.0
|
||||
|
||||
--- Sound played when opening a bag's inventory
|
||||
local OPEN_BAG_SOUND = "prestibags_rustle_bag"
|
||||
local OPEN_BAG_SOUND_GAIN = 1.0
|
||||
local OPEN_BAG_SOUND_DIST = 5.0
|
||||
|
||||
--- HP of undamaged bag (integer >0).
|
||||
local BAG_MAX_HP = 4
|
||||
|
||||
--- How often the inventories of destroyed bags are checked and cleaned up
|
||||
-- (>0.0).
|
||||
local CLEANUP_PERIOD__S = 10.0
|
||||
|
||||
--- How often environmental effects like burning are checked (>0.0).
|
||||
local ENV_CHECK_PERIOD__S = 0.5
|
||||
|
||||
--- Max distance an igniter node can be and still ignite/burn the bag (>=1).
|
||||
local MAX_IGNITE_DIST = 4.0
|
||||
|
||||
--- Probability (0.0 <= p <= 1.0) bag will be damaged for each igniter touching
|
||||
-- it (increases if igniter's max range is greater than current distance).
|
||||
-- Always damaged if in lava.
|
||||
local BURN_DAMAGE_PROB = 0.25
|
||||
|
||||
--- Probability (0.0 <= p <= 1.0) bag will ignite and spawn some flames on or
|
||||
-- touching it for each igniter within igniter range (increases if
|
||||
-- igniter's max range is greater than current distance). Alawys ignites
|
||||
-- if in lava. Ignition is ignored if "fire:basic_flame" is not available.
|
||||
local IGNITE_PROB = 0.25
|
||||
|
||||
--- Amount of damage bag takes each time it is burned. Note that a bag can be
|
||||
-- burned at most once each update cycle, so this is the MAXIMUM damage
|
||||
-- taken by burning each ENV_CHECK_PERIOD__S period.
|
||||
local BURN_DAMAGE__HP = 1
|
||||
|
||||
---- end of configuration
|
||||
|
||||
local EPSILON = 0.001 -- "close enough"
|
||||
|
||||
|
||||
local function serializeContents(contents)
|
||||
if not contents then return "" end
|
||||
|
||||
local tabs = {}
|
||||
for i, stack in ipairs(contents) do
|
||||
tabs[i] = stack and stack:to_table() or ""
|
||||
end
|
||||
|
||||
return minetest.serialize(tabs)
|
||||
end
|
||||
|
||||
local function deserializeContents(data)
|
||||
if not data or data == "" then return nil end
|
||||
local tabs = minetest.deserialize(data)
|
||||
if not tabs or type(tabs) ~= "table" then return nil end
|
||||
|
||||
local contents = {}
|
||||
for i, tab in ipairs(tabs) do
|
||||
contents[i] = ItemStack(tab)
|
||||
end
|
||||
|
||||
return contents
|
||||
end
|
||||
|
||||
|
||||
-- weak references to keep track of what detached inventory lists to remove
|
||||
local idSet = {}
|
||||
local idToWeakEntityMap = {}
|
||||
|
||||
setmetatable(idToWeakEntityMap, { __mode = "v" })
|
||||
|
||||
local entityInv
|
||||
local function cleanInventory()
|
||||
for id, dummy in pairs(idSet) do
|
||||
if not idToWeakEntityMap[id] then
|
||||
entityInv:set_size(id, 0)
|
||||
idSet[id] = nil
|
||||
end
|
||||
end
|
||||
minetest.after(CLEANUP_PERIOD__S, cleanInventory)
|
||||
end
|
||||
minetest.after(CLEANUP_PERIOD__S, cleanInventory)
|
||||
|
||||
entityInv = minetest.create_detached_inventory(
|
||||
"prestibags:bags",
|
||||
{
|
||||
allow_move = function(inv,
|
||||
fromList, fromIndex,
|
||||
toList, toIndex,
|
||||
count,
|
||||
player)
|
||||
return idToWeakEntityMap[fromList] and idToWeakEntityMap[toList]
|
||||
and count
|
||||
or 0
|
||||
end,
|
||||
|
||||
allow_put = function(inv, toList, toIndex, stack, player)
|
||||
return idToWeakEntityMap[toList] and stack:get_count() or 0
|
||||
end,
|
||||
|
||||
allow_take = function(inv, fromList, fromIndex, stack, player)
|
||||
return idToWeakEntityMap[fromList] and stack:get_count() or 0
|
||||
end,
|
||||
|
||||
on_move = function(inv,
|
||||
fromList, fromIndex,
|
||||
toList, toIndex,
|
||||
count,
|
||||
player)
|
||||
local fromEntity = idToWeakEntityMap[fromList]
|
||||
local toEntity = idToWeakEntityMap[toList]
|
||||
local fromStack = fromEntity.contents[fromIndex]
|
||||
local toStack = toEntity.contents[toIndex]
|
||||
|
||||
local moved = fromStack:take_item(count)
|
||||
toStack:add_item(moved)
|
||||
end,
|
||||
|
||||
on_put = function(inv, toList, toIndex, stack, player)
|
||||
local toEntity = idToWeakEntityMap[toList]
|
||||
local toStack = toEntity.contents[toIndex]
|
||||
|
||||
toStack:add_item(stack)
|
||||
end,
|
||||
|
||||
on_take = function(inv, fromList, fromIndex, stack, player)
|
||||
local fromEntity = idToWeakEntityMap[fromList]
|
||||
local fromStack = fromEntity.contents[fromIndex]
|
||||
|
||||
fromStack:take_item(stack:get_count())
|
||||
end
|
||||
})
|
||||
|
||||
|
||||
local function bag_envUpdate(self, dt)
|
||||
end
|
||||
|
||||
minetest.register_entity(
|
||||
"prestibags:bag_entity",
|
||||
{
|
||||
initial_properties =
|
||||
{
|
||||
hp_max = BAG_MAX_HP,
|
||||
physical = false,
|
||||
collisionbox = { -0.44, -0.5, -0.425, 0.44, 0.35, 0.425 },
|
||||
visual = "mesh",
|
||||
visual_size = { x = 1, y = 1 },
|
||||
mesh = "prestibags_bag.obj",
|
||||
textures = { "prestibags_bag.png" }
|
||||
},
|
||||
|
||||
on_activate = function(self, staticData, dt)
|
||||
local id
|
||||
repeat
|
||||
id = "bag"..(math.random(0, 2^15-1)*2^15 + math.random(0, 2^15-1))
|
||||
until not idSet[id]
|
||||
idSet[id] = id
|
||||
idToWeakEntityMap[id] = self
|
||||
|
||||
self.id = id
|
||||
|
||||
self.object:set_armor_groups({ punch_operable = 1, flammable = 1 })
|
||||
|
||||
local contents = deserializeContents(staticData)
|
||||
if not contents then
|
||||
contents = {}
|
||||
for i = 1, BAG_WIDTH*BAG_HEIGHT do
|
||||
contents[#contents+1] = ItemStack(nil)
|
||||
end
|
||||
end
|
||||
self.contents = contents
|
||||
|
||||
self.timer = ENV_CHECK_PERIOD__S
|
||||
end,
|
||||
|
||||
get_staticdata = function(self)
|
||||
return serializeContents(self.contents)
|
||||
end,
|
||||
|
||||
on_punch = function(self, hitterObj, timeSinceLastPunch, toolCaps, dir)
|
||||
local playerName = hitterObj:get_player_name()
|
||||
local playerInv = hitterObj:get_inventory()
|
||||
if not playerName or not playerInv then return end
|
||||
|
||||
local contentData = serializeContents(self.contents)
|
||||
|
||||
local hp = self.object:get_hp()
|
||||
local newItem =
|
||||
ItemStack({ name = "prestibags:bag",
|
||||
metadata = contentData,
|
||||
wear = (2^16) * (BAG_MAX_HP - hp) / BAG_MAX_HP })
|
||||
if not playerInv:room_for_item("main", newItem) then return end
|
||||
|
||||
self:remove()
|
||||
|
||||
playerInv:add_item("main", newItem)
|
||||
end,
|
||||
|
||||
on_rightclick = function(self, player)
|
||||
local invLoc = "detached:"..self.id
|
||||
local w = math.max(8, 2 + BAG_WIDTH)
|
||||
local h = math.max(5 + BAG_HEIGHT)
|
||||
local yImg = math.floor(BAG_HEIGHT/2)
|
||||
local yPlay = BAG_HEIGHT + 1
|
||||
|
||||
if not self.contents or #self.contents <= 0 then return end
|
||||
|
||||
entityInv:set_size(self.id, #self.contents)
|
||||
for i, stack in ipairs(self.contents) do
|
||||
entityInv:set_stack(self.id, i, stack)
|
||||
end
|
||||
|
||||
local formspec =
|
||||
"size["..w..","..h.."]"..
|
||||
"image[1,"..yImg..";1,1;prestibags_bag_inv.png]"..
|
||||
"list[detached:prestibags:bags;"..self.id..";2,0;"..
|
||||
BAG_WIDTH..","..BAG_HEIGHT..";]"..
|
||||
"list[current_player;main;0,"..yPlay..";8,4;]"
|
||||
|
||||
minetest.show_formspec(
|
||||
player:get_player_name(), "prestibags:bag", formspec)
|
||||
|
||||
minetest.sound_play(
|
||||
OPEN_BAG_SOUND,
|
||||
{
|
||||
object = self.object,
|
||||
gain = OPEN_BAG_SOUND_GAIN,
|
||||
max_hear_distance = OPEN_BAG_SOUND_DIST,
|
||||
loop = false
|
||||
})
|
||||
end,
|
||||
|
||||
on_step = function(self, dt)
|
||||
self.timer = self.timer - dt
|
||||
if self.timer > 0.0 then return end
|
||||
self.timer = ENV_CHECK_PERIOD__S
|
||||
|
||||
local haveFlame = minetest.registered_nodes["fire:basic_flame"]
|
||||
local pos = self.object:getpos()
|
||||
local node = minetest.env:get_node(pos)
|
||||
local nodeType = node and minetest.registered_nodes[node.name]
|
||||
|
||||
if nodeType and nodeType.walkable and not nodeType.buildable_to then
|
||||
return self:remove()
|
||||
end
|
||||
|
||||
if minetest.get_item_group(node.name, "lava") > 0 then
|
||||
if haveFlame then
|
||||
local flamePos = minetest.env:find_node_near(pos, 1.0, "air")
|
||||
if flamePos then
|
||||
minetest.env:add_node(flamePos,
|
||||
{ name = "fire:basic_flame" })
|
||||
end
|
||||
end
|
||||
return self:burn()
|
||||
end
|
||||
|
||||
if minetest.env:find_node_near(pos, 1.0, "group:puts_out_fire") then
|
||||
return
|
||||
end
|
||||
|
||||
local minPos = { x = pos.x - MAX_IGNITE_DIST,
|
||||
y = pos.y - MAX_IGNITE_DIST,
|
||||
z = pos.z - MAX_IGNITE_DIST }
|
||||
local maxPos = { x = pos.x + MAX_IGNITE_DIST,
|
||||
y = pos.y + MAX_IGNITE_DIST,
|
||||
z = pos.z + MAX_IGNITE_DIST }
|
||||
local wasIgnited = false
|
||||
local burnLevels = 0.0
|
||||
|
||||
local igniterPosList =
|
||||
minetest.env:find_nodes_in_area(minPos, maxPos, "group:igniter")
|
||||
for i, igniterPos in ipairs(igniterPosList) do
|
||||
local distSq = (igniterPos.x - pos.x)^2 +
|
||||
(igniterPos.y - pos.y)^2 +
|
||||
(igniterPos.z - pos.z)^2
|
||||
if distSq <= MAX_IGNITE_DIST^2 + EPSILON then
|
||||
local igniterNode = minetest.env:get_node(igniterPos)
|
||||
local igniterLevel =
|
||||
minetest.get_item_group(igniterNode.name, "igniter")
|
||||
- math.max(1.0, math.sqrt(distSq) - EPSILON)
|
||||
|
||||
if igniterLevel >= 0.0 then
|
||||
if distSq <= 1.0 then
|
||||
wasIgnited = true
|
||||
end
|
||||
burnLevels = burnLevels + 1.0 + igniterLevel
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
if burnLevels >= 1.0 then
|
||||
if haveFlame and (not wasIgnited) and
|
||||
math.random() >= (1.0 - IGNITE_PROB)^burnLevels
|
||||
then
|
||||
local flamePos =
|
||||
(node.name == "air")
|
||||
and pos
|
||||
or minetest.env:find_node_near(pos, 1.0, "air")
|
||||
if flamePos then
|
||||
minetest.env:add_node(flamePos,
|
||||
{ name = "fire:basic_flame" })
|
||||
end
|
||||
end
|
||||
|
||||
if math.random() >= (1.0 - BURN_DAMAGE_PROB)^burnLevels then
|
||||
self:burn()
|
||||
end
|
||||
end
|
||||
end,
|
||||
|
||||
remove = function(self)
|
||||
entityInv:set_size(self.id, 0)
|
||||
idSet[self.id] = nil
|
||||
self.object:remove()
|
||||
end,
|
||||
|
||||
burn = function(self)
|
||||
local hp = self.object:get_hp() - BURN_DAMAGE__HP
|
||||
self.object:set_hp(hp)
|
||||
print("DEBUG - bag HP = "..hp)
|
||||
if hp <= 0 then
|
||||
return self:remove()
|
||||
end
|
||||
end
|
||||
})
|
||||
|
||||
local function rezEntity(stack, pos, player)
|
||||
local x = pos.x
|
||||
local y = math.floor(pos.y)
|
||||
local z = pos.z
|
||||
|
||||
while true do
|
||||
local node = minetest.env:get_node({ x = x, y = y-1, z = z})
|
||||
local nodeType = node and minetest.registered_nodes[node.name]
|
||||
if not nodeType or nodeType.walkable then
|
||||
break
|
||||
end
|
||||
y = y - 1
|
||||
end
|
||||
|
||||
local obj = minetest.env:add_entity(pos, "prestibags:bag_entity")
|
||||
if not obj then return stack end
|
||||
|
||||
local contentData = stack:get_metadata()
|
||||
local contents = deserializeContents(contentData)
|
||||
if contents then
|
||||
obj:get_luaentity().contents = contents
|
||||
end
|
||||
|
||||
obj:set_hp(BAG_MAX_HP - BAG_MAX_HP * stack:get_wear() / 2^16)
|
||||
|
||||
minetest.sound_play(
|
||||
DROP_BAG_SOUND,
|
||||
{
|
||||
object = obj,
|
||||
gain = DROP_BAG_SOUND_GAIN,
|
||||
max_hear_distance = DROP_BAG_SOUND_DIST,
|
||||
loop = false
|
||||
})
|
||||
|
||||
return ItemStack(nil)
|
||||
end
|
||||
|
||||
-- DEBUG minetest.register_craftitem(
|
||||
minetest.register_tool(
|
||||
"prestibags:bag",
|
||||
{
|
||||
description = "Bag of Stuff",
|
||||
groups = { bag = BAG_WIDTH*BAG_HEIGHT, flammable = 1 },
|
||||
inventory_image = "prestibags_bag_inv.png",
|
||||
wield_image = "prestibags_bag_wield.png",
|
||||
stack_max = 1,
|
||||
|
||||
on_place = function(stack, player, pointedThing)
|
||||
local pos = pointedThing and pointedThing.under
|
||||
local node = pos and minetest.env:get_node(pos)
|
||||
local nodeType = node and minetest.registered_nodes[node.name]
|
||||
if not nodeType or not nodeType.buildable_to then
|
||||
pos = pointedThing and pointedThing.above
|
||||
node = pos and minetest.env:get_node(pos)
|
||||
nodeType = node and minetest.registered_nodes[node.name]
|
||||
end
|
||||
if not pos then pos = player:getpos() end
|
||||
|
||||
return rezEntity(stack, pos, player)
|
||||
end,
|
||||
|
||||
on_drop = function(stack, player, pos)
|
||||
return rezEntity(stack, pos, player)
|
||||
end
|
||||
|
||||
-- Eventually add on_use(stack, player, pointedThing) which actually
|
||||
-- opens the bag from player inventory; trick is, has to track whether
|
||||
-- bag is still in inventory OR replace "player inventory" with a
|
||||
-- detached proxy that doesn't allow the bag's stack to be changed
|
||||
-- while open!
|
||||
})
|
Binary file not shown.
File diff suppressed because it is too large
Load Diff
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
After Width: | Height: | Size: 65 KiB |
Binary file not shown.
After Width: | Height: | Size: 39 KiB |
Binary file not shown.
After Width: | Height: | Size: 36 KiB |
Loading…
Reference in New Issue