-- Dinv init.lua -- Copyright Duane Robertson (duane@duanerobertson.com), 2019 -- Distributed under the LGPLv2.1 (https://www.gnu.org/licenses/old-licenses/lgpl-2.1.en.html) dinv = {} local mod = dinv local mod_name = 'dinv' mod.version = '20190720' mod.path = minetest.get_modpath(minetest.get_current_modname()) mod.world = minetest.get_worldpath() mod.dat = {} local worn_inv = 'worn' local sorted_items minetest.register_craftitem(mod_name..':bag_small', { inventory_image = 'bags_small.png', stack_max = 1, }) minetest.register_craftitem(mod_name..':bag_medium', { inventory_image = 'bags_medium.png', stack_max = 1, }) minetest.register_craftitem(mod_name..':bag_large', { inventory_image = 'bags_large.png', stack_max = 1, }) minetest.register_tool(mod_name..':leather_armor', { inventory_image = 'leather_armour1.png', description = 'Leather Armor', _dinv_armor = 0.9, _dinv_location = 'body', _dinv_texture = 'dinv_char_leather_armor.png', }) minetest.register_tool(mod_name..':boots', { inventory_image = 'boots1_brown.png', description = 'Sturdy Boots', _dinv_armor = 0.9, _dinv_location = 'feet', _dinv_texture = 'dinv_char_boots.png', }) minetest.register_tool(mod_name..':leather_cap', { inventory_image = 'elven_leather_helm.png', description = 'Leather Cap', _dinv_armor = 0.9, _dinv_location = 'head', _dinv_texture = 'dinv_char_leather_helm.png', }) minetest.register_tool(mod_name..':steel_helmet', { inventory_image = 'helmet1.png', description = 'Steel Helmet', _dinv_armor = 0.8, _dinv_location = 'head', --_dinv_texture = '', }) minetest.register_tool(mod_name..':wood_shield', { inventory_image = 'buckler1.png', description = 'Wooden Shield', _dinv_armor = 0.8, _dinv_location = 'arm', _dinv_texture = 'dinv_char_wood_shield.png', }) minetest.register_tool(mod_name..':steel_shield', { inventory_image = 'lshield_dd_dk.png', description = 'Steel Shield', _dinv_armor = 0.7, _dinv_location = 'arm', --_dinv_texture = '', }) minetest.register_tool(mod_name..':chain_armor', { inventory_image = 'chain_mail1.png', description = 'Chain Mail', _dinv_armor = 0.75, _dinv_location = 'body', _dinv_texture = 'dinv_char_chain_armor.png', }) minetest.register_tool(mod_name..':plate_armor', { inventory_image = 'plate1.png', description = 'Plate Mail', _dinv_armor = 0.6, _dinv_location = 'body', --_dinv_texture = '', }) minetest.register_tool(mod_name..':diamond_plate_armor', { inventory_image = 'crystal_plate2.png', description = 'Diamond Plate Mail', _dinv_armor = 0.45, _dinv_location = 'body', --_dinv_texture = '', }) minetest.register_tool(mod_name..':fur_cloak', { inventory_image = 'dinv_fur_cloak.png', description = 'Fur Cloak', _dinv_armor = 0.98, _dinv_warmth = 2, _dinv_location = 'back', --_dinv_texture = '', }) --print(dump(minetest.registered_tools[mod_name..':plate_armor'])) minetest.register_craft({ output = mod_name..':bag_small', recipe = { {'', 'farming:string', ''}, {'group:wool', 'group:wool', 'group:wool'}, {'group:wool', 'group:wool', 'group:wool'}, }, }) minetest.register_craft({ output = mod_name..':bag_medium', recipe = { {'', '', ''}, {'farming:string', mod_name..':bag_small', 'farming:string'}, {'', mod_name..':bag_small', ''}, }, }) minetest.register_craft({ output = mod_name..':bag_large', recipe = { {'', '', ''}, {'farming:string', mod_name..':bag_medium', 'farming:string'}, {'', mod_name..':bag_medium', ''}, }, }) minetest.register_craft({ output = mod_name..':leather_armor', recipe = { {'', 'mobs:leather', 'mobs:wax'}, {'mobs:leather', 'farming:string', 'mobs:leather'}, {'mobs:leather', 'farming:string', 'mobs:leather'}, }, }) minetest.register_craft({ output = mod_name..':fur_cloak', recipe = { {'', 'mobs:fur', ''}, {'mobs:fur', 'farming:string', 'mobs:fur'}, {'mobs:fur', '', 'mobs:fur'}, }, }) minetest.register_craft({ output = mod_name..':leather_cap', recipe = { {'', 'mobs:leather', ''}, {'', 'mobs:leather', 'mobs:wax'}, {'', 'farming:string', ''}, }, }) minetest.register_craft({ output = mod_name..':steel_helmet', recipe = { {'', 'default:steel_ingot', ''}, {'default:steel_ingot', 'mobs:leather', 'default:steel_ingot'}, {'', 'mobs:fur', ''}, }, }) minetest.register_craft({ output = mod_name..':boots', recipe = { {'', '', ''}, {'', 'farming:string', ''}, {'mobs:leather', 'farming:string', 'mobs:leather'}, }, }) minetest.register_craft({ output = mod_name..':wood_shield', recipe = { {'', 'group:wood', ''}, {'group:wood', 'mobs:leather', 'group:wood'}, {'', 'group:wood', ''}, }, }) minetest.register_craft({ output = mod_name..':steel_shield', recipe = { {'', mod_name..':steel_plate', ''}, { mod_name..':steel_plate', 'mobs:leather', mod_name..':steel_plate'}, {'', mod_name..':steel_plate', ''}, }, }) minetest.register_craftitem(mod_name..':steel_rings', { inventory_image = 'dinv_steel_rings.png', }) minetest.register_craftitem(mod_name..':steel_plate', { inventory_image = 'dinv_steel_plate.png', }) minetest.register_craftitem(mod_name..':diamond_plate', { inventory_image = 'dinv_diamond_plate.png', }) minetest.register_craft({ output = mod_name..':steel_rings', recipe = { {'', '', ''}, {'default:steel_ingot', 'default:coal_lump', 'default:steel_ingot'}, {'', 'default:steel_ingot', ''}, }, }) minetest.register_craft({ output = mod_name..':steel_plate', recipe = { {'default:steel_ingot', 'default:steel_ingot', 'default:steel_ingot'}, {'default:steel_ingot', 'default:coal_lump', 'default:steel_ingot'}, {'default:steel_ingot', 'default:steel_ingot', 'default:steel_ingot'}, }, }) minetest.register_craft({ output = mod_name..':diamond_plate', recipe = { {'default:diamond', 'default:diamond', 'default:diamond'}, {'default:diamond', 'default:mese_crystal', 'default:diamond'}, {'default:diamond', 'default:diamond', 'default:diamond'}, }, }) minetest.register_craft({ output = mod_name..':chain_armor', recipe = { {'', mod_name..':steel_rings', ''}, {mod_name..':steel_rings', 'mobs:leather', mod_name..':steel_rings'}, {mod_name..':steel_rings', 'mobs:leather', mod_name..':steel_rings'}, }, }) minetest.register_craft({ output = mod_name..':plate_armor', recipe = { {'', mod_name..':steel_plate', ''}, {mod_name..':steel_plate', mod_name..':chain_armor', mod_name..':steel_plate'}, {mod_name..':steel_plate', 'default:steel_ingot', mod_name..':steel_plate'}, }, }) minetest.register_craft({ output = mod_name..':diamond_plate_armor', recipe = { {'', mod_name..':diamond_plate', ''}, {mod_name..':diamond_plate', mod_name..':chain_armor', mod_name..':diamond_plate'}, {mod_name..':diamond_plate', 'default:mese', mod_name..':diamond_plate'}, }, }) mod.bag_sizes = { [mod_name..':bag_small'] = 8, [mod_name..':bag_medium'] = 16, [mod_name..':bag_large'] = 24, [mod_name..':bag_huge'] = 32, [mod_name..':bag_hole'] = 64, } mod.wearable = { } do for k, v in pairs(mod.bag_sizes) do mod.wearable[k] = true end for k, v in pairs(minetest.registered_items) do if v._dinv_armor then mod.wearable[k] = true end end end local force_rep = { ['wood'] = 'default:wood', ['leaves'] = 'default:leaves', ['stone'] = 'default:stone', ['wool'] = 'wool:white', } -- This tables looks up groups that aren't already stored. mod.group_rep = setmetatable({}, { __index = function(t, k) if not (t and k and type(k) == 'string' and type(t) == 'table') then return end local k = k:gsub('^group:', '') local r if force_rep[k] then r = force_rep[k] else for a, b in pairs(minetest.registered_items) do if b.groups and b.groups[k] then r = a break end end end if r then t[k] = r return t[k] else return end end }) local group_rep = mod.group_rep -- iterator over worn inventory function mod.worn_items(player) if not player then return end local pinv = player:get_inventory() if not pinv then return end local flist = pinv:get_list(worn_inv) return pairs(flist) end local worn_items = mod.worn_items mod.form_size = 'size[11.25,7.75]' mod.main_inventory = 'list[current_player;main;0,4;8,4;' mod.craft_inventory = 'list[current_player;craft;3,0;3,3;]' mod.craft_preview = 'list[current_player;craftpreview;6,1;1,1;]' mod.main_inventory_scroll_up = 'image_button[8,4;1,1;transparent_button.png;dinv_main_inventory_up;Up]' mod.main_inventory_scroll_down = 'image_button[8,7;1,1;transparent_button.png;dinv_main_inventory_down;Down]' mod.recipe_buttons = 'image_button[3,3;1,1;transparent_button.png;dinv_recipe_back;Back]image_button[5,3;1,1;transparent_button.png;dinv_recipe_fore;Fore]' mod.worn_items_inv = 'list[current_player;worn;9.25,4;2,4;]' function mod.make_inventory_spec(player) if not player then return end local player_name = player:get_player_name() local dat = mod.dat[player_name] local scroll_main_to = dat['scroll_main_to'] or 0 local inventory = '' inventory = inventory .. mod.form_size inventory = inventory .. mod.main_inventory .. scroll_main_to .. ']' inventory = inventory .. mod.craft_inventory inventory = inventory .. 'listring[]' inventory = inventory .. mod.recipe_buttons inventory = inventory .. mod.craft_preview inventory = inventory .. mod.main_inventory_scroll_up inventory = inventory .. mod.main_inventory_scroll_down inventory = inventory .. mod.worn_items_inv inventory = inventory .. mod.recipe_grid(player) inventory = inventory .. mod.recipe_list(player) return inventory end function default.chest.get_chest_formspec(pos, player) local scroll_to = 8 if player and player.get_player_name then local player_name = player:get_player_name() if mod.dat[player_name].scroll_main_to then scroll_to = mod.dat[player_name].scroll_main_to end end local spos = pos.x .. ',' .. pos.y .. ',' .. pos.z local formspec = 'size[9,9]' .. 'list[nodemeta:' .. spos .. ';main;0,0.3;8,4;]' .. 'list[current_player;main;0,4.85;8,1;]' .. 'list[current_player;main;0,6.08;8,3;' .. scroll_to .. ']' .. 'listring[nodemeta:' .. spos .. ';main]' .. 'listring[current_player;main]' .. 'image_button[8,6.08;1,1;transparent_button.png;dinv_chest_main_inventory_up;Up]' .. 'image_button[8,8.08;1,1;transparent_button.png;dinv_chest_main_inventory_down;Down]' .. default.get_hotbar_bg(0,4.85) return formspec end local original_chest_functions = {} for _, nd in pairs({ 'default:chest', 'default:chest_locked' }) do original_chest_functions[nd] = {} original_chest_functions[nd].on_rightclick = minetest.registered_items[nd].on_rightclick minetest.override_item(nd, { on_rightclick = function(pos, node, clicker, itemstack, pointed_thing) if clicker.get_player_name then local player_name = clicker:get_player_name() local dat = mod.dat[player_name] if dat then dat.chest_opened = pos end end original_chest_functions[nd].on_rightclick(pos, node, clicker, itemstack, pointed_thing) end }) end minetest.register_on_player_receive_fields(function(player, formname, fields) if formname ~= 'default:chest' then return end if not (player and fields) then return end if not ( fields['dinv_chest_main_inventory_up'] or fields['dinv_chest_main_inventory_down'] ) then return end local player_name = player:get_player_name() local dat = mod.dat[player_name] or {} local pos = dat.chest_opened if not pos then return end local pinv = player:get_inventory() local main_inventory_size = pinv:get_size('main') if fields['dinv_chest_main_inventory_up'] then dat['scroll_main_to'] = math.max(8, (dat['scroll_main_to'] or 8) - 16) minetest.show_formspec(player_name, 'default:chest', default.chest.get_chest_formspec(pos, player)) elseif fields['dinv_chest_main_inventory_down'] then dat['scroll_main_to'] = (dat['scroll_main_to'] or 8) + 16 if dat['scroll_main_to'] >= main_inventory_size then dat['scroll_main_to'] = 8 end minetest.show_formspec(player_name, 'default:chest', default.chest.get_chest_formspec(pos, player)) end end) function mod.recipe_grid(player) local player_name = player:get_player_name() local dat = mod.dat[player_name] local craft_item_number = dat['craft_item_number'] local recipe_number = dat['recipe_number'] or 1 if not craft_item_number then return '' end local craft_item = sorted_items[craft_item_number] if not craft_item then return '' end local recipes = minetest.get_all_craft_recipes(craft_item) if not recipes then return '' end if recipe_number > #recipes then recipe_number = 1 dat['recipe_number'] = recipe_number end local recipe = recipes[recipe_number] --print(dump(recipe)) local inv = '' inv = inv .. 'container[3,0]' local w = recipe.width or 3 if w == 0 or w > 3 then w = 3 end local g_s = {} local i = 0 for y = 0, 2 do for x = 1, w do i = i + 1 local item, group if recipe.type == 'normal' then item = recipe.items[(y * w) + x] end if item and item:find('^group') then --print(item) item = group_rep[item] if item then g_s[i] = true end end if item then inv = inv .. 'item_image[' .. (x - 1) .. ',' .. (y) .. ';1,1;' .. item .. ']' end end end inv = inv .. 'container_end[]' inv = inv .. 'container[3,0]' for i = 1, 9 do if g_s[i] then local x = (i - 1) % w local y = math.floor((i - 1) / w) inv = inv .. 'image[' .. (x - 0) .. ',' .. (y) .. ';1,1;big_g.png]' end end inv = inv .. 'container_end[]' return inv end function mod.recipe_list(player) if not sorted_items then sorted_items = {} for k, v in pairs(minetest.registered_items) do if k and k ~= '' and minetest.get_all_craft_recipes(k) then local recs = minetest.get_all_craft_recipes(k) for a, b in pairs(recs) do if b.type ~= 'cooking' then table.insert(sorted_items, k) break end end end end table.sort(sorted_items) end local inv = '' inv = inv .. 'textlist[7,0;4,3.5;dinv_recipe_list;' local t for k, v in pairs(sorted_items) do if t then inv = inv .. ',' end inv = inv .. v --print(v) t = true end inv = inv .. ';' return inv end function mod.scroll_main(player, amount, max) if not (player and amount) then return end local player_name = player:get_player_name() if not player_name then return end local scroll = mod.dat[player_name].scroll_main_to or 0 if scroll and scroll % 8 == 0 then scroll = (scroll + amount) if scroll > max or scroll < 0 then scroll = 0 end mod.dat[player_name].scroll_main_to = scroll player:set_inventory_formspec(mod.make_inventory_spec(player)) end end function mod.get_main_size_by_bags(player) local isize = 32 for k, v in worn_items(player) do local vs = v:get_name() if mod.bag_sizes[vs] then isize = isize + mod.bag_sizes[vs] end end return isize end function mod.set_main_size_by_bags(player) if not player then return end local pinv = player:get_inventory() if not pinv then return end local prsize = pinv:get_size('main') local isize = mod.get_main_size_by_bags(player) --[[ if isize < prsize then print('*** Preventing lost inventory from reducing bag sizes') isize = prsize end --]] if not pinv:set_size('main', isize) then print('*** ERROR setting inventory size.') end return isize end minetest.register_on_joinplayer(function(player) local player_name = player:get_player_name() if not mod.dat[player_name] then mod.dat[player_name] = {} end local pinv = player:get_inventory() pinv:set_size(worn_inv, 8) mod.set_main_size_by_bags(player) mod.set_armor(player) mod.set_armor_textures(player) player:set_inventory_formspec(mod.make_inventory_spec(player)) end) function mod.show_recipe(player, field) if not (player and field) then return end local player_name = player:get_player_name() if not player_name then return end if field:find('CHG') then local n = field:gsub('.*CHG:(%d+).*', '%1') if n then mod.dat[player_name].craft_item_number = tonumber(n) mod.dat[player_name].recipe_number = 1 player:set_inventory_formspec(mod.make_inventory_spec(player)) end end end function mod.switch_recipe(player, amount) if not (player and amount) then return end local player_name = player:get_player_name() if not player_name then return end local dat = mod.dat[player_name] if not dat.recipe_number then dat.recipe_number = 1 end dat.recipe_number = dat.recipe_number + amount if dat.recipe_number < 1 then dat.recipe_number = 1 end --print(dat.recipe_number) player:set_inventory_formspec(mod.make_inventory_spec(player)) end minetest.register_on_player_receive_fields(function(player, formname, fields) if not (player and fields) then return end if not ( fields['dinv_main_inventory_up'] or fields['dinv_main_inventory_down'] or fields['dinv_recipe_list'] or fields['dinv_recipe_back'] or fields['dinv_recipe_fore'] ) then return end local pinv = player:get_inventory() local main_inventory_size = pinv:get_size('main') if fields and fields['dinv_main_inventory_up'] then mod.scroll_main(player, -16, main_inventory_size) elseif fields and fields['dinv_main_inventory_down'] then mod.scroll_main(player, 16, main_inventory_size) elseif fields and fields['dinv_recipe_list'] then mod.show_recipe(player, fields['dinv_recipe_list']) elseif fields and fields['dinv_recipe_back'] then mod.switch_recipe(player, -1) elseif fields and fields['dinv_recipe_fore'] then mod.switch_recipe(player, 1) end end) minetest.register_allow_player_inventory_action(function(player, action, inventory, inventory_info) if not (player and action and inventory and inventory_info) then return end if not (inventory_info.from_list == worn_inv or inventory_info.to_list == worn_inv or inventory_info.listname == worn_inv) then return end local item_from, item_from_s if action == 'move' and inventory_info.from_list then item_from = inventory:get_stack(inventory_info.from_list, inventory_info.from_index) else item_from = inventory_info.stack end if item_from and item_from.get_name then item_from_s = item_from:get_name() end local item_to, item_to_s if action == 'move' and inventory_info.to_list then item_to = inventory:get_stack(inventory_info.to_list, inventory_info.to_index) end if item_to and item_to.get_name then item_to_s = item_to:get_name() end --print(dump(inventory_info.from_list), dump(inventory_info.to_list)) --print(dump(item_from_s), dump(item_to_s)) --print(action) if action == 'move' then if not mod.wearable[item_from_s] then if inventory_info.to_list == worn_inv then return 0 else return end end if item_to_s and item_to_s ~= '' then return 0 end if inventory_info.to_list == inventory_info.from_list then return end local item_from_it = minetest.registered_items[item_from_s] if item_from_it._dinv_location and inventory_info.to_list == worn_inv and mod.wearing_on_location(player, item_from_it._dinv_location) then return 0 end if mod.bag_sizes[item_from_s] then local prsize = mod.get_main_size_by_bags(player) local isize = prsize if inventory_info.to_list == worn_inv then isize = isize + mod.bag_sizes[item_from_s] elseif inventory_info.from_list == worn_inv then isize = isize - mod.bag_sizes[item_from_s] end if isize < prsize and mod.items_at_range(inventory, 'main', isize, prsize) then return 0 elseif inventory_info.to_index > isize then return 0 end end elseif action == 'take' and mod.wearable[item_from_s] then return 0 elseif action == 'put' then return 0 end end) function mod.wearing_on_location(player, loc) for k, v in worn_items(player) do local vs = v:get_name() local it = minetest.registered_items[vs] if it._dinv_location == loc then return true end end end function mod.get_warmth(player) if not player then return end local warmth = 0 for k, v in worn_items(player) do local vs = v:get_name() local it = minetest.registered_items[vs] if it._dinv_warmth then warmth = warmth + it._dinv_warmth end end return warmth end function mod.set_armor_textures(player) local prop = player:get_properties() local textures = prop.textures --local tex = (textures and textures[1] or 'character.png') local tex = 'character.png' local pile = {} for k, v in worn_items(player) do local vs = v:get_name() local it = minetest.registered_items[vs] if it._dinv_location and it._dinv_texture then pile[it._dinv_location] = it._dinv_texture end end for _, loc in pairs({ 'body', 'feet', 'head', 'arm', 'back' }) do if pile[loc] then tex = tex .. '^' .. pile[loc] end end textures = { tex } player_api.set_textures(player, textures) end function mod.set_armor(player) if not player then return end local armor = 100 for k, v in worn_items(player) do local vs = v:get_name() local it = minetest.registered_items[vs] --print(dump(it)) if it._dinv_armor then armor = armor * it._dinv_armor end end print('armor = ', armor) local armor_g = player:get_armor_groups() if not (armor_g and armor_g.fleshy) then return end armor_g.fleshy = armor --print(dump(armor_g)) player:set_armor_groups(armor_g) end function mod.damage_armor(player, damage) local wear = (damage + 1) * 100 for k, v in worn_items(player) do local vs = v:get_name() local it = minetest.registered_items[vs] if it._dinv_armor then local ow = v:get_wear() v:add_wear(wear) player:get_inventory():set_stack(worn_inv, k, v) if ow + wear > 65535 then mod.set_armor(player) mod.set_armor_textures(player) end end end end minetest.register_on_punchplayer(function(player, hitter, time_from_last_punch, tool_capabilities, dir, damage) mod.damage_armor(player, damage) end) minetest.register_on_player_inventory_action(function(player, action, inventory, inventory_info) if not (player and action and inventory and inventory_info) then return end if not (inventory_info.from_list == worn_inv or inventory_info.to_list == worn_inv or inventory_info.listname == worn_inv) then return end local item if action == 'move' then if not (inventory_info.to_list and inventory_info.to_index) then return end item = inventory:get_stack(inventory_info.to_list, inventory_info.to_index) else item = inventory_info.stack end if not item then return end local item_s = item:get_name() if not mod.wearable[item_s] then return end mod.set_main_size_by_bags(player) mod.set_armor(player) mod.set_armor_textures(player) end) function mod.items_at_range(inv, name, i, j) if not (inv and name and i and j) then return end local a = inv:get_list(name) if not a then return end for ind = i + 1, j do if a[ind] and a[ind]:get_name() ~= '' then return true end end end