123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543 |
- -- Player modifiers mod for Enyekala.
- -- Author of source code: MustTest/BlueBird51
- -- License of source code: MIT
- if not minetest.global_exists("pova") then pova = {} end
- pova.modpath = minetest.get_modpath("pova")
- pova.players = pova.players or {}
- pova.last_properties = pova.last_properties or {}
- -- Used to force table.sort() to be stable.
- -- This counter increases every time a modifier is added.
- pova.counter = pova.counter or 0
- -- Properties which pova must never modify (via pref:set_properties()).
- local properties_blacklist = {
- eye_height = true,
- physical = true,
- collide_with_objects = true,
- colors = true,
- use_texture_alpha = true,
- spritediv = true,
- initial_sprite_basepos = true,
- automatic_rotate = true,
- automatic_face_movement_dir = true,
- automatic_face_movement_max_rotation_per_sec = true,
- backface_culling = true,
- nametag = true,
- nametag_color = true,
- static_save = true,
- shaded = true,
- }
- -- Properties which pova may modify (via pref:set_properties()).
- local properties_whitelist = {
- hp_max = true,
- breath_max = true,
- zoom_fov = true,
- collisionbox = true,
- selectionbox = true,
- pointable = true,
- visual = true,
- visual_size = true,
- mesh = true,
- textures = true,
- is_visible = true,
- makes_footstep_sound = true,
- stepheight = true,
- glow = true,
- infotext = true,
- damage_texture_modifier = true,
- show_on_minimap = true,
- }
- local function filter_properties(data)
- local o = {}
- for k, v in pairs(data) do
- -- Blacklist takes precedence.
- if not properties_blacklist[k] then
- if properties_whitelist[k] then
- o[k] = v
- end
- end
- end
- return o
- end
- local function get_mode(mode)
- if type(mode) == "string" then
- local o = {
- op = mode,
- priority = 0,
- count = pova.counter,
- time = -1,
- }
- pova.counter = pova.counter + 1
- return o
- elseif type(mode) == "nil" then
- local o = {
- priority = 0,
- count = pova.counter,
- time = -1,
- }
- pova.counter = pova.counter + 1
- return o
- else
- local o = {
- op = mode.op or nil,
- priority = mode.priority or 0,
- count = pova.counter,
- time = mode.time or -1,
- }
- if o.priority < -999 then
- o.priority = -999
- end
- pova.counter = pova.counter + 1
- return o
- end
- end
- -- Get data for player, creating an initial table with default data if needed.
- local function get_player(pref)
- local players = pova.players
- local pname = pref:get_player_name()
- local data = players[pname]
- if not data then
- --minetest.log(dump(pref:get_physics_override()))
- --minetest.log(dump({pref:get_eye_offset()}))
- --minetest.log(dump(pref:get_properties()))
- --minetest.log(dump(pref:get_nametag_attributes()))
- -- Initial (default) modifiers MUST be at index 1 in each stack.
- -- Initial modifiers are all named "".
- players[pname] = {
- -- Physics stack.
- physics = {
- {name="", data=pref:get_physics_override(), mode=get_mode({priority=-1000})},
- },
- eye_offset = {
- {name="", data={pref:get_eye_offset()}, mode=get_mode({priority=-1000})},
- },
- properties = {
- {name="", data=filter_properties(pref:get_properties()), mode=get_mode({priority=-1000})},
- },
- nametag = {
- {name="", data=pref:get_nametag_attributes(), mode=get_mode({priority=-1000})},
- },
- }
- data = players[pname]
- end
- return data
- end
- -- Update the initial (default) entry in the player's named stack (index 1).
- local function set_initial_data(data, stack, newdata)
- local initial = data[stack][1].data
- for k, v in pairs(newdata) do
- initial[k] = v
- end
- end
- local function stable_sort(a, b)
- -- Highest priority entries move to the END of the array.
- -- The higher the priority value, the higher the priority (meaning it
- -- overrrides stuff with lower priority).
- if a.mode.priority < b.mode.priority then
- return true
- end
- -- If the priorities are equal, stable-sort according to counter.
- if a.mode.count < b.mode.count then
- return true
- end
- return false
- end
- local function do_sort(t)
- local v = table.copy(t)
- table.sort(v, stable_sort)
- return v
- end
- local function mult_visual_size(o, n)
- return {x=o.x * n.x, y=o.y * n.y, z=o.z * (n.z or n.x)}
- end
- -- Combine all modifiers in named stack to a single table. Numbers are
- -- multiplied together if meaningful to do so. Boolean flags and other data
- -- simply overwrite, with the data at the top of the player's stack taking
- -- precedence.
- local def_visual_size = {x=1, y=1, z=1}
- local function combine_data(data, stack)
- local o = {}
- if stack == "physics" then
- for k, v in ipairs(do_sort(data.physics)) do
- for i, j in pairs(v.data) do
- if type(j) == "number" then
- o[i] = (o[i] or 1.0) * j
- else
- -- Booleans, etc.
- o[i] = j
- end
- end
- end
- elseif stack == "eye_offset" then
- -- Note: 'eye_offset' is an ARRAY, not a key/value map.
- for k, v in ipairs(do_sort(data.eye_offset)) do
- for i, j in ipairs(v.data) do
- o[i] = j
- end
- end
- elseif stack == "properties" then
- for k, v in ipairs(do_sort(data.properties)) do
- if not v.mode.op then
- for i, j in pairs(v.data) do
- if i == "visual_size" then
- -- Visual size is *always* multiplied.
- o[i] = mult_visual_size(o[i] or def_visual_size, j)
- else
- o[i] = j
- end
- end
- elseif v.mode.op == "add" then
- for i, j in pairs(v.data) do
- if i == "visual_size" then
- -- Visual size is *always* multiplied.
- o[i] = mult_visual_size(o[i] or def_visual_size, j)
- else
- if type(j) == "number" then
- o[i] = (o[i] or 0.0) + j
- else
- o[i] = j
- end
- end
- end
- end
- end
- elseif stack == "nametag" then
- for k, v in ipairs(do_sort(data.nametag)) do
- for i, j in pairs(v.data) do
- o[i] = j
- end
- end
- end
- return o
- end
- local function equals(a, b)
- if a == b then
- return true
- end
- local t1 = type(a)
- local t2 = type(b)
- if t1 ~= t2 then
- return false
- end
- if t1 ~= "table" then
- return false
- end
- local key_set = {}
- for key1, value1 in pairs(a) do
- local value2 = b[key1]
- if value2 == nil or equals(value1, value2) == false then
- return false
- end
- key_set[key1] = true
- end
- -- Check if B contains any keys not found in A.
- for key2, _ in pairs(b) do
- if not key_set[key2] then return false end
- end
- return true
- end
- -- Combine all datums in this player's named stack, and apply them.
- local function update_player_data(pref, stack, data)
- if stack == "physics" then
- pref:set_physics_override(combine_data(data, stack))
- elseif stack == "eye_offset" then
- local v1, v2, v3 = unpack(combine_data(data, stack))
- pref:set_eye_offset(v1, v2, v3)
- elseif stack == "properties" then
- local pname = pref:get_player_name()
- local new_props = filter_properties(combine_data(data, stack))
- local old_props = pova.last_properties[pname] or {}
- local changed_props = {}
- for k, v in pairs(new_props) do
- if not equals(old_props[k], v) then
- changed_props[k] = v
- end
- end
- pref:set_properties(changed_props)
- pova.last_properties[pname] = new_props
- elseif stack == "nametag" then
- pref:set_nametag_attributes(combine_data(data, stack))
- end
- end
- -- Get currently active overrides (combining all modifiers in named stack).
- function pova.get_active_modifier(pref, stack)
- local data = get_player(pref)
- return combine_data(data, stack)
- end
- -- Set default overrides for the named stack. AVOID USING THIS FUNCTION WHEN
- -- POSSIBLE. If you call it, you usually need to call it again with original
- -- data in order to restore overrides to what they were before.
- function pova.set_override(pref, stack, overrides)
- local data = get_player(pref)
- set_initial_data(data, stack, overrides)
- update_player_data(pref, stack, data)
- end
- -- Add modifier to player's named stack. The modifier is added to the top.
- function pova.add_modifier(pref, stack, modifiers, name, mode)
- local data = get_player(pref)
- if name ~= "" and stack ~= "" then
- table.insert(data[stack], {name=name, data=modifiers, mode=get_mode(mode)})
- end
- update_player_data(pref, stack, data)
- end
- -- Set named modifier in the player's stack. The modifier is added if it doesn't
- -- exist, otherwise it is replaced. If the modifier data is COMPLETELY replaced;
- -- existing data is NOT combined with the new data.
- function pova.set_modifier(pref, stack, modifiers, name, mode)
- local data = get_player(pref)
- -- Do not allow setting the default data.
- if name ~= "" and stack ~= "" then
- local replaced = false
- for k, v in ipairs(data[stack]) do
- if v.name == name then
- v.data = modifiers
- replaced = true
- break
- end
- end
- if not replaced then
- table.insert(data[stack], {name=name, data=modifiers, mode=get_mode(mode)})
- end
- end
- update_player_data(pref, stack, data)
- end
- -- This is the same as 'pova.set_modifier()', except that if the modifier
- -- already exists, the new data is MERGED with the existing data, INSTEAD of
- -- totally replacing it. Useful if you want to update a modifier, while
- -- providing only a subset of its original data. However, the modifier will be
- -- created if it doesn't exist, and put at the top of the named stack. This
- -- function also allows you to change the modifer's mode table.
- function pova.update_modifier(pref, stack, modifiers, name, mode)
- local data = get_player(pref)
- -- Do not allow setting the default data.
- if name ~= "" and stack ~= "" then
- local replaced = false
- for k, v in ipairs(data[stack]) do
- if v.name == name then
- -- Merge new data with existing data, overwriting as needed.
- for i, j in pairs(modifiers) do
- v.data[i] = j
- end
- if mode then
- v.mode = get_mode(mode)
- end
- replaced = true
- break
- end
- end
- if not replaced then
- table.insert(data[stack], {name=name, data=modifiers, mode=get_mode(mode)})
- end
- end
- update_player_data(pref, stack, data)
- end
- -- Remove modifier by name from named stack. This undoes the effect of adding
- -- the named modifier. Does nothing if the named modifier does not exist in the
- -- named stack.
- function pova.remove_modifier(pref, stack, name)
- local data = get_player(pref)
- local removed = false
- -- Do not allow removing the initial overrides.
- if name ~= "" and stack ~= "" then
- for k, v in ipairs(data[stack]) do
- if v.name == name then
- table.remove(data[stack], k)
- removed = true
- break
- end
- end
- end
- if removed then
- update_player_data(pref, stack, data)
- end
- end
- -- Shall update modifier timers and remove expired ones.
- function pova.globalstep(dtime)
- local function work(t, i)
- local d = t[i].mode
- if d.time >= 0 then
- d.time = d.time - dtime
- if d.time < 0 then
- return false
- end
- end
- return true
- end
- for pname, data in pairs(pova.players) do
- for stack, array in pairs(data) do
- local _, c = utility.array_remove(array, work)
- if c > 0 then
- local pref = minetest.get_player_by_name(pname)
- update_player_data(pref, stack, data)
- end
- end
- end
- end
- -- Remove all modifiers when player leaves game! If there is a bug (and there
- -- will be bugs) this allows a player to reset all their modifiers to defaults.
- function pova.on_leaveplayer(pref)
- local pname = pref:get_player_name()
- pova.players[pname] = nil
- end
- function pova.dump_modifiers(pname, param)
- local pref = minetest.get_player_by_name(pname)
- if not pref then
- return
- end
- local pref2 = minetest.get_player_by_name(param)
- if pref2 and pref2:is_player() then
- pref = pref2
- end
- local tname = pref:get_player_name()
- minetest.chat_send_player(pname, "# Server: Dumping modifiers of <" .. rename.gpn(tname) .. ">.")
- minetest.chat_send_player(pname, "# Server: " .. ("="):rep(80))
- local function round_numbers(t)
- for k, v in pairs(t) do
- if type(v) == "number" then
- t[k] = tonumber(string.format("%.2f", v))
- elseif type(v) == "table" then
- round_numbers(v)
- end
- end
- end
- local data = get_player(pref)
- local function dump_stack(pname, data, stack)
- minetest.chat_send_player(pname, "# Server: === Dumping \"" .. stack .. "\" ===")
- minetest.chat_send_player(pname, "# Server:")
- local tb = do_sort(data[stack])
- for k, v in ipairs(tb) do
- local t = table.copy(v)
- round_numbers(t)
- local dumps = dump(t)
- dumps = dumps:gsub("\n", " ")
- dumps = dumps:gsub("%s+", " ")
- dumps = dumps:gsub(" = ", "=")
- minetest.chat_send_player(pname, "# Server: (" .. k .. "): " .. dumps)
- end
- minetest.chat_send_player(pname, "# Server:")
- end
- for k, v in pairs(data) do
- dump_stack(pname, data, k)
- end
- end
- if not pova.registered then
- pova.registered = true
- minetest.register_globalstep(function(...)
- return pova.globalstep(...)
- end)
- minetest.register_chatcommand("pova", {
- params = "[<player>]",
- description = "List modifiers of self or player.",
- privs = {server=true},
- func = function(...)
- return pova.dump_modifiers(...)
- end
- })
- minetest.register_on_leaveplayer(function(...)
- return pova.on_leaveplayer(...)
- end)
- -- Register mod reloadable.
- local c = "pova:core"
- local f = pova.modpath .. "/init.lua"
- reload.register_file(c, f, false)
- end
|