functions.lua 24 KB

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