functions.lua 22 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790
  1. -- This file is reloadable.
  2. bones = bones or {}
  3. -- Contains the positions of last known player deaths, indexed by player name.
  4. bones.last_known_death_locations = bones.last_known_death_locations or {}
  5. local get_public_time = function()
  6. return os.date("!%Y/%m/%d, %H:%M:%S UTC")
  7. end
  8. local share_bones_time = tonumber(minetest.setting_get("share_bones_time")) or 1200
  9. local share_bones_time_early = tonumber(minetest.setting_get("share_bones_time_early")) or share_bones_time / 4
  10. local function is_owner(pos, name)
  11. local owner = minetest.get_meta(pos):get_string("owner")
  12. -- Don't let dead players pick bones.
  13. local player = minetest.get_player_by_name(name)
  14. if not player or not player:is_player() then return false end
  15. if player:get_hp() <= 0 then return false end
  16. if owner == "" or owner == name or minetest.check_player_privs(name, "protection_bypass") then
  17. return true
  18. end
  19. return false
  20. end
  21. local function may_replace(pos, player)
  22. local nn = minetest.get_node(pos).name
  23. local ndef = minetest.reg_ns_nodes[nn]
  24. -- If the node is unknown, we return false.
  25. if not ndef then
  26. return false
  27. end
  28. local pname = player and player:get_player_name() or ""
  29. local protected = minetest.test_protection(pos, pname)
  30. -- Allow replacing air and (non-walkable) liquids.
  31. if nn == "air" or (ndef.liquidtype ~= "none" and not ndef.walkable) then
  32. return true
  33. end
  34. -- Don't replace filled chests and other nodes that don't allow it.
  35. -- Includes bedrock, admin TNT, etc.
  36. local can_dig = ndef.can_dig
  37. if can_dig and not can_dig(pos, player) then
  38. return false
  39. end
  40. -- Default to each nodes buildable_to; if a placed block would replace it, why
  41. -- shouldn't bones? Flowers being squished by bones are more realistical than
  42. -- a squished stone, too. Exception are of course any protected buildable_to.
  43. return ndef.buildable_to and not protected
  44. end
  45. local drop = function(pos, itemstack)
  46. local obj = minetest.add_item(pos, itemstack:take_item(itemstack:get_count()))
  47. if obj then
  48. obj:setvelocity({
  49. x = math.random(-10, 10) / 9,
  50. y = 5,
  51. z = math.random(-10, 10) / 9,
  52. })
  53. end
  54. end
  55. local player_inventory_empty = function(inv, name)
  56. local list = inv:get_list(name)
  57. -- Nil check.
  58. if not list then
  59. -- Could not get list, list does not exist.
  60. -- This is true in the case of bags.
  61. return true -- Inventory list doesn't exist.
  62. end
  63. for i = 1, #list do
  64. local stack = list[i]
  65. if not stack:is_empty() then
  66. if not passport.is_passport(stack:get_name()) then
  67. return false -- Not empty.
  68. end
  69. end
  70. end
  71. return true -- Inventory list is empty.
  72. end
  73. local find_ground = function(pos, player)
  74. local p = {x=pos.x, y=pos.y, z=pos.z}
  75. local count = 0
  76. local cr1 = may_replace({x=p.x, y=p.y, z=p.z}, player)
  77. local cr2 = may_replace({x=p.x, y=p.y-1, z=p.z}, player)
  78. while (not (cr1 and not cr2)) or (cr1 and cr2) do
  79. p.y = p.y - 1
  80. count = count + 1
  81. if count > 10 then return pos end
  82. cr1 = may_replace({x=p.x, y=p.y, z=p.z}, player)
  83. cr2 = may_replace({x=p.x, y=p.y-1, z=p.z}, player)
  84. end
  85. -- Return position, if we can replace to it, but not to the node under it.
  86. if cr1 and not cr2 then
  87. return p
  88. end
  89. return pos
  90. end
  91. local function find_suitable_bone_location(pos, player)
  92. -- Locate position directly above ground level.
  93. local air = find_ground(pos, player)
  94. -- Check whether the initial location is suitable.
  95. if not may_replace(air, player) then
  96. air = nil
  97. end
  98. -- Is the initial location not suitable? Try to locate a suitable location on
  99. -- a horizontal plane from the actual death location.
  100. if not air then
  101. local sidepos = {
  102. {x=pos.x, y=pos.y, z=pos.z},
  103. {x=pos.x+1, y=pos.y, z=pos.z},
  104. {x=pos.x-1, y=pos.y, z=pos.z},
  105. {x=pos.x, y=pos.y, z=pos.z+1},
  106. {x=pos.x, y=pos.y, z=pos.z-1},
  107. }
  108. for k, v in ipairs(sidepos) do
  109. if may_replace(v, player) then
  110. air = v
  111. break
  112. end
  113. end
  114. end
  115. -- Didn't find a replaceable node that way, try a short-range search for air.
  116. if not air then
  117. air = minetest.find_node_near(pos, 2, {"air"})
  118. end
  119. -- Still didn't find air? Try a longer range search,
  120. -- and include liquid/fire/etc nodes that player can die in.
  121. if not air then
  122. -- If we couldn't find air, fallback to finding a location in these
  123. -- substances. Search 1 meter farther than at first.
  124. air = minetest.find_node_near(pos, 3, {
  125. "air",
  126. "default:water_source",
  127. "default:water_flowing",
  128. "default:river_water_source",
  129. "default:river_water_flowing",
  130. "default:lava_source",
  131. "default:lava_flowing",
  132. "lbrim:lava_source",
  133. "lbrim:lava_flowing",
  134. "group:gas",
  135. "rackstone:nether_grit",
  136. "rackstone:void",
  137. "fire:basic_flame",
  138. })
  139. end
  140. -- If we found air, try to make sure we found a replaceable node directly
  141. -- above ground.
  142. if air then
  143. air = find_ground(air, player)
  144. end
  145. -- By now, we have either found air or nothing.
  146. return air -- May be nil.
  147. end
  148. -- This function may be called from mods to dump player bones EXACTLY as if the
  149. -- player had died, including any side-effects. The sole exception is that the
  150. -- player's health is not actually changed. You might need to update a few other
  151. -- datums too, to get exactly the right result.
  152. function bones.dump_bones(pname)
  153. local player = minetest.get_player_by_name(pname)
  154. if player then
  155. bones.on_dieplayer(player)
  156. end
  157. end
  158. bones.on_dieplayer = function(player)
  159. local bones_mode = "bones"
  160. local player_inv = player:get_inventory()
  161. local pname = player:get_player_name()
  162. -- If player died while attached to cart/boat/etc, they must be detached.
  163. default.detach_player_if_attached(player)
  164. -- Record position of player on death.
  165. -- This is needed because this information is lost on respawn.
  166. -- We must record this info *always*, even if player does not leave bones.
  167. bones.last_known_death_locations[pname] = utility.get_foot_pos(player:get_pos())
  168. -- Death sound.
  169. coresounds.play_death_sound(player, pname)
  170. -- Notify of death.
  171. chat_colorize.notify_death(pname)
  172. -- Don't make bones if player doesn't have anything.
  173. -- This also means that player won't lose XP. Keep this, it is a feature!
  174. if player_inventory_empty(player_inv, "main") and
  175. player_inventory_empty(player_inv, "craft") and
  176. player_inventory_empty(player_inv, "bag1contents") and
  177. player_inventory_empty(player_inv, "bag2contents") and
  178. player_inventory_empty(player_inv, "bag3contents") and
  179. player_inventory_empty(player_inv, "bag4contents") and
  180. player_inventory_empty(player_inv, "bag1") and
  181. player_inventory_empty(player_inv, "bag2") and
  182. player_inventory_empty(player_inv, "bag3") and
  183. player_inventory_empty(player_inv, "bag4") and
  184. player_inventory_empty(player_inv, "armor") then
  185. return
  186. end
  187. local pos = vector.round(utility.get_middle_pos(player:get_pos()))
  188. -- Check if it's possible to place bones, if not find space near player.
  189. if bones_mode == "bones" then
  190. local boneloc = find_suitable_bone_location(pos, player)
  191. if boneloc then
  192. pos = boneloc
  193. else
  194. bones_mode = "drop"
  195. end
  196. end
  197. if not rc.is_valid_realm_pos(pos) then
  198. bones_mode = "drop"
  199. end
  200. if bones_mode ~= "bones" then
  201. -- Cannot create bones, therefore we don't modify player inventories.
  202. minetest.log("action", "Player <" .. pname .. "> died @ " .. minetest.pos_to_string(pos) .. ", but cannot create bones!")
  203. return
  204. end
  205. -- Halve player XP!
  206. -- Note: player doesn't lose any XP if their bones were EMPTY.
  207. local xp_amount = xp.get_xp(pname, "digxp")
  208. xp_amount = xp_amount/2
  209. local xp_for_bones = (xp_amount/3)*2
  210. xp.set_xp(pname, "digxp", xp_amount)
  211. -- Note: portal sickness only removed if player would leave bones.
  212. portal_sickness.on_die_player(pname)
  213. -- These inventories are always cleared.
  214. player_inv:set_list("craftpreview", {})
  215. player_inv:set_list("craftresult", {})
  216. -- Clear the virtual bags inventories.
  217. do
  218. local bags_inv = minetest.get_inventory({type="detached", name=pname .. "_bags"})
  219. bags_inv:set_list("bag1", {})
  220. bags_inv:set_list("bag2", {})
  221. bags_inv:set_list("bag3", {})
  222. bags_inv:set_list("bag4", {})
  223. end
  224. -- The player can die while holding something. Remove it.
  225. minetest.after(0.1, function()
  226. local ply = minetest.get_player_by_name(pname)
  227. if not ply then return end
  228. local inv = ply:get_inventory()
  229. if not inv then return end
  230. if inv:is_empty("hand") then return end
  231. inv:set_list("hand", {}) -- Fixes a bug if the player dies while holding something.
  232. end)
  233. -- Preload map just in case.
  234. local minp = vector.add(pos, -16)
  235. local maxp = vector.add(pos, 16)
  236. utility.ensure_map_loaded(minp, maxp)
  237. local param2 = minetest.dir_to_facedir(player:get_look_dir())
  238. minetest.add_node(pos, {name = "bones:bones", param2 = param2})
  239. local meta = minetest.get_meta(pos)
  240. meta:set_float("digxp", xp_for_bones)
  241. meta:set_string("diedate", get_public_time())
  242. local inv = meta:get_inventory()
  243. inv:set_size("main", 200) -- Enuf space for everything!
  244. -- Keep track of how many stacks are stored in bones.
  245. local location = minetest.pos_to_string(pos)
  246. local num_stacks = 0
  247. -- Note: clear player inventory slot-by-slot to avoid clobbering PoC/KoC items.
  248. -- Empty the player's main inv. We must not to clobber any passports.
  249. do
  250. local list = player_inv:get_list("main")
  251. if list then -- Nil check necessary.
  252. for i = 1, #list do
  253. local stack = list[i]
  254. if not passport.is_passport(stack:get_name()) then
  255. if stack:get_count() > 0 and inv:room_for_item("main", stack) then
  256. inv:add_item("main", stack)
  257. minetest.log("action", "Put " .. stack:to_string() .. " in bones @ " .. location .. ".")
  258. num_stacks = num_stacks + 1
  259. -- Stack was added to bones inventory, remove it from list.
  260. list[i]:set_count(0)
  261. list[i]:set_name("")
  262. else
  263. drop(pos, stack)
  264. end
  265. end
  266. end
  267. player_inv:set_list("main", list)
  268. end
  269. end
  270. -- Empty the player's craft-grid. Passports are not preserved, here.
  271. do
  272. local list = player_inv:get_list("craft")
  273. if list then -- Nil check necessary.
  274. for i = 1, #list do
  275. local stack = list[i]
  276. if stack:get_count() > 0 and inv:room_for_item("main", stack) then
  277. inv:add_item("main", stack)
  278. minetest.log("action", "Put " .. stack:to_string() .. " in bones @ " .. location .. ".")
  279. num_stacks = num_stacks + 1
  280. else
  281. drop(pos, stack)
  282. end
  283. end
  284. player_inv:set_list("craft", {})
  285. end
  286. end
  287. -- Armor goes into bones after main and crafting grid items.
  288. armor.on_dieplayer(player, pos)
  289. -- Empty the bag slots. Passports are not preserved, here. (It should not be possible to store a passport in here, anyway.)
  290. for j = 1, 4, 1 do
  291. local bag = "bag" .. j
  292. local list = player_inv:get_list(bag)
  293. if list then -- Nil check necessary.
  294. for i = 1, #list do
  295. local stack = list[i]
  296. if stack:get_count() > 0 and inv:room_for_item("main", stack) then
  297. inv:add_item("main", stack)
  298. minetest.log("action", "Put " .. stack:to_string() .. " in bones @ " .. location .. ".")
  299. num_stacks = num_stacks + 1
  300. else
  301. drop(pos, stack)
  302. end
  303. end
  304. player_inv:set_list(bag, {})
  305. end
  306. end
  307. -- Empty the bag inventories. Passports are not preserved, here.
  308. for j = 1, 4, 1 do
  309. local bag = "bag" .. j .. "contents"
  310. local list = player_inv:get_list(bag)
  311. if list then -- Nil check necessary.
  312. for i = 1, #list do
  313. local stack = list[i]
  314. if stack:get_count() > 0 and inv:room_for_item("main", stack) then
  315. inv:add_item("main", stack)
  316. minetest.log("action", "Put " .. stack:to_string() .. " in bones @ " .. location .. ".")
  317. num_stacks = num_stacks + 1
  318. else
  319. drop(pos, stack)
  320. end
  321. end
  322. player_inv:set_list(bag, {})
  323. end
  324. end
  325. -- We use on_rightclick instead.
  326. --meta:set_string("formspec", bones_formspec)
  327. meta:set_string("owner", pname)
  328. meta:set_int("numstacks", num_stacks)
  329. -- Notify the mapping code it needs to recalculate the mapkit cache.
  330. minetest.after(0, function()
  331. map.clear_inventory_info(pname)
  332. end)
  333. if share_bones_time ~= 0 then
  334. meta:set_string("infotext",
  335. "Unfortunate <" .. rename.gpn(pname) ..
  336. ">'s Undecayed Bones\nMineral XP: " .. string.format("%.2f", xp_for_bones) .. "\n" ..
  337. "Died On " .. meta:get_string("diedate"))
  338. if share_bones_time_early == 0 or not minetest.test_protection(pos, "") then
  339. meta:set_int("time", 0)
  340. else
  341. meta:set_int("time", (share_bones_time - share_bones_time_early))
  342. end
  343. minetest.get_node_timer(pos):start(10)
  344. else
  345. meta:set_string("infotext",
  346. "Unfortunate <" .. rename.gpn(pname) ..
  347. ">'s Bones\nMineral XP: " .. string.format("%.2f", xp_for_bones) .. "\n" ..
  348. "Died On " .. meta:get_string("diedate"))
  349. end
  350. hud_clock.update_xp(pname)
  351. if bones_mode == "bones" then
  352. if bones and bones.do_messages then
  353. bones.do_messages(pos, pname, num_stacks)
  354. end
  355. if minetest.get_node(pos).name == "bones:bones" then
  356. minetest.log("action", "Successfully spawned bones @ " .. minetest.pos_to_string(pos) .. "!")
  357. end
  358. end
  359. -- Check if bones still exist after 1 second.
  360. minetest.after(1, function()
  361. local name = minetest.get_node(pos).name
  362. if name == "bones:bones" then
  363. minetest.log("action", "Bones exist @ " .. minetest.pos_to_string(pos) .. " after 1 second check!")
  364. elseif name == "ignore" then
  365. minetest.log("action", "Got ignore @ " .. minetest.pos_to_string(pos) .. " after 1 second bone check!")
  366. else
  367. minetest.log("action", "Got " .. name .. " @ " .. minetest.pos_to_string(pos) .. " after 1 second bone check!")
  368. end
  369. end)
  370. end
  371. bones.can_dig = function(pos, player)
  372. local inv = minetest.get_meta(pos):get_inventory()
  373. local name = ""
  374. if player then
  375. name = player:get_player_name()
  376. end
  377. return is_owner(pos, name) and inv:is_empty("main")
  378. end
  379. bones.allow_metadata_inventory_move = function(pos, from_list, from_index, to_list, to_index, count, player)
  380. if is_owner(pos, player:get_player_name()) then
  381. return count
  382. end
  383. return 0
  384. end
  385. bones.allow_metadata_inventory_put = function(pos, listname, index, stack, player)
  386. minetest.chat_send_player(player:get_player_name(), "# Server: Bones are not a place to store items!")
  387. return 0
  388. end
  389. bones.allow_metadata_inventory_take = function(pos, listname, index, stack, player)
  390. -- Prevent picking bones right after respawn, before player is repositioned.
  391. if bones.nohack.on_hackdetect(player) then
  392. local pname = player:get_player_name()
  393. minetest.chat_send_player(pname, "# Server: Wait a bit before taking from bones.")
  394. return 0
  395. end
  396. if is_owner(pos, player:get_player_name()) then
  397. return stack:get_count()
  398. end
  399. return 0
  400. end
  401. bones.on_metadata_inventory_take = function(pos, listname, index, stack, player)
  402. local pname = player:get_player_name()
  403. local meta = minetest.get_meta(pos)
  404. if meta:get_inventory():is_empty("main") then
  405. bones.reward_xp(meta, pname)
  406. bones.do_grab_bones_message(pname, pos, meta)
  407. minetest.after(0, function() minetest.close_formspec(pname, "bones:main") end)
  408. minetest.log("action", "Player " .. pname .. " took all from bones @ " .. minetest.pos_to_string(pos) .. ". Removing bones.")
  409. minetest.remove_node(pos)
  410. end
  411. end
  412. bones.reward_xp = function(meta, pname)
  413. -- Give XP stored in bones to player.
  414. -- But only if player removed the bones!
  415. local digxp = meta:get_float("digxp")
  416. local current_xp = xp.get_xp(pname, "digxp")
  417. current_xp = current_xp + digxp
  418. if current_xp > xp.digxp_max then
  419. current_xp = xp.digxp_max
  420. end
  421. xp.set_xp(pname, "digxp", current_xp)
  422. hud_clock.update_xp(pname)
  423. meta:set_float("digxp", 0)
  424. end
  425. bones.on_punch = function(pos, node, player)
  426. -- Fix stopped timers.
  427. minetest.get_node_timer(pos):start(10)
  428. local pname = player:get_player_name()
  429. if not is_owner(pos, pname) then
  430. return
  431. end
  432. if sheriff.player_punished(pname) then
  433. if sheriff.punish_probability(pname) then
  434. sheriff.punish_player(pname)
  435. return
  436. end
  437. end
  438. -- Prevent picking bones right after respawn, before player is repositioned.
  439. if bones.nohack.on_hackdetect(player) then
  440. minetest.chat_send_player(pname, "# Server: Wait a bit before picking bones.")
  441. return
  442. end
  443. -- Dead players cannot pick bones.
  444. if player:get_hp() <= 0 then
  445. return
  446. end
  447. local meta = minetest.get_meta(pos)
  448. -- Bones that are neither fresh nor old aren't 'punchable'.
  449. if meta:get_string("infotext") == "" then
  450. return
  451. end
  452. local inv = meta:get_inventory()
  453. local player_inv = player:get_inventory()
  454. local has_space = true
  455. local added_map = false
  456. for i = 1, inv:get_size("main") do
  457. local stk = inv:get_stack("main", i)
  458. if player_inv:room_for_item("main", stk) then
  459. inv:set_stack("main", i, nil)
  460. player_inv:add_item("main", stk)
  461. -- Notify if a mapping kit was added.
  462. if map.is_mapping_kit(stk:get_name()) then
  463. added_map = true
  464. end
  465. else
  466. has_space = false
  467. break
  468. end
  469. end
  470. -- Notify if a mapping kit was added.
  471. if added_map then
  472. map.update_inventory_info(pname)
  473. end
  474. -- remove bones if player emptied them
  475. if has_space then
  476. -- Give player the bones.
  477. if player_inv:room_for_item("main", {name = "bones:bones_type2"}) then
  478. player_inv:add_item("main", {name = "bones:bones_type2"})
  479. else
  480. minetest.add_item(pos,"bones:bones_type2")
  481. end
  482. bones.reward_xp(meta, pname)
  483. bones.do_grab_bones_message(pname, pos, meta)
  484. -- Remove the bones from the world.
  485. minetest.log("action", "Bones @ " .. minetest.pos_to_string(pos) .. " punched by " .. pname .. ". Removing bones.")
  486. minetest.remove_node(pos)
  487. end
  488. end
  489. bones.do_grab_bones_message = function(pname, pos, meta)
  490. -- Send chat messages about bones retrieval.
  491. -- Don't spam the chatstream.
  492. if not bones.players[pname] then
  493. local public = true
  494. if player_labels.query_nametag_onoff(pname) == false then
  495. public = false
  496. end
  497. local numstacks = meta:get_int("numstacks")
  498. local stacks = "stacks"
  499. if numstacks == 1 then
  500. stacks = "stack"
  501. elseif numstacks == 0 then
  502. numstacks = "an unknown number of"
  503. end
  504. local boneowner = meta:get_string("owner")
  505. if boneowner == "" then
  506. boneowner = meta:get_string("oldowner")
  507. end
  508. local ownerstring = "<" .. rename.gpn(boneowner) .. ">'s"
  509. if pname == boneowner then
  510. local sex = skins.get_gender_strings(pname)
  511. ownerstring = sex.his .. " own"
  512. end
  513. if boneowner == "" then
  514. ownerstring = "someone's"
  515. end
  516. local agestring = "fresh"
  517. if meta:get_string("owner") == "" then
  518. agestring = "old"
  519. end
  520. if public then
  521. minetest.chat_send_all(
  522. "# Server: Player <" ..
  523. rename.gpn(pname) ..
  524. "> claimed " .. ownerstring .. " " .. agestring .. " bones at " ..
  525. rc.pos_to_namestr(vector.round(pos)) ..
  526. " with " .. numstacks .. " " .. stacks .. ".")
  527. else
  528. minetest.chat_send_all(
  529. "# Server: Someone claimed " .. agestring .. " bones with " .. numstacks ..
  530. " " .. stacks .. ".")
  531. end
  532. bones.players[pname] = true
  533. minetest.after(60, bones.release_player, pname)
  534. end
  535. end
  536. bones.on_timer = function(pos, elapsed)
  537. local meta = minetest.get_meta(pos)
  538. local digxp = string.format("%.2f", meta:get_float("digxp"))
  539. local time = meta:get_int("time") + elapsed
  540. if time >= share_bones_time then
  541. -- Function may have been called twice or more. This prevents an issue.
  542. if meta:get_string("owner") == "" then
  543. return
  544. end
  545. minetest.log("action", "Fresh bones @ " .. minetest.pos_to_string(pos) .. " decay into old bones.")
  546. local diedate = meta:get_string("diedate")
  547. if diedate == "" then
  548. diedate = "An Unknown Date"
  549. end
  550. meta:set_string("infotext",
  551. "Unfortunate <" .. rename.gpn(meta:get_string("owner")) ..
  552. ">'s Old Bones\nMineral XP: " .. digxp .. "\n" ..
  553. "Died On " .. diedate)
  554. meta:set_string("oldowner", meta:get_string("owner"))
  555. meta:set_string("owner", "")
  556. else
  557. meta:set_int("time", time)
  558. return true
  559. end
  560. end
  561. bones.on_blast = function(pos)
  562. minetest.log("action", "Bones @ " .. minetest.pos_to_string(pos) .. " experienced explosion blast.")
  563. end
  564. bones.on_destruct = function(pos)
  565. minetest.log("action", "Bones @ " .. minetest.pos_to_string(pos) .. " destructor called. Bones removed.")
  566. local meta = minetest.get_meta(pos)
  567. local inv = meta:get_inventory()
  568. if inv:is_empty("main") then
  569. return
  570. end
  571. -- If the inventory is not empty, we must respawn the bones.
  572. -- This is a workaround for a bug that just won't go away.
  573. local node = minetest.get_node(pos)
  574. local list = inv:get_list("main")
  575. local infotext = meta:get_string("infotext")
  576. local owner = meta:get_string("owner")
  577. local oldowner = meta:get_string("oldowner")
  578. local numstacks = meta:get_int("numstacks")
  579. local time = meta:get_int("time")
  580. local digxp = meta:get_float("digxp")
  581. local diedate = meta:get_string("diedate")
  582. minetest.after(0, function()
  583. minetest.log("action", "Bones @ " .. minetest.pos_to_string(pos) .. " were not empty! Attempting to restore bones.")
  584. minetest.set_node(pos, {name="bones:bones", param2=node.param2})
  585. local meta2 = minetest.get_meta(pos)
  586. local inv2 = meta2:get_inventory()
  587. inv2:set_size("main", 200)
  588. inv2:set_list("main", list)
  589. meta2:set_string("infotext", infotext)
  590. meta2:set_string("owner", owner)
  591. meta2:set_string("oldowner", oldowner)
  592. meta2:set_int("numstacks", numstacks)
  593. meta2:set_int("time", time)
  594. meta2:set_float("digxp", digxp)
  595. meta2:set_string("diedate", diedate)
  596. if time < share_bones_time then
  597. minetest.get_node_timer(pos):start(10)
  598. end
  599. end)
  600. end
  601. -- Show bones inventory.
  602. function bones.on_rightclick(pos, node, clicker, itemstack, pt)
  603. local meta = minetest.get_meta(pos)
  604. if meta:get_string("infotext") == "" then
  605. return
  606. end
  607. -- Put all inventory contents toward the front of the list.
  608. local inv = meta:get_inventory()
  609. local list = inv:get_list("main") or {}
  610. local tmp = {}
  611. for index, stack in ipairs(list) do
  612. if not stack:is_empty() then
  613. tmp[#tmp+1] = stack
  614. end
  615. end
  616. inv:set_list("main", {})
  617. for index, stack in ipairs(tmp) do
  618. if inv:room_for_item("main", stack) then
  619. inv:add_item("main", stack)
  620. else
  621. minetest.add_item(pos, stack)
  622. end
  623. end
  624. local pname = clicker:get_player_name()
  625. minetest.log("action", "Player " .. pname .. " opens bones @ " .. minetest.pos_to_string(pos) .. ".")
  626. local spos = pos.x .. "," .. pos.y .. "," .. pos.z
  627. local formspec =
  628. "size[8,9]" ..
  629. default.gui_bg ..
  630. default.gui_bg_img ..
  631. default.gui_slots ..
  632. "list[nodemeta:" .. spos .. ";main;0,0.3;8,4;]" ..
  633. "list[current_player;main;0,4.85;8,1;]" ..
  634. "list[current_player;main;0,6.08;8,3;8]" ..
  635. "listring[nodemeta:" .. spos .. ";main]" ..
  636. "listring[current_player;main]" ..
  637. default.get_hotbar_bg(0,4.85)
  638. minetest.show_formspec(pname, "bones:main", formspec)
  639. end
  640. function bones.kill_bully_on_leaveplayer(pref, timeout)
  641. end