player.lua 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381
  1. local MP = minetest.get_modpath(minetest.get_current_modname())
  2. local S, NS = dofile(MP.."/intllib.lua")
  3. local F = minetest.formspec_escape
  4. local modpath_default = minetest.get_modpath("default")
  5. local modpath_sfinv = minetest.get_modpath("sfinv")
  6. local modpath_unified_inventory = minetest.get_modpath("unified_inventory")
  7. -- table_def can have the following:
  8. --{
  9. -- alphabetize_items = true or false,
  10. -- description = string,
  11. -- append_to_formspec = string,
  12. -- input_width = number, -- height and width of the input inventory. Note that if you make this too small some recipes may no longer be craftable.
  13. -- input_height = number,
  14. -- output_width = number, -- height and width of the output inventory
  15. -- output_height = number,
  16. -- controls_x = number, -- location of the column of controls for controlling output display
  17. -- controls_y = number,
  18. --}
  19. simplecrafting_lib.register_player_craft_type = function(craft_type, table_def)
  20. if table_def == nil then
  21. table_def = {}
  22. end
  23. local input_width = table_def.input_width or 2
  24. local input_height = table_def.input_height or 5
  25. local output_width = table_def.output_width or 8
  26. local output_height = table_def.output_height or 6
  27. local controls_x = table_def.controls_x or 9.3
  28. local controls_y = table_def.controls_y or 6.5
  29. local input_list_name = "craft"
  30. local input_count = input_width * input_height
  31. local output_count = output_width * output_height
  32. local show_player_inventory = true
  33. -- Unified inventory has strict limitations on the size of the crafting interface, and
  34. -- has a hard-coded player inventory display
  35. if modpath_unified_inventory then
  36. input_width = 2
  37. input_height = 4
  38. output_width = 5
  39. output_height = 4
  40. controls_x = 7.3
  41. controls_y = 0
  42. show_player_inventory = false
  43. end
  44. -- This is to vertically align the input and output inventories,
  45. -- keeping them centered relative to each other.
  46. local y_displace_input = 0
  47. local y_displace_output = 0
  48. if input_height < output_height then
  49. y_displace_input = (output_height-input_height)/2
  50. elseif input_height > output_height then
  51. y_displace_output = (input_height-output_height)/2
  52. end
  53. -- pre-declare this so that get_or_create_context can reference it
  54. local update_output
  55. -- context contains persistent player state
  56. local get_or_create_context = function(player)
  57. local player_name = player:get_player_name()
  58. local context
  59. if modpath_sfinv then
  60. context = sfinv.get_or_create_context(player)
  61. -- elseif modpath_unified_inventory then
  62. -- context = unified_inventory.get_per_player_formspec(player:get_player_name())
  63. else
  64. simplecrafting_lib.player_contexts = simplecrafting_lib.player_contexts or {}
  65. context = simplecrafting_lib.player_contexts[player_name]
  66. if not context then
  67. context = {}
  68. simplecrafting_lib.player_contexts[player_name] = context
  69. end
  70. end
  71. if context.simplecrafting_lib_row == nil then context.simplecrafting_lib_row = 0 end -- the currently selected output page
  72. -- Create a detached inventory to hold prospective crafting outputs
  73. if context.simplecrafting_lib_output_inventory_name == nil then
  74. context.simplecrafting_lib_output_inventory_name = "simplecrafting_" .. craft_type .. "_" .. player_name
  75. end
  76. if context.simplecrafting_lib_output_inventory == nil then
  77. context.simplecrafting_lib_output_inventory = minetest.create_detached_inventory(context.simplecrafting_lib_output_inventory_name, {
  78. allow_move = function(inv, from_list, from_index, to_list, to_index, count, player)
  79. return 0
  80. end,
  81. allow_put = function(inv, listname, index, stack, player)
  82. return 0
  83. end,
  84. allow_take = function(inv, listname, index, stack, player)
  85. return -1
  86. end,
  87. on_move = function(inv, from_list, from_index, to_list, to_index, count, player)
  88. end,
  89. on_take = function(inv, listname, index, stack, player)
  90. if stack and stack:get_count() > 0 then
  91. local player_inv = minetest.get_inventory({type="player", name=player_name})
  92. if listname == "main" then
  93. simplecrafting_lib.craft_stack(craft_type, stack, player_inv, input_list_name, player_inv, input_list_name, player)
  94. if simplecrafting_lib.award_crafting then
  95. simplecrafting_lib.award_crafting(player, stack)
  96. end
  97. end
  98. update_output(player_inv, player)
  99. return stack:get_count()
  100. end
  101. end,
  102. }, player_name)
  103. if context.simplecrafting_lib_output_inventory == nil then
  104. minetest.log("error", "[simplecrafting_lib] get_or_create_context failed to create a detached inventory to hold crafting outputs")
  105. end
  106. end
  107. if context.simplecrafting_lib_max_mode == nil then context.simplecrafting_lib_max_mode = false end
  108. return context
  109. end
  110. -- updates the contents of the detached output inventory to match the input inventory's contents
  111. update_output = function(player_inv, player)
  112. local context = get_or_create_context(player)
  113. local max_mode = context.simplecrafting_lib_max_mode
  114. local craftable = simplecrafting_lib.get_craftable_items(craft_type, player_inv:get_list(input_list_name), max_mode, table_def.alphabetize_items)
  115. -- Output size is the number of multiples of output_count that we can fit the craftable outputs into,
  116. -- with a minimum of one multuple so there's an empty page if there's no recipes to craft
  117. local output_size = math.max(math.ceil(#craftable / output_count), 1) * output_count
  118. context.simplecrafting_lib_output_inventory:set_size("main", output_size)
  119. context.simplecrafting_lib_output_inventory:set_list("main", craftable)
  120. end
  121. -- refreshes the inventory formspec when there's been a change that would affect it
  122. local update_formspec = function(player, context)
  123. if modpath_unified_inventory then
  124. unified_inventory.set_inventory_formspec(player, unified_inventory.current_page[player:get_player_name()])
  125. elseif modpath_sfinv then
  126. sfinv.set_player_inventory_formspec(player, context)
  127. end
  128. end
  129. local make_formspec = function(player)
  130. local context = get_or_create_context(player)
  131. local player_inv = minetest.get_inventory({type="player", name=player:get_player_name()})
  132. update_output(player_inv, player)
  133. local row = context.simplecrafting_lib_row or 0
  134. local item_count
  135. if context.simplecrafting_lib_output_inventory then
  136. item_count = context.simplecrafting_lib_output_inventory:get_size("main")
  137. else
  138. item_count = output_count
  139. end
  140. local max_mode = context.simplecrafting_lib_max_mode or false
  141. local output_inventory = context.simplecrafting_lib_output_inventory_name
  142. if item_count < output_count then
  143. row = 0
  144. context.simplecrafting_lib_row = row
  145. elseif (row*output_width)+output_count > item_count then
  146. row = (item_count - output_count) / output_width
  147. context.simplecrafting_lib_row = row
  148. end
  149. local inventory = {
  150. "list[current_player;"..input_list_name..";0,"..y_displace_input..";"..input_width..","..input_height..";]"..
  151. "list[detached:"..output_inventory..";main;"..tostring(input_width+0.2)..","..y_displace_output..";"..output_width..","..output_height..";"..tostring(row*output_width), "]",
  152. }
  153. if show_player_inventory then
  154. inventory[#inventory+1] = "list[current_player;main;1.1,"..tostring(output_height+0.25)..";8,1;]"..
  155. "list[current_player;main;1.1,"..tostring(output_height+1.5)..";8,3;8]"
  156. end
  157. inventory[#inventory+1] = "listring[detached:"..output_inventory..";main]"..
  158. "listring[current_player;main]"..
  159. "listring[current_player;"..input_list_name.."]"..
  160. "listring[current_player;main]"
  161. if table_def.description then
  162. inventory[#inventory+1] = "label[0,0;"..table_def.description.."]"
  163. end
  164. if modpath_default then
  165. inventory[#inventory+1] = default.gui_bg .. default.gui_bg_img .. default.gui_slots
  166. end
  167. local pages = false
  168. local page_button_y = controls_y + 0.6
  169. if item_count > ((row/output_height)+1) * output_count then
  170. inventory[#inventory+1] = "button["..controls_x..","..page_button_y..";1,0.75;next;»]"..
  171. "tooltip[next;"..F(S("Next page of crafting products")).."]"
  172. page_button_y = page_button_y + 0.8
  173. pages = true
  174. end
  175. if row >= output_height then
  176. inventory[#inventory+1] = "button["..controls_x..","..page_button_y..";1,0.75;prev;«]"..
  177. "tooltip[prev;"..F(S("Previous page of crafting products")).."]"
  178. pages = true
  179. end
  180. if pages then
  181. inventory[#inventory+1] = "label["..controls_x..","..controls_y..";" .. F(S("Page @1", tostring(row/output_height+1))) .. "]"
  182. end
  183. if max_mode then
  184. inventory[#inventory+1] = "button["..controls_x..","..tostring(controls_y+2.2)..";1,0.75;max_mode;"..F(S("Max\nOutput")).."]"
  185. else
  186. inventory[#inventory+1] = "button["..controls_x..","..tostring(controls_y+2.2)..";1,0.75;max_mode;"..F(S("Min\nOutput")).."]"
  187. end
  188. if table_def.append_to_formspec then
  189. inventory[#inventory+1] = table_def.append_to_formspec
  190. end
  191. return table.concat(inventory)
  192. end
  193. minetest.register_on_joinplayer(function(player)
  194. local player_name = player:get_player_name()
  195. local player_inv = minetest.get_inventory({type="player", name=player_name})
  196. player_inv:set_size(input_list_name, input_count)
  197. -- TEMP code to save crafting inputs from a brief window when this mod was using this inventory for crafting inputs
  198. local deprecated_input_list_name = craft_type .. "_input"
  199. if not player_inv:is_empty(deprecated_input_list_name) then
  200. local old_items = player_inv:get_list(deprecated_input_list_name)
  201. for _, item in ipairs(old_items) do
  202. local leftover = player_inv:add_item(input_list_name, item)
  203. minetest.item_drop(leftover, player, player:get_pos())
  204. end
  205. player_inv:set_list(deprecated_input_list_name, {})
  206. player_inv:set_size(deprecated_input_list_name, 0)
  207. end
  208. --TEMP end of temp code
  209. update_output(player_inv, player)
  210. update_formspec(player, get_or_create_context(player))
  211. end)
  212. minetest.register_on_leaveplayer(function(player)
  213. local player_name = player:get_player_name()
  214. simplecrafting_lib.player_contexts[player_name] = nil
  215. minetest.remove_detached_inventory("simplecrafting_" .. craft_type .. "_" .. player_name)
  216. end)
  217. minetest.register_allow_player_inventory_action(function(player, action, inventory, inventory_info)
  218. if action == "move" then
  219. if inventory_info.to_list == input_list_name then
  220. if inventory_info.from_list == input_list_name then
  221. return inventory_info.count
  222. end
  223. local stack = inventory:get_stack(inventory_info.from_list, inventory_info.from_index)
  224. if simplecrafting_lib.is_possible_input(craft_type, stack:get_name()) then
  225. return inventory_info.count
  226. end
  227. return 0
  228. end
  229. if inventory_info.to_list == "main" then
  230. return inventory_info.count
  231. end
  232. end
  233. end)
  234. minetest.register_on_player_inventory_action(function(player, action, inventory, inventory_info)
  235. if inventory_info.to_list == input_list_name or inventory_info.from_list == input_list_name then
  236. local context = get_or_create_context(player)
  237. update_output(inventory, player)
  238. update_formspec(player, context)
  239. end
  240. end)
  241. local handle_receive_fields = function(player, fields, context)
  242. local row = context.simplecrafting_lib_row
  243. local refresh = false
  244. if fields.next then
  245. minetest.sound_play("paperflip1", {to_player=player:get_player_name(), gain = 1.0})
  246. row = row + output_height
  247. elseif fields.prev then
  248. minetest.sound_play("paperflip2", {to_player=player:get_player_name(), gain = 1.0})
  249. row = row - output_height
  250. elseif fields.max_mode then
  251. context.simplecrafting_lib_max_mode = not context.simplecrafting_lib_max_mode
  252. refresh = true
  253. else
  254. return false
  255. end
  256. context.simplecrafting_lib_row = row
  257. if refresh then
  258. local player_inv = minetest.get_inventory({type="player", name=player:get_player_name()})
  259. update_output(player_inv, player)
  260. end
  261. return true
  262. end
  263. if modpath_unified_inventory then
  264. local background = ""
  265. for x = 0, input_width - 1 do
  266. for y = 0 + y_displace_input, input_height + y_displace_input - 1 do
  267. background = background .. "background["..x..","..y..";1,1;ui_single_slot.png]"
  268. end
  269. end
  270. for x = input_width + 0.2, input_width + 0.2 + output_width - 1 do
  271. for y = 0 + y_displace_output, y_displace_output + output_height - 1 do
  272. background = background .. "background["..x..","..y..";1,1;ui_single_slot.png]"
  273. end
  274. end
  275. unified_inventory.register_page("craft", {
  276. get_formspec = function(player, perplayer_formspec)
  277. local formspec = make_formspec(player) .. background
  278. if unified_inventory.trash_enabled or unified_inventory.is_creative(player_name) or minetest.get_player_privs(player_name).give then
  279. formspec = formspec.."label["..controls_x..","..tostring(controls_y+2.8)..";" .. F(S("Trash:")) .. "]"
  280. .."background["..controls_x..","..tostring(controls_y+3.3)..";1,1;ui_single_slot.png]"
  281. .."list[detached:trash;main;"..controls_x..","..tostring(controls_y+3.3)..";1,1;]"
  282. end
  283. --Alas, have run out of room to fit this.
  284. -- if unified_inventory.is_creative(player_name) then
  285. -- formspec = formspec.."label[0,"..(formspecy + 1.5)..";" .. F(S("Refill:")) .. "]"
  286. -- formspec = formspec.."list[detached:"..F(player_name).."refill;main;0,"..(formspecy +2)..";1,1;]"
  287. -- end
  288. return {formspec=formspec}
  289. end,
  290. })
  291. --unified_inventory.register_page("craftguide", {
  292. --})
  293. minetest.register_on_player_receive_fields(function(player, formname, fields)
  294. if formname ~= "" then -- Unified_inventory is using the empty string as its formname.
  295. return
  296. end
  297. local player_name = player:get_player_name()
  298. local context = get_or_create_context(player)
  299. if handle_receive_fields(player, fields, context) then
  300. unified_inventory.set_inventory_formspec(player, unified_inventory.current_page[player_name])
  301. return true
  302. end
  303. end)
  304. elseif modpath_sfinv then
  305. sfinv.override_page("sfinv:crafting", {
  306. title = "Crafting",
  307. get = function(self, player, context)
  308. return sfinv.make_formspec(player, context, make_formspec(player), false, "size[10.2,10.2]")
  309. end,
  310. on_player_receive_fields = function(self, player, context, fields)
  311. if handle_receive_fields(player, fields, context) then
  312. sfinv.set_player_inventory_formspec(player, context)
  313. return true
  314. end
  315. end,
  316. })
  317. simplecrafting_lib.set_crafting_guide_def(craft_type, {
  318. output_width = 10,
  319. output_height = 6,
  320. recipes_per_page = 3,
  321. })
  322. sfinv.register_page("simplecrafting_lib:guide_"..craft_type, {
  323. title = "Guide",
  324. get = function(self, player, context)
  325. local formspec, size = simplecrafting_lib.make_guide_formspec(craft_type, player:get_player_name())
  326. return sfinv.make_formspec(player, context, formspec, false, "size[10.2,10.2]")
  327. end,
  328. on_player_receive_fields = function(self, player, context, fields)
  329. if simplecrafting_lib.handle_guide_receive_fields(craft_type, player, fields) then
  330. sfinv.set_player_inventory_formspec(player, context)
  331. return true
  332. end
  333. end,
  334. })
  335. end
  336. end