init.lua 21 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728
  1. -- Minetest mod "City block"
  2. -- City block disables use of water/lava buckets and also sends aggressive players to jail
  3. -- 2016.02 - improvements suggested by rnd. removed spawn_jailer support. some small fixes and improvements.
  4. -- This library is free software; you can redistribute it and/or
  5. -- modify it under the terms of the GNU Lesser General Public
  6. -- License as published by the Free Software Foundation; either
  7. -- version 2.1 of the License, or (at your option) any later version.
  8. city_block = city_block or {}
  9. city_block.blocks = city_block.blocks or {}
  10. city_block.filename = minetest.get_worldpath() .. "/city_blocks.txt"
  11. city_block.modpath = minetest.get_modpath("city_block")
  12. -- Localize for performance.
  13. local vector_distance = vector.distance
  14. local vector_round = vector.round
  15. local vector_add = vector.add
  16. local vector_equals = vector.equals
  17. local math_random = math.random
  18. function city_block.on_punch(pos, node, puncher, pt)
  19. if not pos or not node or not puncher or not pt then
  20. return
  21. end
  22. local pname = puncher:get_player_name()
  23. if minetest.test_protection(pos, pname) then
  24. return
  25. end
  26. local wielded = puncher:get_wielded_item()
  27. if wielded:get_name() == "rosestone:head" and wielded:get_count() >= 8 then
  28. for i, v in ipairs(city_block.blocks) do
  29. if vector_equals(v.pos, pos) then
  30. if not v.is_jail then
  31. local p1 = vector_add(pos, {x=-1, y=0, z=-1})
  32. local p2 = vector_add(pos, {x=1, y=0, z=1})
  33. local positions, counts = minetest.find_nodes_in_area(p1, p2, "griefer:grieferstone")
  34. if counts["griefer:grieferstone"] == 8 then
  35. v.is_jail = true
  36. local meta = minetest.get_meta(pos)
  37. local infotext = meta:get_string("infotext")
  38. infotext = infotext .. "\nJail Marker"
  39. meta:set_string("infotext", infotext)
  40. city_block:save()
  41. wielded:take_item(8)
  42. puncher:set_wielded_item(wielded)
  43. minetest.chat_send_player(pname, "# Server: Jail position marked!")
  44. return
  45. end
  46. end
  47. end
  48. end
  49. end
  50. end
  51. -- Returns a table of the N-nearest city-blocks to a given position.
  52. -- The return value format is: {{pos, owner}, {pos, owner}, ...}
  53. -- Note: only returns blocks in the same realm! See RC mod.
  54. -- The 'rangelim' parameter is optional, if specified, blocks farther than this
  55. -- are ignored entirely.
  56. function city_block:nearest_blocks_to_position(pos, num, rangelim)
  57. local get_rn = rc.current_realm_at_pos
  58. local realm = get_rn(pos)
  59. -- Copy the master table's indices so we don't modify it.
  60. -- We do not need to copy the inner table data itself. Just the indices.
  61. -- Only copy over blocks in the same realm, too.
  62. local blocks = {}
  63. local sblocks = self.blocks
  64. for i=1, #sblocks, 1 do
  65. local p = sblocks[i].pos
  66. if rangelim then
  67. if vector_distance(p, pos) < rangelim then
  68. if get_rn(p) == realm then
  69. blocks[#blocks+1] = sblocks[i]
  70. end
  71. end
  72. else
  73. if get_rn(p) == realm then
  74. blocks[#blocks+1] = sblocks[i]
  75. end
  76. end
  77. end
  78. -- Sort blocks, nearest blocks first.
  79. table.sort(blocks,
  80. function(a, b)
  81. local d1 = vector_distance(a.pos, pos)
  82. local d2 = vector_distance(b.pos, pos)
  83. return d1 < d2
  84. end)
  85. -- Return N-nearest blocks (should be at the front of the sorted table).
  86. local ret = {}
  87. for i=1, num, 1 do
  88. if i <= #blocks then
  89. ret[#ret+1] = blocks[i]
  90. else
  91. break
  92. end
  93. end
  94. return ret
  95. end
  96. function city_block:nearest_jails_to_position(pos, num, rangelim)
  97. local get_rn = rc.current_realm_at_pos
  98. local realm = get_rn(pos)
  99. -- Copy the master table's indices so we don't modify it.
  100. -- We do not need to copy the inner table data itself. Just the indices.
  101. -- Only copy over blocks in the same realm, too.
  102. local blocks = {}
  103. local sblocks = self.blocks
  104. for i=1, #sblocks, 1 do
  105. local v = sblocks[i]
  106. local p = v.pos
  107. if v.is_jail then
  108. if rangelim then
  109. if vector_distance(p, pos) < rangelim then
  110. if get_rn(p) == realm then
  111. blocks[#blocks+1] = v
  112. end
  113. end
  114. else
  115. if get_rn(p) == realm then
  116. blocks[#blocks+1] = v
  117. end
  118. end
  119. end
  120. end
  121. -- Sort blocks, nearest blocks first.
  122. table.sort(blocks,
  123. function(a, b)
  124. local d1 = vector_distance(a.pos, pos)
  125. local d2 = vector_distance(b.pos, pos)
  126. return d1 < d2
  127. end)
  128. -- Return N-nearest blocks (should be at the front of the sorted table).
  129. local ret = {}
  130. for i=1, num, 1 do
  131. if i <= #blocks then
  132. ret[#ret+1] = blocks[i]
  133. else
  134. break
  135. end
  136. end
  137. return ret
  138. end
  139. function city_block.erase_jail(pos)
  140. pos = vector_round(pos)
  141. local b = city_block.blocks
  142. for k, v in ipairs(b) do
  143. if vector_equals(pos, v.pos) then
  144. local meta = minetest.get_meta(pos)
  145. local pname = meta:get_string("owner")
  146. local dname = rename.gpn(pname)
  147. meta:set_string("infotext", "City Marker (Placed by <" .. dname .. ">!)")
  148. v.is_jail = nil
  149. city_block:save()
  150. return
  151. end
  152. end
  153. end
  154. -- Get city information for the given position.
  155. function city_block.city_info(pos)
  156. pos = vector_round(pos)
  157. local marker = city_block:nearest_blocks_to_position(pos, 1, 100)
  158. if marker and marker[1] then
  159. -- Covers a 45x45x45 area.
  160. local r = 22
  161. local vpos = marker[1].pos
  162. if pos.x >= (vpos.x - r) and pos.x <= (vpos.x + r) and
  163. pos.z >= (vpos.z - r) and pos.z <= (vpos.z + r) and
  164. pos.y >= (vpos.y - r) and pos.y <= (vpos.y + r) then
  165. return marker[1]
  166. end
  167. end
  168. end
  169. function city_block:save()
  170. local datastring = minetest.serialize(self.blocks)
  171. if not datastring then
  172. return
  173. end
  174. minetest.safe_file_write(self.filename, datastring)
  175. --[[
  176. local file, err = io.open(self.filename, "w")
  177. if err then
  178. return
  179. end
  180. file:write(datastring)
  181. file:close()
  182. --]]
  183. end
  184. function city_block:load()
  185. local file, err = io.open(self.filename, "r")
  186. if err then
  187. self.blocks = {}
  188. return
  189. end
  190. self.blocks = minetest.deserialize(file:read("*all"))
  191. if type(self.blocks) ~= "table" then
  192. self.blocks = {}
  193. end
  194. file:close()
  195. end
  196. function city_block:in_city(pos)
  197. pos = vector_round(pos)
  198. -- Covers a 45x45x45 area.
  199. local r = 22
  200. local blocks = self.blocks
  201. for i=1, #blocks, 1 do -- Convenience of ipairs() does not justify its overhead.
  202. local v = blocks[i]
  203. local vpos = v.pos
  204. if pos.x >= (vpos.x - r) and pos.x <= (vpos.x + r) and
  205. pos.z >= (vpos.z - r) and pos.z <= (vpos.z + r) and
  206. pos.y >= (vpos.y - r) and pos.y <= (vpos.y + r) then
  207. return true
  208. end
  209. end
  210. return false
  211. end
  212. function city_block:in_city_suburbs(pos)
  213. pos = vector_round(pos)
  214. local r = 44
  215. local blocks = self.blocks
  216. for i=1, #blocks, 1 do -- Convenience of ipairs() does not justify its overhead.
  217. local v = blocks[i]
  218. local vpos = v.pos
  219. if pos.x >= (vpos.x - r) and pos.x <= (vpos.x + r) and
  220. pos.z >= (vpos.z - r) and pos.z <= (vpos.z + r) and
  221. pos.y >= (vpos.y - r) and pos.y <= (vpos.y + r) then
  222. return true
  223. end
  224. end
  225. return false
  226. end
  227. function city_block:in_safebed_zone(pos)
  228. -- Covers a 111x111x111 area.
  229. pos = vector_round(pos)
  230. local r = 55
  231. local blocks = self.blocks
  232. for i=1, #blocks, 1 do -- Convenience of ipairs() does not justify its overhead.
  233. local v = blocks[i]
  234. local vpos = v.pos
  235. if pos.x >= (vpos.x - r) and pos.x <= (vpos.x + r) and
  236. pos.z >= (vpos.z - r) and pos.z <= (vpos.z + r) and
  237. pos.y >= (vpos.y - r) and pos.y <= (vpos.y + r) then
  238. return true
  239. end
  240. end
  241. return false
  242. end
  243. function city_block:in_no_tnt_zone(pos)
  244. pos = vector_round(pos)
  245. local r = 50
  246. local blocks = self.blocks
  247. for i=1, #blocks, 1 do -- Convenience of ipairs() does not justify its overhead.
  248. local v = blocks[i]
  249. local vpos = v.pos
  250. if pos.x >= (vpos.x - r) and pos.x <= (vpos.x + r) and
  251. pos.z >= (vpos.z - r) and pos.z <= (vpos.z + r) and
  252. pos.y >= (vpos.y - r) and pos.y <= (vpos.y + r) then
  253. return true
  254. end
  255. end
  256. return false
  257. end
  258. function city_block:in_no_leecher_zone(pos)
  259. pos = vector_round(pos)
  260. local r = 100
  261. local blocks = self.blocks
  262. for i=1, #blocks, 1 do -- Convenience of ipairs() does not justify its overhead.
  263. local v = blocks[i]
  264. local vpos = v.pos
  265. if pos.x >= (vpos.x - r) and pos.x <= (vpos.x + r) and
  266. pos.z >= (vpos.z - r) and pos.z <= (vpos.z + r) and
  267. pos.y >= (vpos.y - r) and pos.y <= (vpos.y + r) then
  268. return true
  269. end
  270. end
  271. return false
  272. end
  273. if not city_block.run_once then
  274. city_block:load()
  275. minetest.register_node("city_block:cityblock", {
  276. description = "Lawful Zone Marker [Marks a 45x45x45 area as a city.]\n\nSaves your bed respawn position, if someone killed you within the city area.\nMurderers and trespassers will be sent to jail if caught in a city.\nPrevents the use of ore leeching equipment within 100 meters radius.\nPrevents mining with TNT nearby.",
  277. tiles = {"moreblocks_circle_stone_bricks.png^default_tool_mesepick.png"},
  278. is_ground_content = false,
  279. groups = utility.dig_groups("obsidian", {
  280. immovable=1,
  281. }),
  282. is_ground_content = false,
  283. sounds = default.node_sound_stone_defaults(),
  284. after_place_node = function(pos, placer)
  285. if placer and placer:is_player() then
  286. local pname = placer:get_player_name()
  287. local meta = minetest.get_meta(pos)
  288. local dname = rename.gpn(pname)
  289. meta:set_string("rename", dname)
  290. meta:set_string("owner", pname)
  291. meta:set_string("infotext", "City Marker (Placed by <" .. dname .. ">!)")
  292. table.insert(city_block.blocks, {
  293. pos = vector_round(pos),
  294. owner = pname,
  295. time = os.time(),
  296. })
  297. city_block:save()
  298. end
  299. end,
  300. -- We don't need an `on_blast` func because TNT calls `on_destruct` properly!
  301. on_destruct = function(pos)
  302. -- The cityblock may not exist in the list if the node was created by falling,
  303. -- and was later dug.
  304. for i, EachBlock in ipairs(city_block.blocks) do
  305. if vector_equals(EachBlock.pos, pos) then
  306. table.remove(city_block.blocks, i)
  307. city_block:save()
  308. end
  309. end
  310. end,
  311. -- Called by rename LBM.
  312. _on_rename_check = function(pos)
  313. local meta = minetest.get_meta(pos)
  314. local owner = meta:get_string("owner")
  315. -- Nobody placed this block.
  316. if owner == "" then
  317. return
  318. end
  319. local dname = rename.gpn(owner)
  320. meta:set_string("rename", dname)
  321. meta:set_string("infotext", "City Marker (Placed by <" .. dname .. ">!)")
  322. end,
  323. on_punch = function(...)
  324. return city_block.on_punch(...)
  325. end,
  326. })
  327. minetest.register_craft({
  328. output = 'city_block:cityblock',
  329. recipe = {
  330. {'default:pick_mese', 'farming:hoe_mese', 'default:sword_diamond'},
  331. {'chests:chest_locked', 'default:goldblock', 'default:sandstone'},
  332. {'default:obsidianbrick', 'default:mese', 'cobble_furnace:inactive'},
  333. }
  334. })
  335. minetest.register_privilege("disable_pvp", "Players cannot damage players with this priv by punching.")
  336. minetest.register_on_punchplayer(function(...)
  337. return city_block.on_punchplayer(...)
  338. end)
  339. local c = "city_block:core"
  340. local f = city_block.modpath .. "/init.lua"
  341. reload.register_file(c, f, false)
  342. city_block.run_once = true
  343. end
  344. function city_block:get_adjective()
  345. local adjectives = {
  346. "murdering",
  347. "slaying",
  348. "killing",
  349. "whacking",
  350. "trashing",
  351. "fatally attacking",
  352. "fatally harming",
  353. "doing away with",
  354. "giving the Chicago treatment to",
  355. "fatally thrashing",
  356. "fatally stabbing",
  357. }
  358. return adjectives[math_random(1, #adjectives)]
  359. end
  360. local murder_messages = {
  361. "<v> collapsed from <k>'s brutal attack.",
  362. "<k>'s <w> apparently wasn't such an unusual weapon after all, as <v> found out.",
  363. "<k> killed <v> with great prejudice.",
  364. "<v> died from <k>'s horrid slaying.",
  365. "<v> fell prey to <k>'s deadly <w>.",
  366. "<k> went out of <k_his> way to slay <v> with <k_his> <w>.",
  367. "<v> danced <v_himself> to death under <k>'s craftily wielded <w>.",
  368. "<k> used <k_his> <w> to kill <v> with prejudice.",
  369. "<k> made a splortching sound with <v>'s head.",
  370. "<v> got flattened by <k>'s skillfully handled <w>.",
  371. "<v> became prey for <k>.",
  372. "<v> didn't get out of <k>'s way in time.",
  373. "<v> SAW <k> coming with <k_his> <w>. Didn't get away in time.",
  374. "<v> made no real attempt to get out of <k>'s way.",
  375. "<k> barreled through <v> as if <v_he> wasn't there.",
  376. "<k> sent <v> to that place where kindling wood isn't needed.",
  377. "<v> didn't suspect that <k> meant <v_him> any harm.",
  378. "<v> fought <k> to the death and lost painfully.",
  379. "<v> knew <k> was wielding <k_his> <w> but didn't guess what <k> meant to do with it.",
  380. "<k> clonked <v> over the head using <k_his> <w> with silent skill.",
  381. "<k> made sure <v> didn't see that coming!",
  382. "<k> has decided <k_his> favorite weapon is <k_his> <w>.",
  383. "<v> did the mad hatter dance just before being killed with <k>'s <w>.",
  384. "<v> played the victim to <k>'s bully behavior!",
  385. "<k> used <v> for weapons practice with <k_his> <w>.",
  386. "<v> failed to avoid <k>'s oncoming weapon.",
  387. "<k> successfully got <v> to complain of a headache.",
  388. "<v> got <v_himself> some serious hurt from <k>'s <w>.",
  389. "Trying to talk peace to <k> didn't win any for <v>.",
  390. "<v> was brutally slain by <k>'s <w>.",
  391. "<v> jumped the mad-hatter dance under <k>'s <w>.",
  392. "<v> got <v_himself> a fatal mauling by <k>'s <w>.",
  393. "<k> just assassinated <v> with <k_his> <w>.",
  394. "<k> split <v>'s wig.",
  395. "<k> took revenge on <v>.",
  396. "<k> flattened <v>.",
  397. "<v> played dead. Permanently.",
  398. "<v> never saw what hit <v_him>.",
  399. "<k> took <v> by surprise.",
  400. "<v> was assassinated.",
  401. "<k> didn't take any prisoners from <v>.",
  402. "<k> pinned <v> to the wall with <k_his> <w>.",
  403. "<v> failed <v_his> weapon checks.",
  404. }
  405. function city_block.murder_message(killer, victim, sendto)
  406. local msg = murder_messages[math_random(1, #murder_messages)]
  407. msg = string.gsub(msg, "<v>", "<" .. rename.gpn(victim) .. ">")
  408. msg = string.gsub(msg, "<k>", "<" .. rename.gpn(killer) .. ">")
  409. local ksex = skins.get_gender_strings(killer)
  410. local vsex = skins.get_gender_strings(victim)
  411. msg = string.gsub(msg, "<k_himself>", ksex.himself)
  412. msg = string.gsub(msg, "<k_his>", ksex.his)
  413. msg = string.gsub(msg, "<v_himself>", vsex.himself)
  414. msg = string.gsub(msg, "<v_his>", vsex.his)
  415. msg = string.gsub(msg, "<v_him>", vsex.him)
  416. msg = string.gsub(msg, "<v_he>", vsex.he)
  417. if string.find(msg, "<w>") then
  418. local hitter = minetest.get_player_by_name(killer)
  419. if hitter then
  420. local wield = hitter:get_wielded_item()
  421. local def = minetest.registered_items[wield:get_name()]
  422. local meta = wield:get_meta()
  423. local description = meta:get_string("description")
  424. if description ~= "" then
  425. msg = string.gsub(msg, "<w>", "'" .. utility.get_short_desc(description):trim() .. "'")
  426. elseif def and def.description then
  427. local str = utility.get_short_desc(def.description)
  428. if str == "" then
  429. str = "Potato Fist"
  430. end
  431. msg = string.gsub(msg, "<w>", str)
  432. end
  433. end
  434. end
  435. if type(sendto) == "string" then
  436. minetest.chat_send_player(sendto, "# Server: " .. msg)
  437. else
  438. minetest.chat_send_all("# Server: " .. msg)
  439. end
  440. end
  441. function city_block.hit_possible(p1pos, p2pos)
  442. -- Range limit, stops hackers with long reach.
  443. if vector_distance(p1pos, p2pos) > 6 then
  444. return false
  445. end
  446. -- Cannot attack through walls.
  447. -- But if node wouldn't stop an arrow, keep testing the line.
  448. --local raycast = minetest.raycast(p1pos, p2pos, false, false)
  449. -- This seems to cause random freezes and 100% CPU.
  450. --[[
  451. local los, pstop = minetest.line_of_sight(p1pos, p2pos)
  452. while not los do
  453. if throwing.node_blocks_arrow(minetest.get_node(vector_round(pstop)).name) then
  454. return false
  455. end
  456. local dir = vector.direction(pstop, p2pos)
  457. local ns = vector.add(pstop, dir)
  458. los, pstop = minetest.line_of_sight(ns, p2pos)
  459. end
  460. --]]
  461. return true
  462. end
  463. function city_block.send_to_jail(victim_pname, attack_pname)
  464. -- Killers don't go to jail if the victim is a registered cheater.
  465. if not sheriff.is_cheater(victim_pname) then
  466. local hitter = minetest.get_player_by_name(attack_pname)
  467. if hitter and jail.go_to_jail(hitter, nil) then
  468. minetest.chat_send_all(
  469. "# Server: Criminal <" .. rename.gpn(attack_pname) .. "> was sent to gaol for " ..
  470. city_block:get_adjective() .. " <" .. rename.gpn(victim_pname) .. "> within city limits.")
  471. end
  472. end
  473. end
  474. function city_block.handle_assassination(p2pos, victim_pname, attack_pname, melee)
  475. -- Bed position is only lost if player died outside city to a melee weapon.
  476. if not city_block:in_safebed_zone(p2pos) and melee then
  477. -- Victim doesn't lose their bed respawn if they were killed by a cheater.
  478. if not sheriff.is_cheater(attack_pname) then
  479. minetest.chat_send_player(victim_pname, "# Server: Your bed is lost! You were assassinated in the wilds.")
  480. beds.clear_player_spawn(victim_pname)
  481. end
  482. end
  483. end
  484. function city_block.handle_consequences(player, hitter, damage, melee)
  485. local victim_pname = player:get_player_name()
  486. local attack_pname = hitter:get_player_name()
  487. local time = os.time()
  488. local hp = player:get_hp()
  489. local p2pos = utility.get_head_pos(player:get_pos())
  490. local vpos = vector_round(p2pos)
  491. city_block.attackers[victim_pname] = attack_pname
  492. city_block.victims[victim_pname] = time
  493. if not (hp > 0 and (hp - damage) <= 0) then
  494. return
  495. end
  496. default.detach_player_if_attached(player)
  497. city_block.murder_message(attack_pname, victim_pname)
  498. if city_block:in_city(p2pos) then
  499. local t0 = city_block.victims[attack_pname] or time
  500. local tdiff = (time - t0)
  501. if not city_block.attackers[attack_pname] then
  502. city_block.attackers[attack_pname] = ""
  503. end
  504. --[[
  505. Behavior Table (obtained through testing):
  506. In city-block area, no protection:
  507. A kills B, B did not retaliate -> A goes to jail
  508. A kills B, B had retaliated -> Nobody jailed
  509. (The table is the same if A and B are inverted)
  510. In city-block area, protected by A (with nearby jail available):
  511. A kills B, B did not retaliate -> A goes to jail
  512. A kills B, B had retaliated -> Nobody jailed
  513. B kills A, A did not retaliate -> B goes to jail
  514. B kills A, A had retaliated -> B goes to jail
  515. (The table is the same if A and B are inverted, and protection is B's)
  516. Notes:
  517. A hit from A or B is considered retaliation if it happens very soon
  518. after the other player hit. Thus, if both A and B are hitting, then both
  519. are considered to be retaliating -- in that case, land ownership is used
  520. to resolve who should go to jail.
  521. It does not matter who hits first in a fight -- only who kills the other
  522. player first.
  523. If there is no jail available for a crook to be sent to, then nothing
  524. happens in any case, regardless of who wins the fight or owns the land.
  525. --]]
  526. -- Victim is "landowner" if area is protected, but they have access.
  527. local landowner = (minetest.test_protection(vpos, "") and
  528. not minetest.test_protection(vpos, victim_pname))
  529. -- Killing justified after provocation, but not if victim owns the land.
  530. if city_block.attackers[attack_pname] == victim_pname and
  531. tdiff < 30 and not landowner then
  532. return
  533. else
  534. -- Go to jail! Do not pass Go. Do not collect $200.
  535. city_block.send_to_jail(victim_pname, attack_pname)
  536. end
  537. else
  538. -- Player killed outside town.
  539. -- This only does something if the attack was with a melee weapon!
  540. city_block.handle_assassination(p2pos, victim_pname, attack_pname, melee)
  541. end
  542. end
  543. city_block.attackers = city_block.attackers or {}
  544. city_block.victims = city_block.victims or {}
  545. -- Return `true' to prevent the default damage mechanism.
  546. -- Note: player is sometimes the hitter (player punches self). This is sometimes
  547. -- necessary when a mod needs to punch a player, but has no entity that can do
  548. -- the actual punch.
  549. function city_block.on_punchplayer(player, hitter, time_from_last_punch, tool_capabilities, dir, damage)
  550. if not player:is_player() then
  551. return
  552. end
  553. local melee_hit = true
  554. if tool_capabilities.damage_groups.from_arrow then
  555. -- Someone launched this weapon. The hitter is most likely the nearest
  556. -- player that isn't the player going to be hit.
  557. melee_hit = false
  558. -- We don't have enough information to know exactly who fired this weapon,
  559. -- but it's probably a safe bet that it was the nearest player who is NOT
  560. -- the player being hit. But if we were explicitly provided a player object
  561. -- that is NOT self, then we don't need to do this.
  562. if hitter == player or not hitter:is_player() then
  563. -- If initial hitter is the player, or the hitter isn't a player, then
  564. -- get the nearest other player to this position (who is not the initial
  565. -- player) and use that player as the hitter.
  566. local pos = player:get_pos()
  567. local culprit = hb4.nearest_player_not(pos, player)
  568. if culprit then
  569. local cpos = culprit:get_pos()
  570. -- Only if culprit is nearby.
  571. if vector.distance(cpos, pos) < 50 then
  572. hitter = culprit
  573. end
  574. end
  575. end
  576. end
  577. if not hitter:is_player() then
  578. return
  579. end
  580. -- Random accidents happen to punished players during PvP.
  581. do
  582. local attacker = hitter:get_player_name()
  583. if sheriff.is_cheater(attacker) then
  584. if sheriff.punish_probability(attacker) then
  585. sheriff.punish_player(attacker)
  586. end
  587. end
  588. end
  589. local p1pos = utility.get_head_pos(hitter:get_pos())
  590. local p2pos = utility.get_head_pos(player:get_pos())
  591. -- Check if hit is physically possible (range, blockage, etc).
  592. if melee_hit and not city_block.hit_possible(p1pos, p2pos) then
  593. return true
  594. end
  595. -- PvP is disabled for players in jail. This fixes a possible way to exploit jail.
  596. if jail.is_player_in_jail(hitter) or jail.is_player_in_jail(player) then
  597. minetest.chat_send_player(hitter:get_player_name(), "# Server: Brawling is not allowed in jail.")
  598. return true
  599. end
  600. -- Admins cannot be punched.
  601. if minetest.check_player_privs(player:get_player_name(), {disable_pvp=true}) then
  602. return true
  603. end
  604. -- Let others hear sounds of nearby combat.
  605. if damage > 0 then
  606. ambiance.sound_play("player_damage", p2pos, 2.0, 30)
  607. end
  608. -- If hitter is self, punch was (most likely) due to game code.
  609. if player == hitter then
  610. return
  611. end
  612. -- Stuff that happens when one player kills another.
  613. city_block.handle_consequences(player, hitter, damage, melee_hit)
  614. end