From 129e7ba35a322a86edc44dd15f39cee86a81865a Mon Sep 17 00:00:00 2001 From: raymoo Date: Sat, 9 Jan 2016 02:24:01 -0800 Subject: [PATCH] It doesn't crash on startup, at least --- init.lua | 692 +++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 692 insertions(+) create mode 100644 init.lua diff --git a/init.lua b/init.lua new file mode 100644 index 0000000..3f3718d --- /dev/null +++ b/init.lua @@ -0,0 +1,692 @@ +minetest.log("info", "[monoidal_effects] Loading mod") + +local monoidal_effects = {} + +local mod_path = minetest.get_modpath("monoidal_effects") .. "/" +local world_path = minetest.get_worldpath() .. "/" +local save_path = world_path .. "monoidal_effects.mt" +local backup_path = world_path .. "monoidal_effects_bak.mt" + +local effectset = dofile(mod_path .. "effect_set.lua") + +local hud_interval = tonumber(minetest.setting_get("effect_hud_interval")) or 1 +local effect_interval = tonumber(minetest.setting_get("effect_interval")) or 0.5 + +local save_interval = tonumber(minetest.setting_get("effect_save_interval")) or 10 +local backup_interval = + tonumber(minetest.setting_get("effect_backup_interval")) or 6 + +-- The effect database +local effects + +-- Try to load the data +local save_file = io.open(save_path, "rb") + +if (save_file == nil) then + minetest.log("info", "[monoidal_effects] Save not found, generating anew.") + effects = effectset.new_set() +else + minetest.log("info", "[monoidal_effects] Save found. Loading.") + + local contents = save_file:read("*a") + save_file:close() + + effects = effectset.deserialize(contents) + + if (effects == nil) then + error("[monoidal_effects] Save corrupted. Delete or replace with a backup.") + end +end + + +-- Name-indexed table of monoid definitions +local monoids = {} + +-- Child monoids - map from parent monoid names to a list of tables with: +-- +-- name - the child monoid's name +-- convert - the conversion function +local children = {} + +-- Name-indexed table of effect type definitions +local types = {} + +-- Keeps track of temporary effects that affect single players. Is a table +-- mapping effect ids to a table containing: +-- +-- time_started - The last time the effect was brought into activity +-- duration - How long after time_started to wait until canceling +-- players - Affected players +local active_effects = {} + +-- Complements the above, mapping player names to a set of effect IDs that +-- are currently in the above table. Used to efficiently find a player's active +-- effects, so that they can be updated when the player leaves. +local player_effects = {} + +-- Keeps track of HUDs the player needs updated. Is a map from player names +-- to another map from effect ids to a table containing: +-- text - HUD id of the display name text +-- icon - HUD id of the icon (may be nil) +-- offset - Y offset +local huds = {} + +-- For each online player name, is a map from monoid names to the last +-- calculated value. Does not cache the total with the child values +local monoid_cache = {} + + +-- Calculates monoid value, ignoring children +local function calculate_monoid_value(p_name, m_name) + local p_effects = effects:with_index("player", p_name) + + local m_effects = effects:with_index("monoid", m_name) + + local p_m_effects = effectset.set_union(p_effects, m_effects) + + + local fold = monoids[m_name].fold + + local res = fold(p_m_effects) + + local p_cache = monoid_cache[p_name] + + if (p_cache == nil) then + p_cache = {} + monoid_cache[p_name] = p_cache + end + + p_cache[m_name] = res + + return res +end + + +local function clear_cache(m_name, p_name) + local p_cache = monoid_cache[p_name] + if p_cache == nil then return end + + p_cache[m_name] = nil +end + + +-- Gets monoid value, including children +local function get_monoid_value(m_name, p_name) + local children = children[m_name] + + local mon_def = monoids[m_name] + + if mon_def == nil then error("Bad monoid") end + + local combine = mon_def.combine + + local child_tot = mon_def.identity + + if (children ~= nil) then + for i, child in ipairs(children) do + local val = get_monoid_value(child.name, p_name) + local convert = child.convert or function(x) return x end + if val ~= nil then + child_tot = combine(child_tot, convert(val)) + end + end + end + + local cached = monoid_cache[p_name] and monoid_cache[p_name][m_name] + + if (cached == nil) then + return combine(child_tot, calculate_monoid_value(p_name, m_name)) + else + return combine(child_tot, cached) + end +end + + +local function add_active_effect(uid, dur, players) + + active_effects[uid] = { time_started = os.time(), + duration = dur, + players = players + } + + for k, p_name in ipairs(players) do + if player_effects[p_name] == nil then + player_effects[p_name] = {} + end + player_effects[p_name][uid] = true + end +end + + +-- Saves the current remaining duration of an effect +local function save_active_effect(uid) + local effect = active_effect[uid] + + if effect == nil then + minetest.log("error", + "[monoidal_effects] Tried to save an effect not active") + return + end + + local new_dur = os.difftime(effect.dur + effect.time_started, os.time()) + + local effect_record = effects:get(uid) + + if effect_record == nil then + minetest.log("error", + "[monoidal_effects] Tried to save nonexistent effect") + return + end + + effect_record.duration = new_dur +end + + + +-- Suspend an active effect, for when the player logs off. +local function hibernate_active_effect(uid) + + local effect = active_effects[uid] + + if (effect == nil) then + error("Not an active effect") + end + + local new_dur = effect.dur - (os.difftime(os.time(), effect.time_started)) + + local effect_record = effects:get(uid) + + if (effect_record == nil) then + error("Hibernating nonexistent effect") + end + + effect_record.duration = new_dur + + local affected = effect.players + + for p_name in pairs(affected) do + local p_effs = player_effects[p_name] + if p_effs ~= nil then + p_effs[uid] = nil + end + end + + active_effects[uid] = nil +end + + +-- Returns two hud defs. icon is optional +local function mk_hud(uid, disp_name, dur, offset, icon) + local text_def, icon_def + + local color = 0xFFFFFF + + text_def = { hud_elem_type = "text", + position = { x = 1, y = 0.3 }, + name = "effect_" .. uid, + text = disp_name .. " (" .. dur .. "s)", + scale = { x = 170, y = 20 }, + alignment = { x = -1, y = 0 }, + direction = 1, + number = color, + offset = { x = -5, y = offset * 20 }, + } + + if icon ~= nil then + icon_def = { hud_elem_type = "image", + scale = { x = 1, y = 1 }, + position = { x = 1, y = 0.3 }, + name = "effect_icon_" .. uid, + text = icon, + alignment = { x = -1, y = 0 }, + direction = 0, + offset = { x = -186, y = offset * 20 }, + } + end + + return text_def, icon_def +end + + +local function add_hud(player, uid, disp_name, dur, icon) + local p_name = player:get_player_name() + + if (huds[p_name] == nil) then + huds[p_name] = {} + end + + local p_huds = huds[p_name] + + local min_hudpos + local max_hudpos = -1 + local free_hudpos + + for uid, hudinfo in pairs(p_huds) do + local hudpos = hudinfo.offset + if (hudpos > max_hudpos) then + max_hudpos = hudpos + end + if (min_hudpos == nil) then + min_hudpos = hudpos + elseif (hudpos < min_hudpos) then + min_hudpos = hudpos + end + end + + if (min_hudpos == nil) then + free_hudpos = 0 + elseif(min_hudpos >= 0) then + free_hudpos = min_hudpos - 1 + else + free_hudpos = max_hudpos + 1 + end + + if (free_hudpos > 20) then return end + + local text_def, icon_def = mk_hud(uid, disp_name, dur, free_hudpos, icon) + + local text_id = player:hud_add(text_def) + local icon_id + if (icon_def ~= nil) then icon_id = player:hud_add(icon_def) end + + p_huds[uid] = { text = text_id, + icon = icon_id, + offset = free_hudpos, + } + +end + + +local function update_hud(player) + local now = os.time() + local p_name = player:get_player_name() + local p_huds = huds[p_name] + + if (p_huds ~= nil) then + for uid, hudinfo in pairs(p_huds) do + local active_info = active_effects[uid] + local effect = effects:get(uid) + + if (effect ~= nil and active_info ~= nil) then + local effect_type = effect.effect_type + local type_def = types[effect_type] + local text = hudinfo.text + local desc = type_def.disp_name + local time_started = active_info.time_started + local dur = active_info.duration + local time_left = os.difftime(time_started + dur, now) + + local new_text = desc .. " ("..tostring(time_left).." s)" + + player:hud_change(hudinfo.text, "text", new_text) + end + end + end +end + + +monoidal_effects.register_monoid = function(name, def) + local identity = def.identity + + if (identity == nil) then + error("No identity defined") + end + + local fold = def.fold + local combine = def.combine + + if (combine == nil) then + if (fold == nil) then + error("Neither combine nor fold is defined") + else + def.combine = function(v1, v2) + return fold({v1, v2}) + end + end + elseif (fold == nil) then + def.fold = function(elems) + + local result = identity + + for k, v in pairs(elems) do + result = combine(result, v) + end + + return result + end + end + + def.apply = def.apply or function() return end + def.on_change = def.on_change or function() return end + + monoids[name] = def +end + + +monoidal_effects.register_type = function(name, def) + types[name] = def +end + + +monoidal_effects.add_child_monoid = function (parent_name, child_name, convert) + local entry = { name = child_name, + convert = convert + } + + if children[parent_name] == nil then + children[parent_name] = {} + end + + table.insert(children[parent_name], entry) +end + + +monoidal_effects.apply_effect = function(effect_type, dur, player_name, values) + + local type_def = types[effect_type] + + if (type_def == nil) then + error("Tried to apply nonexistent effect type") + end + + local dyn = type_def.dynamic + + local players = {player_name} + + local tags = type_def[tags] + + local monoids = type_def[monoids] + + local record = + effectset.record(dyn, effect_type, players, tags, monoids, dur, values) + + local p_cache = monoid_cache[player_name] + + if (p_cache == nil) then + p_cache = {} + monoid_cache[player_name] = p_cache + end + + local eff_values + + if (dyn) then + eff_values = values + else + eff_values = type_def.values + end + + local player = minetest.get_player_by_name(player_name) + + if (player ~= nil) then + + for monoid in pairs(monoids) do + local existing = get_monoid_value(monoid, player_name) + + local new_val + + if (existing == nil) then + new_val = eff_values[monoid] + else + new_val = type_def.combine(existing, eff_values[monoid]) + end + + p_cache[monoid] = new_val + + local mon_def = monoids[monoid] + + local apply = mon_def.apply + + local on_change = mon_def.on_change + + if (apply ~= nil) then + apply(new_val, player) + end + + if (on_change ~= nil) then + on_change(existing, new_val, player) + end + end + end + + local uid = effects:insert(record) + + if not type_def.hidden then + add_hud(player, uid, type_def.disp_name, dur, type_def.icon) + end + + if (dur ~= "perm") then + add_active_effect(uid, dur, {player_name}) + end + + return uid +end + + +monoidal_effects.cancel_effect = function(uid) + + local all_players = minetest.get_connected_players() + + local player_set = {} + + local effect = effects:get(uid) + local players = effect.players + local monoids = effect.monoids + + for i, player in ipairs(all_players) do + local p_name = player:get_player_name() + if players[p_name] then + player_set[p_name] = player + end + end + + local old_vals = {} + + if (effect == nil) then return end + + for p_name in pairs(player_set) do + old_vals[player] = {} + local p_vals = old_vals[player] + for monoid in pairs(monoids) do + p_vals[monoid] = get_monoid_value(monoid, player) + end + end + + effects:delete(uid) + + for p_name, player in pairs(player_set) do + local p_vals = old_vals[p_name] + for monoid in pairs(monoids) do + clear_cache(monoid, p_name) + local val = get_monoid_value(p_name, monoid) + local mon_def = monoids[monoid] + + if (mon_def == nil) then + minetest.log("error", + "[monoidal_effects] Effect with bad monoid") + else + mon_def.apply(val, player) + mon_def.on_change(p_vals[monoid], val, player) + + local p_huds = huds[p_name] + if p_huds ~= nil then + local hudinfo = p_huds[uid] + if hudinfo ~= nil then + player:remove_hud(hudinfo.text) + if hudinfo.icon ~= nil then + player:remove_hud(hudinfo.icon) + end + end + p_huds[uid] = nil + end + end + end + end + + local active_data = active_effects(uid) + + if active_data ~= nil then + for player, set in pairs(active_data.players) do + player_effects[player][uid] = nil + end + end +end + + +local function cancel_index(index_name) + + return function(index, p_name) + + local indexed_effects = effects:with_index(index_name, index) + + local affected_effects + + if p_name ~= nil then + local p_effects = effects:with_index("player", p_name) + affected_effects = effectset.set_intersect(indexed_effects, p_effects) + else + affected_effects = indexed_effects + end + + for uid in pairs(affected_effects) do + monoidal_effects.cancel_effect(uid) + end + end +end + +monoidal_effects.cancel_monoid = cancel_index("monoid") + +monoidal_effects.cancel_effect_type = cancel_index("name") + +monoidal_effects.cancel_tag = cancel_index("tag") + +monoidal_effects.get_remaining_time = function(uid) + local active = active_effects[uid] + + if (active ~= nil) then + return os.difftime(active.time_started + active.duration, os.time()) + end + + local effect = effects:get(uid) + + if effect == nil then return nil end + + return effect.duration +end + +monoidal_effects.get_player_effects = function(p_name) + + return effects:with_index("player", p_name) +end + +monoidal_effects.get_monoid_value = get_monoid_value + + +local function apply_effects(player) + local p_name = player:get_player_name() + + if (p_name == nil) then return end + + local p_effects = monoidal_effects.get_player_effects(p_name) + + local monoids = {} + + for uid in pairs(p_effects) do + + local effect = effects:get(uid) + + + if (effect ~= nil) then + for monoid in pairs(effect.monoids) do + monoids[monoid] = true + end + end + end + + for monoid in pairs(monoids) do + local mon_def = monoids[monoid] + + if (mon_def ~= nil) then + local val = get_monoid_value(monoid, p_name) + mon_def.apply(val, player) + end + end +end + + +-- Pulls all of a player's active effects out of hibernation +local function defrost_actives(p_name) + local p_effects = effects:with_index("player", p_name) + local perm_effects = effects:with_index("perm", false) + + local to_defrost = effectset.set_intersect(p_effects, perm_effects) + + for uid in pairs(to_defrost) do + local effect = effects:get(uid) + + if effect ~= nil then + add_active_effect(uid, effect.duration, {p_name}) + end + end +end + + +local function frost_actives(p_name) + local p_active = player_effects[p_name] + + if p_active == nil then return end + + for uid in pairs(p_active) do + hibernate_active_effect(uid) + end +end + + +minetest.register_on_joinplayer(function(player) + local p_name = player:get_player_name() + + defrost_actives(p_name) + apply_effects(player) + + local p_effects = effects:with_index("player", p_name) + + for uid in pairs(p_effects) do + local effect = effects:get(uid) + + if effect ~=nil then + local type_def = types[effect.effect_type] + + if type_def ~= nil then + add_hud(player, + uid, + type_def.disp_name, + effect.duration, + type_def.icon) + end + end + end +end) + + +minetest.register_on_leaveplayer(function(player) + local p_name = player:get_player_name() + frost_actives(p_name) + huds[p_name] = nil +end) + +minetest.register_on_respawnplayer(function(player) + apply_effects(player) +end) + +minetest.register_on_dieplayer(function(player) + for uid in pairs(effects:with_index("player", player:get_player_name())) do + local effect = effects:get(uid) + + local type_def = types[effect.effect_type] + + local cancel_on_death = (type_def and type_def.cancel_on_death) + + if (cancel_on_death) then + monoidal_effects.cancel_effect(uid) + end + end +end)