diff --git a/game.conf b/game.conf index 6f2a4a1..c27530a 100644 --- a/game.conf +++ b/game.conf @@ -1,2 +1,3 @@ -title = red_glazed_terracotta -description = Segmentation fault (core dumped). \ No newline at end of file +title = Red Glazed Terracotta +description = Segmentation fault (core dumped). +disabled_settings = !enable_damage \ No newline at end of file diff --git a/mods/rgt_hud/init.lua b/mods/rgt_hud/init.lua new file mode 100644 index 0000000..d22e876 --- /dev/null +++ b/mods/rgt_hud/init.lua @@ -0,0 +1,335 @@ +rgt_hud = { + types = {}, + Element = { + animate = function(e, target) + if not e.targets then e.targets = {} end + local time = minetest.get_us_time() + for k, v in pairs(target) do + e.targets[k] = { + ref = { + time = time, + value = e[k] + }, + target = { + time = time +(v.duration or 1) *1000000, + value = v.value + }, + ease_fn = v.ease_fn + } + end + end + } +} +local ns = rgt_hud + +-- Helper function to compute the eased factor (0 to 1) using cubic Bezier interpolation. +-- This solves for the parameter u where the x-component matches the input t, +-- then returns the corresponding y-component. +local function bezier_ease(t, x1, y1, x2, y2) + if t <= 0 then return 0 end + if t >= 1 then return 1 end + + -- Binary search to find u such that bezier_x(u) ≈ t + local low = 0 + local high = 1 + local epsilon = 1e-6 + local iterations = 0 + while (high - low > epsilon) and (iterations < 100) do -- Safeguard max iterations + local mid = (low + high) / 2 + local x = 3 * mid * (1 - mid) ^ 2 * x1 + 3 * mid ^ 2 * (1 - mid) * x2 + mid ^ 3 + if x < t then + low = mid + else + high = mid + end + iterations = iterations + 1 + end + local u = (low + high) / 2 + + -- Compute y at u + local y = 3 * u * (1 - u) ^ 2 * y1 + 3 * u ^ 2 * (1 - u) * y2 + u ^ 3 + return y +end + +-- Main interpolation function. +-- Interpolates between ref and target using weight t (0 to 1), +-- eased by the specified cubic Bezier control points (x1, y1, x2, y2). +local function interpolate(ref, target, t, x1, y1, x2, y2) +-- if type(ref) == "table" then +-- local out = {} +-- for k, v in pairs(ref) do +-- out[k] = interpolate(ref[k], target[k], (el[key] -ref[k]) /(target[k] -ref[k])) +-- end +-- return out +-- end + local eased_t = bezier_ease(t, x1 or 0, y1 or 0, x2 or 1, y2 or 1) + return ref + (target - ref) * eased_t +end + +--[[ + Register a data layout and render delegate for a given HUD type name. + { + name = "", -- The name of the type. + required_fields = {"pos"}, -- A list of the names of fields that must be provided for this element to render properly. + field_types = { -- A mapping of field names to their types. Used to notify the API which fields should be treated as vectors. If a field is just a number, it need not be listed. + pos = "vec2" + }, + defaults = { -- Specifies default values for optional fields. + color = "#ffffff" + }, + add = function(self, m) end, -- Called when the element should be added to `m`'s HUD. + update = function(self, m, changes) end, -- Called when the element should be visually updated. `changes` is a table containing only the fields that have been changed in the current step, in order to facilitate interaction with hud_change. + remove = function(self, m) end, -- Called when the element should be removed from `m`'s HUD. + } + Types are responsible for managing the actual HUD elements. +--]] +function ns.register_hud_type(def) + ns.types[def.name] = setmetatable(def, {__index = ns.Element}) +end + +function ns.validate_type(elem, type) + if not ns.types[elem.type] then + warn("Unknown HUD type `"..type.."` for element `"..elem.name.."`; ignoring.") + return false + end + if ns.types[type].required_fields then + for _, field in ipairs(ns.types[type].required_fields) do + if elem[field] == nil then return false end + end + end + return true +end + +--[[ + { + name = "name", -- The name of the element. If an element with this name already exists, it will be updated. + type = ""|""|"", + + } +--]] +function ns.hud_add(m, def) + if not ns.validate_type(def, def.type) then + return false + end + local type = ns.types[def.type] + if type.defaults then + def = extend(table.copy(type.defaults), def) + end + local el + if m.hud[def.name] then + el = m.hud[def.name] + -- Simply write all modified fields to the existing element. + extend(el, def) + else + el = setmetatable(def, {__index = type}) + m.hud[def.name] = el + el:add(m) + end + return el +end + +function ns.update_poi(m) + for _, x in pairs(m.poi) do + x:remove(m) + end + m.poi = {} + for _, x in ipairs(minetest.find_nodes_in_area(m.pos:offset(-100, -100, -100), m.pos:offset(100,100,100), "group:poi")) do + m.poi[#m.poi +1] = ns.hud_add(m, { + name = "poi:"..x:to_string(), + type = "poi", + world_pos = x, + }) + end +end + +rgt.register_node("poi", { + tiles = {"rgt_oak_planks.png^[colorize:#fff:0.5"}, + groups = {poi = 1} +}) + +local default_ease_fn = {0,0,1,1} +minetest.register_globalstep(function(dtime) + local time = minetest.get_us_time() + for _, m in pairs(rgt.players) do + for k, el in pairs(m.hud) do + if el.remove_after then + el.remove_after = el.remove_after -dtime + if el.remove_after < 0 then + el:remove(m) + m.hud[k] = nil + end + end + + if el.targets and next(el.targets) then + local changes = {} + for key, target in pairs(el.targets) do + local fac = (time -target.ref.time) /(target.target.time -target.ref.time) + local ease_fn = target.ease_fn or default_ease_fn + local value + if el.field_types[key] == "vec2" then + value = { + x = interpolate(target.ref.value.x, target.target.value.x, fac, ease_fn[1], ease_fn[2], ease_fn[3], ease_fn[4]), + y = interpolate(target.ref.value.y, target.target.value.y, fac, ease_fn[1], ease_fn[2], ease_fn[3], ease_fn[4]) + } + if fac >= 1 then --value.x == target.target.value.x and value.y == target.target.value.y then + el.targets[key] = nil + end + elseif el.field_types[key] == "color" then + value = { + r = interpolate(target.ref.value.r, target.target.value.r, fac, ease_fn[1], ease_fn[2], ease_fn[3], ease_fn[4]), + g = interpolate(target.ref.value.g, target.target.value.g, fac, ease_fn[1], ease_fn[2], ease_fn[3], ease_fn[4]), + b = interpolate(target.ref.value.b, target.target.value.b, fac, ease_fn[1], ease_fn[2], ease_fn[3], ease_fn[4]), + a = interpolate(target.ref.value.a, target.target.value.a, fac, ease_fn[1], ease_fn[2], ease_fn[3], ease_fn[4]), + } + if value.r == target.target.value.r and value.g == target.target.value.g and value.b == target.target.value.b and value.a == target.target.value.a then + el.targets[key] = nil + end + else + value = interpolate(target.ref.value, target.target.value, fac, ease_fn[1], ease_fn[2], ease_fn[3], ease_fn[4]) + if value == target.target.value then + el.targets[key] = nil + end + end + el[key] = value + -- We could just set this to true, but since we already + -- have a new table, we might as well use it. + changes[key] = value + end + el:update(m, changes) + end + end + + for k, el in ipairs(m.poi) do + if m.dir:distance(m.pos:direction(el.world_pos)) < 0.05 then + el.focused = true + el:animate { + scale = { + value = {x=2,y=2}, + duration = 0.2, +-- ease_fn = {0.42, 0, 1, 1} + } + } + elseif el.focused then + el.focused = false + el:animate { + scale = { + value = {x=1,y=1}, + duration = 0.2, +-- ease_fn = {0.42, 0, 1, 1} + } + } + end + end + end +end) + +function ns.color_to_number(color) + return tonumber(string.format("0x%.2x%.2x%.2x%.2x", color.r, color.g, color.b, color.a)) +end + +-- Pre-register some basic HUD types + +ns.register_hud_type { + name = "text", + required_fields = {"pos", "text"}, + field_types = { + offset = "vec2", + pos = "vec2", + color = "color" + }, + defaults = { + dir = 0, + align = {x=0, y=0}, + offset = {x=0, y=0}, + color = {r = 0xff, g = 0xff, b = 0xff, a = 0xff} + }, + add = function(e, m) + e._id = m.object:hud_add { + type = "text", + position = e.pos, + direction = e.dir, + alignment = e.align, + offset = e.offset, + -- What does this even do? + scale = {x=100, y=100}, + text = e.text, + number = ns.color_to_number(e.color) + } + end, + update = function(e, m, changes) + for k, v in pairs(changes) do + if k == "color" then + k = "number" + v = ns.color_to_number(v) + elseif k == "dir" then + k = "direction" + elseif k == "align" then + k = "alignment" + end + m.object:hud_change(e._id, k, v) + end + end, + remove = function(e, m) + m.object:hud_remove(e._id) + e._id = nil + end +} + +ns.register_hud_type { + name = "poi", + required_fields = {"world_pos"}, + field_types = { + scale = "vec2" + }, + defaults = { + scale = {x=1,y=1} + }, + add = function(e, m) + e._id = m.object:hud_add { + type = "image_waypoint", + scale = e.scale, + world_pos = e.world_pos, + text = "rgt_acacia_planks.png" + } + end, + update = function(e, m, changes) + for k, v in pairs(changes) do + if k == "image" then + k = "text" + end + m.object:hud_change(e._id, k, v) + end + end, + remove = function(e, m) + m.object:hud_remove(e._id) + end +} + +minetest.register_chatcommand("hudtest", { + func = function(name) + ns.hud_add(rgt.players[name], { + name = "test_toast", + type = "text", + pos = {x=0.5, y=0.9}, + offset = {x=0, y=9}, + text = "Hello", + remove_after = 5 + }):animate { + offset = { + value = {x = 0, y = -100}, + duration = 1, + ease_fn = {0.42, 0, 0.58, 1} + }, + color = { + value = minetest.colorspec_to_table("#33aaff00"), + duration = 2 + } + } + end +}) + +minetest.register_chatcommand("poi", { + func = function(name) + ns.update_poi(rgt.players[name]) + end +}) diff --git a/mods/rgt_hud/mod.conf b/mods/rgt_hud/mod.conf new file mode 100644 index 0000000..1a71bef --- /dev/null +++ b/mods/rgt_hud/mod.conf @@ -0,0 +1,2 @@ +name = rgt_hud +depends = rgt_player \ No newline at end of file diff --git a/mods/rgt_player/init.lua b/mods/rgt_player/init.lua index a1f3054..232da71 100644 --- a/mods/rgt_player/init.lua +++ b/mods/rgt_player/init.lua @@ -40,6 +40,9 @@ Player = { e.eye_height = 1.6 + e.hud = {} + e.poi = {} + e:update_hp(p:get_hp()) return e @@ -76,7 +79,7 @@ Player = { end local pitch = p:get_look_vertical() local yaw = p:get_look_horizontal() - local dir = p:get_look_dir() + m.dir = p:get_look_dir() local pos = p:get_pos() pos.y = pos.y +m.eye_height @@ -223,7 +226,7 @@ Player = { m.pointed_node = nil local pointed_found = false - for pointed in minetest.raycast(pos, pos +(dir *7)) do -- TODO: Automatic range + for pointed in minetest.raycast(pos, pos +(m.dir *7)) do -- TODO: Automatic range if pointed and pointed.type == "object" then local e = pointed.ref:get_luaentity() if e then diff --git a/mods/rgt_ui/init.lua b/mods/rgt_ui/init.lua new file mode 100644 index 0000000..eeffd06 --- /dev/null +++ b/mods/rgt_ui/init.lua @@ -0,0 +1,46 @@ +-- The purpose of this module is to abstract away element theming in formspecs. +-- While formspecs themselves are a good UI format, custom theming can become +-- troublesome, especially when it needs to be added into every single formspec. +-- This module also abstracts away the construction of some more complex widgets. + +ui = {} +local ns = ui + +function ns.headers(w, h) + return "formspec_version[10]\ + size["..w..","..h.."]\ + " +end + +function ns.container(x --[[Float]], y --[[Float]], body --[[String]]) --> String + return string.format("container[%f,%f]\ + %s\ + container_end[]\ + ", x, y, body) +end + +-- Note: This automatically creates the container's associated scrollbar, just in case scrollbar theming is ever added. +function ns.scroll_container(x, y, width, height, name, orientation, factor, padding, body) + return string.format("scroll_container[%f,%f;%f,%f;%s;%s;%s;%s]\ + %s\ + scroll_container_end[]\ + %s\ + ", x, y, width, height, name or "", orientation or "", factor and tostring(factor) or "", padding or "", ns.scrollbar(x +width -0.25, y, 0.25, height, name, orientation)) +end + +function ns.scrollbar(x, y, width, height, name, value, main, max, smallstep, largestep, thumbsize, arrows) + local out = string.format("scrollbar[%f,%f;%f,%f;%s;%s]", x, y, width, height, name, value and tostring(value) or "") + return out +end + +function ns.label(x --[[FLoat]], y --[[Float]], text --[[String]]) --> String + return string.format("label[%f,%f;%s]\n", x, y, text) +end + +function ns.button(x --[[Float]], y --[[Float]], width --[[Float]], height --[[Float]], name --[[String?]], label --[[String?]]) --> String + return string.format("button[%f,%f;%f,%f;%s;%s]\n", x, y, name or "", label or "") +end + +local function show_test_view() + ui.begin() +end diff --git a/mods/rgt_ui/mod.conf b/mods/rgt_ui/mod.conf new file mode 100644 index 0000000..438f20d --- /dev/null +++ b/mods/rgt_ui/mod.conf @@ -0,0 +1,2 @@ +name = rgt_ui +depends = rgt_base \ No newline at end of file diff --git a/mods/rgt_world/textures/rgt_grass_side.png b/mods/rgt_world/textures/rgt_grass_side.png index 5b9ccd1..0d3ff89 100644 Binary files a/mods/rgt_world/textures/rgt_grass_side.png and b/mods/rgt_world/textures/rgt_grass_side.png differ