init.lua 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481
  1. -- Player modifiers mod for Enyekala.
  2. -- Author of source code: MustTest/BlueBird51
  3. -- License of source code: MIT
  4. if not minetest.global_exists("pova") then pova = {} end
  5. pova.modpath = minetest.get_modpath("pova")
  6. pova.players = pova.players or {}
  7. -- Used to force table.sort() to be stable.
  8. -- This counter increases every time a modifier is added.
  9. pova.counter = pova.counter or 0
  10. -- Properties which pova must never modify (via pref:set_properties()).
  11. local properties_blacklist = {
  12. eye_height = true,
  13. physical = true,
  14. collide_with_objects = true,
  15. colors = true,
  16. use_texture_alpha = true,
  17. spritediv = true,
  18. initial_sprite_basepos = true,
  19. automatic_rotate = true,
  20. automatic_face_movement_dir = true,
  21. automatic_face_movement_max_rotation_per_sec = true,
  22. backface_culling = true,
  23. nametag = true,
  24. nametag_color = true,
  25. static_save = true,
  26. shaded = true,
  27. }
  28. -- Properties which pova may modify (via pref:set_properties()).
  29. local properties_whitelist = {
  30. hp_max = true,
  31. breath_max = true,
  32. zoom_fov = true,
  33. collisionbox = true,
  34. selectionbox = true,
  35. pointable = true,
  36. visual = true,
  37. visual_size = true,
  38. mesh = true,
  39. textures = true,
  40. is_visible = true,
  41. makes_footstep_sound = true,
  42. stepheight = true,
  43. glow = true,
  44. infotext = true,
  45. damage_texture_modifier = true,
  46. show_on_minimap = true,
  47. }
  48. local function filter_properties(data)
  49. local o = {}
  50. for k, v in pairs(data) do
  51. -- Blacklist takes precedence.
  52. if not properties_blacklist[k] then
  53. if properties_whitelist[k] then
  54. o[k] = v
  55. end
  56. end
  57. end
  58. return o
  59. end
  60. local function get_mode(mode)
  61. if type(mode) == "string" then
  62. local o = {
  63. op = mode,
  64. priority = 0,
  65. count = pova.counter,
  66. time = -1,
  67. }
  68. pova.counter = pova.counter + 1
  69. return o
  70. elseif type(mode) == "nil" then
  71. local o = {
  72. priority = 0,
  73. count = pova.counter,
  74. time = -1,
  75. }
  76. pova.counter = pova.counter + 1
  77. return o
  78. else
  79. local o = {
  80. op = mode.op or nil,
  81. priority = mode.priority or 0,
  82. count = pova.counter,
  83. time = mode.time or -1,
  84. }
  85. if o.priority < -999 then
  86. o.priority = -999
  87. end
  88. pova.counter = pova.counter + 1
  89. return o
  90. end
  91. end
  92. -- Get data for player, creating an initial table with default data if needed.
  93. local function get_player(pref)
  94. local players = pova.players
  95. local pname = pref:get_player_name()
  96. local data = players[pname]
  97. if not data then
  98. --minetest.log(dump(pref:get_physics_override()))
  99. --minetest.log(dump({pref:get_eye_offset()}))
  100. --minetest.log(dump(pref:get_properties()))
  101. --minetest.log(dump(pref:get_nametag_attributes()))
  102. -- Initial (default) modifiers MUST be at index 1 in each stack.
  103. -- Initial modifiers are all named "".
  104. players[pname] = {
  105. -- Physics stack.
  106. physics = {
  107. {name="", data=pref:get_physics_override(), mode=get_mode({priority=-1000})},
  108. },
  109. eye_offset = {
  110. {name="", data={pref:get_eye_offset()}, mode=get_mode({priority=-1000})},
  111. },
  112. properties = {
  113. {name="", data=filter_properties(pref:get_properties()), mode=get_mode({priority=-1000})},
  114. },
  115. nametag = {
  116. {name="", data=pref:get_nametag_attributes(), mode=get_mode({priority=-1000})},
  117. },
  118. }
  119. data = players[pname]
  120. end
  121. return data
  122. end
  123. -- Update the initial (default) entry in the player's named stack (index 1).
  124. local function set_initial_data(data, stack, newdata)
  125. local initial = data[stack][1].data
  126. for k, v in pairs(newdata) do
  127. initial[k] = v
  128. end
  129. end
  130. local function stable_sort(a, b)
  131. -- Highest priority entries move to the END of the array.
  132. -- The higher the priority value, the higher the priority (meaning it
  133. -- overrrides stuff with lower priority).
  134. if a.mode.priority < b.mode.priority then
  135. return true
  136. end
  137. -- If the priorities are equal, stable-sort according to counter.
  138. if a.mode.count < b.mode.count then
  139. return true
  140. end
  141. return false
  142. end
  143. local function do_sort(t)
  144. local v = table.copy(t)
  145. table.sort(v, stable_sort)
  146. return v
  147. end
  148. -- Combine all modifiers in named stack to a single table. Numbers are
  149. -- multiplied together if meaningful to do so. Boolean flags and other data
  150. -- simply overwrite, with the data at the top of the player's stack taking
  151. -- precedence.
  152. local function combine_data(data, stack)
  153. local o = {}
  154. if stack == "physics" then
  155. for k, v in ipairs(do_sort(data.physics)) do
  156. for i, j in pairs(v.data) do
  157. if type(j) == "number" then
  158. o[i] = (o[i] or 1.0) * j
  159. else
  160. -- Booleans, etc.
  161. o[i] = j
  162. end
  163. end
  164. end
  165. elseif stack == "eye_offset" then
  166. -- Note: 'eye_offset' is an ARRAY, not a key/value map.
  167. for k, v in ipairs(do_sort(data.eye_offset)) do
  168. for i, j in ipairs(v.data) do
  169. o[i] = j
  170. end
  171. end
  172. elseif stack == "properties" then
  173. for k, v in ipairs(do_sort(data.properties)) do
  174. if not v.mode.op then
  175. for i, j in pairs(v.data) do
  176. o[i] = j
  177. end
  178. elseif v.mode.op == "add" then
  179. for i, j in pairs(v.data) do
  180. if type(j) == "number" then
  181. o[i] = (o[i] or 0.0) + j
  182. else
  183. o[i] = j
  184. end
  185. end
  186. end
  187. end
  188. elseif stack == "nametag" then
  189. for k, v in ipairs(do_sort(data.nametag)) do
  190. for i, j in pairs(v.data) do
  191. o[i] = j
  192. end
  193. end
  194. end
  195. return o
  196. end
  197. -- Combine all datums in this player's named stack, and apply them.
  198. local function update_player_data(pref, stack, data)
  199. if stack == "physics" then
  200. pref:set_physics_override(combine_data(data, stack))
  201. elseif stack == "eye_offset" then
  202. local v1, v2, v3 = unpack(combine_data(data, stack))
  203. pref:set_eye_offset(v1, v2, v3)
  204. elseif stack == "properties" then
  205. pref:set_properties(filter_properties(combine_data(data, stack)))
  206. elseif stack == "nametag" then
  207. pref:set_nametag_attributes(combine_data(data, stack))
  208. end
  209. end
  210. -- Get currently active overrides (combining all modifiers in named stack).
  211. function pova.get_active_modifier(pref, stack)
  212. local data = get_player(pref)
  213. return combine_data(data, stack)
  214. end
  215. -- Set default overrides for the named stack. AVOID USING THIS FUNCTION WHEN
  216. -- POSSIBLE. If you call it, you usually need to call it again with original
  217. -- data in order to restore overrides to what they were before.
  218. function pova.set_override(pref, stack, overrides)
  219. local data = get_player(pref)
  220. set_initial_data(data, stack, overrides)
  221. update_player_data(pref, stack, data)
  222. end
  223. -- Add modifier to player's named stack. The modifier is added to the top.
  224. function pova.add_modifier(pref, stack, modifiers, name, mode)
  225. local data = get_player(pref)
  226. if name ~= "" and stack ~= "" then
  227. table.insert(data[stack], {name=name, data=modifiers, mode=get_mode(mode)})
  228. end
  229. update_player_data(pref, stack, data)
  230. end
  231. -- Set named modifier in the player's stack. The modifier is added if it doesn't
  232. -- exist, otherwise it is replaced. If the modifier data is COMPLETELY replaced;
  233. -- existing data is NOT combined with the new data.
  234. function pova.set_modifier(pref, stack, modifiers, name, mode)
  235. local data = get_player(pref)
  236. -- Do not allow setting the default data.
  237. if name ~= "" and stack ~= "" then
  238. local replaced = false
  239. for k, v in ipairs(data[stack]) do
  240. if v.name == name then
  241. v.data = modifiers
  242. replaced = true
  243. break
  244. end
  245. end
  246. if not replaced then
  247. table.insert(data[stack], {name=name, data=modifiers, mode=get_mode(mode)})
  248. end
  249. end
  250. update_player_data(pref, stack, data)
  251. end
  252. -- This is the same as 'pova.set_modifier()', except that if the modifier
  253. -- already exists, the new data is MERGED with the existing data, INSTEAD of
  254. -- totally replacing it. Useful if you want to update a modifier, while
  255. -- providing only a subset of its original data. However, the modifier will be
  256. -- created if it doesn't exist, and put at the top of the named stack. This
  257. -- function also allows you to change the modifer's mode table.
  258. function pova.update_modifier(pref, stack, modifiers, name, mode)
  259. local data = get_player(pref)
  260. -- Do not allow setting the default data.
  261. if name ~= "" and stack ~= "" then
  262. local replaced = false
  263. for k, v in ipairs(data[stack]) do
  264. if v.name == name then
  265. -- Merge new data with existing data, overwriting as needed.
  266. for i, j in pairs(modifiers) do
  267. v.data[i] = j
  268. end
  269. if mode then
  270. v.mode = get_mode(mode)
  271. end
  272. replaced = true
  273. break
  274. end
  275. end
  276. if not replaced then
  277. table.insert(data[stack], {name=name, data=modifiers, mode=get_mode(mode)})
  278. end
  279. end
  280. update_player_data(pref, stack, data)
  281. end
  282. -- Remove modifier by name from named stack. This undoes the effect of adding
  283. -- the named modifier. Does nothing if the named modifier does not exist in the
  284. -- named stack.
  285. function pova.remove_modifier(pref, stack, name)
  286. local data = get_player(pref)
  287. local removed = false
  288. -- Do not allow removing the initial overrides.
  289. if name ~= "" and stack ~= "" then
  290. for k, v in ipairs(data[stack]) do
  291. if v.name == name then
  292. table.remove(data[stack], k)
  293. removed = true
  294. break
  295. end
  296. end
  297. end
  298. if removed then
  299. update_player_data(pref, stack, data)
  300. end
  301. end
  302. -- Shall update modifier timers and remove expired ones.
  303. function pova.globalstep(dtime)
  304. local function work(t, i)
  305. local d = t[i].mode
  306. if d.time >= 0 then
  307. d.time = d.time - dtime
  308. if d.time < 0 then
  309. return false
  310. end
  311. end
  312. return true
  313. end
  314. for pname, data in pairs(pova.players) do
  315. for stack, array in pairs(data) do
  316. local _, c = utility.array_remove(array, work)
  317. if c > 0 then
  318. local pref = minetest.get_player_by_name(pname)
  319. update_player_data(pref, stack, data)
  320. end
  321. end
  322. end
  323. end
  324. -- Remove all modifiers when player leaves game! If there is a bug (and there
  325. -- will be bugs) this allows a player to reset all their modifiers to defaults.
  326. function pova.on_leaveplayer(pref)
  327. local pname = pref:get_player_name()
  328. pova.players[pname] = nil
  329. end
  330. function pova.dump_modifiers(pname, param)
  331. local pref = minetest.get_player_by_name(pname)
  332. if not pref then
  333. return
  334. end
  335. local pref2 = minetest.get_player_by_name(param)
  336. if pref2 and pref2:is_player() then
  337. pref = pref2
  338. end
  339. local tname = pref:get_player_name()
  340. minetest.chat_send_player(pname, "# Server: Dumping modifiers of <" .. rename.gpn(tname) .. ">.")
  341. minetest.chat_send_player(pname, "# Server: " .. ("="):rep(80))
  342. local function round_numbers(t)
  343. for k, v in pairs(t) do
  344. if type(v) == "number" then
  345. t[k] = tonumber(string.format("%.2f", v))
  346. elseif type(v) == "table" then
  347. round_numbers(v)
  348. end
  349. end
  350. end
  351. local data = get_player(pref)
  352. local function dump_stack(pname, data, stack)
  353. minetest.chat_send_player(pname, "# Server: === Dumping \"" .. stack .. "\" ===")
  354. minetest.chat_send_player(pname, "# Server:")
  355. local tb = do_sort(data[stack])
  356. for k, v in ipairs(tb) do
  357. local t = table.copy(v)
  358. round_numbers(t)
  359. local dumps = dump(t)
  360. dumps = dumps:gsub("\n", " ")
  361. dumps = dumps:gsub("%s+", " ")
  362. dumps = dumps:gsub(" = ", "=")
  363. minetest.chat_send_player(pname, "# Server: (" .. k .. "): " .. dumps)
  364. end
  365. minetest.chat_send_player(pname, "# Server:")
  366. end
  367. for k, v in pairs(data) do
  368. dump_stack(pname, data, k)
  369. end
  370. end
  371. if not pova.registered then
  372. pova.registered = true
  373. minetest.register_globalstep(function(...)
  374. return pova.globalstep(...)
  375. end)
  376. minetest.register_chatcommand("pova", {
  377. params = "[<player>]",
  378. description = "List modifiers of self or player.",
  379. privs = {server=true},
  380. func = function(...)
  381. return pova.dump_modifiers(...)
  382. end
  383. })
  384. minetest.register_on_leaveplayer(function(...)
  385. return pova.on_leaveplayer(...)
  386. end)
  387. -- Register mod reloadable.
  388. local c = "pova:core"
  389. local f = pova.modpath .. "/init.lua"
  390. reload.register_file(c, f, false)
  391. end