init.lua 21 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677
  1. -- For the future:
  2. -- Reduce checks if the server is laggy (globalstep is slowed down).
  3. -- * Requires: a reliable way to determine current server lag.
  4. -- Increase checks on a player if others report them for cheating.
  5. -- * Requires: a reliable way for players to report cheaters that is not
  6. -- * trivially open to abuse.
  7. -- This line was added by Dresdan.
  8. -- I'm ganna learn today boys.
  9. -- im ganna make flowers
  10. -- This is Nakilashiva. I'm ganna lorn too.
  11. if not minetest.global_exists("ac") then ac = {} end
  12. ac.modpath = minetest.get_modpath("ac")
  13. ac.wpath = minetest.get_worldpath()
  14. ac.players = ac.players or {} -- Per-player data for this session ONLY.
  15. -- Random delay to first check on player's first join (this session).
  16. ac.initial_delay_min = 1
  17. ac.initial_delay_max = 20
  18. -- Default (base) random delay between player checks.
  19. ac.default_delay_min = 5
  20. ac.default_delay_max = 30
  21. -- Random time to decrease between checks for players in city areas.
  22. ac.city_reduce_min = 1
  23. ac.city_reduce_max = 20
  24. -- Random time to add between checks for players in outlands.
  25. ac.outland_increase_min = 5
  26. ac.outland_increase_max = 30
  27. -- The average amounts of accumulated suspicion per session considered low/high.
  28. -- Average suspicion is calculated by total suspicion ever recorded, divided by
  29. -- number of clean sessions in which no suspicion was recorded (for that player).
  30. ac.low_average_suspicion = 5
  31. ac.high_average_suspicion = 20
  32. -- Random time to decrease between checks for players with high avg suspicion.
  33. ac.high_suspicion_reduce_min = 1
  34. ac.high_suspicion_reduce_max = 10
  35. -- Random time to add between checks for players with low suspicion.
  36. ac.low_suspicion_increase_min = 1
  37. ac.low_suspicion_increase_max = 10
  38. -- Once accumulated suspicion for a single session (not including total suspicion
  39. -- over all sessions) exceeds this amount, player is registered as a confirmed
  40. -- cheater. Note that the player will not automatically be registered as a cheater
  41. -- if they merely have high avg suspicion over multiple sessions.
  42. ac.cheat_registration_threshold = 50
  43. -- Localize for performance.
  44. local vector_distance = vector.distance
  45. local vector_round = vector.round
  46. local math_floor = math.floor
  47. local math_random = math.random
  48. -- Open logfile if not already opened.
  49. if not ac.logfile then
  50. -- Open file in append mode.
  51. ac.logfile = io.open(ac.wpath .. "/ac.txt", "a")
  52. end
  53. -- Open mod storage if not already opened.
  54. if not ac.storage then
  55. ac.storage = minetest.get_mod_storage()
  56. end
  57. function ac.get_suspicion_count(pname)
  58. if ac.players[pname] then
  59. if ac.players[pname].suspicion_count then
  60. -- Must be a non-negative integer.
  61. return ac.players[pname].suspicion_count
  62. end
  63. end
  64. return 0
  65. end
  66. -- Get timestamp of the last time a cheat was detected by the standard checking
  67. -- function (and its confirmation spawns), not including cheats detected along a
  68. -- path.
  69. function ac.get_last_cheat_time(pname)
  70. if ac.players[pname] then
  71. if ac.players[pname].last_cheat_time then
  72. return ac.players[pname].last_cheat_time
  73. end
  74. end
  75. return 0
  76. end
  77. function ac.get_total_suspicion(pname)
  78. if ac.players[pname] then
  79. local cs = ac.get_suspicion_count(pname)
  80. if ac.players[pname].total_suspicion then
  81. -- Must be a non-negative integer.
  82. return ac.players[pname].total_suspicion + cs
  83. end
  84. local k = pname .. ":total_suspicion"
  85. local ts = ac.storage:get_int(k)
  86. ac.players[pname].total_suspicion = ts
  87. return ts + cs
  88. else
  89. local cs = ac.get_suspicion_count(pname)
  90. local k = pname .. ":total_suspicion"
  91. local ts = ac.storage:get_int(k)
  92. ac.players[pname] = {total_suspicion=ts}
  93. return ts + cs
  94. end
  95. return 0
  96. end
  97. function ac.get_clean_sessions(pname)
  98. if ac.players[pname] then
  99. if ac.players[pname].clean_sessions then
  100. -- Must be a non-negative integer.
  101. return ac.players[pname].clean_sessions
  102. end
  103. local k = pname .. ":clean_sessions"
  104. local cc = ac.storage:get_int(k)
  105. ac.players[pname].clean_sessions = cc
  106. return cc
  107. else
  108. local k = pname .. ":clean_sessions"
  109. local cc = ac.storage:get_int(k)
  110. ac.players[pname] = {clean_sessions=cc}
  111. return cc
  112. end
  113. return 0
  114. end
  115. function ac.get_position_at_last_check_or_nil(pname)
  116. if ac.players[pname] then
  117. if ac.players[pname].last_pos then
  118. return ac.players[pname].last_pos
  119. end
  120. end
  121. end
  122. -- Log to file.
  123. function ac.log_suspicious_act(pname, pos, time, act)
  124. local s = pname .. "|" .. act .. "|" .. time .. "|" ..
  125. math_floor(pos.x) .. "," .. math_floor(pos.y) .. "," .. math_floor(pos.z) ..
  126. "|" .. ac.get_suspicion_count(pname) .. "\n"
  127. ac.logfile:write(s)
  128. ac.logfile:flush()
  129. end
  130. -- Record in current session memory.
  131. -- Note: this may be called out of sequence! Therefore we shouldn't use current
  132. -- time or player's current position.
  133. function ac.record_suspicious_act(pname, time, act)
  134. local pdata = ac.players[pname]
  135. if not pdata then
  136. ac.players[pname] = {}
  137. pdata = ac.players[pname]
  138. end
  139. -- Increment suspicion count.
  140. pdata.suspicion_count = (pdata.suspicion_count or 0) + 1
  141. if act == "fly" then
  142. pdata.fly_count = (pdata.fly_count or 0) + 1
  143. elseif act == "clip" then
  144. pdata.clip_count = (pdata.clip_count or 0) + 1
  145. end
  146. -- This is used for recording the most recent last time when a cheat was
  147. -- detected by the cheat-confirmation functions. This should be nil when we're
  148. -- checking a path, as that is considered "a separate feature". Cheats detected
  149. -- along a path should all be considered part of the same "instance" of cheating,
  150. -- so we don't record timestamps in that case.
  151. if time ~= nil then
  152. pdata.last_cheat_time = time
  153. end
  154. end
  155. -- Report to admin (if logged in).
  156. function ac.report_suspicious_act(pname, pos, act)
  157. local pref = utility.get_first_available_admin()
  158. if pref then
  159. minetest.chat_send_player(pref:get_player_name(),
  160. "# Server: <" .. rename.gpn(pname) ..
  161. "> caught in suspicious activity: '" .. act .. "' at " ..
  162. rc.pos_to_namestr(pos) .. ". Suspicion: " ..
  163. ac.get_suspicion_count(pname) .. ".")
  164. end
  165. end
  166. function ac.record_player_position(pname, pos)
  167. local pdata = ac.players[pname]
  168. if not pdata then
  169. ac.players[pname] = {}
  170. pdata = ac.players[pname]
  171. end
  172. -- This is for recording the position of the player when they were last
  173. -- checked by the standard check function. Thus, if the standard check func
  174. -- detects a possible cheat, following sub-checks that get spawned via
  175. -- minetest.after() can refer back to the player's position at the first
  176. -- check.
  177. pdata.last_pos = pos
  178. end
  179. -- This function is the main "brain" of the AC logic (for fly cheaters).
  180. -- It must be as accurate as possible for a given position! Note: second
  181. -- argument is nil unless checking prior path.
  182. function ac.is_flying(pos, data)
  183. -- We assume that the input position is rounded to nearest integer.
  184. local under = vector.add(pos, {x=0, y=-1, z=0})
  185. local node = minetest.get_node(under).name
  186. -- If we're checking prior path, then choose the OLD node-name for the foot
  187. -- node position. This should be the name of the node at that location at the
  188. -- time the record was made. We need to use this OLD name, instead of the most
  189. -- recent map information, because player could have dug their supports out
  190. -- (e.g., ladders or scaffolding)!
  191. if data then
  192. node = data.snode or ""
  193. end
  194. -- If non-air below this position, then player is probably not flying.
  195. if node ~= "air" then return false end
  196. -- Check up to 2 meters below player, and 1 meter all around.
  197. -- Fly cheaters tend to be pretty blatent in their cheating,
  198. -- and I want to avoid logging players who do a lot of jumping.
  199. local minp = {x=pos.x-1, y=pos.y-2, z=pos.z-1}
  200. local maxp = {x=pos.x+1, y=pos.y+0, z=pos.z+1}
  201. local tb = minetest.find_nodes_in_area(minp, maxp, "air")
  202. if #tb >= 27 then
  203. -- If all nodes under player are air, then player is not supported.
  204. -- However, they could be jumping with help of a bouncer.
  205. -- Note: trampolines do not throw player high enough to be a concern.
  206. local p = vector_round(pos)
  207. local z = p.y
  208. local d = p.y - 30
  209. local get_node = minetest.get_node
  210. for y = z, d, -1 do
  211. p.y = y
  212. local n = get_node(p).name
  213. if n:find("^jumping:") then
  214. -- Bouncer underneath. Not flying.
  215. return false
  216. elseif n ~= "air" and n ~= "ignore" then
  217. -- We have found a node that isn't a bouncer.
  218. -- If this node is walkable then we found the ground.
  219. local ndef = minetest.registered_nodes[n] or {}
  220. if ndef.walkable then
  221. -- Found walkable node. Probably the ground.
  222. return true
  223. end
  224. end
  225. end
  226. -- Could most likely be flying.
  227. return true
  228. end
  229. -- Not flying.
  230. return false
  231. end
  232. local is_solid_dt = function(dt)
  233. if dt == "normal" then
  234. return true
  235. elseif dt == "glasslike" then
  236. return true
  237. elseif dt == "glasslike_framed" then
  238. return true
  239. elseif dt == "glasslike_framed_optional" then
  240. return true
  241. elseif dt == "allfaces" then
  242. return true
  243. elseif dt == "allfaces_optional" then
  244. return true
  245. end
  246. end
  247. -- This function is the main "brain" of the AC logic (for noclip cheaters).
  248. -- It must be as accurate as possible for a given position! Note: second
  249. -- argument is nil unless checking prior path.
  250. function ac.is_clipping(pos, data)
  251. -- We assume that the input position is rounded to nearest integer.
  252. local under = vector.add(pos, {x=0, y=-1, z=0})
  253. local above = vector.add(pos, {x=0, y=1, z=0})
  254. local n1 = minetest.get_node(under).name
  255. local n2 = minetest.get_node(pos).name
  256. local n3 = minetest.get_node(above).name
  257. -- If we're checking prior path, then choose the OLD node-name for the middle
  258. -- node position. This should be the name of the node at that location at the
  259. -- time the record was made. We need to use this OLD name, instead of the most
  260. -- recent map information, because player could have placed blocks on top of
  261. -- their trail (e.g., when building tall walls)!
  262. if data then
  263. n2 = data.wnode or ""
  264. end
  265. if n1 ~= "air" and n2 ~= "air" and n3 ~= "air" then
  266. local d1 = minetest.reg_ns_nodes[n1]
  267. local d2 = minetest.reg_ns_nodes[n2]
  268. local d3 = minetest.reg_ns_nodes[n3]
  269. -- One of the nodes is a stairsplus node, or similar.
  270. if not d1 or not d2 or not d3 then
  271. return false
  272. end
  273. -- Check if all three nodes are solid, walkable nodes.
  274. if d1.walkable and d2.walkable and d3.walkable then
  275. local d1d = d1.drawtype
  276. local d2d = d2.drawtype
  277. local d3d = d3.drawtype
  278. if is_solid_dt(d1d) and is_solid_dt(d2d) and is_solid_dt(d3d) then
  279. return true
  280. end
  281. end
  282. end
  283. -- Not clipping.
  284. return false
  285. end
  286. function ac.check_prior_position(pname, data, act)
  287. local pos = vector_round(data.pos)
  288. local time = data.time
  289. local cheat = false
  290. if act == "fly" then
  291. if ac.is_flying(pos, data) then cheat = true end
  292. elseif act == "clip" then
  293. if ac.is_clipping(pos, data) then cheat = true end
  294. end
  295. if cheat then
  296. ac.record_suspicious_act(pname, nil, act) -- Record in current session memory.
  297. ac.report_suspicious_act(pname, pos, act) -- Report to admin (if logged in).
  298. ac.log_suspicious_act(pname, pos, time, act) -- Log to file.
  299. end
  300. end
  301. function ac.check_prior_path(pname, act)
  302. -- Get prior known locations for this player.
  303. -- Locations should be provided in order, with timestamps.
  304. local path = ap.get_position_list(pname)
  305. -- Spread checking of the path out over a few seconds.
  306. for i=1, #path, 1 do
  307. local data = path[i]
  308. -- Get fractional random number between 1 and 10.
  309. local delay = (math_random(1, 300) / 30)
  310. minetest.after(delay, ac.check_prior_position, pname, data, act)
  311. end
  312. end
  313. function ac.confirm_flying(pname, last_pos)
  314. -- Check if player still logged on.
  315. -- This function is designed to be called from minetest.after right after an
  316. -- initial trigger of suspicion, to try and confirm it.
  317. local pref = minetest.get_player_by_name(pname)
  318. if pref then
  319. local pos = vector_round(pref:get_pos())
  320. -- If player is falling at least somewhat quickly, then they aren't flying.
  321. if pos.y < (last_pos.y - 1) then return end
  322. -- If player stopped flying, then it might have been a false-positive.
  323. if not ac.is_flying(pos) then return end
  324. local time = os.time()
  325. local prevtime = ac.get_last_cheat_time(pname)
  326. -- If we reach here then the player is still flying!
  327. ac.record_suspicious_act(pname, time, "fly") -- Record in current session memory.
  328. ac.report_suspicious_act(pname, pos, "fly") -- Report to admin (if logged in).
  329. ac.log_suspicious_act(pname, pos, time, "fly") -- Log to file.
  330. -- Register as confirmed cheater if suspicion for this session exceeds threshold.
  331. local ts = ac.get_suspicion_count(pname)
  332. if ts > ac.cheat_registration_threshold then
  333. if not sheriff.is_cheater(pname) then
  334. sheriff.register_cheater(pname)
  335. end
  336. end
  337. -- Check the player's prior path if we haven't done so recently.
  338. if (time - prevtime) > ap.get_record_time() then
  339. ac.check_prior_path(pname, "fly")
  340. end
  341. end
  342. end
  343. function ac.confirm_clipping(pname, last_pos)
  344. -- Check if player still logged on.
  345. -- This function is designed to be called from minetest.after right after an
  346. -- initial trigger of suspicion, to try and confirm it.
  347. local pref = minetest.get_player_by_name(pname)
  348. if pref then
  349. local pos = vector_round(pref:get_pos())
  350. -- If player stopped clipping, then it might have been a false-positive.
  351. if not ac.is_clipping(pos) then return end
  352. local time = os.time()
  353. local prevtime = ac.get_last_cheat_time(pname)
  354. -- If we reach here then the player is still clipping!
  355. ac.record_suspicious_act(pname, time, "clip") -- Record in current session memory.
  356. ac.report_suspicious_act(pname, pos, "clip") -- Report to admin (if logged in).
  357. ac.log_suspicious_act(pname, pos, time, "clip") -- Log to file.
  358. -- Register as confirmed cheater if suspicion for this session exceeds threshold.
  359. local ts = ac.get_suspicion_count(pname)
  360. if ts > ac.cheat_registration_threshold then
  361. if not sheriff.is_cheater(pname) then
  362. sheriff.register_cheater(pname)
  363. end
  364. end
  365. -- Check the player's prior path if we haven't done so recently.
  366. if (time - prevtime) > ap.get_record_time() then
  367. ac.check_prior_path(pname, "clip")
  368. end
  369. end
  370. end
  371. function ac.do_standard_check(pname, pref)
  372. --minetest.chat_send_player(pname, "# Server: Check player!")
  373. local pos = vector_round(pref:get_pos())
  374. if ac.is_flying(pos) then
  375. -- Check again in a moment.
  376. local delay = math_random(1, 3)
  377. minetest.after(delay, ac.confirm_flying, pname, pos)
  378. end
  379. if ac.is_clipping(pos) then
  380. -- Check again in a moment.
  381. local delay = math_random(1, 3)
  382. minetest.after(delay, ac.confirm_clipping, pname, pos)
  383. end
  384. end
  385. function ac.nearby_player_count(pname, pref)
  386. local p1 = pref:get_pos()
  387. local players = minetest.get_connected_players()
  388. local count = 0
  389. for k, v in ipairs(players) do
  390. if v:get_player_name() ~= pname then
  391. local p2 = v:get_pos()
  392. if vector_distance(p1, p2) < 75 then
  393. count = count + 1
  394. end
  395. end
  396. end
  397. return count
  398. end
  399. function ac.check_player(pname)
  400. -- If this player is already a registered cheater, don't bother checking them
  401. -- for further cheats. Unless such checks ought to trigger immediate punishments
  402. -- when failed, it's probably a waste of resources.
  403. if sheriff.is_cheater(pname) then
  404. return
  405. end
  406. -- Check if player still logged in.
  407. local pref = minetest.get_player_by_name(pname)
  408. if pref then
  409. local pp = pref:get_pos()
  410. -- Don't bother performing checks for dead players.
  411. if pref:get_hp() > 0 then
  412. -- Don't check players attached to entities.
  413. if not default.player_attached[pname] then
  414. local op = ac.get_position_at_last_check_or_nil(pname)
  415. -- Don't bother checking player if they haven't moved.
  416. if not op or vector_distance(pp, op) > 1 then
  417. -- Don't check players in the Outback.
  418. if rc.current_realm_at_pos(pp) ~= "abyss" then
  419. ac.record_player_position(pname, pp)
  420. ac.do_standard_check(pname, pref)
  421. end
  422. end
  423. end
  424. end
  425. -- Check this player again after some delay.
  426. -- Reduce time to next check if player has some suspicion on them.
  427. local delay = math_random(ac.default_delay_min, ac.default_delay_max)
  428. delay = delay - ac.get_suspicion_count(pname)
  429. if city_block:in_city(pp) then
  430. -- Decrease time to next check if the position is within the city.
  431. delay = delay - math_random(ac.city_reduce_min, ac.city_reduce_max)
  432. elseif not city_block:in_no_leecher_zone(pp) then
  433. -- Increase time to next check if the position is in the outlands.
  434. delay = delay + math_random(ac.outland_increase_min, ac.outland_increase_max)
  435. end
  436. -- Increase time between standard checks if many players are logged in.
  437. local players = minetest.get_connected_players()
  438. delay = delay + ((#players) - 1) * 4
  439. -- Increase time to next standard check if player has little recorded
  440. -- suspicion generated from prior sessions. Decrease time to next check if
  441. -- player's average suspicion levels seem to be high.
  442. local total_suspicion = ac.get_total_suspicion(pname)
  443. local clean_sessions = ac.get_clean_sessions(pname)
  444. if clean_sessions < 1 then clean_sessions = 1 end
  445. local avg_suspicion = total_suspicion / clean_sessions
  446. if avg_suspicion < ac.low_average_suspicion then
  447. delay = delay + math_random(ac.low_suspicion_increase_min, ac.low_suspicion_increase_max)
  448. elseif avg_suspicion > ac.high_average_suspicion then
  449. delay = delay - math_random(ac.high_suspicion_reduce_min, ac.high_suspicion_reduce_max)
  450. end
  451. -- Reduce time to next check if player is near others.
  452. local others = ac.nearby_player_count(pname, pref)
  453. if others > 0 then
  454. delay = delay - math_random(0, others * 10)
  455. end
  456. -- Schedule check not less than 1 second future.
  457. if delay < 1 then delay = 1 end
  458. minetest.after(delay, ac.check_player, pname)
  459. end
  460. end
  461. function ac.on_joinplayer(pref)
  462. local pname = pref:get_player_name()
  463. -- Do not perform AC checks for admin player.
  464. if gdac.player_is_admin(pname) then return end
  465. local delay = math_random(ac.initial_delay_min, ac.initial_delay_max)
  466. -- Reduce time to next check if they have some suspicion on them.
  467. delay = delay - ac.get_suspicion_count(pname)
  468. if delay < 1 then delay = 1 end
  469. -- Schedule check.
  470. minetest.after(delay, ac.check_player, pname)
  471. end
  472. function ac.erase_statistics(pname)
  473. local k1 = pname .. ":dirty_sessions"
  474. local k2 = pname .. ":last_session_dirty"
  475. local k3 = pname .. ":total_suspicion"
  476. local k4 = pname .. ":clean_sessions"
  477. ac.storage:set_int(k1, 0)
  478. ac.storage:set_int(k2, 0)
  479. ac.storage:set_int(k3, 0)
  480. ac.storage:set_int(k4, 0)
  481. ac.players[pname] = nil
  482. end
  483. function ac.on_shutdown()
  484. -- On session shutdown (usually nightly) record overall clean/dirty status for
  485. -- registered players.
  486. for pname, pdata in pairs(ac.players) do
  487. if passport.player_registered(pname) then
  488. local suspicion = ac.get_suspicion_count(pname)
  489. if suspicion == 0 then
  490. local k1 = pname .. ":clean_sessions"
  491. local k2 = pname .. ":last_session_dirty"
  492. local cc = ac.storage:get_int(k1)
  493. cc = cc + 1
  494. ac.storage:set_int(k1, cc)
  495. ac.storage:set_int(k2, 0) -- The last session (this one) was clean.
  496. else
  497. local k1 = pname .. ":dirty_sessions"
  498. local k2 = pname .. ":last_session_dirty"
  499. local k3 = pname .. ":total_suspicion"
  500. local dd = ac.storage:get_int(k1)
  501. dd = dd + 1
  502. ac.storage:set_int(k1, dd)
  503. ac.storage:set_int(k2, 1) -- The last session (this one) was dirty.
  504. -- Add suspicion count from this session to the permanent total for this
  505. -- player.
  506. local ts = ac.storage:get_int(k3)
  507. ts = ts + ac.get_suspicion_count(pname)
  508. ac.storage:set_int(k3, ts)
  509. end
  510. end
  511. end
  512. end
  513. function ac.show_path(user, target, origin, distance, duration)
  514. local path = ap.get_position_list(target)
  515. if not path or #path == 0 then
  516. return
  517. end
  518. local lpath = #path
  519. if not duration then
  520. duration = 60
  521. end
  522. for k = 1, lpath, 1 do
  523. local data = path[k]
  524. local pos = vector.offset(data.pos, 0, 0.5, 0)
  525. if origin and distance then
  526. if vector.distance(pos, origin) <= distance then
  527. utility.original_add_particle({
  528. playername = user,
  529. pos = pos,
  530. velocity = {x=0, y=0, z=0},
  531. acceleration = {x=0, y=0, z=0},
  532. expirationtime = duration,
  533. size = 4,
  534. collisiondetection = false,
  535. vertical = false,
  536. texture = "track_marker.png",
  537. glow = 3,
  538. })
  539. end
  540. else
  541. utility.original_add_particle({
  542. playername = user,
  543. pos = pos,
  544. velocity = {x=0, y=0, z=0},
  545. acceleration = {x=0, y=0, z=0},
  546. expirationtime = duration,
  547. size = 4,
  548. collisiondetection = false,
  549. vertical = false,
  550. texture = "track_marker.png",
  551. glow = 14,
  552. })
  553. end
  554. end
  555. end
  556. if not ac.registered then
  557. minetest.register_on_joinplayer(function(...)
  558. ac.on_joinplayer(...)
  559. end)
  560. minetest.register_on_shutdown(function(...)
  561. ac.on_shutdown(...)
  562. end)
  563. minetest.register_chatcommand("show-path", {
  564. params = "<player>",
  565. description = "Show user path.",
  566. privs = {server=true},
  567. func = function(pname, param)
  568. ac.show_path(pname, param)
  569. end,
  570. })
  571. local c = "ac:core"
  572. local f = ac.modpath .. "/init.lua"
  573. reload.register_file(c, f, false)
  574. ac.registered = true
  575. end