functions.lua 9.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336
  1. -- Localize for performance.
  2. local math_random = math.random
  3. local math_min = math.min
  4. local math_max = math.max
  5. -- Copied from builtin so we can actually USE the damn code. >:(
  6. -- Builtin has been getting very modder-unfriendly!
  7. function utility.drop_attached_node(p)
  8. local n = core.get_node(p)
  9. local drops = core.get_node_drops(n, "")
  10. local def = core.registered_items[n.name]
  11. if def and def.preserve_metadata then
  12. local oldmeta = core.get_meta(p):to_table().fields
  13. -- Copy pos and node because the callback can modify them.
  14. local pos_copy = vector.copy(p)
  15. local node_copy = {name=n.name, param1=n.param1, param2=n.param2}
  16. local drop_stacks = {}
  17. for k, v in pairs(drops) do
  18. drop_stacks[k] = ItemStack(v)
  19. end
  20. drops = drop_stacks
  21. def.preserve_metadata(pos_copy, node_copy, oldmeta, drops)
  22. end
  23. if def and def.sounds and def.sounds.fall then
  24. core.sound_play(def.sounds.fall, {pos = p}, true)
  25. end
  26. core.remove_node(p)
  27. for _, item in pairs(drops) do
  28. local pos = {
  29. x = p.x + math.random()/2 - 0.25,
  30. y = p.y + math.random()/2 - 0.25,
  31. z = p.z + math.random()/2 - 0.25,
  32. }
  33. core.add_item(pos, item)
  34. end
  35. end
  36. -- Copied from builtin so we can actually USE the damn code. >:(
  37. -- Builtin has been getting very modder-unfriendly!
  38. function utility.check_attached_node(p, n, group_rating)
  39. local def = core.registered_nodes[n.name]
  40. local d = vector.zero()
  41. if group_rating == 3 then
  42. -- always attach to floor
  43. d.y = -1
  44. elseif group_rating == 4 then
  45. -- always attach to ceiling
  46. d.y = 1
  47. elseif group_rating == 2 then
  48. -- attach to facedir or 4dir direction
  49. if (def.paramtype2 == "facedir" or
  50. def.paramtype2 == "colorfacedir") then
  51. -- Attach to whatever facedir is "mounted to".
  52. -- For facedir, this is where tile no. 5 point at.
  53. -- The fallback vector here is in case 'facedir to dir' is nil due
  54. -- to voxelmanip placing a wallmounted node without resetting a
  55. -- pre-existing param2 value that is out-of-range for facedir.
  56. -- The fallback vector corresponds to param2 = 0.
  57. d = core.facedir_to_dir(n.param2) or vector.new(0, 0, 1)
  58. elseif (def.paramtype2 == "4dir" or
  59. def.paramtype2 == "color4dir") then
  60. -- Similar to facedir handling
  61. d = core.fourdir_to_dir(n.param2) or vector.new(0, 0, 1)
  62. end
  63. elseif def.paramtype2 == "wallmounted" or
  64. def.paramtype2 == "colorwallmounted" then
  65. -- Attach to whatever this node is "mounted to".
  66. -- This where tile no. 2 points at.
  67. -- The fallback vector here is used for the same reason as
  68. -- for facedir nodes.
  69. d = core.wallmounted_to_dir(n.param2) or vector.new(0, 1, 0)
  70. else
  71. d.y = -1
  72. end
  73. local p2 = vector.add(p, d)
  74. local nn = core.get_node(p2).name
  75. local def2 = core.registered_nodes[nn]
  76. if def2 and not def2.walkable then
  77. return false
  78. end
  79. return true
  80. end
  81. function utility.check_hanging_node(p, n, group_rating)
  82. local def = core.registered_nodes[n.name]
  83. local p2 = vector.offset(p, 0, 1, 0)
  84. local nn = core.get_node(p2).name
  85. local def2 = core.registered_nodes[nn]
  86. -- Node can hang from a node with the same name.
  87. if n.name == nn then
  88. return true
  89. end
  90. -- Node can hang from another hanging node above it.
  91. if ((def2.groups or {}).hanging_node or 0) ~= 0 then
  92. return true
  93. end
  94. -- Node can hang from any solid node.
  95. if def2 and def2.walkable then
  96. return true
  97. end
  98. return false
  99. end
  100. function utility.check_standing_node(p, n, group_rating)
  101. local def = core.registered_nodes[n.name]
  102. local p2 = vector.offset(p, 0, -1, 0)
  103. local nn = core.get_node(p2).name
  104. local def2 = core.registered_nodes[nn]
  105. -- Node can stand on a node with the same name.
  106. if n.name == nn then
  107. return true
  108. end
  109. -- Node can stand on another standing node below it.
  110. if ((def2.groups or {}).handing_node or 0) ~= 0 then
  111. return true
  112. end
  113. -- Node can stand on any solid node.
  114. if def2 and def2.walkable then
  115. return true
  116. end
  117. return false
  118. end
  119. -- Use this to damage a player, instead of player:set_hp(), because this takes
  120. -- player's current armor groups into account.
  121. function utility.damage_player(player, damage_type, damage, reason)
  122. -- Inform 3D armor what the reason for this punch is.
  123. -- Cannot pass the PlayerHPChangeReason table through :punch()!
  124. -- Note: reason.type will be "punch", per 3D armor code.
  125. local rt = type(reason)
  126. if rt == "string" or rt == "nil" then
  127. armor.notify_punch_reason({reason = reason or damage_type})
  128. elseif rt == "table" then
  129. local nr = {reason = reason.reason or ""}
  130. for k, v in pairs(reason) do
  131. nr[k] = v
  132. end
  133. armor.notify_punch_reason(nr)
  134. end
  135. player:punch(player, 1.0, {
  136. full_punch_interval = 1.0,
  137. damage_groups = {[damage_type] = damage, from_env = 1},
  138. }, nil)
  139. -- Note: never set 'damage_groups.from_arrow' here.
  140. -- That has special meaning to the cityblock code!
  141. --
  142. -- Note: 'from_env' informs the cityblock code that this punch should NOT be
  143. -- treated as PvP. This is damage from the environment, OR from a mob.
  144. end
  145. function utility.detach_player_with_message(player)
  146. local k = default.detach_player_if_attached(player)
  147. if k then
  148. local t = player:get_player_name()
  149. if k == "cart" then
  150. minetest.chat_send_all("# Server: Someone threw <" .. rename.gpn(t) .. "> out of a minecart.")
  151. elseif k == "boat" then
  152. minetest.chat_send_all("# Server: Boater <" .. rename.gpn(t) .. "> was tossed overboard.")
  153. elseif k == "sled" then
  154. minetest.chat_send_all("# Server: Someone kicked <" .. rename.gpn(t) .. "> off a sled.")
  155. elseif k == "bed" then
  156. minetest.chat_send_all("# Server: <" .. rename.gpn(t) .. "> was rudely kicked out of bed.")
  157. end
  158. end
  159. end
  160. -- Function to find an ignore node NOT beyond the world edge.
  161. -- This is useful when we must check for `ignore`, but don't want to be confused at the edge of the world.
  162. function utility.find_node_near_not_world_edge(pos, rad, node)
  163. local minp = vector.subtract(pos, rad)
  164. local maxp = vector.add(pos, rad)
  165. minp.x = math_max(minp.x, -30912)
  166. minp.y = math_max(minp.y, -30912)
  167. minp.z = math_max(minp.z, -30912)
  168. maxp.x = math_min(maxp.x, 30927)
  169. maxp.y = math_min(maxp.y, 30927)
  170. maxp.z = math_min(maxp.z, 30927)
  171. local positions = minetest.find_nodes_in_area(minp, maxp, node)
  172. if (#positions > 0) then
  173. return positions[math_random(1, #positions)]
  174. end
  175. end
  176. local function add_effects(pos, radius)
  177. minetest.add_particlespawner({
  178. amount = 8,
  179. time = 0.5,
  180. minpos = vector.subtract(pos, radius / 2),
  181. maxpos = vector.add(pos, radius / 2),
  182. minvel = {x=-10, y=-10, z=-10},
  183. maxvel = {x=10, y=10, z=10},
  184. minacc = vector.new(),
  185. maxacc = vector.new(),
  186. minexptime = 0.5,
  187. maxexptime = 1,
  188. minsize = 0.5,
  189. maxsize = 1,
  190. texture = "tnt_smoke.png",
  191. })
  192. end
  193. function utility.shell_boom(pos)
  194. minetest.sound_play("throwing_shell_explode", {pos=pos, gain=1.5, max_hear_distance=2*64}, true)
  195. -- Don't destroy things.
  196. if minetest.get_node(pos).name == "air" then
  197. minetest.set_node(pos, {name="tnt:boom"})
  198. minetest.get_node_timer(pos):start(0.5)
  199. end
  200. add_effects(pos, 1)
  201. end
  202. -- Note: this works for boats, too.
  203. -- Returns [""] if player wasn't attached. Otherwise, name of previous attachment type.
  204. function default.detach_player_if_attached(player)
  205. local pname = player:get_player_name()
  206. -- Player might be in bed! Get them out properly.
  207. if beds.kick_one_player(pname) then
  208. return "bed"
  209. end
  210. local ents = minetest.get_objects_inside_radius(utility.get_foot_pos(player:get_pos()), 2)
  211. local result = ""
  212. for k, obj in ipairs(ents) do
  213. local ent = obj:get_luaentity()
  214. if ent and ent.name == "carts:cart" then
  215. -- Must detach player from cart.
  216. carts:manage_attachment(player, nil)
  217. result = "cart"
  218. elseif ent and ent.name == "boats:boat" then
  219. if ent.driver and ent.driver == player then
  220. -- Since boat driver == player, this should always detach.
  221. boats.on_rightclick(ent, player)
  222. result = "boat"
  223. end
  224. elseif ent and ent.name == "sleds:sled" then
  225. if ent.driver and ent.driver == player then
  226. sleds.on_rightclick(ent, player)
  227. result = "sled"
  228. end
  229. end
  230. end
  231. return result
  232. end
  233. -- Override Minetest's builtin knockback calculation.
  234. -- Warning: 'player' can also be any other entity, including a mob.
  235. function minetest.calculate_knockback(player, hitter, tflp, tcaps, dir, distance, damage)
  236. -- Get knockback value from weapon capabilities.
  237. local knockback = (tcaps.damage_groups and tcaps.damage_groups.knockback) or 0
  238. tflp = math.min(4.0, math.max(tflp, 0))
  239. --minetest.log('knockback: ' .. knockback .. ', tflp: ' .. tflp)
  240. if knockback <= 0 then
  241. return 0.0
  242. end
  243. -- Only on full punch.
  244. --if tflp < 4.0 then
  245. -- return 0.0
  246. --end
  247. -- Divide by 100 to get meters per second.
  248. local res = knockback / 100
  249. if distance < 2.0 then
  250. res = res * 1.1 -- more knockback when closer
  251. elseif distance > 4.0 then
  252. res = res * 0.9 -- less when far away
  253. end
  254. return res * 5 * (tflp / 4)
  255. end
  256. -- Process Lua array, removing elements as we go. The array is modified in-place.
  257. function utility.array_remove(t, keep)
  258. local j, n, c = 1, #t, 0
  259. for i = 1, n do
  260. if keep(t, i, j) then
  261. -- Move i's kept value to j's position, if it's not already there.
  262. if (i ~= j) then
  263. t[j] = t[i]
  264. t[i] = nil
  265. end
  266. j = j + 1 -- Increment position of where we'll place the next kept value.
  267. else
  268. c = c + 1 -- Increment number removed.
  269. t[i] = nil
  270. end
  271. end
  272. return t, c
  273. end