functions.lua 9.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324
  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_blocks_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 beacones.
  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. function throwing_shoot_arrow(itemstack, player, stiffness, is_cross)
  40. if not player or not player:is_player() then return end
  41. local arrow = itemstack:get_metadata()
  42. local imeta = itemstack:get_meta()
  43. if arrow == "" then
  44. arrow = imeta:get_string("arrow")
  45. end
  46. if arrow == "" then return end
  47. local playerpos = utility.get_foot_pos(player:get_pos())
  48. local obj = minetest.add_entity({x=playerpos.x, y=playerpos.y+1.4, z=playerpos.z}, arrow)
  49. if not obj then return end
  50. local luaent = obj:get_luaentity()
  51. if not luaent then return end
  52. itemstack:set_metadata("")
  53. imeta:set_string("arrow", nil)
  54. imeta:set_string("ar_desc", nil)
  55. toolranks.apply_description(imeta, itemstack:get_definition())
  56. local dir = player:get_look_dir()
  57. obj:set_velocity({x=dir.x*stiffness, y=dir.y*stiffness, z=dir.z*stiffness})
  58. obj:set_acceleration({x=dir.x*-3, y=-8.5, z=dir.z*-3})
  59. obj:set_yaw(player:get_look_horizontal() - (math.pi / 2))
  60. if is_cross then
  61. minetest.sound_play("throwing_crossbow_sound", {pos=playerpos}, true)
  62. else
  63. minetest.sound_play("throwing_bow_sound", {pos=playerpos}, true)
  64. end
  65. luaent.player = player
  66. luaent.player_name = player:get_player_name()
  67. luaent.inventory = player:get_inventory()
  68. luaent.stack = player:get_inventory():get_stack("main", player:get_wield_index()-1)
  69. -- Return the modified itemstack.
  70. return itemstack
  71. end
  72. function throwing_unload (itemstack, player, unloaded, wear)
  73. if itemstack:get_metadata() then
  74. for _,arrow in ipairs(throwing_arrows) do
  75. local arw = itemstack:get_metadata()
  76. if arw == "" then
  77. local imeta = itemstack:get_meta()
  78. arw = imeta:get_string("arrow")
  79. end
  80. if arw ~= "" then
  81. if arw == arrow[2] then
  82. local leftover = player:get_inventory():add_item("main", arrow[1])
  83. minetest.item_drop(leftover, player, player:get_pos())
  84. end
  85. end
  86. end
  87. end
  88. if wear >= 65535 then
  89. ambiance.sound_play("default_tool_breaks", player:get_pos(), 1.0, 20)
  90. itemstack:take_item(itemstack:get_count())
  91. return itemstack
  92. else
  93. local newstack = ItemStack(unloaded)
  94. newstack:set_wear(wear)
  95. local imeta = newstack:get_meta()
  96. local ometa = itemstack:get_meta()
  97. imeta:set_string("en_desc", ometa:get_string("en_desc"))
  98. toolranks.apply_description(imeta, newstack:get_definition())
  99. return newstack
  100. end
  101. end
  102. function throwing_arrow_punch_entity (target, self, damage)
  103. -- Get tool capabilities from the tool-data API.
  104. local toolcaps = td_api.arrow_toolcaps(self._name or "", damage)
  105. local player = minetest.get_player_by_name(self.player_name or "")
  106. if player and player:is_player() then
  107. target:punch(player, 1.0, toolcaps, nil)
  108. else
  109. -- Shooter logged off game after firing arrow. Use basic fallback.
  110. toolcaps.damage_groups.from_arrow = nil
  111. target:punch(self.object, 1.0, toolcaps, nil)
  112. end
  113. end
  114. function throwing_reload (index, indexname, pname, pos, is_cross, loaded)
  115. -- This function is called after some delay.
  116. local player = minetest.get_player_by_name(pname)
  117. -- Check for nil. Can happen if player leaves game right after reloading.
  118. if not player or not players[pname] then
  119. return
  120. end
  121. players[pname].reloading = false
  122. local playerinv = player:get_inventory()
  123. local itemstack = playerinv:get_stack("main", index)
  124. if not itemstack or itemstack:get_count() ~= 1 then
  125. return
  126. end
  127. -- Check if the player is still wielding the same object.
  128. -- This check isn't very secure, but we don't care too much.
  129. local same_selected = false
  130. if index == player:get_wield_index() then
  131. if indexname == itemstack:get_name() then
  132. same_selected = true
  133. end
  134. end
  135. if same_selected then
  136. 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
  137. local wear = itemstack:get_wear()
  138. local bowdef = minetest.registered_items[itemstack:get_name()]
  139. local bowname = bowdef.description
  140. local arrow_stack = playerinv:get_stack("main", index + 1)
  141. for _, arrow in ipairs(throwing_arrows) do
  142. if arrow_stack:get_name() == arrow[1] then
  143. -- Remove arrow from beside bow.
  144. arrow_stack:take_item()
  145. playerinv:set_stack("main", index + 1, arrow_stack)
  146. local name = arrow[1]
  147. local arrowdesc = utility.get_short_desc(minetest.registered_items[name].description or "")
  148. local entity = arrow[2]
  149. -- Replace with loaded bow item.
  150. local newstack = ItemStack(loaded)
  151. newstack:set_wear(wear)
  152. local imeta = newstack:get_meta()
  153. -- Preserve name of bow (if named).
  154. local ometa = itemstack:get_meta()
  155. imeta:set_string("en_desc", ometa:get_string("en_desc"))
  156. imeta:set_string("arrow", entity)
  157. imeta:set_string("ar_desc", arrowdesc)
  158. toolranks.apply_description(imeta, bowdef)
  159. -- Don't need to iterate through remaining arrow types.
  160. playerinv:set_stack("main", index, newstack)
  161. return
  162. end
  163. end
  164. end
  165. end
  166. end
  167. -- Bows and crossbows
  168. function throwing_register_bow (name, desc, scale, stiffness, reload_time, toughness, is_cross, craft)
  169. minetest.register_tool("throwing:" .. name, {
  170. description = desc,
  171. inventory_image = "throwing_" .. name .. ".png",
  172. wield_scale = scale,
  173. stack_max = 1,
  174. groups = {not_repaired_by_anvil=1},
  175. on_use = function(itemstack, user, pt)
  176. if not user or not user:is_player() then
  177. return
  178. end
  179. local pos = user:get_pos()
  180. local pname = user:get_player_name()
  181. local index = user:get_wield_index()
  182. local inv = user:get_inventory()
  183. local stack = inv:get_stack("main", index)
  184. local indexname = ""
  185. if stack and stack:get_count() == 1 then
  186. indexname = stack:get_name()
  187. end
  188. -- Reload bow after some delay.
  189. if not players[pname].reloading then
  190. players[pname].reloading = true
  191. minetest.after(reload_time, throwing_reload, index, indexname, pname, pos, is_cross, "throwing:" .. name .. "_loaded")
  192. end
  193. end,
  194. })
  195. minetest.register_tool("throwing:" .. name .. "_loaded", {
  196. description = desc,
  197. inventory_image = "throwing_" .. name .. "_loaded.png",
  198. wield_scale = scale,
  199. stack_max = 1,
  200. groups = {not_in_creative_inventory=1, not_repaired_by_anvil=1},
  201. on_use = function(itemstack, user, pt)
  202. if not user or not user:is_player() then
  203. return
  204. end
  205. local control = user:get_player_control()
  206. local unloaded = "throwing:" .. name
  207. local wear = itemstack:get_wear()
  208. -- Unload the bow.
  209. if control.sneak then
  210. local newstack = throwing_unload(itemstack, user, unloaded, wear)
  211. if newstack then
  212. return newstack
  213. end
  214. return itemstack
  215. end
  216. -- Fire the bow.
  217. local newstack = throwing_shoot_arrow(itemstack, user, stiffness, is_cross)
  218. if newstack then
  219. wear = wear + (65535 / toughness)
  220. newstack = throwing_unload(newstack, user, unloaded, wear)
  221. end
  222. if newstack then
  223. return newstack
  224. end
  225. return itemstack
  226. end,
  227. })
  228. minetest.register_craft({
  229. output = 'throwing:' .. name,
  230. recipe = craft
  231. })
  232. minetest.register_craft({
  233. output = 'throwing:' .. name,
  234. recipe = {
  235. {craft[1][3], craft[1][2], craft[1][1]},
  236. {craft[2][3], craft[2][2], craft[2][1]},
  237. {craft[3][3], craft[3][2], craft[3][1]},
  238. }
  239. })
  240. end
  241. -- Determine if a node should block an arrow.
  242. -- Cheapest checks should come first.
  243. function throwing_node_should_block_arrow (nn)
  244. if nn == "air" then return false end
  245. if snow.is_snow(nn) then return false end
  246. --if nn == "ignore" then return true end
  247. if string.find(nn, "^throwing:") or
  248. string.find(nn, "^fire:") or
  249. string.find(nn, "^default:fence") or
  250. string.find(nn, "ladder") then
  251. return false
  252. end
  253. local def = minetest.reg_ns_nodes[nn]
  254. if def then
  255. local dt = def.drawtype
  256. local pt2 = def.paramtype2
  257. if dt == "airlike" or
  258. dt == "signlike" or
  259. dt == "torchlike" or
  260. dt == "raillike" or
  261. dt == "plantlike" or
  262. (dt == "nodebox" and pt2 == "wallmounted") then
  263. return false
  264. end
  265. end
  266. return true
  267. end
  268. throwing.node_blocks_arrow = throwing_node_should_block_arrow