drawers/lua/visual.lua

466 lines
13 KiB
Lua
Executable File

--[[
Minetest Mod Storage Drawers - A Mod adding storage drawers
Copyright (C) 2017-2019 Linus Jahn <lnj@kaidan.im>
MIT License
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
]]
-- Load support for intllib.
local MP = core.get_modpath(core.get_current_modname())
local S, NS = dofile(MP.."/intllib.lua")
core.register_entity("drawers:visual", {
initial_properties = {
hp_max = 1,
physical = false,
collide_with_objects = false,
collisionbox = {-0.4374, -0.4374, 0, 0.4374, 0.4374, 0}, -- for param2 0, 2
visual = "upright_sprite", -- "wielditem" for items without inv img?
visual_size = {x = 0.6, y = 0.6},
textures = {"blank.png"},
spritediv = {x = 1, y = 1},
initial_sprite_basepos = {x = 0, y = 0},
is_visible = true,
},
get_staticdata = function(self)
return core.serialize({
drawer_posx = self.drawer_pos.x,
drawer_posy = self.drawer_pos.y,
drawer_posz = self.drawer_pos.z,
texture = self.texture,
drawerType = self.drawerType,
visualId = self.visualId
})
end,
on_activate = function(self, staticdata, dtime_s)
-- Restore data
local data = core.deserialize(staticdata)
if data then
self.drawer_pos = {
x = data.drawer_posx,
y = data.drawer_posy,
z = data.drawer_posz,
}
self.texture = data.texture
self.drawerType = data.drawerType or 1
self.visualId = data.visualId or ""
-- backwards compatibility
if self.texture == "drawers_empty.png" then
self.texture = "blank.png"
end
else
self.drawer_pos = drawers.last_drawer_pos
self.texture = drawers.last_texture or "blank.png"
self.visualId = drawers.last_visual_id
self.drawerType = drawers.last_drawer_type
end
local node = minetest.get_node(self.object:get_pos())
if core.get_item_group(node.name, "drawer") == 0 then
self.object:remove()
return
end
-- add self to public drawer visuals
-- this is needed because there is no other way to get this class
-- only the underlying LuaEntitySAO
-- PLEASE contact me, if this is wrong
local vId = self.visualId
if vId == "" then vId = 1 end
local posstr = core.hash_node_position(self.drawer_pos)
if not drawers.drawer_visuals[posstr] then
drawers.drawer_visuals[posstr] = {[vId] = self}
else
drawers.drawer_visuals[posstr][vId] = self
end
-- get meta
self.meta = core.get_meta(self.drawer_pos)
-- collisionbox
node = core.get_node(self.drawer_pos)
local colbox
if self.drawerType ~= 2 then
if node.param2 == 1 or node.param2 == 3 then
colbox = {0, -0.4374, -0.4374, 0, 0.4374, 0.4374}
else
colbox = {-0.4374, -0.4374, 0, 0.4374, 0.4374, 0} -- for param2 = 0 or 2
end
-- only half the size if it's a small drawer
if self.drawerType > 1 then
for i,j in pairs(colbox) do
colbox[i] = j * 0.5
end
end
else
if node.param2 == 1 or node.param2 == 3 then
colbox = {0, -0.2187, -0.4374, 0, 0.2187, 0.4374}
else
colbox = {-0.4374, -0.2187, 0, 0.4374, 0.2187, 0} -- for param2 = 0 or 2
end
end
-- visual size
local visual_size = {x = 0.6, y = 0.6}
if self.drawerType >= 2 then
visual_size = {x = 0.3, y = 0.3}
end
-- drawer values
local vid = self.visualId
self.count = self.meta:get_int("count"..vid)
self.itemName = self.meta:get_string("name"..vid)
self.maxCount = self.meta:get_int("max_count"..vid)
self.itemStackMax = self.meta:get_int("base_stack_max"..vid)
self.stackMaxFactor = self.meta:get_int("stack_max_factor"..vid)
-- infotext
local infotext = self.meta:get_string("entity_infotext"..vid) .. "\n\n\n\n\n"
self.object:set_properties({
collisionbox = colbox,
infotext = infotext,
textures = {self.texture},
visual_size = visual_size
})
-- make entity undestroyable
self.object:set_armor_groups({immortal = 1})
end,
on_rightclick = function(self, clicker)
if core.is_protected(self.drawer_pos, clicker:get_player_name()) then
core.record_protection_violation(self.drawer_pos, clicker:get_player_name())
return
end
-- used to check if we need to play a sound in the end
local inventoryChanged = false
-- When the player uses the drawer with their bare hand all
-- stacks from the inventory will be added to the drawer.
if self.itemName ~= "" and
clicker:get_wielded_item():get_name() == "" and
not clicker:get_player_control().sneak then
-- try to insert all items from inventory
local i = 0
local inv = clicker:get_inventory()
while i <= inv:get_size("main") do
-- set current stack to leftover of insertion
local leftover = self.try_insert_stack(
self,
inv:get_stack("main", i),
true
)
-- check if something was added
if leftover:get_count() < inv:get_stack("main", i):get_count() then
inventoryChanged = true
end
-- set new stack
inv:set_stack("main", i, leftover)
i = i + 1
end
else
-- try to insert wielded item only
local leftover = self.try_insert_stack(
self,
clicker:get_wielded_item(),
not clicker:get_player_control().sneak
)
-- check if something was added
if clicker:get_wielded_item():get_count() > leftover:get_count() then
inventoryChanged = true
end
-- set the leftover as new wielded item for the player
clicker:set_wielded_item(leftover)
end
if inventoryChanged then
self:play_interact_sound()
end
end,
on_punch = function(self, puncher, time_from_last_punch, tool_capabilities, dir)
local node = minetest.get_node(self.object:get_pos())
if core.get_item_group(node.name, "drawer") == 0 then
self.object:remove()
return
end
local add_stack = not puncher:get_player_control().sneak
if core.is_protected(self.drawer_pos, puncher:get_player_name()) then
core.record_protection_violation(self.drawer_pos, puncher:get_player_name())
return
end
local inv = puncher:get_inventory()
if inv == nil then
return
end
local spaceChecker = ItemStack(self.itemName)
if add_stack then
spaceChecker:set_count(spaceChecker:get_stack_max())
end
if not inv:room_for_item("main", spaceChecker) then
return
end
local stack
if add_stack then
stack = self:take_stack()
else
stack = self:take_items(1)
end
if stack ~= nil then
-- add removed stack to player's inventory
inv:add_item("main", stack)
-- play the interact sound
self:play_interact_sound()
end
end,
take_items = function(self, removeCount)
local meta = core.get_meta(self.drawer_pos)
if self.count <= 0 then
return
end
if removeCount > self.count then
removeCount = self.count
end
local stack = ItemStack(self.itemName)
stack:set_count(removeCount)
-- update the drawer count
self.count = self.count - removeCount
self:updateInfotext()
self:updateTexture()
self:saveMetaData()
-- return the stack that was removed from the drawer
return stack
end,
take_stack = function(self)
return self:take_items(ItemStack(self.itemName):get_stack_max())
end,
can_insert_stack = function(self, stack)
if stack:get_name() == "" or stack:get_count() <= 0 then
return 0
end
-- don't allow unstackable stacks
if self.itemName == "" and stack:get_stack_max() ~= 1 then
return stack:get_count()
end
if self.itemName ~= stack:get_name() then
return 0
end
if (self.count + stack:get_count()) <= self.maxCount then
return stack:get_count()
end
return self.maxCount - self.count
end,
try_insert_stack = function(self, itemstack, insert_all)
local stackCount = itemstack:get_count()
local stackName = itemstack:get_name()
local insertCount = self:can_insert_stack(itemstack)
if insertCount == 0 then
return itemstack
end
-- only add one, if player holding sneak key
if not insert_all then
insertCount = 1
end
-- in case the drawer was empty, initialize count, itemName, maxCount
if self.itemName == "" then
self.count = 0
self.itemName = itemstack:get_name()
self.maxCount = itemstack:get_stack_max() * self.stackMaxFactor
self.itemStackMax = itemstack:get_stack_max()
end
-- update everything
self.count = self.count + insertCount
self:updateInfotext()
self:updateTexture()
self:saveMetaData()
-- return leftover
itemstack:take_item(insertCount)
if itemstack:get_count() == 0 then
return ItemStack("")
end
return itemstack
end,
updateInfotext = function(self)
local itemDescription = ""
if core.registered_items[self.itemName] then
itemDescription = core.registered_items[self.itemName].description
end
if self.count <= 0 then
self.itemName = ""
self.meta:set_string("name"..self.visualId, self.itemName)
self.texture = "blank.png"
itemDescription = S("Empty")
end
local infotext = drawers.gen_info_text(itemDescription,
self.count, self.stackMaxFactor, self.itemStackMax)
self.meta:set_string("entity_infotext"..self.visualId, infotext)
self.object:set_properties({
infotext = infotext .. "\n\n\n\n\n"
})
end,
updateTexture = function(self)
-- texture
self.texture = drawers.get_inv_image(self.itemName)
self.object:set_properties({
textures = {self.texture}
})
end,
dropStack = function(self, itemStack)
-- print warning if dropping higher stack counts than allowed
if itemStack:get_count() > itemStack:get_stack_max() then
core.log("warning", "[drawers] Dropping item stack with higher count than allowed")
end
-- find a position containing air
local dropPos = core.find_node_near(self.drawer_pos, 1, {"air"}, false)
-- if no pos found then drop on the top of the drawer
if not dropPos then
dropPos = self.object:get_pos()
dropPos.y = dropPos.y + 1
end
-- drop the item stack
core.item_drop(itemStack, nil, dropPos)
end,
dropItemOverload = function(self)
-- drop stacks until there are no more items than allowed
while self.count > self.maxCount do
-- remove the overflow
local removeCount = self.count - self.maxCount
-- if this is too much for a single stack, only take the
-- stack limit
if removeCount > self.itemStackMax then
removeCount = self.itemStackMax
end
-- remove this count from the drawer
self.count = self.count - removeCount
-- create a new item stack having the size of the remove
-- count
local stack = ItemStack(self.itemName)
stack:set_count(removeCount)
print(stack:to_string())
-- drop the stack
self:dropStack(stack)
end
end,
setStackMaxFactor = function(self, stackMaxFactor)
self.stackMaxFactor = stackMaxFactor
self.maxCount = self.stackMaxFactor * self.itemStackMax
-- will drop possible overflowing items
self:dropItemOverload()
self:updateInfotext()
self:saveMetaData()
end,
play_interact_sound = function(self)
core.sound_play("drawers_interact", {
pos = self.object:get_pos(),
max_hear_distance = 6,
gain = 2.0
})
end,
saveMetaData = function(self, meta)
self.meta:set_int("count"..self.visualId, self.count)
self.meta:set_string("name"..self.visualId, self.itemName)
self.meta:set_int("max_count"..self.visualId, self.maxCount)
self.meta:set_int("base_stack_max"..self.visualId, self.itemStackMax)
self.meta:set_int("stack_max_factor"..self.visualId, self.stackMaxFactor)
end
})
core.register_lbm({
name = "drawers:restore_visual",
nodenames = {"group:drawer"},
run_at_every_load = true,
action = function(pos, node)
local meta = core.get_meta(pos)
-- create drawer upgrade inventory
meta:get_inventory():set_size("upgrades", 5)
-- set the formspec
meta:set_string("formspec", drawers.drawer_formspec)
-- count the drawer visuals
local drawerType = core.registered_nodes[node.name].groups.drawer
local foundVisuals = 0
local objs = core.get_objects_inside_radius(pos, 0.56)
if objs then
for _, obj in pairs(objs) do
if obj and obj:get_luaentity() and
obj:get_luaentity().name == "drawers:visual" then
foundVisuals = foundVisuals + 1
end
end
end
-- if all drawer visuals were found, return
if foundVisuals == drawerType then
return
end
-- not enough visuals found, remove existing and create new ones
drawers.remove_visuals(pos)
drawers.spawn_visuals(pos)
end
})