init.lua 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463
  1. throwing = {}
  2. throwing.arrows = {}
  3. throwing.target_object = 1
  4. throwing.target_node = 2
  5. throwing.target_both = 3
  6. throwing.modname = minetest.get_current_modname()
  7. local S = minetest.get_translator("throwing")
  8. local use_toolranks = minetest.get_modpath("toolranks") and minetest.settings:get_bool("throwing.toolranks", true)
  9. --------- Arrows functions ---------
  10. function throwing.is_arrow(itemstack)
  11. return throwing.arrows[ItemStack(itemstack):get_name()]
  12. end
  13. function throwing.spawn_arrow_entity(pos, arrow, player)
  14. if throwing.is_arrow(arrow) then
  15. return minetest.add_entity(pos, arrow.."_entity")
  16. elseif minetest.registered_items[arrow].throwing_entity then
  17. if type(minetest.registered_items[arrow].throwing_entity) == "string" then
  18. return minetest.add_entity(pos, minetest.registered_items[arrow].throwing_entity)
  19. else -- Type is a function
  20. return minetest.registered_items[arrow].throwing_entity(pos, player)
  21. end
  22. else
  23. return minetest.add_entity(pos, "__builtin:item", arrow)
  24. end
  25. end
  26. local function apply_realistic_acceleration(obj, mass)
  27. if not minetest.settings:get_bool("throwing.realistic_trajectory", false) then
  28. return
  29. end
  30. local vertical_acceleration = tonumber(minetest.settings:get("throwing.vertical_acceleration")) or -10
  31. local friction_coef = tonumber(minetest.settings:get("throwing.frictional_coefficient")) or -3
  32. local velocity = obj:get_velocity()
  33. obj:set_acceleration({
  34. x = friction_coef * velocity.x / mass,
  35. y = friction_coef * velocity.y / mass + vertical_acceleration,
  36. z = friction_coef * velocity.z / mass
  37. })
  38. end
  39. local function shoot_arrow(def, toolranks_data, player, bow_index, throw_itself, new_stack)
  40. local inventory = player:get_inventory()
  41. local arrow_index
  42. if throw_itself then
  43. arrow_index = bow_index
  44. else
  45. if bow_index >= player:get_inventory():get_size("main") then
  46. return false
  47. end
  48. arrow_index = bow_index + 1
  49. end
  50. local arrow_stack = inventory:get_stack("main", arrow_index)
  51. local arrow = arrow_stack:get_name()
  52. local playerpos = player:get_pos()
  53. local pos = {x=playerpos.x,y=playerpos.y+1.5,z=playerpos.z}
  54. local obj = (def.spawn_arrow_entity or throwing.spawn_arrow_entity)(pos, arrow, player)
  55. local luaentity = obj:get_luaentity()
  56. -- Set custom data in the entity
  57. luaentity.player = player:get_player_name()
  58. if not luaentity.item then
  59. luaentity.item = arrow
  60. end
  61. luaentity.data = {}
  62. luaentity.timer = 0
  63. luaentity.toolranks = toolranks_data -- May be nil if toolranks is disabled
  64. if luaentity.on_throw then
  65. if luaentity:on_throw(pos, player, arrow_stack, arrow_index, luaentity.data) == false then
  66. obj:remove()
  67. return false
  68. end
  69. end
  70. local dir = player:get_look_dir()
  71. local vertical_acceleration = tonumber(minetest.settings:get("throwing.vertical_acceleration")) or -10
  72. local velocity_factor = tonumber(minetest.settings:get("throwing.velocity_factor")) or 19
  73. local velocity_mode = minetest.settings:get("throwing.velocity_mode") or "strength"
  74. local velocity
  75. if velocity_mode == "simple" then
  76. velocity = velocity_factor
  77. elseif velocity_mode == "momentum" then
  78. velocity = def.strength * velocity_factor / luaentity.mass
  79. else
  80. velocity = def.strength * velocity_factor
  81. end
  82. obj:set_velocity({
  83. x = dir.x * velocity,
  84. y = dir.y * velocity,
  85. z = dir.z * velocity
  86. })
  87. obj:set_acceleration({x = 0, y = vertical_acceleration, z = 0})
  88. obj:set_yaw(player:get_look_horizontal()-math.pi/2)
  89. apply_realistic_acceleration(obj, luaentity.mass)
  90. if luaentity.on_throw_sound ~= "" then
  91. minetest.sound_play(luaentity.on_throw_sound or "throwing_sound", {pos=playerpos, gain = 0.5})
  92. end
  93. if not minetest.settings:get_bool("creative_mode") then
  94. inventory:set_stack("main", arrow_index, new_stack)
  95. end
  96. return true
  97. end
  98. function throwing.arrow_step(self, dtime)
  99. if not self.timer or not self.player then
  100. self.object:remove()
  101. return
  102. end
  103. self.timer = self.timer + dtime
  104. local pos = self.object:get_pos()
  105. local node = minetest.get_node(pos)
  106. local logging = function(message, level)
  107. minetest.log(level or "action", "[throwing] Arrow "..(self.item or self.name).." throwed by player "..self.player.." "..tostring(self.timer).."s ago "..message)
  108. end
  109. local hit = function(pos1, node1, obj)
  110. if obj then
  111. if obj:is_player() then
  112. if obj:get_player_name() == self.player then -- Avoid hitting the hitter
  113. return false
  114. end
  115. end
  116. end
  117. local player = minetest.get_player_by_name(self.player)
  118. if not player then -- Possible if the player disconnected
  119. return
  120. end
  121. local function hit_failed()
  122. if not minetest.settings:get_bool("creative_mode") and self.item then
  123. player:get_inventory():add_item("main", self.item)
  124. end
  125. if self.on_hit_fails then
  126. self:on_hit_fails(pos1, player, self.data)
  127. end
  128. end
  129. if not self.last_pos then
  130. logging("hitted a node during its first call to the step function")
  131. hit_failed()
  132. return
  133. end
  134. if node1 and minetest.is_protected(pos1, self.player) and not self.allow_protected then -- Forbid hitting nodes in protected areas
  135. minetest.record_protection_violation(pos1, self.player)
  136. logging("hitted a node into a protected area")
  137. return
  138. end
  139. if self.on_hit then
  140. local ret, reason = self:on_hit(pos1, self.last_pos, node1, obj, player, self.data)
  141. if ret == false then
  142. if reason then
  143. logging(": on_hit function failed for reason: "..reason)
  144. else
  145. logging(": on_hit function failed")
  146. end
  147. hit_failed()
  148. return
  149. end
  150. end
  151. if self.on_hit_sound then
  152. minetest.sound_play(self.on_hit_sound, {pos = pos1, gain = 0.8})
  153. end
  154. local identifier
  155. if node1 then
  156. identifier = "node " .. node1.name
  157. elseif obj then
  158. if obj:get_luaentity() then
  159. identifier = "luaentity " .. obj:get_luaentity().name
  160. elseif obj:is_player() then
  161. identifier = "player " .. obj:get_player_name()
  162. else
  163. identifier = "unknown object"
  164. end
  165. end
  166. if identifier then
  167. logging("collided with " .. identifier .. " at " .. minetest.pos_to_string(pos1) .. ")")
  168. end
  169. -- Toolranks support: update bow uses
  170. if self.toolranks then
  171. local inventory = player:get_inventory()
  172. -- Check that the player did not move the bow
  173. local current_stack = inventory:get_stack("main", self.toolranks.index)
  174. if current_stack:get_name() == self.toolranks.name then
  175. local new_itemstack = toolranks.new_afteruse(current_stack, player, nil, {wear = self.toolranks.wear})
  176. inventory:set_stack("main", self.toolranks.index, new_itemstack)
  177. end
  178. end
  179. end
  180. -- Collision with a node
  181. if node.name == "ignore" then
  182. self.object:remove()
  183. logging("reached ignore. Removing.")
  184. return
  185. elseif (minetest.registered_items[node.name] or {}).drawtype ~= "airlike" then
  186. if self.target ~= throwing.target_object then -- throwing.target_both, nil, throwing.target_node, or any invalid value
  187. if hit(pos, node, nil) ~= false then
  188. self.object:remove()
  189. end
  190. else
  191. self.object:remove()
  192. end
  193. return
  194. end
  195. -- Collision with an object
  196. local objs = minetest.get_objects_inside_radius(pos, 1)
  197. for k, obj in pairs(objs) do
  198. if obj:get_luaentity() then
  199. if obj:get_luaentity().name ~= self.name and obj:get_luaentity().name ~= "__builtin:item" then
  200. if self.target ~= throwing.target_node then -- throwing.target_both, nil, throwing.target_object, or any invalid value
  201. if hit(pos, nil, obj) ~= false then
  202. self.object:remove()
  203. end
  204. else
  205. self.object:remove()
  206. end
  207. end
  208. else
  209. if self.target ~= throwing.target_node then -- throwing.target_both, nil, throwing.target_object, or any invalid value
  210. if hit(pos, nil, obj) ~= false then
  211. self.object:remove()
  212. end
  213. else
  214. self.object:remove()
  215. end
  216. end
  217. end
  218. -- Support for shining items using wielded light
  219. if minetest.global_exists("wielded_light") and self.object then
  220. wielded_light.update_light_by_item(self.item, self.object:get_pos())
  221. end
  222. apply_realistic_acceleration(self.object, self.mass) -- Physics: air friction
  223. self.last_pos = pos -- Used by the build arrow
  224. end
  225. -- Backwards compatibility
  226. function throwing.make_arrow_def(def)
  227. def.on_step = throwing.arrow_step
  228. return def
  229. end
  230. --[[
  231. on_hit(pos, last_pos, node, object, hitter)
  232. Either node or object is nil, depending whether the arrow collided with an object (luaentity or player) or with a node.
  233. No log message is needed in this function (a generic log message is automatically emitted), except on error or warning.
  234. Should return false or false, reason on failure.
  235. on_throw(pos, hitter)
  236. Unlike on_hit, it is optional.
  237. ]]
  238. function throwing.register_arrow(name, def)
  239. throwing.arrows[name] = true
  240. local registration_name = name
  241. if name:sub(1,9) == "throwing:" then
  242. registration_name = ":"..name
  243. end
  244. if not def.groups then
  245. def.groups = {}
  246. end
  247. if not def.groups.dig_immediate then
  248. def.groups.dig_immediate = 3
  249. end
  250. def.inventory_image = def.tiles[1]
  251. def.on_place = function(itemstack, placer, pointed_thing)
  252. if minetest.settings:get_bool("throwing.allow_arrow_placing") and pointed_thing.above then
  253. local playername = placer:get_player_name()
  254. if not minetest.is_protected(pointed_thing.above, playername) then
  255. minetest.log("action", "Player "..playername.." placed arrow "..name.." at "..minetest.pos_to_string(pointed_thing.above))
  256. minetest.set_node(pointed_thing.above, {name = name})
  257. itemstack:take_item()
  258. return itemstack
  259. else
  260. minetest.log("warning", "Player "..playername.." tried to place arrow "..name.." into a protected area at "..minetest.pos_to_string(pointed_thing.above))
  261. minetest.record_protection_violation(pointed_thing.above, playername)
  262. return itemstack
  263. end
  264. else
  265. return itemstack
  266. end
  267. end
  268. def.drawtype = "nodebox"
  269. def.paramtype = "light"
  270. def.node_box = {
  271. type = "fixed",
  272. fixed = {
  273. -- Shaft
  274. {-6.5/17, -1.5/17, -1.5/17, 6.5/17, 1.5/17, 1.5/17},
  275. -- Spitze
  276. {-4.5/17, 2.5/17, 2.5/17, -3.5/17, -2.5/17, -2.5/17},
  277. {-8.5/17, 0.5/17, 0.5/17, -6.5/17, -0.5/17, -0.5/17},
  278. -- Federn
  279. {6.5/17, 1.5/17, 1.5/17, 7.5/17, 2.5/17, 2.5/17},
  280. {7.5/17, -2.5/17, 2.5/17, 6.5/17, -1.5/17, 1.5/17},
  281. {7.5/17, 2.5/17, -2.5/17, 6.5/17, 1.5/17, -1.5/17},
  282. {6.5/17, -1.5/17, -1.5/17, 7.5/17, -2.5/17, -2.5/17},
  283. {7.5/17, 2.5/17, 2.5/17, 8.5/17, 3.5/17, 3.5/17},
  284. {8.5/17, -3.5/17, 3.5/17, 7.5/17, -2.5/17, 2.5/17},
  285. {8.5/17, 3.5/17, -3.5/17, 7.5/17, 2.5/17, -2.5/17},
  286. {7.5/17, -2.5/17, -2.5/17, 8.5/17, -3.5/17, -3.5/17},
  287. }
  288. }
  289. minetest.register_node(registration_name, def)
  290. minetest.register_entity(registration_name.."_entity", {
  291. physical = false,
  292. visual = "wielditem",
  293. visual_size = {x = 0.125, y = 0.125},
  294. textures = {name},
  295. collisionbox = {0, 0, 0, 0, 0, 0},
  296. on_hit = def.on_hit,
  297. on_hit_sound = def.on_hit_sound,
  298. on_throw_sound = def.on_throw_sound,
  299. on_throw = def.on_throw,
  300. allow_protected = def.allow_protected,
  301. target = def.target,
  302. on_hit_fails = def.on_hit_fails,
  303. on_step = throwing.arrow_step,
  304. item = name,
  305. mass = def.mass or 1,
  306. })
  307. end
  308. ---------- Bows -----------
  309. if use_toolranks and minetest.get_modpath("toolranks_extras") and toolranks_extras.register_tool_type then
  310. toolranks_extras.register_tool_type("bow", S("bow"), S("Arrows thrown"))
  311. end
  312. function throwing.register_bow(name, def)
  313. local enable_toolranks = use_toolranks and not def.no_toolranks
  314. def.name = name
  315. if not def.allow_shot then
  316. def.allow_shot = function(player, itemstack, index)
  317. if index >= player:get_inventory():get_size("main") and not def.throw_itself then
  318. return false
  319. end
  320. return throwing.is_arrow(itemstack) or def.throw_itself
  321. end
  322. end
  323. if not def.inventory_image then
  324. def.inventory_image = def.texture
  325. end
  326. if not def.strength then
  327. def.strength = 20
  328. end
  329. def.on_use = function(itemstack, user, pointed_thing)
  330. -- Cooldown
  331. local meta = itemstack:get_meta()
  332. local cooldown = def.cooldown or tonumber(minetest.settings:get("throwing.bow_cooldown")) or 0.2
  333. if cooldown > 0 and meta:get_int("cooldown") > os.time()
  334. or meta:get_int("delay") > os.time() then
  335. return
  336. end
  337. local bow_index = user:get_wield_index()
  338. local arrow_index = (def.throw_itself and bow_index) or bow_index+1
  339. local res, new_stack = def.allow_shot(user, user:get_inventory():get_stack("main", arrow_index), arrow_index, false)
  340. if not res then
  341. return (def.throw_itself and new_stack) or itemstack
  342. end
  343. -- Sound
  344. if def.sound then
  345. minetest.sound_play(def.sound, {to_player=user:get_player_name()})
  346. end
  347. meta:set_int("delay", os.time() + (def.delay or 0))
  348. minetest.after(def.delay or 0, function()
  349. -- Re-check that the arrow can be thrown. Overwrite the new_stack
  350. local old_new_stack = new_stack
  351. local arrow_stack = user:get_inventory():get_stack("main", arrow_index)
  352. res, new_stack = def.allow_shot(user, arrow_stack, arrow_index, true)
  353. if not res then
  354. return
  355. end
  356. if not new_stack then
  357. new_stack = old_new_stack
  358. end
  359. if not new_stack then
  360. arrow_stack:take_item()
  361. new_stack = arrow_stack
  362. end
  363. -- Shoot arrow
  364. local uses = 65535 / (def.uses or 50)
  365. local toolranks_data
  366. if enable_toolranks then
  367. toolranks_data = {
  368. name = itemstack:get_name(),
  369. index = bow_index,
  370. wear = uses
  371. }
  372. end
  373. if shoot_arrow(def, toolranks_data, user, bow_index, def.throw_itself, new_stack) then
  374. if not minetest.settings:get_bool("creative_mode") then
  375. itemstack:add_wear(uses)
  376. end
  377. end
  378. if def.throw_itself then
  379. -- This is a bug. If we return ItemStack(nil), the player punches the entity,
  380. -- and if the entity is a __builtin:item, it gets back to his inventory.
  381. minetest.after(0.1, function()
  382. user:get_inventory():remove_item("main", itemstack)
  383. end)
  384. elseif cooldown > 0 then
  385. meta:set_int("cooldown", os.time() + cooldown)
  386. end
  387. user:get_inventory():set_stack("main", bow_index, itemstack)
  388. end)
  389. return itemstack
  390. end
  391. if enable_toolranks then
  392. def.original_description = def.original_description or def.description
  393. def.description = toolranks.create_description(def.description)
  394. end
  395. minetest.register_tool(name, def)
  396. end