From 8a764a5cbb96693979a4d0a5f121ecd1676f9552 Mon Sep 17 00:00:00 2001 From: Duane Robertson Date: Sun, 28 Jul 2019 06:40:13 -0500 Subject: [PATCH] Separate recipes. --- init.lua | 1352 +------------------------------------------------ inventory.lua | 1019 +++++++++++++++++++++++++++++++++++++ recipes.lua | 347 +++++++++++++ 3 files changed, 1368 insertions(+), 1350 deletions(-) create mode 100644 inventory.lua create mode 100644 recipes.lua diff --git a/init.lua b/init.lua index e7e72db..2439474 100644 --- a/init.lua +++ b/init.lua @@ -14,1353 +14,5 @@ mod.dat = {} mod.magic_ingredient = 'default:mese_crystal' -- Hmmm.... ? -local sorted_items - - -local ADD_SCROLL_TO_DEFAULT_CHEST = true -local FLIGHT_PRIV = false -local GRAVITY_OFF = { gravity = 0.1 } -local GRAVITY_ON = { gravity = 1 } -local PROTECT_INVENTORY = false -- Prevent ANY inventory loss. -local WORN_INV = 'worn' - - -minetest.register_craftitem(mod_name..':bag_medium', { - description = 'Medium Bag', - inventory_image = 'bags_medium.png', - stack_max = 1, - _dinv_storage_size = 16, -}) - -minetest.register_craft({ - output = mod_name..':bag_medium', - recipe = { - {'', '', ''}, - {'farming:string', mod_name..':bag_small', 'farming:string'}, - {'', mod_name..':bag_small', ''}, - }, -}) - -minetest.register_craftitem(mod_name..':bag_large', { - description = 'Large Bag', - inventory_image = 'bags_large.png', - stack_max = 1, - _dinv_storage_size = 24, -}) - -minetest.register_craft({ - output = mod_name..':bag_large', - recipe = { - {'', '', ''}, - {'farming:string', mod_name..':bag_medium', 'farming:string'}, - {'', mod_name..':bag_medium', ''}, - }, -}) - -minetest.register_craftitem(mod_name..':bag_small', { - description = 'Small Bag', - inventory_image = 'bags_small.png', - stack_max = 1, - _dinv_storage_size = 8, -}) - -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_tool(mod_name..':boots', { - inventory_image = 'boots1_brown.png', - description = 'Sturdy Boots', - _dinv_armor = 0.95, - _dinv_location = 'feet', - _dinv_texture = 'dinv_char_boots.png', -}) - -minetest.register_craft({ - output = mod_name..':boots', - recipe = { - {'', '', ''}, - {'', 'farming:string', ''}, - {'mobs:leather', 'farming:string', 'mobs:leather'}, - }, -}) - -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_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_craftitem(mod_name..':diamond_plate', { - description = 'Diamond Plate', - inventory_image = 'dinv_diamond_plate.png', -}) - -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_tool(mod_name..':diamond_plate_armor', { - inventory_image = 'crystal_plate2.png', - description = 'Diamond Plate Mail', - _dinv_armor = 0.45, - _dinv_location = 'body', - _dinv_texture = 'dinv_char_diamond_plate_armor.png', -}) - -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'}, - }, -}) - -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 = 'dinv_char_fur_cloak.png', -}) - -minetest.register_craft({ - output = mod_name..':fur_cloak', - recipe = { - {'', 'mobs:fur', ''}, - {'mobs:fur', 'farming:string', 'mobs:fur'}, - {'mobs:fur', '', 'mobs:fur'}, - }, -}) - -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_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_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_craft({ - output = mod_name..':leather_cap', - recipe = { - {'', 'mobs:leather', ''}, - {'', 'mobs:leather', 'mobs:wax'}, - {'', 'farming:string', ''}, - }, -}) - -minetest.register_tool(mod_name..':plate_armor', { - inventory_image = 'plate1.png', - description = 'Plate Mail', - _dinv_armor = 0.6, - _dinv_location = 'body', - _dinv_texture = 'dinv_char_plate_armor.png', -}) - -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_tool(mod_name..':steel_helmet', { - inventory_image = 'helmet1.png', - description = 'Steel Helmet', - _dinv_armor = 0.8, - _dinv_location = 'head', - _dinv_texture = 'dinv_char_steel_helm.png', -}) - -minetest.register_craft({ - output = mod_name..':steel_helmet', - recipe = { - {'', 'default:steel_ingot', ''}, - {'default:steel_ingot', 'mobs:leather', 'default:steel_ingot'}, - {'', 'mobs:fur', ''}, - }, -}) - -minetest.register_craftitem(mod_name..':steel_plate', { - description = 'Steel Plate', - inventory_image = 'dinv_steel_plate.png', -}) - -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_craftitem(mod_name..':steel_rings', { - description = 'Steel Rings', - inventory_image = 'dinv_steel_rings.png', -}) - -minetest.register_craft({ - output = mod_name..':steel_rings', - recipe = { - {'', '', ''}, - {'default:steel_ingot', 'default:coal_lump', 'default:steel_ingot'}, - {'', 'default:steel_ingot', ''}, - }, -}) - -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 = 'dinv_char_steel_shield.png', -}) - -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_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_craft({ - output = mod_name..':wood_shield', - recipe = { - {'', 'group:wood', ''}, - {'group:wood', 'mobs:leather', 'group:wood'}, - {'', 'group:wood', ''}, - }, -}) - - -minetest.register_tool(mod_name..':ring_breath', { - inventory_image = 'coral.png', - description = 'Ring of Breath', - _dinv_wears_out = 91, -- 1 hour - _dinv_while_worn = function(player) - player:set_breath(11) - end, -}) - -minetest.register_craft({ - type = 'shapeless', - output = mod_name..':ring_breath', - recipe = { - mod.magic_ingredient, - 'default:coral_skeleton', - 'default:gold_ingot', - }, -}) - -if FLIGHT_PRIV then - minetest.register_tool(mod_name..':ring_flight', { - inventory_image = 'glass.png', - description = 'Ring of Flight', - _dinv_wears_out = 546, -- 10 minutes - _dinv_on_wear = function(player) - mod.modify_privs(player, { fly = true, noclip = 0 }) - end, - _dinv_on_remove = function(player) - mod.modify_privs(player, { fly = 0, noclip = 0 }) - end, - }) -end - -minetest.register_tool(mod_name..':ring_leap', { - inventory_image = 'tiger_eye.png', - description = 'Ring of Leaping', - _dinv_wears_out = 273, -- 20 minutes - _dinv_on_wear = function(player) - player:set_physics_override(GRAVITY_OFF) - end, - _dinv_on_remove = function(player) - player:set_physics_override(GRAVITY_ON) - end, -}) - -minetest.register_craft({ - type = 'shapeless', - output = mod_name..':ring_leap', - recipe = { - mod.magic_ingredient, - 'flowers:mushroom_red', - 'default:gold_ingot', - }, -}) - -minetest.register_tool(mod_name..':ring_protection_9', { - inventory_image = 'anvil.png', - description = 'Ring of Protection', - _dinv_location = 'protection_finger', -- Only one per person! - _dinv_armor = 0.9, -}) - -minetest.register_craft({ - type = 'shapeless', - output = mod_name..':ring_protection_9', - recipe = { - mod.magic_ingredient, - 'default:diamond', - 'default:gold_ingot', - }, -}) - - - -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 - - - --- Formspec definitions are confusing. -mod.form_size = 'size[11.25,7.25]' -mod.main_inventory = 'list[current_player;main;2.25,3.5;8,4;' -mod.craft_inventory = 'list[current_player;craft;3.25,0;3,3;]' -mod.craft_preview = 'list[current_player;craftpreview;6.25,2;1,1;]' -mod.main_inventory_scroll_up = 'image_button[10.25,3.5;1,1;transparent_button.png;dinv_main_inventory_up;Up]' -mod.main_inventory_scroll_down = 'image_button[10.25,6.5;1,1;transparent_button.png;dinv_main_inventory_down;Down]' -mod.recipe_button = 'image_button[6.25,1;1,1;transparent_button.png;dinv_recipe_next;Next]' -mod.worn_items_inv = 'list[current_player;worn;0,3.5;2,4;]' -mod.empty_button = 'image_button[2.25,2;1,1;transparent_button.png;dinv_empty_craft;Empty]' -mod.fill_button = 'image_button[2.25,1;1,1;transparent_button.png;dinv_fill_craft;Fill]' - - --- All this is necessary to add scroll buttons to chests. -if ADD_SCROLL_TO_DEFAULT_CHEST then - 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) -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 - - -function mod.empty_craft(player) - local pinv = player:get_inventory() - local craft_size = pinv:get_size('craft') - - for i = 1, craft_size do - local st = pinv:get_stack('craft', i) - if pinv:room_for_item('main', st) then - pinv:add_item('main', st) - pinv:set_stack('craft', i, nil) - else - return - end - end - - return true -end - - --- Calculate how big the main inventory should be. -function mod.get_main_size_by_bags(player) - local isize = 32 - - for k, v in worn_items(player) do - local vs = v:get_name() - local it = minetest.registered_items[vs] - if it._dinv_storage_size then - isize = isize + it._dinv_storage_size - end - end - - return isize -end - - --- Any warmth over zero is sufficient at the moment. -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 - - --- Check if there are any stacks in the given inventory, --- at the given positions. This is important to avoid losing --- those items when a bag is removed. -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 - - --- The main formspec... -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.worn_items_inv - inventory = inventory .. mod.empty_button - inventory = inventory .. mod.fill_button - inventory = inventory .. mod.main_inventory .. scroll_main_to .. ']' - inventory = inventory .. mod.craft_inventory - inventory = inventory .. 'listring[]' - inventory = inventory .. mod.craft_preview - inventory = inventory .. mod.main_inventory_scroll_up - inventory = inventory .. mod.main_inventory_scroll_down - inventory = inventory .. mod.recipe_grid(player) - inventory = inventory .. mod.recipe_list(player) - - return inventory -end - - --- Change one or more privileges. Set a privilege value --- to 0, in the input table, to remove it. ------------------------------------------------ --- Note that this can remove granted privileges. ------------------------------------------------ -function mod.modify_privs(player, p) - local player_name = player:get_player_name() - local privs = minetest.get_player_privs(player_name) or {} - - for k, v in pairs(p) do - if v == 0 then - privs[k] = nil - else - privs[k] = v - end - end - - minetest.set_player_privs(player_name, privs) -end - - --- Quick table of all wearable items. ------------------------------------------------------ --- Populating this after the game starts might cause --- problems, so I do it twice, just in case. ------------------------------------------------------ -mod.wearable = { } -function mod.populate_wearable_table() - for k, v in pairs(minetest.registered_items) do - if v._dinv_armor or v._dinv_storage_size - or v._dinv_on_wear or v._dinv_on_remove - or v._dinv_while_worn then - mod.wearable[k] = true - end - end -end -mod.populate_wearable_table() -minetest.after(0, function() - mod.populate_wearable_table() -end) - - -function mod.get_all_craft_recipes(craft_item) - local recipes = {} - for k, v in pairs(minetest.get_all_craft_recipes(craft_item) or {}) do - if v.type ~= 'cooking' then - table.insert(recipes, v) - end - end - - if #recipes < 1 then - return - end - - return recipes -end - - --- Return a 3 x 3 grid of images matching the selected recipe. --- This appears over then actual craft grid, but won't interfere --- with it. -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 = mod.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] - - local inv = '' - inv = inv .. 'container[3.25,0]' - - do - local tooltip = craft_item - inv = inv .. 'item_image[3,2;1,1;' .. craft_item .. ']' - tooltip = minetest.registered_items[craft_item].description or tooltip - inv = inv .. 'tooltip[3,2;0.8,0.8;' .. 'recipe: ' .. tooltip .. ']' - end - - 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 - local tooltip = item - if item and item:find('^group') then - --print(item) - item = group_rep[item] - if item then - g_s[i] = true - end - elseif item then - tooltip = minetest.registered_items[item].description or tooltip - end - if item then - inv = inv .. 'item_image[' .. (x - 1) .. ',' .. (y) .. ';1,1;' .. item .. ']' - inv = inv .. 'tooltip[' .. (x - 1) .. ',' .. (y) .. ';0.8,0.8;' .. 'recipe: ' .. tooltip .. ']' - end - end - end - - 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[]' - - if #recipes > 1 then - inv = inv .. mod.recipe_button - end - - return inv -end - - --- Return a listbox filled with items that can be crafted. -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 mod.get_all_craft_recipes(k) then - local recs = mod.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.25,0;3.75,2.9;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 - - --- Recreate the inventory formspec when the player scrolls --- up or down in the main inventory. -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.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] - if it._dinv_armor then - armor = armor * it._dinv_armor - end - end - - local player_name = player:get_player_name() - if player_name then - minetest.chat_send_player(player_name, 'Your armor: ' .. armor) - end - - local armor_g = player:get_armor_groups() - if not (armor_g and armor_g.fleshy) then - return - end - - armor_g.fleshy = armor - player:set_armor_groups(armor_g) -end - - ------------------------------------------------ --- Todo: Handle different character textures. ------------------------------------------------ -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 - - --- Set the size of the main inventory. -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 PROTECT_INVENTORY and isize < prsize then - print(mod_name..': *** Preventing lost inventory from reducing bag sizes') - isize = prsize - end - - if not pinv:set_size('main', isize) then - print(mod_name..': *** ERROR setting inventory size.') - end - - return isize -end - - --- Recreate the inventory formspec to show a recipe given --- by the return value from the recipe list. -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 - - local t = minetest.explode_textlist_event(field) - if t.type == 'CHG' and t.index then - mod.dat[player_name].craft_item_number = tonumber(t.index) - mod.dat[player_name].recipe_number = 1 - player:set_inventory_formspec(mod.make_inventory_spec(player)) - end -end - - --- Recreate the inventory formspec to show a different recipe --- for the current item (if it has more than one). -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 - - --- Return true if there's an item at that body location. -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 - - - --- Check for BAD THINGS if a player moves/adds/removes this item. -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 - - 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 item_from_it._dinv_storage_size then - local prsize = mod.get_main_size_by_bags(player) - local isize = prsize - if inventory_info.to_list == WORN_INV then - isize = isize + item_from_it._dinv_storage_size - elseif inventory_info.from_list == WORN_INV then - isize = isize - item_from_it._dinv_storage_size - 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) - - --- Damage items that can only be worn for a limited time. -local last_wear_check = 0 -minetest.register_globalstep(function(dtime) - local time = minetest.get_gametime() - if type(time) ~= 'number' then - return - end - - if time - last_wear_check < 5 then - return - end - - local players = minetest.get_connected_players() - - for i = 1, #players do - local player = players[i] - - for k, v in worn_items(player) do - local vs = v:get_name() - local it = minetest.registered_items[vs] - if it._dinv_wears_out then - local ow = v:get_wear() - local wear = it._dinv_wears_out - v:add_wear(wear) - player:get_inventory():set_stack(WORN_INV, k, v) - if ow + wear > 65535 then - if it._dinv_on_remove then - it._dinv_on_remove(player) - end - --[[ - mod.set_armor(player) - mod.set_armor_textures(player) - --]] - end - - if it._dinv_while_worn then - it._dinv_while_worn(player) - end - end - end - end - - last_wear_check = minetest.get_gametime() -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) - - --- Handle inventory moves (change armor, etc.). -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) - - local it = minetest.registered_items[item_s] - if inventory_info.to_list == inventory_info.from_list then - -- nop - elseif it._dinv_on_wear and inventory_info.to_list == WORN_INV then - it._dinv_on_wear(player) - elseif it._dinv_on_remove and inventory_info.from_list == WORN_INV then - it._dinv_on_remove(player) - end -end) - - --- Get input from the formspec buttons/list. -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_next'] - or fields['dinv_empty_craft'] - or fields['dinv_fill_craft'] - ) 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_next'] then - mod.switch_recipe(player, 1) - elseif fields and fields['dinv_empty_craft'] then - mod.empty_craft(player) - elseif fields and fields['dinv_fill_craft'] then - mod.recipe_fill(player) - end -end) - - -function mod.recipe_fill(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 = mod.get_all_craft_recipes(craft_item) - if not recipes then - return - end - - local pinv = player:get_inventory() - local main_size = pinv:get_size('main') - - if recipe_number > #recipes then - recipe_number = 1 - dat['recipe_number'] = recipe_number - end - local recipe = recipes[recipe_number] - - local w = recipe.width or 3 - if w == 0 or w > 3 then - w = 3 - end - - local items = {} - local totals = {} - 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 then - items[i] = item - totals[item] = (totals[item] or 0) + 1 - end - end - end - - local avail = {} - local groups = {} - local stack_max = {} - local only_one - local max = 999999 - for k, v in pairs(totals) do - if k:find('^group:') then - local group = k:gsub('^group:', '') - for i = 1, main_size do - local st = pinv:get_stack('main', i) - local name = st:get_name() - local it = minetest.registered_items[name] - if not it then - --print(mod_name..': Cannot find item: ' .. name) - return - end - if it.groups and it.groups[group] then - local ct = st:get_count() - avail[k] = (avail[k] or 0) + ct - groups[k] = (groups[k] or {}) - groups[k][name] = (groups[k][name] or 0) + ct - stack_max[name] = it.stack_max or 99 - end - end - - local gmax - for l, w in pairs(groups[k] or {}) do - if not gmax then - gmax = w / v - end - gmax = math.min(math.max(gmax, w / v), stack_max[l]) - end - if gmax then - max = math.min(max, gmax) - end - else - for i = 1, main_size do - local st = pinv:get_stack('main', i) - local name = st:get_name() - if k == name then - avail[k] = (avail[k] or 0) + st:get_count() - local it = minetest.registered_items[k] - if not it then - --print(mod_name..': Cannot find item: ' .. name) - return - end - stack_max[name] = it.stack_max or 99 - if stack_max[name] < 2 then - only_one = true - end - end - end - if max and avail[k] and stack_max[k] then - max = math.min(math.min(max, avail[k] / v), stack_max[k]) - end - end - end - - for k, v in pairs(totals) do - if not (avail[k] and avail[k] >= v) then - --print(mod_name..': Cannot find ' .. k .. ' in inventory.') - return - end - end - - if only_one then - max = 1 - else - max = math.floor(max) - end - - if max < 1 then - return - end - - if not mod.empty_craft(player) then - --print(mod_name..': Cannot empty craft inventory') - return - end - - for k, v in pairs(items) do - if v:find('^group:') then - for l in pairs(groups[v]) do - local w = groups[v][l] - if w >= max then - local st = pinv:remove_item('main', l .. ' ' .. max) - groups[v][l] = groups[v][l] - st:get_count() - pinv:set_stack('craft', k, st) - break - end - end - else - local st = pinv:remove_item('main', v .. ' ' .. max) - pinv:set_stack('craft', k, st) - end - end -end - - --- Every time the player gets punched, armor is damaged, --- even if no damage is done to the player. -minetest.register_on_punchplayer(function(player, hitter, time_from_last_punch, tool_capabilities, dir, damage) - mod.damage_armor(player, damage) -end) +dofile(mod.path .. '/recipes.lua') +dofile(mod.path .. '/inventory.lua') diff --git a/inventory.lua b/inventory.lua new file mode 100644 index 0000000..9616f7e --- /dev/null +++ b/inventory.lua @@ -0,0 +1,1019 @@ +-- Dinv inventory.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) + + +local mod = dinv +local mod_name = 'dinv' + + +local sorted_items + + +local ADD_SCROLL_TO_DEFAULT_CHEST = true +local PROTECT_INVENTORY = false -- Prevent ANY inventory loss. +local WORN_INV = 'worn' + + +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 + + + +-- Formspec definitions are confusing. +mod.form_size = 'size[11.25,7.25]' +mod.main_inventory = 'list[current_player;main;2.25,3.5;8,4;' +mod.craft_inventory = 'list[current_player;craft;3.25,0;3,3;]' +mod.craft_preview = 'list[current_player;craftpreview;6.25,2;1,1;]' +mod.main_inventory_scroll_up = 'image_button[10.25,3.5;1,1;transparent_button.png;dinv_main_inventory_up;Up]' +mod.main_inventory_scroll_down = 'image_button[10.25,6.5;1,1;transparent_button.png;dinv_main_inventory_down;Down]' +mod.recipe_button = 'image_button[6.25,1;1,1;transparent_button.png;dinv_recipe_next;Next]' +mod.worn_items_inv = 'list[current_player;worn;0,3.5;2,4;]' +mod.empty_button = 'image_button[2.25,2;1,1;transparent_button.png;dinv_empty_craft;Empty]' +mod.fill_button = 'image_button[2.25,1;1,1;transparent_button.png;dinv_fill_craft;Fill]' + + +-- All this is necessary to add scroll buttons to chests. +if ADD_SCROLL_TO_DEFAULT_CHEST then + 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) +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 + + +function mod.empty_craft(player) + local pinv = player:get_inventory() + local craft_size = pinv:get_size('craft') + + for i = 1, craft_size do + local st = pinv:get_stack('craft', i) + if pinv:room_for_item('main', st) then + pinv:add_item('main', st) + pinv:set_stack('craft', i, nil) + else + return + end + end + + return true +end + + +function mod.get_all_craft_recipes(craft_item) + local recipes = {} + for k, v in pairs(minetest.get_all_craft_recipes(craft_item) or {}) do + if v.type ~= 'cooking' then + table.insert(recipes, v) + end + end + + if #recipes < 1 then + return + end + + return recipes +end + + +-- Calculate how big the main inventory should be. +function mod.get_main_size_by_bags(player) + local isize = 32 + + for k, v in worn_items(player) do + local vs = v:get_name() + local it = minetest.registered_items[vs] + if it._dinv_storage_size then + isize = isize + it._dinv_storage_size + end + end + + return isize +end + + +-- Any warmth over zero is sufficient at the moment. +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 + + +-- Check if there are any stacks in the given inventory, +-- at the given positions. This is important to avoid losing +-- those items when a bag is removed. +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 + + +-- The main formspec... +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.worn_items_inv + inventory = inventory .. mod.empty_button + inventory = inventory .. mod.fill_button + inventory = inventory .. mod.main_inventory .. scroll_main_to .. ']' + inventory = inventory .. mod.craft_inventory + inventory = inventory .. 'listring[]' + inventory = inventory .. mod.craft_preview + inventory = inventory .. mod.main_inventory_scroll_up + inventory = inventory .. mod.main_inventory_scroll_down + inventory = inventory .. mod.recipe_grid(player) + inventory = inventory .. mod.recipe_list(player) + + return inventory +end + + +-- Change one or more privileges. Set a privilege value +-- to 0, in the input table, to remove it. +----------------------------------------------- +-- Note that this can remove granted privileges. +----------------------------------------------- +function mod.modify_privs(player, p) + local player_name = player:get_player_name() + local privs = minetest.get_player_privs(player_name) or {} + + for k, v in pairs(p) do + if v == 0 then + privs[k] = nil + else + privs[k] = v + end + end + + minetest.set_player_privs(player_name, privs) +end + + +-- Quick table of all wearable items. +----------------------------------------------------- +-- Populating this after the game starts might cause +-- problems, so I do it twice, just in case. +----------------------------------------------------- +mod.wearable = { } +function mod.populate_wearable_table() + for k, v in pairs(minetest.registered_items) do + if v._dinv_armor or v._dinv_storage_size + or v._dinv_on_wear or v._dinv_on_remove + or v._dinv_while_worn then + mod.wearable[k] = true + end + end +end +mod.populate_wearable_table() +minetest.after(0, function() + mod.populate_wearable_table() +end) + + +function mod.recipe_fill(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 = mod.get_all_craft_recipes(craft_item) + if not recipes then + return + end + + local pinv = player:get_inventory() + local main_size = pinv:get_size('main') + + if recipe_number > #recipes then + recipe_number = 1 + dat['recipe_number'] = recipe_number + end + local recipe = recipes[recipe_number] + + local w = recipe.width or 3 + if w == 0 or w > 3 then + w = 3 + end + + local items = {} + local totals = {} + 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 then + items[i] = item + totals[item] = (totals[item] or 0) + 1 + end + end + end + + local avail = {} + local groups = {} + local stack_max = {} + local only_one + local max = 999999 + for k, v in pairs(totals) do + if k:find('^group:') then + local group = k:gsub('^group:', '') + for i = 1, main_size do + local st = pinv:get_stack('main', i) + local name = st:get_name() + local it = minetest.registered_items[name] + if not it then + --print(mod_name..': Cannot find item: ' .. name) + return + end + if it.groups and it.groups[group] then + local ct = st:get_count() + avail[k] = (avail[k] or 0) + ct + groups[k] = (groups[k] or {}) + groups[k][name] = (groups[k][name] or 0) + ct + stack_max[name] = it.stack_max or 99 + end + end + + local gmax + for l, w in pairs(groups[k] or {}) do + if not gmax then + gmax = w / v + end + gmax = math.min(math.max(gmax, w / v), stack_max[l]) + end + if gmax then + max = math.min(max, gmax) + end + else + for i = 1, main_size do + local st = pinv:get_stack('main', i) + local name = st:get_name() + if k == name then + avail[k] = (avail[k] or 0) + st:get_count() + local it = minetest.registered_items[k] + if not it then + --print(mod_name..': Cannot find item: ' .. name) + return + end + stack_max[name] = it.stack_max or 99 + if stack_max[name] < 2 then + only_one = true + end + end + end + if max and avail[k] and stack_max[k] then + max = math.min(math.min(max, avail[k] / v), stack_max[k]) + end + end + end + + for k, v in pairs(totals) do + if not (avail[k] and avail[k] >= v) then + --print(mod_name..': Cannot find ' .. k .. ' in inventory.') + return + end + end + + if only_one then + max = 1 + else + max = math.floor(max) + end + + if max < 1 then + return + end + + if not mod.empty_craft(player) then + --print(mod_name..': Cannot empty craft inventory') + return + end + + for k, v in pairs(items) do + if v:find('^group:') then + for l in pairs(groups[v]) do + local w = groups[v][l] + if w >= max then + local st = pinv:remove_item('main', l .. ' ' .. max) + groups[v][l] = groups[v][l] - st:get_count() + pinv:set_stack('craft', k, st) + break + end + end + else + local st = pinv:remove_item('main', v .. ' ' .. max) + pinv:set_stack('craft', k, st) + end + end +end + + +-- Return a 3 x 3 grid of images matching the selected recipe. +-- This appears over then actual craft grid, but won't interfere +-- with it. +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 = mod.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] + + local inv = '' + inv = inv .. 'container[3.25,0]' + + do + local tooltip = craft_item + inv = inv .. 'item_image[3,2;1,1;' .. craft_item .. ']' + tooltip = minetest.registered_items[craft_item].description or tooltip + inv = inv .. 'tooltip[3,2;0.8,0.8;' .. 'recipe: ' .. tooltip .. ']' + end + + 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 + local tooltip = item + if item and item:find('^group') then + --print(item) + item = group_rep[item] + if item then + g_s[i] = true + end + elseif item then + tooltip = minetest.registered_items[item].description or tooltip + end + if item then + inv = inv .. 'item_image[' .. (x - 1) .. ',' .. (y) .. ';1,1;' .. item .. ']' + inv = inv .. 'tooltip[' .. (x - 1) .. ',' .. (y) .. ';0.8,0.8;' .. 'recipe: ' .. tooltip .. ']' + end + end + end + + 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[]' + + if #recipes > 1 then + inv = inv .. mod.recipe_button + end + + return inv +end + + +-- Return a listbox filled with items that can be crafted. +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 mod.get_all_craft_recipes(k) then + local recs = mod.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.25,0;3.75,2.9;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 + + +-- Recreate the inventory formspec when the player scrolls +-- up or down in the main inventory. +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.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] + if it._dinv_armor then + armor = armor * it._dinv_armor + end + end + + local player_name = player:get_player_name() + if player_name then + minetest.chat_send_player(player_name, 'Your armor: ' .. armor) + end + + local armor_g = player:get_armor_groups() + if not (armor_g and armor_g.fleshy) then + return + end + + armor_g.fleshy = armor + player:set_armor_groups(armor_g) +end + + +----------------------------------------------- +-- Todo: Handle different character textures. +----------------------------------------------- +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 + + +-- Set the size of the main inventory. +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 PROTECT_INVENTORY and isize < prsize then + print(mod_name..': *** Preventing lost inventory from reducing bag sizes') + isize = prsize + end + + if not pinv:set_size('main', isize) then + print(mod_name..': *** ERROR setting inventory size.') + end + + return isize +end + + +-- Recreate the inventory formspec to show a recipe given +-- by the return value from the recipe list. +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 + + local t = minetest.explode_textlist_event(field) + if t.type == 'CHG' and t.index then + mod.dat[player_name].craft_item_number = tonumber(t.index) + mod.dat[player_name].recipe_number = 1 + player:set_inventory_formspec(mod.make_inventory_spec(player)) + end +end + + +-- Recreate the inventory formspec to show a different recipe +-- for the current item (if it has more than one). +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 + + +-- Return true if there's an item at that body location. +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 + + + +-- Check for BAD THINGS if a player moves/adds/removes this item. +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 + + 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 item_from_it._dinv_storage_size then + local prsize = mod.get_main_size_by_bags(player) + local isize = prsize + if inventory_info.to_list == WORN_INV then + isize = isize + item_from_it._dinv_storage_size + elseif inventory_info.from_list == WORN_INV then + isize = isize - item_from_it._dinv_storage_size + 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) + + +-- Damage items that can only be worn for a limited time. +local last_wear_check = 0 +minetest.register_globalstep(function(dtime) + local time = minetest.get_gametime() + if type(time) ~= 'number' then + return + end + + if time - last_wear_check < 5 then + return + end + + local players = minetest.get_connected_players() + + for i = 1, #players do + local player = players[i] + + for k, v in worn_items(player) do + local vs = v:get_name() + local it = minetest.registered_items[vs] + if it._dinv_wears_out then + local ow = v:get_wear() + local wear = it._dinv_wears_out + v:add_wear(wear) + player:get_inventory():set_stack(WORN_INV, k, v) + if ow + wear > 65535 then + if it._dinv_on_remove then + it._dinv_on_remove(player) + end + --[[ + mod.set_armor(player) + mod.set_armor_textures(player) + --]] + end + + if it._dinv_while_worn then + it._dinv_while_worn(player) + end + end + end + end + + last_wear_check = minetest.get_gametime() +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) + + +-- Handle inventory moves (change armor, etc.). +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) + + local it = minetest.registered_items[item_s] + if inventory_info.to_list == inventory_info.from_list then + -- nop + elseif it._dinv_on_wear and inventory_info.to_list == WORN_INV then + it._dinv_on_wear(player) + elseif it._dinv_on_remove and inventory_info.from_list == WORN_INV then + it._dinv_on_remove(player) + end +end) + + +-- Get input from the formspec buttons/list. +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_next'] + or fields['dinv_empty_craft'] + or fields['dinv_fill_craft'] + ) 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_next'] then + mod.switch_recipe(player, 1) + elseif fields and fields['dinv_empty_craft'] then + mod.empty_craft(player) + elseif fields and fields['dinv_fill_craft'] then + mod.recipe_fill(player) + end +end) + + +-- Every time the player gets punched, armor is damaged, +-- even if no damage is done to the player. +minetest.register_on_punchplayer(function(player, hitter, time_from_last_punch, tool_capabilities, dir, damage) + mod.damage_armor(player, damage) +end) diff --git a/recipes.lua b/recipes.lua new file mode 100644 index 0000000..ba79c6b --- /dev/null +++ b/recipes.lua @@ -0,0 +1,347 @@ +-- Dinv recipes.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) + + +local mod = dinv +local mod_name = 'dinv' + +local FLIGHT_PRIV = false +local GRAVITY_OFF = { gravity = 0.1 } +local GRAVITY_ON = { gravity = 1 } + + +minetest.register_craftitem(mod_name..':bag_medium', { + description = 'Medium Bag', + inventory_image = 'bags_medium.png', + stack_max = 1, + _dinv_storage_size = 16, +}) + +minetest.register_craft({ + output = mod_name..':bag_medium', + recipe = { + {'', '', ''}, + {'farming:string', mod_name..':bag_small', 'farming:string'}, + {'', mod_name..':bag_small', ''}, + }, +}) + +minetest.register_craftitem(mod_name..':bag_large', { + description = 'Large Bag', + inventory_image = 'bags_large.png', + stack_max = 1, + _dinv_storage_size = 24, +}) + +minetest.register_craft({ + output = mod_name..':bag_large', + recipe = { + {'', '', ''}, + {'farming:string', mod_name..':bag_medium', 'farming:string'}, + {'', mod_name..':bag_medium', ''}, + }, +}) + +minetest.register_craftitem(mod_name..':bag_small', { + description = 'Small Bag', + inventory_image = 'bags_small.png', + stack_max = 1, + _dinv_storage_size = 8, +}) + +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_tool(mod_name..':boots', { + inventory_image = 'boots1_brown.png', + description = 'Sturdy Boots', + _dinv_armor = 0.95, + _dinv_location = 'feet', + _dinv_texture = 'dinv_char_boots.png', +}) + +minetest.register_craft({ + output = mod_name..':boots', + recipe = { + {'', '', ''}, + {'', 'farming:string', ''}, + {'mobs:leather', 'farming:string', 'mobs:leather'}, + }, +}) + +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_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_craftitem(mod_name..':diamond_plate', { + description = 'Diamond Plate', + inventory_image = 'dinv_diamond_plate.png', +}) + +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_tool(mod_name..':diamond_plate_armor', { + inventory_image = 'crystal_plate2.png', + description = 'Diamond Plate Mail', + _dinv_armor = 0.45, + _dinv_location = 'body', + _dinv_texture = 'dinv_char_diamond_plate_armor.png', +}) + +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'}, + }, +}) + +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 = 'dinv_char_fur_cloak.png', +}) + +minetest.register_craft({ + output = mod_name..':fur_cloak', + recipe = { + {'', 'mobs:fur', ''}, + {'mobs:fur', 'farming:string', 'mobs:fur'}, + {'mobs:fur', '', 'mobs:fur'}, + }, +}) + +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_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_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_craft({ + output = mod_name..':leather_cap', + recipe = { + {'', 'mobs:leather', ''}, + {'', 'mobs:leather', 'mobs:wax'}, + {'', 'farming:string', ''}, + }, +}) + +minetest.register_tool(mod_name..':plate_armor', { + inventory_image = 'plate1.png', + description = 'Plate Mail', + _dinv_armor = 0.6, + _dinv_location = 'body', + _dinv_texture = 'dinv_char_plate_armor.png', +}) + +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_tool(mod_name..':steel_helmet', { + inventory_image = 'helmet1.png', + description = 'Steel Helmet', + _dinv_armor = 0.8, + _dinv_location = 'head', + _dinv_texture = 'dinv_char_steel_helm.png', +}) + +minetest.register_craft({ + output = mod_name..':steel_helmet', + recipe = { + {'', 'default:steel_ingot', ''}, + {'default:steel_ingot', 'mobs:leather', 'default:steel_ingot'}, + {'', 'mobs:fur', ''}, + }, +}) + +minetest.register_craftitem(mod_name..':steel_plate', { + description = 'Steel Plate', + inventory_image = 'dinv_steel_plate.png', +}) + +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_craftitem(mod_name..':steel_rings', { + description = 'Steel Rings', + inventory_image = 'dinv_steel_rings.png', +}) + +minetest.register_craft({ + output = mod_name..':steel_rings', + recipe = { + {'', '', ''}, + {'default:steel_ingot', 'default:coal_lump', 'default:steel_ingot'}, + {'', 'default:steel_ingot', ''}, + }, +}) + +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 = 'dinv_char_steel_shield.png', +}) + +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_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_craft({ + output = mod_name..':wood_shield', + recipe = { + {'', 'group:wood', ''}, + {'group:wood', 'mobs:leather', 'group:wood'}, + {'', 'group:wood', ''}, + }, +}) + + +minetest.register_tool(mod_name..':ring_breath', { + inventory_image = 'coral.png', + description = 'Ring of Breath', + _dinv_wears_out = 91, -- 1 hour + _dinv_while_worn = function(player) + player:set_breath(11) + end, +}) + +minetest.register_craft({ + type = 'shapeless', + output = mod_name..':ring_breath', + recipe = { + mod.magic_ingredient, + 'default:coral_skeleton', + 'default:gold_ingot', + }, +}) + +if FLIGHT_PRIV then + minetest.register_tool(mod_name..':ring_flight', { + inventory_image = 'glass.png', + description = 'Ring of Flight', + _dinv_wears_out = 546, -- 10 minutes + _dinv_on_wear = function(player) + mod.modify_privs(player, { fly = true, noclip = 0 }) + end, + _dinv_on_remove = function(player) + mod.modify_privs(player, { fly = 0, noclip = 0 }) + end, + }) +end + +minetest.register_tool(mod_name..':ring_leap', { + inventory_image = 'tiger_eye.png', + description = 'Ring of Leaping', + _dinv_wears_out = 273, -- 20 minutes + _dinv_on_wear = function(player) + player:set_physics_override(GRAVITY_OFF) + end, + _dinv_on_remove = function(player) + player:set_physics_override(GRAVITY_ON) + end, +}) + +minetest.register_craft({ + type = 'shapeless', + output = mod_name..':ring_leap', + recipe = { + mod.magic_ingredient, + 'flowers:mushroom_red', + 'default:gold_ingot', + }, +}) + +minetest.register_tool(mod_name..':ring_protection_9', { + inventory_image = 'anvil.png', + description = 'Ring of Protection', + _dinv_location = 'protection_finger', -- Only one per person! + _dinv_armor = 0.9, +}) + +minetest.register_craft({ + type = 'shapeless', + output = mod_name..':ring_protection_9', + recipe = { + mod.magic_ingredient, + 'default:diamond', + 'default:gold_ingot', + }, +})