2022-07-05 19:14:48 +02:00
--|||||||||||||||||||||||
--||||| STONECUTTER |||||
--|||||||||||||||||||||||
2023-11-19 01:42:12 +01:00
-- The stonecutter is implemented just like the crafting table, meaning the node doesn't have any state.
-- Instead it trigger the display of a per-player menu. The input and output slots, the wanted item are stored into the player meta.
--
-- Player inventory lists:
-- * stonecutter_input (1)
-- * stonecutter_output (1)
-- Player meta:
2023-11-24 01:33:32 +01:00
-- * mcl_stonecutter:selected (string, wanted item name)
-- * mcl_stonecutter:switch_stack (int, wanted craft count: 1 or 64 = once or until full stack)
2023-11-19 01:42:12 +01:00
2022-07-05 19:14:48 +02:00
2023-07-30 22:58:42 +02:00
local S = minetest.get_translator ( " mcl_stonecutter " )
2023-11-19 01:42:12 +01:00
local C = minetest.colorize
local show_formspec = minetest.show_formspec
local formspec_name = " mcl_stonecutter:stonecutter "
2022-07-05 19:14:48 +02:00
2023-11-13 17:01:37 +01:00
mcl_stonecutter = { }
2023-11-19 01:42:12 +01:00
---Table of registered recipes
---
---```lua
---mcl_stonecutter.registered_recipes = {
--- ["mcl_core:input_item"] = {
--- ["mcl_core:output_item"] = 1,
--- ["mcl_core:output_item2"] = 2,
--- },
---}
---```
---@type table<string, table<string, integer>>
2023-11-13 17:01:37 +01:00
mcl_stonecutter.registered_recipes = { }
2023-11-19 01:42:12 +01:00
---Registers a recipe for the stonecutter
---@param input string Name of a registered item
---@param output string Name of a registered item
---@param count? integer Number of the output, defaults to `1`
2023-11-13 17:01:37 +01:00
function mcl_stonecutter . register_recipe ( input , output , count )
2023-11-19 01:42:12 +01:00
if mcl_stonecutter.registered_recipes [ input ] and mcl_stonecutter.registered_recipes [ input ] [ output ] then
minetest.log ( " warning " ,
" [mcl_stonecutter] Recipe already registered: [ " .. input .. " ] -> [ " .. output .. " " .. count .. " ] " )
return
end
2023-11-13 21:13:16 +01:00
if not minetest.registered_items [ input ] then
2023-11-19 01:42:12 +01:00
error ( " Input is not a registered item: " .. input )
2023-11-13 21:13:16 +01:00
end
2023-11-19 01:42:12 +01:00
2023-11-13 21:13:16 +01:00
if not minetest.registered_items [ output ] then
2023-11-19 01:42:12 +01:00
error ( " Output is not a registered item: " .. output )
2023-11-13 17:01:37 +01:00
end
2023-11-19 01:42:12 +01:00
count = count or 1
2023-11-13 17:01:37 +01:00
if not mcl_stonecutter.registered_recipes [ input ] then
mcl_stonecutter.registered_recipes [ input ] = { }
end
2023-11-19 01:42:12 +01:00
mcl_stonecutter.registered_recipes [ input ] [ output ] = count
2023-11-13 21:13:16 +01:00
local fallthrough = mcl_stonecutter.registered_recipes [ output ]
if fallthrough then
for o , c in pairs ( fallthrough ) do
2024-05-24 12:31:27 +02:00
if not mcl_stonecutter.registered_recipes [ input ] [ o ] then
mcl_stonecutter.register_recipe ( input , o , c * count )
end
2023-11-13 21:13:16 +01:00
end
end
for i , recipes in pairs ( mcl_stonecutter.registered_recipes ) do
for name , c in pairs ( recipes ) do
2024-05-24 12:31:27 +02:00
if name == input and not mcl_stonecutter.registered_recipes [ i ] [ output ] then
2023-11-19 01:42:12 +01:00
mcl_stonecutter.register_recipe ( i , output , c * count )
2023-11-13 21:13:16 +01:00
end
end
end
2023-11-13 17:01:37 +01:00
end
2023-11-19 01:42:12 +01:00
---Minetest currently (5.7) doesn't prevent using `:` characters in field names
---But using them prevent the buttons from beeing styled with `style[]` elements
---https://github.com/minetest/minetest/issues/14013
---@param itemname string
local function itenname_to_fieldname ( itemname )
return string.gsub ( itemname , " : " , " __ " )
end
---@param fieldname string
local function fieldname_to_itemname ( fieldname )
return string.gsub ( fieldname , " __ " , " : " )
end
2023-11-23 03:14:22 +01:00
-- Get the player configured stack size when taking items from creative inventory
---@param player mt.PlayerObjectRef
---@return integer
local function get_stack_size ( player )
return player : get_meta ( ) : get_int ( " mcl_stonecutter:switch_stack " )
end
-- Set the player configured stack size when taking items from creative inventory
---@param player mt.PlayerObjectRef
---@param n integer
local function set_stack_size ( player , n )
player : get_meta ( ) : set_int ( " mcl_stonecutter:switch_stack " , n )
end
2023-11-19 01:42:12 +01:00
---Build the formspec for the stonecutter with given output button
---@param player mt.PlayerObjectRef
---@param items? table<string, integer>
local function build_stonecutter_formspec ( player , items )
local meta = player : get_meta ( )
2023-11-24 01:33:32 +01:00
local selected = meta : get_string ( " mcl_stonecutter:selected " )
2023-11-19 01:42:12 +01:00
items = items or { }
-- Buttons are 3.5 / 4 = 0.875 wide
local c = 0
local items_content = " style_type[item_image_button;noclip=false;content_offset=0] " ..
( selected ~= " " and " style[ " .. itenname_to_fieldname ( selected ) .. " ;border=false;bgimg=mcl_inventory_button9_pressed.png;bgimg_pressed=mcl_inventory_button9_pressed.png;bgimg_middle=2,2] " or " " )
for name , count in table.pairs_by_keys ( items ) do
c = c + 1
local x = ( ( c - 1 ) % 4 ) * 0.875
local y = ( math.floor ( ( c - 1 ) / 4 ) ) * 0.875
items_content = items_content ..
string.format ( " item_image_button[%f,%f;0.875,0.875;%s;%s;] " , x , y ,
name , itenname_to_fieldname ( name ) , tostring ( count ) )
2023-07-30 19:25:48 +02:00
end
2023-11-19 01:42:12 +01:00
local formspec = table.concat ( {
" formspec_version[4] " ,
" size[11.75,10.425] " ,
" label[0.375,0.375; " .. C ( mcl_formspec.label_color , S ( " Stone Cutter " ) ) .. " ] " ,
-- Pattern input slot
mcl_formspec.get_itemslot_bg_v4 ( 1.625 , 2 , 1 , 1 ) ,
" list[current_player;stonecutter_input;1.625,2;1,1;] " ,
-- Container background
" image[4.075,0.7;3.6,3.6;mcl_inventory_background9.png;2] " ,
-- Style for item image buttons
" style_type[item_image_button;noclip=false;content_offset=0] " ,
-- Scroll Container with buttons if needed
" scroll_container[4.125,0.75;3.5,3.5;scroll;vertical;0.875] " ,
items_content ,
" scroll_container_end[] " ,
-- Scrollbar
-- TODO: style the scrollbar correctly when possible
" scrollbaroptions[min=0;max= " ..
math.max ( math.floor ( # items / 4 ) + 1 - 4 , 0 ) .. " ;smallstep=1;largesteps=1] " ,
" scrollbar[7.625,0.7;0.75,3.6;vertical;scroll;0] " ,
2023-11-23 03:14:22 +01:00
-- Switch stack size button
" image_button[9.75,0.75;1,1;mcl_stonecutter_saw.png^[verticalframe:3:1;__switch_stack;] " ,
" label[10.25,1.5; " .. C ( " #FFFFFF " , tostring ( get_stack_size ( player ) ) ) .. " ] " ,
" tooltip[__switch_stack; " .. S ( " Switch stack size " ) .. " ] " ,
2023-11-19 01:42:12 +01:00
-- Output slot
mcl_formspec.get_itemslot_bg_v4 ( 9.75 , 2 , 1 , 1 , 0.2 ) ,
" list[current_player;stonecutter_output;9.75,2;1,1;] " ,
-- Player inventory
" label[0.375,4.7; " .. C ( mcl_formspec.label_color , S ( " Inventory " ) ) .. " ] " ,
mcl_formspec.get_itemslot_bg_v4 ( 0.375 , 5.1 , 9 , 3 ) ,
" list[current_player;main;0.375,5.1;9,3;9] " ,
mcl_formspec.get_itemslot_bg_v4 ( 0.375 , 9.05 , 9 , 1 ) ,
" list[current_player;main;0.375,9.05;9,1;] " ,
" listring[current_player;stonecutter_output] " ,
" listring[current_player;main] " ,
" listring[current_player;stonecutter_input] " ,
" listring[current_player;main] " ,
} )
2023-07-30 19:25:48 +02:00
return formspec
end
2023-11-19 01:42:12 +01:00
---Display stonecutter menu to a player
---@param player mt.PlayerObjectRef
function mcl_stonecutter . show_stonecutter_form ( player )
show_formspec ( player : get_player_name ( ) , formspec_name ,
build_stonecutter_formspec ( player ,
mcl_stonecutter.registered_recipes [ player : get_inventory ( ) : get_stack ( " stonecutter_input " , 1 ) : get_name ( ) ] ) )
end
---Change the selected output item.
---@param player mt.PlayerObjectRef
---@param item_name? string The item name of the output
function set_selected_item ( player , item_name )
2023-11-24 01:33:32 +01:00
player : get_meta ( ) : set_string ( " mcl_stonecutter:selected " , item_name and item_name or " " )
2023-11-19 01:42:12 +01:00
end
minetest.register_on_joinplayer ( function ( player )
local inv = player : get_inventory ( )
inv : set_size ( " stonecutter_input " , 1 )
inv : set_size ( " stonecutter_output " , 1 )
set_selected_item ( player , nil )
--The player might have items remaining in the slots from the previous join; this is likely
--when the server has been shutdown and the server didn't clean up the player inventories.
mcl_util.move_player_list ( player , " stonecutter_input " )
2023-11-26 00:39:55 +01:00
player : get_inventory ( ) : set_list ( " stonecutter_output " , { } )
2023-11-19 01:42:12 +01:00
end )
minetest.register_on_leaveplayer ( function ( player )
set_selected_item ( player , nil )
mcl_util.move_player_list ( player , " stonecutter_input " )
2023-11-26 00:39:55 +01:00
player : get_inventory ( ) : set_list ( " stonecutter_output " , { } )
2023-11-19 01:42:12 +01:00
end )
---Update content of the stonecutter output slot with the input slot and the selected item
---@param player mt.PlayerObjectRef
function update_stonecutter_slots ( player )
local meta = player : get_meta ( )
local inv = player : get_inventory ( )
local input = inv : get_stack ( " stonecutter_input " , 1 )
local recipes = mcl_stonecutter.registered_recipes [ input : get_name ( ) ]
2023-11-24 01:33:32 +01:00
local output_item = meta : get_string ( " mcl_stonecutter:selected " )
2023-11-23 03:14:22 +01:00
local stack_size = meta : get_int ( " mcl_stonecutter:switch_stack " )
2023-08-01 19:17:48 +02:00
2023-11-13 17:01:37 +01:00
if recipes then
2023-11-19 01:42:12 +01:00
if output_item then
local recipe = recipes [ output_item ]
if recipe then
local cut_item = ItemStack ( output_item )
2023-11-23 03:14:22 +01:00
local count = math.min ( math.floor ( stack_size / recipe ) , input : get_count ( ) ) * recipe
if count < recipe then count = recipe end
cut_item : set_count ( count )
2023-11-19 01:42:12 +01:00
inv : set_stack ( " stonecutter_output " , 1 , cut_item )
else
inv : set_stack ( " stonecutter_output " , 1 , nil )
end
2023-08-01 19:05:01 +02:00
else
2023-11-19 01:42:12 +01:00
inv : set_stack ( " stonecutter_output " , 1 , nil )
2023-08-01 19:05:01 +02:00
end
2023-08-01 23:39:24 +02:00
else
2023-11-19 01:42:12 +01:00
inv : set_stack ( " stonecutter_output " , 1 , nil )
2023-07-30 23:41:19 +02:00
end
2023-11-19 01:42:12 +01:00
mcl_stonecutter.show_stonecutter_form ( player )
2023-07-30 23:41:19 +02:00
end
2023-07-30 19:25:48 +02:00
2023-11-19 01:42:12 +01:00
--Drop items in slots and reset selected item on closing
minetest.register_on_player_receive_fields ( function ( player , formname , fields )
if formname ~= formspec_name then return end
if fields.quit then
mcl_util.move_player_list ( player , " stonecutter_input " )
2023-11-26 00:39:55 +01:00
player : get_inventory ( ) : set_list ( " stonecutter_output " , { } )
2023-11-19 01:42:12 +01:00
return
end
2023-11-23 03:14:22 +01:00
if fields.__switch_stack then
local switch = 1
if get_stack_size ( player ) == 1 then
switch = 64
end
set_stack_size ( player , switch )
update_stonecutter_slots ( player )
mcl_stonecutter.show_stonecutter_form ( player )
return
end
2023-11-19 01:42:12 +01:00
for field_name , value in pairs ( fields ) do
if field_name ~= " scroll " then
local itemname = fieldname_to_itemname ( field_name )
set_selected_item ( player , itemname )
update_stonecutter_slots ( player )
mcl_stonecutter.show_stonecutter_form ( player )
break
2023-08-01 19:17:48 +02:00
end
end
2023-11-19 01:42:12 +01:00
end )
minetest.register_allow_player_inventory_action ( function ( player , action , inventory , inventory_info )
if action == " move " then
if inventory_info.to_list == " stonecutter_output " then
return 0
end
if inventory_info.from_list == " stonecutter_output " and inventory_info.to_list == " stonecutter_input " then
if inventory : get_stack ( inventory_info.to_list , inventory_info.to_index ) : is_empty ( ) then
return inventory_info.count
else
return 0
end
end
2023-11-23 03:42:49 +01:00
if inventory_info.from_list == " stonecutter_output " then
2023-11-24 01:33:32 +01:00
local selected = player : get_meta ( ) : get_string ( " mcl_stonecutter:selected " )
2023-11-23 03:42:49 +01:00
local istack = inventory : get_stack ( " stonecutter_input " , 1 )
local recipes = mcl_stonecutter.registered_recipes [ istack : get_name ( ) ]
if not selected or not recipes then return 0 end
local recipe = recipes [ selected ]
local remainder = inventory_info.count % recipe
if remainder ~= 0 then
return 0
end
end
2023-11-19 01:42:12 +01:00
elseif action == " put " then
if inventory_info.to_list == " stonecutter_output " then
return 0
end
2023-11-23 03:42:49 +01:00
if inventory_info.from_list == " stonecutter_output " then
2023-11-24 01:33:32 +01:00
local selected = player : get_meta ( ) : get_string ( " mcl_stonecutter:selected " )
2023-11-23 03:42:49 +01:00
local istack = inventory : get_stack ( " stonecutter_input " , 1 )
local recipes = mcl_stonecutter.registered_recipes [ istack : get_name ( ) ]
if not selected or not recipes then return 0 end
local recipe = recipes [ selected ]
local remainder = inventory_info.stack : get_count ( ) % recipe
if remainder ~= 0 then
return 0
end
end
2023-11-19 01:42:12 +01:00
end
end )
2023-11-23 03:42:49 +01:00
function remove_from_input ( player , inventory , crafted_count )
2023-11-19 01:42:12 +01:00
local meta = player : get_meta ( )
2023-11-24 01:33:32 +01:00
local selected = meta : get_string ( " mcl_stonecutter:selected " )
2023-11-19 01:42:12 +01:00
local istack = inventory : get_stack ( " stonecutter_input " , 1 )
2023-11-23 03:14:22 +01:00
local recipes = mcl_stonecutter.registered_recipes [ istack : get_name ( ) ]
local stack_size = meta : get_int ( " mcl_stonecutter:switch_stack " )
2023-11-19 01:42:12 +01:00
-- selected should normally never be nil, but just in case
2023-11-23 03:14:22 +01:00
if selected and recipes then
local recipe = recipes [ selected ]
2023-11-23 03:42:49 +01:00
local count = crafted_count / recipe
2023-11-23 03:14:22 +01:00
if count < 1 then count = 1 end
istack : set_count ( math.max ( 0 , istack : get_count ( ) - count ) )
2023-11-19 01:42:12 +01:00
inventory : set_stack ( " stonecutter_input " , 1 , istack )
end
2023-08-01 19:17:48 +02:00
end
2023-11-19 01:42:12 +01:00
minetest.register_on_player_inventory_action ( function ( player , action , inventory , inventory_info )
if action == " move " then
if inventory_info.to_list == " stonecutter_input " or inventory_info.from_list == " stonecutter_input " then
update_stonecutter_slots ( player )
return
elseif inventory_info.from_list == " stonecutter_output " then
2023-11-23 03:42:49 +01:00
remove_from_input ( player , inventory , inventory_info.count )
2023-11-19 01:42:12 +01:00
update_stonecutter_slots ( player )
end
elseif action == " put " then
if inventory_info.listname == " stonecutter_input " or inventory_info.listname == " stonecutter_input " then
update_stonecutter_slots ( player )
end
elseif action == " take " then
if inventory_info.listname == " stonecutter_output " then
2023-11-23 03:42:49 +01:00
remove_from_input ( player , inventory , inventory_info.stack : get_count ( ) )
2023-11-19 01:42:12 +01:00
update_stonecutter_slots ( player )
end
end
end )
2022-07-05 19:14:48 +02:00
minetest.register_node ( " mcl_stonecutter:stonecutter " , {
description = S ( " Stone Cutter " ) ,
_tt_help = S ( " Used to cut stone like materials. " ) ,
_doc_items_longdesc = S ( " Stonecutters are used to create stairs and slabs from stone like materials. It is also the jobsite for the Stone Mason Villager. " ) ,
tiles = {
" mcl_stonecutter_top.png " ,
" mcl_stonecutter_bottom.png " ,
" mcl_stonecutter_side.png " ,
" mcl_stonecutter_side.png " ,
2023-11-19 01:42:12 +01:00
{
name = " mcl_stonecutter_saw.png " ,
animation = {
type = " vertical_frames " ,
aspect_w = 16 ,
aspect_h = 16 ,
length = 1
}
} ,
{
name = " mcl_stonecutter_saw.png " ,
animation = {
type = " vertical_frames " ,
aspect_w = 16 ,
aspect_h = 16 ,
2023-12-13 03:39:25 +01:00
length = 0.15
2023-11-19 01:42:12 +01:00
}
}
2022-07-05 19:14:48 +02:00
} ,
2022-08-07 04:38:15 +02:00
use_texture_alpha = " clip " ,
2022-07-05 19:14:48 +02:00
drawtype = " nodebox " ,
paramtype = " light " ,
paramtype2 = " facedir " ,
2023-11-19 01:42:12 +01:00
groups = { pickaxey = 1 , material_stone = 1 } ,
2022-07-05 19:14:48 +02:00
node_box = {
type = " fixed " ,
fixed = {
2023-11-19 01:42:12 +01:00
{ - 0.5 , - 0.5 , - 0.5 , 0.5 , 0.0625 , 0.5 } , -- NodeBox1
{ - 0.4375 , 0.0625 , 0 , 0.4375 , 0.5 , 0 } , -- NodeBox2
2022-07-05 19:14:48 +02:00
}
} ,
_mcl_blast_resistance = 3.5 ,
_mcl_hardness = 3.5 ,
sounds = mcl_sounds.node_sound_stone_defaults ( ) ,
2023-07-30 19:25:48 +02:00
on_rightclick = function ( pos , node , player , itemstack )
if not player : get_player_control ( ) . sneak then
2023-11-19 01:42:12 +01:00
mcl_stonecutter.show_stonecutter_form ( player )
2023-07-30 22:58:42 +02:00
end
end ,
2022-07-05 19:14:48 +02:00
} )
minetest.register_craft ( {
output = " mcl_stonecutter:stonecutter " ,
recipe = {
{ " " , " " , " " } ,
{ " " , " mcl_core:iron_ingot " , " " } ,
{ " mcl_core:stone " , " mcl_core:stone " , " mcl_core:stone " } ,
}
} )