functions.lua 27 KB

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