init.lua 15 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555
  1. -- Localize for performance.
  2. local vector_round = vector.round
  3. local armor_stand_formspec = "size[8,7]" ..
  4. default.gui_bg ..
  5. default.gui_bg_img ..
  6. default.gui_slots ..
  7. default.get_hotbar_bg(0,3) ..
  8. "button[0.5,0.5;2,1;equip;Equip]" ..
  9. "button[0.5,1.5;2,1;unequip;Unequip]" ..
  10. "list[context;armor_head;3,0.5;1,1;]" ..
  11. "list[context;armor_torso;4,0.5;1,1;]" ..
  12. "list[context;armor_legs;3,1.5;1,1;]" ..
  13. "list[context;armor_feet;4,1.5;1,1;]" ..
  14. "list[context;armor_shield;5,1.5;1,1;]" ..
  15. "image[3,0.5;1,1;3d_armor_stand_head.png]" ..
  16. "image[4,0.5;1,1;3d_armor_stand_torso.png]" ..
  17. "image[3,1.5;1,1;3d_armor_stand_legs.png]" ..
  18. "image[4,1.5;1,1;3d_armor_stand_feet.png]" ..
  19. "image[5,1.5;1,1;3d_armor_stand_shield.png]" ..
  20. "list[current_player;main;0,3;8,1;]" ..
  21. "list[current_player;main;0,4.25;8,3;8]" ..
  22. "tooltip[equip;Equip this armor.\n\n" ..
  23. "If you are already wearing some armor\n" ..
  24. "pieces, they will be swapped instead.]" ..
  25. "tooltip[unequip;Unequip your armor.\n\n" ..
  26. "The slots in the armor stand must be\n" ..
  27. "free, or the corresponding armor pieces\n" ..
  28. "will not be unequipped.]"
  29. local elements = {"head", "torso", "legs", "feet", "shield"}
  30. local function drop_armor(pos)
  31. local meta = minetest.get_meta(pos)
  32. local inv = meta:get_inventory()
  33. for _, element in pairs(elements) do
  34. local stack = inv:get_stack("armor_"..element, 1)
  35. if stack and stack:get_count() > 0 then
  36. armor.drop_armor(pos, stack)
  37. inv:set_stack("armor_"..element, 1, nil)
  38. end
  39. end
  40. end
  41. local function get_stand_object(pos)
  42. local object = nil
  43. local objects = minetest.get_objects_inside_radius(pos, 0.5) or {}
  44. for _, obj in pairs(objects) do
  45. local ent = obj:get_luaentity()
  46. if ent then
  47. if ent.name == "3d_armor_stand:armor_entity" then
  48. -- Remove duplicates
  49. if object then
  50. obj:remove()
  51. else
  52. object = obj
  53. end
  54. end
  55. end
  56. end
  57. return object
  58. end
  59. local function update_entity(pos)
  60. local node = minetest.get_node(pos)
  61. local object = get_stand_object(pos)
  62. if object then
  63. if not string.find(node.name, "3d_armor_stand:") then
  64. object:remove()
  65. return
  66. end
  67. else
  68. object = minetest.add_entity(pos, "3d_armor_stand:armor_entity")
  69. end
  70. if object then
  71. local texture = "3d_armor_trans.png"
  72. local textures = {}
  73. local meta = minetest.get_meta(pos)
  74. local inv = meta:get_inventory()
  75. local yaw = 0
  76. if inv then
  77. for _, element in pairs(elements) do
  78. local stack = inv:get_stack("armor_"..element, 1)
  79. if stack:get_count() == 1 then
  80. local item = stack:get_name() or ""
  81. local def = stack:get_definition() or {}
  82. local groups = def.groups or {}
  83. if groups["armor_"..element] then
  84. if def.texture then
  85. table.insert(textures, def.texture)
  86. else
  87. table.insert(textures, item:gsub("%:", "_")..".png")
  88. end
  89. end
  90. end
  91. end
  92. end
  93. if #textures > 0 then
  94. texture = table.concat(textures, "^")
  95. end
  96. if node.param2 then
  97. local rot = node.param2 % 4
  98. if rot == 1 then
  99. yaw = 3 * math.pi / 2
  100. elseif rot == 2 then
  101. yaw = math.pi
  102. elseif rot == 3 then
  103. yaw = math.pi / 2
  104. end
  105. end
  106. object:set_yaw(yaw)
  107. object:set_properties({textures={texture}})
  108. end
  109. end
  110. local function has_locked_armor_stand_privilege(meta, player)
  111. local name = ""
  112. if player then
  113. if minetest.check_player_privs(player, "protection_bypass") then
  114. return true
  115. end
  116. name = player:get_player_name()
  117. end
  118. if name ~= meta:get_string("owner") then
  119. return false
  120. end
  121. return true
  122. end
  123. local function add_hidden_node(pos, player)
  124. local p = {x=pos.x, y=pos.y + 1, z=pos.z}
  125. local name = player:get_player_name()
  126. local node = minetest.get_node(p)
  127. if node.name == "air" and not minetest.is_protected(pos, name) then
  128. minetest.add_node(p, {name="3d_armor_stand:top"})
  129. end
  130. end
  131. local function remove_hidden_node(pos)
  132. local p = {x=pos.x, y=pos.y + 1, z=pos.z}
  133. local node = minetest.get_node(p)
  134. if node.name == "3d_armor_stand:top" then
  135. minetest.remove_node(p)
  136. end
  137. end
  138. local function on_receive_fields(pos, formname, fields, sender)
  139. if not sender or not sender:is_player() then
  140. return
  141. end
  142. local node = minetest.get_node(pos)
  143. local is_locked = nil
  144. if node.name == "3d_armor_stand:armor_stand" then
  145. is_locked = false
  146. elseif node.name == "3d_armor_stand:locked_armor_stand" then
  147. is_locked = true
  148. else
  149. -- Not an armor stand. This should never happen, anyways.
  150. return
  151. end
  152. if fields.quit then
  153. return
  154. end
  155. local name = sender:get_player_name()
  156. local meta = minetest.get_meta(pos)
  157. if is_locked and not has_locked_armor_stand_privilege(meta, sender) then
  158. minetest.chat_send_player(name, "# Server: This armor stand isn't yours.")
  159. easyvend.sound_error(sender)
  160. return
  161. end
  162. if fields.equip or fields.unequip then
  163. local inv = meta:get_inventory()
  164. local pname, player_inv, armor_inv = armor:get_valid_player(sender, "[3d_armor_stand]")
  165. -- Note: a nil check on pname alone would suffice, as other cases are
  166. -- already caught (and logged!) by armor:get_valid_player() itself. However,
  167. -- these additional checks make luacheck happy.
  168. if not pname or not player_inv or not armor_inv then
  169. minetest.chat_send_player(name, "# Server: General protection fault! " ..
  170. "Please, try again or report to admin.")
  171. return
  172. end
  173. -- Working on a list will hopefully make things a little faster, and partly
  174. -- atomic. This cannot be done with the armor stand own inventory, though,
  175. -- because it uses a separate inventory list per each group.
  176. local armor_list = armor_inv:get_list("armor")
  177. -- The logic is simple. For each existing element (aka, group) we aim to
  178. -- swap the items in the corresponding slots of the armor stand inventory
  179. -- and the player's armor inventory. Things are slightly different in the
  180. -- two cases, though:
  181. -- - equip: equip the pieces OR swap them if already worn.
  182. -- - unequip: unequip pieces if there's room in the stand, otherwise skip.
  183. local swap_count = 0
  184. for _, element in ipairs(elements) do
  185. local group = "armor_" .. element
  186. inv:set_size(group, 1) -- Old stands might be missing a list.
  187. local stand_stack = inv:get_stack(group, 1)
  188. if
  189. (fields.unequip and stand_stack:is_empty())
  190. or
  191. (fields.equip and not stand_stack:is_empty())
  192. then
  193. local swap_pos = nil
  194. -- Search for a wore piece of the same group, and/or an empty slot in
  195. -- the player's armor inventory.
  196. for i = 1, #armor_list do
  197. local armor_stack = armor_list[i]
  198. if fields.equip and armor_stack:is_empty() then
  199. if not swap_pos then
  200. -- First free slot.
  201. swap_pos = i
  202. end
  203. elseif not armor_stack:is_empty() then
  204. local def = armor_stack:get_definition()
  205. if def.groups and def.groups[group] then
  206. -- Slot with a piece with the same group.
  207. swap_pos = i
  208. break
  209. end
  210. end
  211. end
  212. -- If a swap position has been found, swap! Note that the source or
  213. -- destination slots may be empty item stacks. (But not both, ofc.)
  214. if swap_pos then
  215. inv:set_stack(group, 1, armor_list[swap_pos])
  216. armor_list[swap_pos] = stand_stack
  217. swap_count = swap_count + 1
  218. end
  219. end
  220. end
  221. if swap_count == 0 then
  222. if fields.equip then
  223. minetest.chat_send_player(name, "# Server: Nothing to equip.")
  224. else
  225. minetest.chat_send_player(name, "# Server: Nothing to unequip, or no room on the armor stand.")
  226. end
  227. else
  228. local pieces = swap_count > 1 and "pieces" or "piece"
  229. local equipd = fields.equip and "equipped" or "unequipped"
  230. minetest.chat_send_player(name, string.format(
  231. "# Server: %d armor %s %s.", swap_count, pieces, equipd))
  232. if fields.equip then
  233. easyvend.sound_vend(pos)
  234. else
  235. easyvend.sound_setup(pos)
  236. end
  237. -- Update player's armor inventories (both detached and pinv).
  238. armor_inv:set_list("armor", armor_list)
  239. player_inv:set_list("armor", armor_list)
  240. -- Actually apply the new armor stats to player.
  241. armor:set_player_armor(sender)
  242. -- Update player's armor formspec.
  243. armor:update_inventory(sender)
  244. -- Update the armor stand entity.
  245. update_entity(pos)
  246. end
  247. end -- fields.equip or fields.unequip
  248. end
  249. minetest.register_node("3d_armor_stand:top", {
  250. description = "Armor Stand Top",
  251. paramtype = "light",
  252. drawtype = "plantlike",
  253. sunlight_propagates = true,
  254. walkable = true,
  255. pointable = false,
  256. diggable = false,
  257. buildable_to = false,
  258. drop = "",
  259. groups = {not_in_creative_inventory = 1},
  260. on_blast = function() end,
  261. tiles = {"3d_armor_trans.png"},
  262. })
  263. minetest.register_node("3d_armor_stand:armor_stand", {
  264. description = "Armor Stand",
  265. drawtype = "mesh",
  266. mesh = "3d_armor_stand.obj",
  267. tiles = {"3d_armor_stand.png"},
  268. paramtype = "light",
  269. paramtype2 = "facedir",
  270. walkable = false,
  271. stack_max = 1,
  272. selection_box = {
  273. type = "fixed",
  274. fixed = {
  275. {-0.25, -0.4375, -0.25, 0.25, 1.4, 0.25},
  276. {-0.5, -0.5, -0.5, 0.5, -0.4375, 0.5},
  277. },
  278. },
  279. groups = {choppy=2, oddly_breakable_by_hand=2},
  280. sounds = default.node_sound_wood_defaults(),
  281. on_construct = function(pos)
  282. local meta = minetest.get_meta(pos)
  283. meta:set_string("formspec", armor_stand_formspec)
  284. meta:set_string("infotext", "Armor Stand")
  285. local inv = meta:get_inventory()
  286. for _, element in pairs(elements) do
  287. inv:set_size("armor_"..element, 1)
  288. end
  289. local timer = minetest.get_node_timer(pos)
  290. timer:start(60*60) -- 1 hour.
  291. end,
  292. on_timer = function(pos, elapsed)
  293. update_entity(pos)
  294. return true -- Restart timer with same timeout.
  295. end,
  296. on_receive_fields = on_receive_fields,
  297. can_dig = function(pos, player)
  298. local meta = minetest.get_meta(pos)
  299. local inv = meta:get_inventory()
  300. for _, element in pairs(elements) do
  301. if not inv:is_empty("armor_"..element) then
  302. return false
  303. end
  304. end
  305. return true
  306. end,
  307. after_place_node = function(pos, placer)
  308. minetest.add_entity(pos, "3d_armor_stand:armor_entity")
  309. add_hidden_node(pos, placer)
  310. end,
  311. _on_update_formspec = function(pos)
  312. local meta = minetest.get_meta(pos)
  313. meta:set_string("formspec", armor_stand_formspec)
  314. end,
  315. _on_update_entity = function(pos)
  316. update_entity(pos)
  317. end,
  318. allow_metadata_inventory_put = function(pos, listname, index, stack)
  319. local def = stack:get_definition() or {}
  320. local groups = def.groups or {}
  321. if groups[listname] then
  322. return 1
  323. end
  324. return 0
  325. end,
  326. allow_metadata_inventory_move = function(pos)
  327. return 0
  328. end,
  329. on_metadata_inventory_put = function(pos)
  330. update_entity(pos)
  331. local timer = minetest.get_node_timer(pos)
  332. if not timer:is_started() then
  333. timer:start(60*60)
  334. end
  335. end,
  336. on_metadata_inventory_take = function(pos)
  337. update_entity(pos)
  338. local timer = minetest.get_node_timer(pos)
  339. if not timer:is_started() then
  340. timer:start(60*60)
  341. end
  342. end,
  343. after_destruct = function(pos)
  344. update_entity(pos)
  345. remove_hidden_node(pos)
  346. end,
  347. on_blast = function(pos)
  348. drop_armor(pos)
  349. armor.drop_armor(pos, "3d_armor_stand:armor_stand")
  350. minetest.remove_node(pos)
  351. end,
  352. })
  353. minetest.register_node("3d_armor_stand:locked_armor_stand", {
  354. description = "Locked Armor Stand",
  355. drawtype = "mesh",
  356. mesh = "3d_armor_stand.obj",
  357. tiles = {"3d_armor_stand_locked.png"},
  358. paramtype = "light",
  359. paramtype2 = "facedir",
  360. walkable = false,
  361. stack_max = 1,
  362. selection_box = {
  363. type = "fixed",
  364. fixed = {
  365. {-0.25, -0.4375, -0.25, 0.25, 1.4, 0.25},
  366. {-0.5, -0.5, -0.5, 0.5, -0.4375, 0.5},
  367. },
  368. },
  369. groups = {choppy=2, oddly_breakable_by_hand=2},
  370. sounds = default.node_sound_wood_defaults(),
  371. on_construct = function(pos)
  372. local meta = minetest.get_meta(pos)
  373. meta:set_string("formspec", armor_stand_formspec)
  374. meta:set_string("infotext", "Armor Stand")
  375. meta:set_string("owner", "")
  376. local inv = meta:get_inventory()
  377. for _, element in pairs(elements) do
  378. inv:set_size("armor_"..element, 1)
  379. end
  380. local timer = minetest.get_node_timer(pos)
  381. timer:start(60*60) -- 1 hour.
  382. end,
  383. on_timer = function(pos, elapsed)
  384. update_entity(pos)
  385. return true -- Restart timer with same timeout.
  386. end,
  387. on_receive_fields = on_receive_fields,
  388. can_dig = function(pos, player)
  389. local meta = minetest.get_meta(pos)
  390. local inv = meta:get_inventory()
  391. for _, element in pairs(elements) do
  392. if not inv:is_empty("armor_"..element) then
  393. return false
  394. end
  395. end
  396. return true
  397. end,
  398. after_place_node = function(pos, placer)
  399. minetest.add_entity(pos, "3d_armor_stand:armor_entity")
  400. local meta = minetest.get_meta(pos)
  401. meta:set_string("owner", placer:get_player_name() or "")
  402. meta:set_string("infotext", "Armor Stand (Owned by <" .. rename.gpn(meta:get_string("owner")) .. ">!)")
  403. add_hidden_node(pos, placer)
  404. end,
  405. -- Called by rename LBM.
  406. _on_update_infotext = function(pos)
  407. local meta = minetest.get_meta(pos)
  408. meta:set_string("infotext", "Armor Stand (Owned by <" .. rename.gpn(meta:get_string("owner")) .. ">!)")
  409. end,
  410. _on_update_formspec = function(pos)
  411. local meta = minetest.get_meta(pos)
  412. meta:set_string("formspec", armor_stand_formspec)
  413. end,
  414. _on_update_entity = function(pos)
  415. update_entity(pos)
  416. end,
  417. allow_metadata_inventory_put = function(pos, listname, index, stack, player)
  418. local meta = minetest.get_meta(pos)
  419. if not has_locked_armor_stand_privilege(meta, player) then
  420. return 0
  421. end
  422. local def = stack:get_definition() or {}
  423. local groups = def.groups or {}
  424. if groups[listname] then
  425. return 1
  426. end
  427. return 0
  428. end,
  429. allow_metadata_inventory_take = function(pos, listname, index, stack, player)
  430. local meta = minetest.get_meta(pos)
  431. if not has_locked_armor_stand_privilege(meta, player) then
  432. return 0
  433. end
  434. return stack:get_count()
  435. end,
  436. allow_metadata_inventory_move = function(pos)
  437. return 0
  438. end,
  439. on_metadata_inventory_put = function(pos)
  440. update_entity(pos)
  441. local timer = minetest.get_node_timer(pos)
  442. if not timer:is_started() then
  443. timer:start(60*60)
  444. end
  445. end,
  446. on_metadata_inventory_take = function(pos)
  447. update_entity(pos)
  448. local timer = minetest.get_node_timer(pos)
  449. if not timer:is_started() then
  450. timer:start(60*60)
  451. end
  452. end,
  453. after_destruct = function(pos)
  454. update_entity(pos)
  455. remove_hidden_node(pos)
  456. end,
  457. on_blast = function(pos)
  458. -- Not affected by TNT
  459. end,
  460. })
  461. minetest.register_entity("3d_armor_stand:armor_entity", {
  462. initial_properties = {
  463. physical = true,
  464. visual = "mesh",
  465. mesh = "3d_armor_entity.obj",
  466. visual_size = {x=1, y=1},
  467. collisionbox = {0,0,0,0,0,0},
  468. textures = {"3d_armor_trans.png"},
  469. },
  470. _pos = nil,
  471. get_staticdata = function(self)
  472. return ""
  473. end,
  474. on_activate = function(self)
  475. local pos = self.object:get_pos()
  476. if pos then
  477. self._pos = vector_round(pos)
  478. update_entity(pos)
  479. end
  480. end,
  481. on_blast = function(self, damage)
  482. local drops = {}
  483. local node = minetest.get_node(self._pos)
  484. if node.name == "3d_armor_stand:armor_stand" then
  485. drop_armor(self._pos)
  486. self.object:remove()
  487. end
  488. return false, false, drops
  489. end,
  490. })
  491. minetest.register_craft({
  492. output = "3d_armor_stand:armor_stand",
  493. recipe = {
  494. {"", "default:fence_pine_wood", ""},
  495. {"", "default:fence_pine_wood", ""},
  496. {"default:steel_ingot", "default:steel_ingot", "default:steel_ingot"},
  497. }
  498. })
  499. minetest.register_craft({
  500. output = "3d_armor_stand:locked_armor_stand",
  501. recipe = {
  502. {"3d_armor_stand:armor_stand", "default:padlock"},
  503. }
  504. })