Initial content

master
prestidigitator 2013-03-07 15:48:55 -08:00
parent e57d59b20c
commit 1df24a2d22
14 changed files with 17934 additions and 4 deletions

View File

@ -1,4 +0,0 @@
minetest-mod-prestibags
=======================
Prestibags are simple bags that act like chests you can pick up.

57
README.txt Normal file
View File

@ -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

414
init.lua Normal file
View File

@ -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!
})

BIN
models/prestibags_bag.blend Normal file

Binary file not shown.

17463
models/prestibags_bag.obj Normal file

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.

BIN
textures/prestibags_bag.png Normal file

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