functions.lua 19 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709
  1. -- Function is badly named! Should be 'entity_ignores_arrow'.
  2. -- Return 'true' if the entity cannot be hit, otherwise return 'false' if the entity should be punched.
  3. -- Note: 'entity_name' is the registered name of the entity to be checked for hit.
  4. function throwing.entity_ignores_arrow(entity_name)
  5. -- Dropped itemstacks don't take damage.
  6. if entity_name == "__builtin:item" then
  7. return true
  8. end
  9. -- Ignore other arrows/fireballs in flight.
  10. local is_arrow = (string.find(entity_name, "arrow") or string.find(entity_name, "fireball"))
  11. if is_arrow then
  12. return true
  13. end
  14. -- Ignore health gauges above players.
  15. if entity_name == "gauges:hp_bar" then
  16. return true
  17. end
  18. -- Ignore sound beacons.
  19. if entity_name:find("^soundbeacon:") then
  20. return true
  21. end
  22. -- Entity is unknown, so punch it for damage!
  23. return false
  24. end
  25. --~
  26. --~ Shot and reload system
  27. --~
  28. local players = {}
  29. minetest.register_on_joinplayer(function(player)
  30. local playerName = player:get_player_name()
  31. players[playerName] = {
  32. reloading=false,
  33. }
  34. end)
  35. minetest.register_on_leaveplayer(function(player)
  36. local playerName = player:get_player_name()
  37. players[playerName] = nil
  38. end)
  39. -- This function copied and translated from C++, from one of my C++ projects.
  40. local function rotate_point_2d(p, r)
  41. local x = p.x
  42. local y = p.y
  43. -- Rotate.
  44. local s = math.sin(r)
  45. local c = math.cos(r)
  46. -- Temp vars required to avoid clobbering the equation.
  47. -- This mistake caused me quite a bit of wasted time!
  48. local nx = x*c - y*s
  49. local ny = x*s + y*c
  50. x = nx
  51. y = ny
  52. return {x=x, y=y}
  53. end
  54. local function get_shoot_position(player)
  55. local yaw = player:get_look_horizontal()
  56. local pos = player:get_pos()
  57. local off = {x=0.3, y=0}
  58. --local off = {x=0, y=0}
  59. off = rotate_point_2d(off, yaw)
  60. pos.x = pos.x + off.x
  61. pos.y = pos.y + 1.5
  62. pos.z = pos.z + off.y
  63. return pos
  64. end
  65. -- Can't do this, MT's API is broken here.
  66. -- I posted an issue to the devs: https://github.com/minetest/minetest/issues/14143
  67. local function do_bow_recoil(player)
  68. --local off1 = math.random(-(math.pi*100), (math.pi*100))/100 * 0.02
  69. --local off2 = math.random(-(math.pi*100), 0.0)/100 * 0.03
  70. -- set_look_horizontal() is broken.
  71. --[[
  72. local oldyaw = player:get_look_horizontal()
  73. local yaw = oldyaw-- + off1
  74. while yaw < 0 do yaw = yaw + math.pi * 2 end
  75. while yaw > math.pi * 2 do yaw = yaw - math.pi * 2 end
  76. minetest.chat_send_all(math.deg(oldyaw) .. ', ' .. math.deg(yaw))
  77. player:set_look_horizontal(yaw)
  78. --]]
  79. -- this is also broken.
  80. --local pitch = player:get_look_vertical() + off2
  81. --player:set_look_vertical(pitch)
  82. end
  83. function throwing_shoot_arrow(itemstack, player, stiffness, is_cross)
  84. if not player or not player:is_player() then return end
  85. local pname = player:get_player_name()
  86. local arrow = itemstack:get_metadata()
  87. local imeta = itemstack:get_meta()
  88. if arrow == "" then
  89. arrow = imeta:get_string("arrow")
  90. end
  91. if arrow == "" then return end
  92. local playerpos = player:get_pos()
  93. local spawnpos = get_shoot_position(player)
  94. local obj = minetest.add_entity(spawnpos, arrow)
  95. if not obj then return end
  96. local luaent = obj:get_luaentity()
  97. if not luaent then return end
  98. itemstack:set_metadata("")
  99. imeta:set_string("arrow", nil)
  100. imeta:set_string("ar_desc", nil)
  101. toolranks.apply_description(imeta, itemstack:get_definition())
  102. local accuracy = math.rad(math.random(-300, 300)/300)
  103. local dir = player:get_look_dir()
  104. local vel = stiffness * 2
  105. obj:set_velocity({x=dir.x*vel, y=dir.y*vel, z=dir.z*vel})
  106. obj:set_acceleration({x=0, y=-5, z=0})
  107. obj:set_yaw(player:get_look_horizontal() - (math.pi / 2) + accuracy)
  108. if is_cross then
  109. minetest.sound_play("throwing_crossbow_sound", {pos=playerpos}, true)
  110. else
  111. minetest.sound_play("throwing_bow_launch", {pos=playerpos}, true)
  112. end
  113. luaent.player = player
  114. luaent.player_name = pname
  115. luaent.inventory = player:get_inventory()
  116. luaent.stack = player:get_inventory():get_stack("main", player:get_wield_index()-1)
  117. luaent.lastpos = table.copy(spawnpos)
  118. -- Firing anything disables your cloak.
  119. cloaking.disable_if_enabled(pname, true)
  120. do_bow_recoil(player)
  121. -- Return the modified itemstack.
  122. return itemstack
  123. end
  124. function throwing.flight_particle(lpos, cpos)
  125. local mpos = {
  126. x = (lpos.x + cpos.x) / 2,
  127. y = (lpos.y + cpos.y) / 2,
  128. z = (lpos.z + cpos.z) / 2,
  129. }
  130. local mpos2 = {
  131. x = (mpos.x + cpos.x) / 2,
  132. y = (mpos.y + cpos.y) / 2,
  133. z = (mpos.z + cpos.z) / 2,
  134. }
  135. local mpos3 = {
  136. x = (lpos.x + mpos.x) / 2,
  137. y = (lpos.y + mpos.y) / 2,
  138. z = (lpos.z + mpos.z) / 2,
  139. }
  140. local targets = {
  141. mpos3,
  142. mpos,
  143. mpos2,
  144. cpos,
  145. }
  146. for i = 1, #targets, 1 do
  147. local mpos = targets[i]
  148. minetest.add_particlespawner({
  149. amount = 3,
  150. time = 0.1,
  151. minpos = mpos,
  152. maxpos = mpos,
  153. minvel = {x=-0.1, y=-0.1, z=-0.1},
  154. maxvel = {x=0.1, y=0.1, z=0.1},
  155. minacc = vector.new(),
  156. maxacc = vector.new(),
  157. minexptime = 0.5,
  158. maxexptime = 2.5,
  159. minsize = 0.5,
  160. maxsize = 1,
  161. texture = "throwing_sparkle.png",
  162. glow = 8,
  163. })
  164. end
  165. end
  166. local function highlight_position(pos)
  167. utility.original_add_particle({
  168. pos = pos,
  169. velocity = {x=0, y=0, z=0},
  170. acceleration = {x=0, y=0, z=0},
  171. expirationtime = 1.5,
  172. size = 4,
  173. collisiondetection = false,
  174. vertical = false,
  175. texture = "heart.png",
  176. })
  177. end
  178. -- Do flying/collision logic, and execute entity callbacks.
  179. -- This should be called inside of the entity's on_step() function.
  180. -- Returns 'true' if the entity was removed and should no longer be used.
  181. function throwing.do_fly(self, dtime)
  182. -- Get arrow's current and previous position.
  183. local cpos = self.object:get_pos()
  184. local lpos = self.lastpos
  185. ambiance.sound_play("throwing_arrow_fly", cpos, 1.0, 4)
  186. -- Detect collisions: raycast from last position to current. (Note: 'lastpos'
  187. -- table is never nil because it is part of entity definition. This is why
  188. -- test is against 'x' key here.)
  189. --
  190. -- Update: arrow throwing function now always sets 'lastpos' when the arrow
  191. -- entity is spawned (to solve problems where the arrow has moved some distance
  192. -- before the 'on_step' function gets called). Still checking this to avoid
  193. -- problems with old arrow entities in the world.
  194. if lpos.x ~= nil then
  195. local ray = minetest.raycast(lpos, cpos, true, true)
  196. for thing in ray do
  197. if thing.type == "node" then
  198. local nodeu = minetest.get_node(thing.under)
  199. local nodea = minetest.get_node(thing.above)
  200. local blocku = throwing_node_should_block_arrow(nodeu.name)
  201. local blocka = throwing_node_should_block_arrow(nodea.name)
  202. if not blocka and blocku then
  203. -- Test shows that nodeboxes are indeed collisioned correctly.
  204. --if thing.intersection_point then
  205. -- highlight_position(thing.intersection_point)
  206. --end
  207. if self.hit_node then
  208. self:hit_node(thing.under, thing.above, thing.intersection_point)
  209. end
  210. self.object:remove()
  211. return true
  212. elseif (blocka and blocku) or (blocka and not blocku) then
  213. -- Arrow was fired from inside solid nodes.
  214. self.object:remove()
  215. return true
  216. end
  217. elseif thing.type == "object" and thing.ref then
  218. local obj = thing.ref
  219. if obj:is_player() then
  220. -- Not permitted to hit the player that fired it.
  221. if obj:get_player_name() ~= self.player_name then
  222. local continue = false
  223. if self.hit_player then
  224. -- If function returns true, arrow continues flight through this object.
  225. if self:hit_player(obj, thing.intersection_point) then
  226. continue = true
  227. end
  228. end
  229. if not continue then
  230. self.object:remove()
  231. return true
  232. end
  233. end
  234. else
  235. local ent = obj:get_luaentity()
  236. if ent and not throwing.entity_ignores_arrow(ent.name) then
  237. local continue = false
  238. if self.hit_object then
  239. -- If function returns true, arrow continues flight through this object.
  240. if self:hit_object(obj, thing.intersection_point) then
  241. continue = true
  242. end
  243. end
  244. if not continue then
  245. self.object:remove()
  246. return true
  247. end
  248. end
  249. end
  250. end
  251. end
  252. if self.flight_particle then
  253. self:flight_particle(lpos, cpos)
  254. else
  255. throwing.flight_particle(lpos, cpos)
  256. end
  257. end
  258. self.lastpos = {x=cpos.x, y=cpos.y, z=cpos.z}
  259. end
  260. function throwing_unload (itemstack, player, unloaded, wear)
  261. if itemstack:get_metadata() then
  262. for _,arrow in ipairs(throwing_arrows) do
  263. local arw = itemstack:get_metadata()
  264. if arw == "" then
  265. local imeta = itemstack:get_meta()
  266. arw = imeta:get_string("arrow")
  267. end
  268. if arw ~= "" then
  269. if arw == arrow[2] then
  270. local leftover = player:get_inventory():add_item("main", arrow[1])
  271. minetest.item_drop(leftover, player, player:get_pos())
  272. end
  273. end
  274. end
  275. end
  276. if wear >= 65535 then
  277. ambiance.sound_play("default_tool_breaks", player:get_pos(), 1.0, 20)
  278. itemstack:take_item(itemstack:get_count())
  279. return itemstack
  280. else
  281. local newstack = ItemStack(unloaded)
  282. newstack:set_wear(wear)
  283. local imeta = newstack:get_meta()
  284. local ometa = itemstack:get_meta()
  285. imeta:set_string("en_desc", ometa:get_string("en_desc"))
  286. toolranks.apply_description(imeta, newstack:get_definition())
  287. return newstack
  288. end
  289. end
  290. function throwing_arrow_punch_entity (target, self, damage)
  291. -- Get tool capabilities from the tool-data API.
  292. local toolcaps = td_api.arrow_toolcaps(self._name or "", damage)
  293. local player = minetest.get_player_by_name(self.player_name or "")
  294. if player and player:is_player() then
  295. armor.notify_punch_reason({reason="arrow"})
  296. target:punch(player, 1.0, toolcaps, nil)
  297. else
  298. -- Shooter logged off game after firing arrow. Use basic fallback.
  299. toolcaps.damage_groups.from_arrow = nil
  300. armor.notify_punch_reason({reason="arrow"})
  301. target:punch(self.object, 1.0, toolcaps, nil)
  302. end
  303. end
  304. function throwing_reload (index, indexname, controls, pname, pos, is_cross, loaded)
  305. -- This function is called after some delay.
  306. local player = minetest.get_player_by_name(pname)
  307. -- Check for nil. Can happen if player leaves game right after reloading.
  308. if not player or not players[pname] then
  309. return
  310. end
  311. pova.remove_modifier(player, "physics", "bow_reloading")
  312. players[pname].reloading = false
  313. local playerinv = player:get_inventory()
  314. local itemstack = playerinv:get_stack("main", index)
  315. if not itemstack or itemstack:get_count() ~= 1 then
  316. return
  317. end
  318. -- Check if the player is still wielding the same object.
  319. -- This check isn't very secure, but we don't care too much.
  320. local same_selected = false
  321. if index == player:get_wield_index() then
  322. if indexname == itemstack:get_name() then
  323. same_selected = true
  324. end
  325. end
  326. if same_selected then
  327. if (pos.x == player:get_pos().x and pos.y == player:get_pos().y and pos.z == player:get_pos().z) or not is_cross then
  328. local wear = itemstack:get_wear()
  329. local bowdef = minetest.registered_items[itemstack:get_name()]
  330. local bowname = bowdef.description
  331. local arrow_index = 1
  332. -- The selected arrow can come from 1 of 4 places to the right of the bow,
  333. -- depending on player's controls at the time the load operation was started.
  334. if controls.sneak and not controls.aux1 then
  335. arrow_index = 2
  336. elseif not controls.sneak and controls.aux1 then
  337. arrow_index = 3
  338. elseif controls.sneak and controls.aux1 then
  339. arrow_index = 4
  340. end
  341. local arrow_stack = playerinv:get_stack("main", index + arrow_index)
  342. for _, arrow in ipairs(throwing_arrows) do
  343. if arrow_stack:get_name() == arrow[1] then
  344. -- Remove arrow from beside bow.
  345. arrow_stack:take_item()
  346. playerinv:set_stack("main", index + arrow_index, arrow_stack)
  347. local name = arrow[1]
  348. local arrowdesc = utility.get_short_desc(minetest.registered_items[name].description or "")
  349. local entity = arrow[2]
  350. -- Replace with loaded bow item.
  351. local newstack = ItemStack(loaded)
  352. newstack:set_wear(wear)
  353. local imeta = newstack:get_meta()
  354. -- Preserve name of bow (if named).
  355. local ometa = itemstack:get_meta()
  356. imeta:set_string("en_desc", ometa:get_string("en_desc"))
  357. imeta:set_string("arrow", entity)
  358. imeta:set_string("ar_desc", arrowdesc)
  359. toolranks.apply_description(imeta, bowdef)
  360. playerinv:set_stack("main", index, newstack)
  361. -- Start checking to see if player unwields this bow. If they do, we
  362. -- must unload it.
  363. throwing.wield_check(pname, index, indexname, loaded)
  364. ambiance.sound_play("throwing_arrow_nock", pos, 1.0, 16)
  365. -- Don't need to iterate through remaining arrow types.
  366. return
  367. end
  368. end
  369. end
  370. end
  371. end
  372. function throwing.wield_check(pname, index, unloaded, loaded)
  373. local player = minetest.get_player_by_name(pname)
  374. if not player then
  375. return
  376. end
  377. local cindex = player:get_wield_index()
  378. if cindex ~= index then
  379. local stack = player:get_inventory():get_stack("main", index)
  380. if stack:get_name() == loaded then
  381. local newstack = throwing_unload(stack, player, unloaded, stack:get_wear())
  382. if newstack then
  383. player:get_inventory():set_stack("main", index, newstack)
  384. end
  385. end
  386. return
  387. end
  388. minetest.after(0, throwing.wield_check, pname, index, unloaded, loaded)
  389. end
  390. -- Bows and crossbows
  391. function throwing_register_bow (name, desc, scale, stiffness, reload_time, toughness, is_cross, craft)
  392. local bow_unloaded_name = "throwing:" .. name
  393. local bow_loaded_name = "throwing:" .. name .. "_loaded"
  394. minetest.register_tool(bow_unloaded_name, {
  395. description = desc,
  396. inventory_image = "throwing_" .. name .. ".png",
  397. wield_scale = scale,
  398. stack_max = 1,
  399. groups = {not_repaired_by_anvil=1},
  400. on_use = function(itemstack, user, pt)
  401. if not user or not user:is_player() then
  402. return
  403. end
  404. local pos = user:get_pos()
  405. local pname = user:get_player_name()
  406. local index = user:get_wield_index()
  407. local inv = user:get_inventory()
  408. local stack = inv:get_stack("main", index)
  409. local indexname = ""
  410. if stack and stack:get_count() == 1 then
  411. indexname = stack:get_name()
  412. end
  413. -- Reload bow after some delay.
  414. if not players[pname].reloading then
  415. pova.set_modifier(user, "physics", {speed=0.7}, "bow_reloading")
  416. players[pname].reloading = true
  417. local controls = user:get_player_control()
  418. minetest.after(reload_time, throwing_reload, index, indexname, controls, pname, pos, is_cross, "throwing:" .. name .. "_loaded")
  419. end
  420. end,
  421. })
  422. minetest.register_tool(bow_loaded_name, {
  423. description = desc,
  424. inventory_image = "throwing_" .. name .. "_loaded.png",
  425. wield_scale = scale,
  426. stack_max = 1,
  427. groups = {not_in_creative_inventory=1, not_repaired_by_anvil=1},
  428. on_use = function(itemstack, user, pt)
  429. if not user or not user:is_player() then
  430. return
  431. end
  432. local control = user:get_player_control()
  433. local unloaded = "throwing:" .. name
  434. local wear = itemstack:get_wear()
  435. -- Unload the bow.
  436. if control.sneak then
  437. local newstack = throwing_unload(itemstack, user, unloaded, wear)
  438. if newstack then
  439. return newstack
  440. end
  441. return itemstack
  442. end
  443. -- Fire the bow.
  444. local newstack = throwing_shoot_arrow(itemstack, user, stiffness, is_cross)
  445. if newstack then
  446. wear = wear + (65535 / toughness)
  447. newstack = throwing_unload(newstack, user, unloaded, wear)
  448. end
  449. if newstack then
  450. return newstack
  451. end
  452. return itemstack
  453. end,
  454. -- Prevent dropping loaded bows.
  455. on_drop = function(itemstack, dropper, pos) return itemstack end,
  456. })
  457. -- Store loaded/unloaded bow names.
  458. throwing.bow_names_loaded[#(throwing.bow_names_loaded) + 1] = {
  459. name = bow_loaded_name,
  460. unloaded = bow_unloaded_name,
  461. }
  462. throwing.bow_names_unloaded[#(throwing.bow_names_unloaded) + 1] = {
  463. name = bow_unloaded_name,
  464. loaded = bow_loaded_name,
  465. }
  466. minetest.register_craft({
  467. output = 'throwing:' .. name,
  468. recipe = craft
  469. })
  470. minetest.register_craft({
  471. output = 'throwing:' .. name,
  472. recipe = {
  473. {craft[1][3], craft[1][2], craft[1][1]},
  474. {craft[2][3], craft[2][2], craft[2][1]},
  475. {craft[3][3], craft[3][2], craft[3][1]},
  476. }
  477. })
  478. end
  479. -- Determine if a node should block an arrow.
  480. -- Cheapest checks should come first.
  481. function throwing_node_should_block_arrow (nn)
  482. if nn == "air" then return false end
  483. if snow.is_snow(nn) then return false end
  484. --if nn == "ignore" then return true end
  485. if string.find(nn, "^throwing:") or
  486. string.find(nn, "^fire:") or
  487. string.find(nn, "^default:fence") or
  488. string.find(nn, "ladder") then
  489. return false
  490. end
  491. local def = minetest.reg_ns_nodes[nn]
  492. if def then
  493. local dt = def.drawtype
  494. local pt2 = def.paramtype2
  495. if dt == "airlike" or
  496. dt == "signlike" or
  497. dt == "torchlike" or
  498. dt == "raillike" or
  499. dt == "plantlike" or
  500. (dt == "nodebox" and pt2 == "wallmounted") then
  501. return false
  502. end
  503. end
  504. return true
  505. end
  506. throwing.node_blocks_arrow = throwing_node_should_block_arrow
  507. -- Prevent moving loaded bows out of player inventory to other inventories.
  508. -- The action has to be prevented completely (we cannot simply unload the bow)
  509. -- because we cannot track the itemstack after it has been moved out of the
  510. -- player's inventory.
  511. local function inventory_allow(player, action, inventory, inventory_info)
  512. if action == "take" then
  513. local sname = inventory_info.stack:get_name()
  514. if sname:find("^throwing:") then
  515. for k, v in ipairs(throwing.bow_names_loaded) do
  516. if v.name == sname then
  517. return 0
  518. end
  519. end
  520. end
  521. end
  522. end
  523. local function inventory_action(player, action, inventory, inventory_info)
  524. if action == "put" then
  525. local sname = inventory_info.stack:get_name()
  526. if sname:find("^throwing:") then
  527. for k, v in ipairs(throwing.bow_names_loaded) do
  528. if v.name == sname then
  529. -- Player has put a loaded bow into their inventory from somewhere else.
  530. -- Unload the bow to prevent a possible exploit.
  531. local bowstack = inventory_info.stack
  532. local newstack = throwing_unload(bowstack, player, v.unloaded, bowstack:get_wear())
  533. if newstack then
  534. player:get_inventory():set_stack(inventory_info.listname, inventory_info.index, newstack)
  535. end
  536. end
  537. end
  538. end
  539. elseif action == "move" then
  540. local movedstack = player:get_inventory():get_stack(inventory_info.to_list, inventory_info.to_index)
  541. local sname = movedstack:get_name()
  542. if sname:find("^throwing:") then
  543. for k, v in ipairs(throwing.bow_names_loaded) do
  544. if v.name == sname then
  545. -- Player has moved a bow around in their inventory.
  546. -- Unloaded it, as they almost certainly aren't wielding it anymore.
  547. local bowstack = movedstack
  548. local newstack = throwing_unload(bowstack, player, v.unloaded, bowstack:get_wear())
  549. if newstack then
  550. player:get_inventory():set_stack(inventory_info.to_list, inventory_info.to_index, newstack)
  551. end
  552. end
  553. end
  554. end
  555. end
  556. end
  557. local function get_unloaded_name(loaded)
  558. for k, v in ipairs(throwing.bow_names_loaded) do
  559. if v.name == loaded then
  560. return v.unloaded
  561. end
  562. end
  563. end
  564. local function unload_all_bows(player)
  565. local inv = player:get_inventory()
  566. local sz = inv:get_size("main")
  567. for k = 1, sz do
  568. local stack = inv:get_stack("main", k)
  569. local sname = stack:get_name()
  570. if sname:find("^throwing:") and sname:find("_loaded$") then
  571. local unloaded = get_unloaded_name(sname)
  572. if unloaded then
  573. local newstack = throwing_unload(stack, player, unloaded, stack:get_wear())
  574. if newstack then
  575. inv:set_stack("main", k, newstack)
  576. end
  577. end
  578. end
  579. end
  580. end
  581. minetest.register_allow_player_inventory_action(inventory_allow)
  582. minetest.register_on_player_inventory_action(inventory_action)
  583. minetest.register_on_joinplayer(unload_all_bows)