init.lua 30 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105
  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. if not minetest.global_exists("city_block") then city_block = {} end
  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. city_block.formspecs = city_block.formspecs or {}
  13. -- Localize for performance.
  14. local vector_distance = vector.distance
  15. local vector_round = vector.round
  16. local vector_add = vector.add
  17. local vector_equals = vector.equals
  18. local math_random = math.random
  19. -- Cityblocks take 6 hours to become "active".
  20. -- This prevents certain classes of exploits (such as using them offensively
  21. -- during PvP). This also strongly discourages constantly moving them around
  22. -- for trivial reasons.
  23. local CITYBLOCK_DELAY_TIME = 60*60*6
  24. local function time_active(t1, t2)
  25. return (math.abs(t2 - t1) > CITYBLOCK_DELAY_TIME)
  26. end
  27. function city_block.delete_blocks_from_area(minp, maxp)
  28. local i = 1
  29. local blocks = city_block.blocks
  30. ::do_next::
  31. if i > #blocks then
  32. return
  33. end
  34. local p = blocks[i].pos
  35. if p.x >= minp.x and p.x <= maxp.x and
  36. p.y >= minp.y and p.y <= maxp.y and
  37. p.z >= minp.z and p.z <= maxp.z then
  38. -- Don't need to worry about relative ordering.
  39. -- This is your standard swap'n'pop.
  40. blocks[i] = blocks[#blocks]
  41. blocks[#blocks] = nil
  42. goto do_next
  43. end
  44. i = i + 1
  45. goto do_next
  46. end
  47. function city_block.on_punch(pos, node, puncher, pt)
  48. if not pos or not node or not puncher or not pt then
  49. return
  50. end
  51. local pname = puncher:get_player_name()
  52. if minetest.test_protection(pos, pname) then
  53. return
  54. end
  55. local wielded = puncher:get_wielded_item()
  56. if wielded:get_name() == "rosestone:head" and wielded:get_count() >= 8 then
  57. for i, v in ipairs(city_block.blocks) do
  58. if vector_equals(v.pos, pos) then
  59. if not v.is_jail then
  60. local p1 = vector_add(pos, {x=-1, y=0, z=-1})
  61. local p2 = vector_add(pos, {x=1, y=0, z=1})
  62. local positions, counts = minetest.find_nodes_in_area(p1, p2, "griefer:grieferstone")
  63. if counts["griefer:grieferstone"] == 8 then
  64. v.is_jail = true
  65. local meta = minetest.get_meta(pos)
  66. local infotext = meta:get_string("infotext")
  67. infotext = infotext .. "\nJail Marker"
  68. meta:set_string("infotext", infotext)
  69. city_block:save()
  70. wielded:take_item(8)
  71. puncher:set_wielded_item(wielded)
  72. minetest.chat_send_player(pname, "# Server: Jail position marked!")
  73. return
  74. end
  75. end
  76. end
  77. end
  78. end
  79. end
  80. -- Returns a table of the N-nearest city-blocks to a given position.
  81. -- The return value format is: {{pos, owner}, {pos, owner}, ...}
  82. -- Note: only returns blocks in the same realm! See RC mod.
  83. -- The 'rangelim' parameter is optional, if specified, blocks farther than this
  84. -- are ignored entirely.
  85. function city_block:nearest_blocks_to_position(pos, num, rangelim)
  86. local get_rn = rc.current_realm_at_pos
  87. local realm = get_rn(pos)
  88. -- Copy the master table's indices so we don't modify it.
  89. -- We do not need to copy the inner table data itself. Just the indices.
  90. -- Only copy over blocks in the same realm, too.
  91. local blocks = {}
  92. local sblocks = self.blocks
  93. local t2 = os.time()
  94. for i=1, #sblocks, 1 do
  95. local p = sblocks[i].pos
  96. local t1 = sblocks[i].time or 0
  97. if time_active(t1, t2) then
  98. if rangelim then
  99. if vector_distance(p, pos) < rangelim then
  100. if get_rn(p) == realm then
  101. blocks[#blocks+1] = sblocks[i]
  102. end
  103. end
  104. else
  105. if get_rn(p) == realm then
  106. blocks[#blocks+1] = sblocks[i]
  107. end
  108. end
  109. end
  110. end
  111. -- Sort blocks, nearest blocks first.
  112. table.sort(blocks,
  113. function(a, b)
  114. local d1 = vector_distance(a.pos, pos)
  115. local d2 = vector_distance(b.pos, pos)
  116. return d1 < d2
  117. end)
  118. -- Return N-nearest blocks (should be at the front of the sorted table).
  119. local ret = {}
  120. for i = 1, num, 1 do
  121. if i <= #blocks then
  122. ret[#ret + 1] = blocks[i]
  123. else
  124. break
  125. end
  126. end
  127. return ret
  128. end
  129. function city_block:nearest_jails_to_position(pos, num, rangelim)
  130. local get_rn = rc.current_realm_at_pos
  131. local realm = get_rn(pos)
  132. -- Copy the master table's indices so we don't modify it.
  133. -- We do not need to copy the inner table data itself. Just the indices.
  134. -- Only copy over blocks in the same realm, too.
  135. local blocks = {}
  136. local sblocks = self.blocks
  137. local t2 = os.time()
  138. for i=1, #sblocks, 1 do
  139. local v = sblocks[i]
  140. local p = v.pos
  141. local t1 = v.time or 0
  142. if v.is_jail and time_active(t1, t2) then
  143. if rangelim then
  144. if vector_distance(p, pos) < rangelim then
  145. if get_rn(p) == realm then
  146. blocks[#blocks+1] = v
  147. end
  148. end
  149. else
  150. if get_rn(p) == realm then
  151. blocks[#blocks+1] = v
  152. end
  153. end
  154. end
  155. end
  156. -- Sort blocks, nearest blocks first.
  157. table.sort(blocks,
  158. function(a, b)
  159. local d1 = vector_distance(a.pos, pos)
  160. local d2 = vector_distance(b.pos, pos)
  161. return d1 < d2
  162. end)
  163. -- Return N-nearest blocks (should be at the front of the sorted table).
  164. local ret = {}
  165. for i=1, num, 1 do
  166. if i <= #blocks then
  167. ret[#ret+1] = blocks[i]
  168. else
  169. break
  170. end
  171. end
  172. return ret
  173. end
  174. -- Get nearest named cityblock to position which is owned by 'owner'. If 'owner'
  175. -- is nil or an empty string, returns any nearest named cityblock.
  176. function city_block:nearest_named_region(pos, owner)
  177. local get_rn = rc.current_realm_at_pos
  178. local realm = get_rn(pos)
  179. -- Copy the master table's indices so we don't modify it.
  180. -- We do not need to copy the inner table data itself. Just the indices.
  181. -- Only copy over blocks in the same realm, too.
  182. local blocks = {}
  183. local sblocks = self.blocks
  184. local t2 = os.time()
  185. for i=1, #sblocks, 1 do
  186. local b = sblocks[i]
  187. local p = b.pos
  188. local t1 = b.time or 0
  189. if time_active(t1, t2) then
  190. if b.area_name and vector_distance(p, pos) < 100 and
  191. (not owner or owner == "" or b.owner == owner) then
  192. if get_rn(p) == realm then
  193. blocks[#blocks+1] = sblocks[i]
  194. end
  195. end
  196. end
  197. end
  198. -- Sort blocks, nearest blocks first.
  199. table.sort(blocks,
  200. function(a, b)
  201. local d1 = vector_distance(a.pos, pos)
  202. local d2 = vector_distance(b.pos, pos)
  203. return d1 < d2
  204. end)
  205. -- Return N-nearest blocks (should be at the front of the sorted table).
  206. if #blocks > 0 then
  207. return {blocks[1]}
  208. end
  209. return {}
  210. end
  211. function city_block.erase_jail(pos)
  212. pos = vector_round(pos)
  213. local b = city_block.blocks
  214. for k, v in ipairs(b) do
  215. if vector_equals(pos, v.pos) then
  216. local meta = minetest.get_meta(pos)
  217. local pname = meta:get_string("owner")
  218. local dname = rename.gpn(pname)
  219. meta:set_string("infotext", "City Marker (Placed by <" .. dname .. ">!)")
  220. v.is_jail = nil
  221. city_block:save()
  222. return
  223. end
  224. end
  225. end
  226. -- Get city information for the given position.
  227. function city_block.city_info(pos)
  228. pos = vector_round(pos)
  229. local marker = city_block:nearest_blocks_to_position(pos, 1, 100)
  230. if marker and marker[1] then
  231. -- Covers a 45x45x45 area.
  232. local r = 22
  233. local vpos = marker[1].pos
  234. if pos.x >= (vpos.x - r) and pos.x <= (vpos.x + r) and
  235. pos.z >= (vpos.z - r) and pos.z <= (vpos.z + r) and
  236. pos.y >= (vpos.y - r) and pos.y <= (vpos.y + r) then
  237. return marker[1]
  238. end
  239. end
  240. end
  241. function city_block:save()
  242. local datastring = minetest.serialize(self.blocks)
  243. if not datastring then
  244. return
  245. end
  246. minetest.safe_file_write(self.filename, datastring)
  247. --[[
  248. local file, err = io.open(self.filename, "w")
  249. if err then
  250. return
  251. end
  252. file:write(datastring)
  253. file:close()
  254. --]]
  255. end
  256. function city_block:load()
  257. local file, err = io.open(self.filename, "r")
  258. if err then
  259. self.blocks = {}
  260. return
  261. end
  262. self.blocks = minetest.deserialize(file:read("*all"))
  263. if type(self.blocks) ~= "table" then
  264. self.blocks = {}
  265. end
  266. file:close()
  267. end
  268. function city_block:in_city(pos)
  269. pos = vector_round(pos)
  270. -- Covers a 45x45x45 area.
  271. local r = 22
  272. local blocks = self.blocks
  273. local t2 = os.time()
  274. for i=1, #blocks, 1 do -- Convenience of ipairs() does not justify its overhead.
  275. local v = blocks[i]
  276. local vpos = v.pos
  277. local t1 = v.time or 0
  278. if time_active(t1, t2) then
  279. if pos.x >= (vpos.x - r) and pos.x <= (vpos.x + r) and
  280. pos.z >= (vpos.z - r) and pos.z <= (vpos.z + r) and
  281. pos.y >= (vpos.y - r) and pos.y <= (vpos.y + r) then
  282. return true
  283. end
  284. end
  285. end
  286. return false
  287. end
  288. -- Pass the player doing the liquid dig/place action.
  289. function city_block:in_disallow_liquid_zone(pos, player)
  290. -- Never in city zone, if not a player doing this.
  291. if not player or not player:is_player() then
  292. return false
  293. end
  294. local pname = player:get_player_name()
  295. pos = vector_round(pos)
  296. -- Copy the master table's indices so we don't modify it.
  297. -- We do not need to copy the inner table data itself. Just the indices.
  298. local blocks = {}
  299. local sblocks = self.blocks
  300. local t2 = os.time()
  301. -- Covers a 45x45x45 area.
  302. local r = 22
  303. for i=1, #sblocks, 1 do
  304. local vpos = sblocks[i].pos
  305. local t1 = sblocks[i].time or 0
  306. -- Only include active blocks.
  307. if time_active(t1, t2) then
  308. -- This is a cubic distance check.
  309. if pos.x >= (vpos.x - r) and pos.x <= (vpos.x + r) and
  310. pos.z >= (vpos.z - r) and pos.z <= (vpos.z + r) and
  311. pos.y >= (vpos.y - r) and pos.y <= (vpos.y + r) then
  312. -- Add this block to list.
  313. blocks[#blocks+1] = sblocks[i]
  314. end
  315. end
  316. end
  317. -- Sort blocks, nearest blocks first.
  318. table.sort(blocks,
  319. function(a, b)
  320. local d1 = vector_distance(a.pos, pos)
  321. local d2 = vector_distance(b.pos, pos)
  322. return d1 < d2
  323. end)
  324. -- No intersecting blocks at all?
  325. if #blocks == 0 then
  326. return false
  327. end
  328. -- Check only the first, nearest block. Assumed active.
  329. local bcheck = blocks[1]
  330. if bcheck.owner == pname then
  331. return false
  332. end
  333. -- Nearest block NOT owned by player.
  334. -- This means this position is "in city" for purposes of placing/digging liquid.
  335. return true
  336. end
  337. function city_block:in_city_suburbs(pos)
  338. pos = vector_round(pos)
  339. local r = 44
  340. local blocks = self.blocks
  341. local t2 = os.time()
  342. for i=1, #blocks, 1 do -- Convenience of ipairs() does not justify its overhead.
  343. local v = blocks[i]
  344. local vpos = v.pos
  345. local t1 = v.time or 0
  346. if time_active(t1, t2) then
  347. if pos.x >= (vpos.x - r) and pos.x <= (vpos.x + r) and
  348. pos.z >= (vpos.z - r) and pos.z <= (vpos.z + r) and
  349. pos.y >= (vpos.y - r) and pos.y <= (vpos.y + r) then
  350. return true
  351. end
  352. end
  353. end
  354. return false
  355. end
  356. function city_block:in_safebed_zone(pos)
  357. -- Covers a 111x111x111 area.
  358. pos = vector_round(pos)
  359. local r = 55
  360. local blocks = self.blocks
  361. local t2 = os.time()
  362. for i=1, #blocks, 1 do -- Convenience of ipairs() does not justify its overhead.
  363. local v = blocks[i]
  364. local vpos = v.pos
  365. local t1 = v.time or 0
  366. if time_active(t1, t2) then
  367. if pos.x >= (vpos.x - r) and pos.x <= (vpos.x + r) and
  368. pos.z >= (vpos.z - r) and pos.z <= (vpos.z + r) and
  369. pos.y >= (vpos.y - r) and pos.y <= (vpos.y + r) then
  370. return true
  371. end
  372. end
  373. end
  374. return false
  375. end
  376. function city_block:in_no_tnt_zone(pos)
  377. pos = vector_round(pos)
  378. local r = 50
  379. local blocks = self.blocks
  380. local t2 = os.time()
  381. for i=1, #blocks, 1 do -- Convenience of ipairs() does not justify its overhead.
  382. local v = blocks[i]
  383. local vpos = v.pos
  384. local t1 = v.time or 0
  385. if time_active(t1, t2) then
  386. if pos.x >= (vpos.x - r) and pos.x <= (vpos.x + r) and
  387. pos.z >= (vpos.z - r) and pos.z <= (vpos.z + r) and
  388. pos.y >= (vpos.y - r) and pos.y <= (vpos.y + r) then
  389. return true
  390. end
  391. end
  392. end
  393. return false
  394. end
  395. function city_block:in_no_leecher_zone(pos)
  396. pos = vector_round(pos)
  397. local r = 100
  398. local blocks = self.blocks
  399. local t2 = os.time()
  400. for i=1, #blocks, 1 do -- Convenience of ipairs() does not justify its overhead.
  401. local v = blocks[i]
  402. local vpos = v.pos
  403. local t1 = v.time or 0
  404. if time_active(t1, t2) then
  405. if pos.x >= (vpos.x - r) and pos.x <= (vpos.x + r) and
  406. pos.z >= (vpos.z - r) and pos.z <= (vpos.z + r) and
  407. pos.y >= (vpos.y - r) and pos.y <= (vpos.y + r) then
  408. return true
  409. end
  410. end
  411. end
  412. return false
  413. end
  414. function city_block.on_rightclick(pos, node, clicker, itemstack)
  415. if not clicker or not clicker:is_player() then
  416. return
  417. end
  418. local pname = clicker:get_player_name()
  419. local meta = minetest.get_meta(pos)
  420. -- Player must be owner of city block.
  421. if meta:get_string("owner") ~= pname then
  422. return
  423. end
  424. -- Create formspec context.
  425. city_block.formspecs[pname] = pos
  426. local formspec = city_block.create_formspec(pos, pname)
  427. minetest.show_formspec(pname, "city_block:main", formspec)
  428. end
  429. function city_block.create_formspec(pos, pname)
  430. local formspec = "size[4.1,2.0]" ..
  431. default.gui_bg ..
  432. default.gui_bg_img ..
  433. default.gui_slots ..
  434. "label[0,0;Enter city/area region name:]" ..
  435. "field[0.30,0.75;4,1;CITYNAME;;]" ..
  436. "button_exit[0,1.30;2,1;OK;Confirm]" ..
  437. "button_exit[2,1.30;2,1;CANCEL;Abort]" ..
  438. "field_close_on_enter[CITYNAME;true]"
  439. return formspec
  440. end
  441. function check_cityname(cityname)
  442. return not string.match(cityname, "[^%a%s]")
  443. end
  444. function city_block.on_receive_fields(player, formname, fields)
  445. if formname ~= "city_block:main" then
  446. return
  447. end
  448. if not player or not player:is_player() then
  449. return
  450. end
  451. local pname = player:get_player_name()
  452. local pos = city_block.formspecs[pname]
  453. -- Context should have been created in 'on_rightclick'. CSM protection.
  454. if not pos then
  455. return true
  456. end
  457. local meta = minetest.get_meta(pos)
  458. local owner = meta:get_string("owner")
  459. -- Form sender must be owner.
  460. if pname ~= owner then
  461. return true
  462. end
  463. if fields.key_enter_field == "CITYNAME" or fields.OK then
  464. local area_name = (fields.CITYNAME or ""):trim()
  465. area_name = area_name:gsub("%s+", " ")
  466. -- Ensure city name is valid.
  467. local is_valid = true
  468. if #area_name == 0 then
  469. is_valid = false
  470. end
  471. if #area_name > 20 then
  472. is_valid = false
  473. end
  474. if not check_cityname(area_name) then
  475. is_valid = false
  476. end
  477. if anticurse.check(pname, area_name, "foul") then
  478. is_valid = false
  479. elseif anticurse.check(pname, area_name, "curse") then
  480. is_valid = false
  481. end
  482. if not is_valid then
  483. minetest.chat_send_player(pname, "# Server: Region name not valid.")
  484. return
  485. end
  486. local allblocks = city_block.blocks
  487. local numblocks = #(city_block.blocks)
  488. local block
  489. for i = 1, numblocks do
  490. local entry = allblocks[i]
  491. if vector_equals(entry.pos, pos) then
  492. block = entry
  493. break
  494. end
  495. end
  496. -- Ensure we got the city block data.
  497. if not block then
  498. return
  499. end
  500. -- Write out.
  501. meta:set_string("cityname", area_name)
  502. meta:set_string("infotext", city_block.get_infotext(pos))
  503. block.area_name = area_name
  504. city_block:save()
  505. end
  506. return true
  507. end
  508. function city_block.get_infotext(pos)
  509. local meta = minetest.get_meta(pos)
  510. local pname = meta:get_string("owner")
  511. local cityname = meta:get_string("cityname")
  512. local dname = rename.gpn(pname)
  513. local text = "City Marker (Placed by <" .. dname .. ">!)"
  514. if cityname ~= "" then
  515. text = text .. "\nRegion Designate: \"" .. cityname .. "\""
  516. end
  517. return text
  518. end
  519. if not city_block.run_once then
  520. city_block:load()
  521. minetest.register_on_player_receive_fields(function(...)
  522. return city_block.on_receive_fields(...) end)
  523. minetest.register_node("city_block:cityblock", {
  524. 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.",
  525. tiles = {"moreblocks_circle_stone_bricks.png^default_tool_mesepick.png"},
  526. is_ground_content = false,
  527. groups = utility.dig_groups("obsidian", {
  528. immovable=1,
  529. }),
  530. is_ground_content = false,
  531. sounds = default.node_sound_stone_defaults(),
  532. on_rightclick = function(...)
  533. return city_block.on_rightclick(...)
  534. end,
  535. after_place_node = function(pos, placer)
  536. if placer and placer:is_player() then
  537. local pname = placer:get_player_name()
  538. local meta = minetest.get_meta(pos)
  539. local dname = rename.gpn(pname)
  540. meta:set_string("rename", dname)
  541. meta:set_string("owner", pname)
  542. meta:set_string("infotext", city_block.get_infotext(pos))
  543. table.insert(city_block.blocks, {
  544. pos = vector_round(pos),
  545. owner = pname,
  546. time = os.time(),
  547. })
  548. city_block:save()
  549. end
  550. end,
  551. -- We don't need an `on_blast` func because TNT calls `on_destruct` properly!
  552. on_destruct = function(pos)
  553. -- The cityblock may not exist in the list if the node was created by falling,
  554. -- and was later dug.
  555. for i, EachBlock in ipairs(city_block.blocks) do
  556. if vector_equals(EachBlock.pos, pos) then
  557. table.remove(city_block.blocks, i)
  558. city_block:save()
  559. end
  560. end
  561. end,
  562. -- Called by rename LBM.
  563. _on_update_infotext = function(pos)
  564. local meta = minetest.get_meta(pos)
  565. local owner = meta:get_string("owner")
  566. -- Nobody placed this block.
  567. if owner == "" then
  568. return
  569. end
  570. local dname = rename.gpn(owner)
  571. meta:set_string("rename", dname)
  572. meta:set_string("infotext", city_block.get_infotext(pos))
  573. end,
  574. on_punch = function(...)
  575. return city_block.on_punch(...)
  576. end,
  577. })
  578. minetest.register_craft({
  579. output = 'city_block:cityblock',
  580. recipe = {
  581. {'default:pick_mese', 'farming:hoe_mese', 'default:sword_diamond'},
  582. {'chests:chest_locked', 'default:goldblock', 'default:sandstone'},
  583. {'default:obsidianbrick', 'default:mese', 'cobble_furnace:inactive'},
  584. }
  585. })
  586. minetest.register_privilege("disable_pvp", "Players cannot damage players with this priv by punching.")
  587. minetest.register_on_punchplayer(function(...)
  588. return city_block.on_punchplayer(...)
  589. end)
  590. local c = "city_block:core"
  591. local f = city_block.modpath .. "/init.lua"
  592. reload.register_file(c, f, false)
  593. city_block.run_once = true
  594. end
  595. function city_block:get_adjective()
  596. local adjectives = {
  597. "murdering",
  598. "slaying",
  599. "killing",
  600. "whacking",
  601. "trashing",
  602. "fatally attacking",
  603. "fatally harming",
  604. "doing away with",
  605. "giving the Chicago treatment to",
  606. "fatally thrashing",
  607. "fatally stabbing",
  608. }
  609. return adjectives[math_random(1, #adjectives)]
  610. end
  611. local murder_messages = {
  612. "<v> collapsed from <k>'s brutal attack.",
  613. "<k>'s <w> apparently wasn't such an unusual weapon after all, as <v> found out.",
  614. "<k> killed <v> with great prejudice.",
  615. "<v> died from <k>'s horrid slaying.",
  616. "<v> fell prey to <k>'s deadly <w>.",
  617. "<k> went out of <k_his> way to slay <v> with <k_his> <w>.",
  618. "<v> danced <v_himself> to death under <k>'s craftily wielded <w>.",
  619. "<k> used <k_his> <w> to kill <v> with prejudice.",
  620. "<k> made a splortching sound with <v>'s head.",
  621. "<v> got flattened by <k>'s skillfully handled <w>.",
  622. "<v> became prey for <k>.",
  623. "<v> didn't get out of <k>'s way in time.",
  624. "<v> SAW <k> coming with <k_his> <w>. Didn't get away in time.",
  625. "<v> made no real attempt to get out of <k>'s way.",
  626. "<k> barreled through <v> as if <v_he> wasn't there.",
  627. "<k> sent <v> to that place where kindling wood isn't needed.",
  628. "<v> didn't suspect that <k> meant <v_him> any harm.",
  629. "<v> fought <k> to the death and lost painfully.",
  630. "<v> knew <k> was wielding <k_his> <w> but didn't guess what <k> meant to do with it.",
  631. "<k> clonked <v> over the head using <k_his> <w> with silent skill.",
  632. "<k> made sure <v> didn't see that coming!",
  633. "<k> has decided <k_his> favorite weapon is <k_his> <w>.",
  634. "<v> did the mad hatter dance just before being killed with <k>'s <w>.",
  635. "<v> played the victim to <k>'s bully behavior!",
  636. "<k> used <v> for weapons practice with <k_his> <w>.",
  637. "<v> failed to avoid <k>'s oncoming weapon.",
  638. "<k> successfully got <v> to complain of a headache.",
  639. "<v> got <v_himself> some serious hurt from <k>'s <w>.",
  640. "Trying to talk peace to <k> didn't win any for <v>.",
  641. "<v> was brutally slain by <k>'s <w>.",
  642. "<v> jumped the mad-hatter dance under <k>'s <w>.",
  643. "<v> got <v_himself> a fatal mauling by <k>'s <w>.",
  644. "<k> just assassinated <v> with <k_his> <w>.",
  645. "<k> split <v>'s wig.",
  646. "<k> took revenge on <v>.",
  647. "<k> flattened <v>.",
  648. "<v> played dead. Permanently.",
  649. "<v> never saw what hit <v_him>.",
  650. "<k> took <v> by surprise.",
  651. "<v> was assassinated.",
  652. "<k> didn't take any prisoners from <v>.",
  653. "<k> pinned <v> to the wall with <k_his> <w>.",
  654. "<v> failed <v_his> weapon checks.",
  655. }
  656. function city_block.murder_message(killer, victim, sendto)
  657. if spam.test_key("kill" .. victim .. "15662") then
  658. return
  659. end
  660. spam.mark_key("kill" .. victim .. "15662", 30)
  661. local msg = murder_messages[math_random(1, #murder_messages)]
  662. msg = string.gsub(msg, "<v>", "<" .. rename.gpn(victim) .. ">")
  663. msg = string.gsub(msg, "<k>", "<" .. rename.gpn(killer) .. ">")
  664. local ksex = skins.get_gender_strings(killer)
  665. local vsex = skins.get_gender_strings(victim)
  666. msg = string.gsub(msg, "<k_himself>", ksex.himself)
  667. msg = string.gsub(msg, "<k_his>", ksex.his)
  668. msg = string.gsub(msg, "<v_himself>", vsex.himself)
  669. msg = string.gsub(msg, "<v_his>", vsex.his)
  670. msg = string.gsub(msg, "<v_him>", vsex.him)
  671. msg = string.gsub(msg, "<v_he>", vsex.he)
  672. if string.find(msg, "<w>") then
  673. local hitter = minetest.get_player_by_name(killer)
  674. if hitter then
  675. local wield = hitter:get_wielded_item()
  676. local def = minetest.registered_items[wield:get_name()]
  677. local meta = wield:get_meta()
  678. local description = meta:get_string("description")
  679. if description ~= "" then
  680. msg = string.gsub(msg, "<w>", "'" .. utility.get_short_desc(description):trim() .. "'")
  681. elseif def and def.description then
  682. local str = utility.get_short_desc(def.description)
  683. if str == "" then
  684. str = "Potato Fist"
  685. end
  686. msg = string.gsub(msg, "<w>", str)
  687. end
  688. end
  689. end
  690. if type(sendto) == "string" then
  691. minetest.chat_send_player(sendto, "# Server: " .. msg)
  692. else
  693. minetest.chat_send_all("# Server: " .. msg)
  694. end
  695. end
  696. function city_block.hit_possible(p1pos, p2pos)
  697. -- Range limit, stops hackers with long reach.
  698. if vector_distance(p1pos, p2pos) > 6 then
  699. return false
  700. end
  701. -- Cannot attack through walls.
  702. -- But if node wouldn't stop an arrow, keep testing the line.
  703. --local raycast = minetest.raycast(p1pos, p2pos, false, false)
  704. -- This seems to cause random freezes and 100% CPU.
  705. --[[
  706. local los, pstop = minetest.line_of_sight(p1pos, p2pos)
  707. while not los do
  708. if throwing.node_blocks_arrow(minetest.get_node(vector_round(pstop)).name) then
  709. return false
  710. end
  711. local dir = vector.direction(pstop, p2pos)
  712. local ns = vector.add(pstop, dir)
  713. los, pstop = minetest.line_of_sight(ns, p2pos)
  714. end
  715. --]]
  716. return true
  717. end
  718. function city_block.send_to_jail(victim_pname, attack_pname)
  719. -- Killers don't go to jail if the victim is a registered cheater.
  720. if not sheriff.is_cheater(victim_pname) then
  721. local hitter = minetest.get_player_by_name(attack_pname)
  722. if hitter and jail.go_to_jail(hitter, nil) then
  723. minetest.chat_send_all(
  724. "# Server: Criminal <" .. rename.gpn(attack_pname) .. "> was sent to gaol for " ..
  725. city_block:get_adjective() .. " <" .. rename.gpn(victim_pname) .. "> within city limits.")
  726. end
  727. end
  728. end
  729. function city_block.handle_assassination(p2pos, victim_pname, attack_pname, melee)
  730. -- Bed position is only lost if player died outside city to a melee weapon.
  731. if not city_block:in_safebed_zone(p2pos) and melee then
  732. -- Victim doesn't lose their bed respawn if they were killed by a cheater.
  733. if not sheriff.is_cheater(attack_pname) then
  734. local pref = minetest.get_player_by_name(victim_pname)
  735. if pref then
  736. local meta = pref:get_meta()
  737. meta:set_int("was_assassinated", 1)
  738. end
  739. --minetest.chat_send_player(victim_pname, "# Server: Your bed is lost! You were assassinated in the wilds.")
  740. --beds.clear_player_spawn(victim_pname)
  741. end
  742. end
  743. end
  744. -- Note: this is called on the next server step after the punch, otherwise we
  745. -- cannot know if the player died as a result.
  746. function city_block.handle_consequences(player, hitter, melee, stomp)
  747. --minetest.log('handle_consequences')
  748. local victim_pname = player:get_player_name()
  749. local attack_pname = hitter:get_player_name()
  750. local time = os.time()
  751. local hp = player:get_hp()
  752. local p2pos = utility.get_head_pos(player:get_pos())
  753. local vpos = vector_round(p2pos)
  754. city_block.attackers[victim_pname] = attack_pname
  755. city_block.victims[victim_pname] = time
  756. -- Victim didn't die yet.
  757. if hp > 0 then
  758. return
  759. end
  760. default.detach_player_if_attached(player)
  761. -- Stomp messages are handled elsewhere.
  762. if not stomp then
  763. city_block.murder_message(attack_pname, victim_pname)
  764. end
  765. if city_block:in_city(p2pos) then
  766. local t0 = city_block.victims[attack_pname] or time
  767. local tdiff = (time - t0)
  768. if not city_block.attackers[attack_pname] then
  769. city_block.attackers[attack_pname] = ""
  770. end
  771. --[[
  772. Behavior Table (obtained through testing):
  773. In city-block area, no protection:
  774. A kills B, B did not retaliate -> A goes to jail
  775. A kills B, B had retaliated -> Nobody jailed
  776. (The table is the same if A and B are inverted)
  777. In city-block area, protected by A (with nearby jail available):
  778. A kills B, B did not retaliate -> A goes to jail
  779. A kills B, B had retaliated -> Nobody jailed
  780. B kills A, A did not retaliate -> B goes to jail
  781. B kills A, A had retaliated -> B goes to jail
  782. (The table is the same if A and B are inverted, and protection is B's)
  783. Notes:
  784. A hit from A or B is considered retaliation if it happens very soon
  785. after the other player hit. Thus, if both A and B are hitting, then both
  786. are considered to be retaliating -- in that case, land ownership is used
  787. to resolve who should go to jail.
  788. It does not matter who hits first in a fight -- only who kills the other
  789. player first.
  790. If there is no jail available for a crook to be sent to, then nothing
  791. happens in any case, regardless of who wins the fight or owns the land.
  792. --]]
  793. -- Victim is "landowner" if area is protected, but they have access.
  794. local landowner = (minetest.test_protection(vpos, "") and
  795. not minetest.test_protection(vpos, victim_pname))
  796. -- Killing justified after provocation, but not if victim owns the land.
  797. if city_block.attackers[attack_pname] == victim_pname and
  798. tdiff < 30 and not landowner then
  799. return
  800. else
  801. -- Go to jail! Do not pass Go. Do not collect $200.
  802. city_block.send_to_jail(victim_pname, attack_pname)
  803. end
  804. else
  805. -- Player killed outside town.
  806. -- This only does something if the attack was with a melee weapon!
  807. city_block.handle_assassination(p2pos, victim_pname, attack_pname, melee)
  808. end
  809. end
  810. city_block.attackers = city_block.attackers or {}
  811. city_block.victims = city_block.victims or {}
  812. -- Return `true' to prevent the default damage mechanism.
  813. -- Note: player is sometimes the hitter (player punches self). This is sometimes
  814. -- necessary when a mod needs to punch a player, but has no entity that can do
  815. -- the actual punch.
  816. function city_block.on_punchplayer(player, hitter, time_from_last_punch, tool_capabilities, dir, damage)
  817. if not player:is_player() then
  818. return
  819. end
  820. -- Callback is called even if player is dead. Shortcut.
  821. if player:get_hp() <= 0 or hitter:get_hp() <= 0 then
  822. return
  823. end
  824. --minetest.log('on_punchplayer')
  825. local melee_hit = true
  826. local stomp_hit = false
  827. local from_env = false
  828. if tool_capabilities.damage_groups.from_stomp then
  829. stomp_hit = true
  830. end
  831. if tool_capabilities.damage_groups.from_env then
  832. from_env = true
  833. end
  834. if tool_capabilities.damage_groups.from_arrow then
  835. -- Someone launched this weapon. The hitter is most likely the nearest
  836. -- player that isn't the player going to be hit.
  837. melee_hit = false
  838. -- We don't have enough information to know exactly who fired this weapon,
  839. -- but it's probably a safe bet that it was the nearest player who is NOT
  840. -- the player being hit. But if we were explicitly provided a player object
  841. -- that is NOT self, then we don't need to do this.
  842. if hitter == player or not hitter:is_player() then
  843. -- If initial hitter is the player, or the hitter isn't a player, then
  844. -- get the nearest other player to this position (who is not the initial
  845. -- player) and use that player as the hitter.
  846. local pos = player:get_pos()
  847. local culprit = hb4.nearest_player_not(pos, player)
  848. if culprit then
  849. local cpos = culprit:get_pos()
  850. -- Only if culprit is nearby.
  851. if vector.distance(cpos, pos) < 50 then
  852. hitter = culprit
  853. end
  854. end
  855. end
  856. end
  857. if not hitter:is_player() then
  858. return
  859. end
  860. -- Random accidents happen to punished players during PvP.
  861. do
  862. local attacker = hitter:get_player_name()
  863. if sheriff.is_cheater(attacker) then
  864. if sheriff.punish_probability(attacker) then
  865. sheriff.punish_player(attacker)
  866. end
  867. end
  868. end
  869. local p1pos = utility.get_head_pos(hitter:get_pos())
  870. local p2pos = utility.get_head_pos(player:get_pos())
  871. -- Check if hit is physically possible (range, blockage, etc).
  872. if melee_hit and not city_block.hit_possible(p1pos, p2pos) then
  873. return true
  874. end
  875. -- PvP is disabled for players in jail. This fixes a possible way to exploit jail.
  876. if not from_env and (jail.is_player_in_jail(hitter) or jail.is_player_in_jail(player)) then
  877. minetest.chat_send_player(hitter:get_player_name(), "# Server: Brawling is not allowed in jail.")
  878. return true
  879. end
  880. -- Admins cannot be punched.
  881. if gdac.player_is_admin(player) then
  882. return true
  883. end
  884. -- Let others hear sounds of nearby combat.
  885. if damage > 0 then
  886. ambiance.sound_play("player_damage", p2pos, 2.0, 30)
  887. end
  888. -- If hitter is self, punch was (most likely) due to game code.
  889. if player == hitter then
  890. return
  891. end
  892. -- Stuff that happens when one player kills another.
  893. -- Must be executed on the next server step, so we can determine if victim
  894. -- really died! (This is because damage will often be modified.)
  895. local pname = player:get_player_name()
  896. local hname = hitter:get_player_name()
  897. minetest.after(0, function()
  898. local pref = minetest.get_player_by_name(pname)
  899. local href = minetest.get_player_by_name(hname)
  900. if pref and href then
  901. city_block.handle_consequences(pref, href, melee_hit, stomp_hit)
  902. end
  903. end)
  904. end