init.lua 30 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100
  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. minetest.chat_send_player(victim_pname, "# Server: Your bed is lost! You were assassinated in the wilds.")
  735. beds.clear_player_spawn(victim_pname)
  736. end
  737. end
  738. end
  739. -- Note: this is called on the next server step after the punch, otherwise we
  740. -- cannot know if the player died as a result.
  741. function city_block.handle_consequences(player, hitter, melee, stomp)
  742. --minetest.log('handle_consequences')
  743. local victim_pname = player:get_player_name()
  744. local attack_pname = hitter:get_player_name()
  745. local time = os.time()
  746. local hp = player:get_hp()
  747. local p2pos = utility.get_head_pos(player:get_pos())
  748. local vpos = vector_round(p2pos)
  749. city_block.attackers[victim_pname] = attack_pname
  750. city_block.victims[victim_pname] = time
  751. -- Victim didn't die yet.
  752. if hp > 0 then
  753. return
  754. end
  755. default.detach_player_if_attached(player)
  756. -- Stomp messages are handled elsewhere.
  757. if not stomp then
  758. city_block.murder_message(attack_pname, victim_pname)
  759. end
  760. if city_block:in_city(p2pos) then
  761. local t0 = city_block.victims[attack_pname] or time
  762. local tdiff = (time - t0)
  763. if not city_block.attackers[attack_pname] then
  764. city_block.attackers[attack_pname] = ""
  765. end
  766. --[[
  767. Behavior Table (obtained through testing):
  768. In city-block area, no protection:
  769. A kills B, B did not retaliate -> A goes to jail
  770. A kills B, B had retaliated -> Nobody jailed
  771. (The table is the same if A and B are inverted)
  772. In city-block area, protected by A (with nearby jail available):
  773. A kills B, B did not retaliate -> A goes to jail
  774. A kills B, B had retaliated -> Nobody jailed
  775. B kills A, A did not retaliate -> B goes to jail
  776. B kills A, A had retaliated -> B goes to jail
  777. (The table is the same if A and B are inverted, and protection is B's)
  778. Notes:
  779. A hit from A or B is considered retaliation if it happens very soon
  780. after the other player hit. Thus, if both A and B are hitting, then both
  781. are considered to be retaliating -- in that case, land ownership is used
  782. to resolve who should go to jail.
  783. It does not matter who hits first in a fight -- only who kills the other
  784. player first.
  785. If there is no jail available for a crook to be sent to, then nothing
  786. happens in any case, regardless of who wins the fight or owns the land.
  787. --]]
  788. -- Victim is "landowner" if area is protected, but they have access.
  789. local landowner = (minetest.test_protection(vpos, "") and
  790. not minetest.test_protection(vpos, victim_pname))
  791. -- Killing justified after provocation, but not if victim owns the land.
  792. if city_block.attackers[attack_pname] == victim_pname and
  793. tdiff < 30 and not landowner then
  794. return
  795. else
  796. -- Go to jail! Do not pass Go. Do not collect $200.
  797. city_block.send_to_jail(victim_pname, attack_pname)
  798. end
  799. else
  800. -- Player killed outside town.
  801. -- This only does something if the attack was with a melee weapon!
  802. city_block.handle_assassination(p2pos, victim_pname, attack_pname, melee)
  803. end
  804. end
  805. city_block.attackers = city_block.attackers or {}
  806. city_block.victims = city_block.victims or {}
  807. -- Return `true' to prevent the default damage mechanism.
  808. -- Note: player is sometimes the hitter (player punches self). This is sometimes
  809. -- necessary when a mod needs to punch a player, but has no entity that can do
  810. -- the actual punch.
  811. function city_block.on_punchplayer(player, hitter, time_from_last_punch, tool_capabilities, dir, damage)
  812. if not player:is_player() then
  813. return
  814. end
  815. -- Callback is called even if player is dead. Shortcut.
  816. if player:get_hp() <= 0 or hitter:get_hp() <= 0 then
  817. return
  818. end
  819. --minetest.log('on_punchplayer')
  820. local melee_hit = true
  821. local stomp_hit = false
  822. local from_env = false
  823. if tool_capabilities.damage_groups.from_stomp then
  824. stomp_hit = true
  825. end
  826. if tool_capabilities.damage_groups.from_env then
  827. from_env = true
  828. end
  829. if tool_capabilities.damage_groups.from_arrow then
  830. -- Someone launched this weapon. The hitter is most likely the nearest
  831. -- player that isn't the player going to be hit.
  832. melee_hit = false
  833. -- We don't have enough information to know exactly who fired this weapon,
  834. -- but it's probably a safe bet that it was the nearest player who is NOT
  835. -- the player being hit. But if we were explicitly provided a player object
  836. -- that is NOT self, then we don't need to do this.
  837. if hitter == player or not hitter:is_player() then
  838. -- If initial hitter is the player, or the hitter isn't a player, then
  839. -- get the nearest other player to this position (who is not the initial
  840. -- player) and use that player as the hitter.
  841. local pos = player:get_pos()
  842. local culprit = hb4.nearest_player_not(pos, player)
  843. if culprit then
  844. local cpos = culprit:get_pos()
  845. -- Only if culprit is nearby.
  846. if vector.distance(cpos, pos) < 50 then
  847. hitter = culprit
  848. end
  849. end
  850. end
  851. end
  852. if not hitter:is_player() then
  853. return
  854. end
  855. -- Random accidents happen to punished players during PvP.
  856. do
  857. local attacker = hitter:get_player_name()
  858. if sheriff.is_cheater(attacker) then
  859. if sheriff.punish_probability(attacker) then
  860. sheriff.punish_player(attacker)
  861. end
  862. end
  863. end
  864. local p1pos = utility.get_head_pos(hitter:get_pos())
  865. local p2pos = utility.get_head_pos(player:get_pos())
  866. -- Check if hit is physically possible (range, blockage, etc).
  867. if melee_hit and not city_block.hit_possible(p1pos, p2pos) then
  868. return true
  869. end
  870. -- PvP is disabled for players in jail. This fixes a possible way to exploit jail.
  871. if not from_env and (jail.is_player_in_jail(hitter) or jail.is_player_in_jail(player)) then
  872. minetest.chat_send_player(hitter:get_player_name(), "# Server: Brawling is not allowed in jail.")
  873. return true
  874. end
  875. -- Admins cannot be punched.
  876. if gdac.player_is_admin(player) then
  877. return true
  878. end
  879. -- Let others hear sounds of nearby combat.
  880. if damage > 0 then
  881. ambiance.sound_play("player_damage", p2pos, 2.0, 30)
  882. end
  883. -- If hitter is self, punch was (most likely) due to game code.
  884. if player == hitter then
  885. return
  886. end
  887. -- Stuff that happens when one player kills another.
  888. -- Must be executed on the next server step, so we can determine if victim
  889. -- really died! (This is because damage will often be modified.)
  890. local pname = player:get_player_name()
  891. local hname = hitter:get_player_name()
  892. minetest.after(0, function()
  893. local pref = minetest.get_player_by_name(pname)
  894. local href = minetest.get_player_by_name(hname)
  895. if pref and href then
  896. city_block.handle_consequences(pref, href, melee_hit, stomp_hit)
  897. end
  898. end)
  899. end