functions.lua 10 KB

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