From 6439f11c2f4400e4f1e5212b13dcce173329b2d4 Mon Sep 17 00:00:00 2001 From: Signal Date: Sun, 9 Nov 2025 23:35:26 -0500 Subject: [PATCH] Interpolated HUD and radial menu UI. --- mods/artifact_base/init.lua | 24 ++ mods/artifact_hud/init.lua | 274 ++++++++++++++++++ .../textures/artifact_construct_test_icon.png | Bin 0 -> 13188 bytes .../textures/artifact_radial_cursor.png | Bin 0 -> 162 bytes mods/artifact_hud/textures/crosshair.png | Bin 0 -> 118 bytes mods/artifact_player/init.lua | 140 +++++++-- mods/artifact_player/radial_menu.lua | 48 +++ 7 files changed, 456 insertions(+), 30 deletions(-) create mode 100644 mods/artifact_hud/textures/artifact_construct_test_icon.png create mode 100644 mods/artifact_hud/textures/artifact_radial_cursor.png create mode 100644 mods/artifact_hud/textures/crosshair.png create mode 100644 mods/artifact_player/radial_menu.lua diff --git a/mods/artifact_base/init.lua b/mods/artifact_base/init.lua index 1844dab..b83fde1 100644 --- a/mods/artifact_base/init.lua +++ b/mods/artifact_base/init.lua @@ -22,6 +22,30 @@ end say = minetest.chat_send_all +function extend(dst, src) + for k, v in pairs(src) do + dst[k] = v + end + return dst +end + +-- Some kind of promise API. (Forget about async-await, though.) +function Promise(fn) + local p = {resolved = false} + p.resolve = function(...) + if p.resolved then return end + p.resolved = true + if p.after then p.after(...) end + end + fn(p.resolve) + return { + after = function(fn) + p.after = fn + end + } +end + + minetest.register_lbm{ name = ":artifact:on_load", nodenames = {"group:call_on_load"}, diff --git a/mods/artifact_hud/init.lua b/mods/artifact_hud/init.lua index e69de29..8049304 100644 --- a/mods/artifact_hud/init.lua +++ b/mods/artifact_hud/init.lua @@ -0,0 +1,274 @@ + +local ns = artifact + +ns.hud_types = {} +ns.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 function bezier_ease(t, x1, y1, x2, y2) + if t <= 0 then return 0 end + if t >= 1 then return 1 end + + local low = 0 + local high = 1 + local epsilon = 1e-6 + local iterations = 0 + while (high - low > epsilon) and (iterations < 100) do + 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 + + local y = 3 * u * (1 - u) ^ 2 * y1 + 3 * u ^ 2 * (1 - u) * y2 + u ^ 3 + return y +end + +local function interpolate(ref, target, t, x1, y1, x2, y2) + 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 + + +function ns.register_hud_type(def) + ns.hud_types[def.name] = setmetatable(def, {__index = ns.Element}) +end + +function ns.validate_type(elem, type) + if not ns.hud_types[elem.type] then + warn("Unknown HUD type `"..type.."` for element `"..elem.name.."`; ignoring.") + return false + end + if ns.hud_types[type].required_fields then + for _, field in ipairs(ns.hud_types[type].required_fields) do + if elem[field] == nil then return false end + end + end + return true +end + +function ns.hud_add(m, def) + if not ns.validate_type(def, def.type) then + return false + end + local type = ns.hud_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 + +local default_ease_fn = {0,0,1,1} +minetest.register_globalstep(function(dtime) + local time = minetest.get_us_time() + for _, m in pairs(artifact.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 + 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, + } + } + elseif el.focused then + el.focused = false + el:animate { + scale = { + value = {x=1,y=1}, + duration = 0.2, + } + } + 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 + +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, + 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 = "image", + required_fields = {"pos", "image"}, + field_types = { + offset = "vec2", + scale = "vec2", + pos = "vec2" + }, + defaults = { + dir = 0, + align = {x=0, y=0}, + offset = {x=0, y=0}, + scale = {x=1, y=1}, + opacity = 256 + }, + add = function(e, m) + e._id = m.object:hud_add { + type = "image", + position = e.pos, + direction = e.dir, + alignment = e.align, + offset = e.offset, + scale = e.scale, + text = e.image..string.format("^[opacity:%i", e.opacity) + } + end, + update = function(e, m, changes) + for k, v in pairs(changes) do + if k == "align" then + k = "alignment" + elseif k == "pos" then + k = "position" + elseif k == "opacity" then + k = "text" + v = e.image..string.format("^[opacity:%i", e.opacity) + end + m.object:hud_change(e._id, k, v) + end + end, + remove = function(e, m) + m.hud[e.name] = nil + m.object:hud_remove(e._id) + e._id = nil + end +} diff --git a/mods/artifact_hud/textures/artifact_construct_test_icon.png b/mods/artifact_hud/textures/artifact_construct_test_icon.png new file mode 100644 index 0000000000000000000000000000000000000000..9f44b88811760ac0c752e76d2ace4a632593db0e GIT binary patch literal 13188 zcmbumiCa?J8#attQpeQFQgh6#>>Ov!0VnF%(L9z^=8Pk$DUP5iibIx~IhE#&Qdy~q zbL2cLs3=q_&NE1fv!a62$N7El^?rZA$Hm&u;$Hh&d$IOj&$R9xf8X3h;1BUX004l1 z>7CmTxiad%a*~gG>@aj8afOzfjj8*+dw{>Vbun(a!!11jS03XL2b}mn$VncF|64!9 z#Q?8ERvWqEl%KIN^1t8z`;7hdjm)i_^1oxt1^LhYS9pF-M(lIJn}Pq>1R8sKy8%=z zk!}d2il?ieTVNpUi5o)Ywi^;2=;yEE+7~lpc_D4Rb5j>O-)4=bV*%RSN(>ry7ql3s15g=Gyk3Q$kWFS zpsK3-f4*u<9oO3b8{q%Of^hj?qgva83zxZ|z<;0-7elxJ{$KIrX!SR@f&ae-3z(a) z8{mIOx~xM67Z&~xe#jk^8{+@FjIyeh>7%_s0N@h9^!BYsAv~Y*>??viEI$uJ?nE8G zbK~U`i_}YJ-k!V-8c&Kl_vvMawTtJN{2Y403yEl!r8YO;mAxYnFwk`m()7S~k0L)588> zFcpqo*)=!r9$Aq*N(;>el(OcpC~DH+#mKWAUou6cTv_Uw&AP6P-kKGCsoX!0C;NFj zHlrlOJtAU@3w$@azGe?-#|>#~SNGWOo19;D!=rUDyax-FFdqcoU2iN&FIeCppV%#T0Wfv%WNE z;D$yyL})8xx9!4Vp*%dr z#Jda+%aysR6?Vw1Uom~EUCdv`V%>;T#pC}(CnQF}kx{R?N_$IayEGhUGqy#4UgsMh zZ=bfQF7KqUv+FGq8)L^`86V{2XdXyVgCxHRD7!i51@Avw^4u?cRN*hQqY|Wyeu(H3 z{kFpMu*83B<@%D5+qCVhd|1Tnr7tBk zL_SQ+itb_BY-D{`{FV=8yhaw48gGb_jrhYnl~hXTaQ=o>+_#E`637Y?z3GFAGb^v>QKN7B(G%mGJ+u3bE z(v(uZJ~fZ$Gd5uEH>ph>w>(BznEe7Hhr;SMx-}Aq`>k8AP+if+qLT$CDGRr%tb8_U z=W+v59;I-|fuho3RplyqjJGSp=>}kyg;`{FM;qVk&DWVo1ec%EAZIak5;WEB^mNYaxQOr5@p3G`+Y3^N?z#%hY*f$wbEQ2ie5@mM0W(5>zAH;)zzW3J_O|z zAwR0b-qSxIXVW_?IK|mEa~uP3KF%8_q-F&T8R`2|46@kUCcEm25y8Iy96>D-Sh#-F z76Y!Cv(BXSOvTvpri_e9t4vh#Jo0T!Ym?;ImK%@q|8=xxCvAb+-kShBullQg|HV4f zDz$5%6{k53DmF{j0eQXbIXi5=1Gu8}b_518f1Cj>redg}xSWNYrXn4P!jh$1OP%8I z4m0#V_W8&O{ewkh1RBiXKOMK0zDIjpPkaC52HqS4S7qjC*t!K za*4W5`<(^DvOUq>W#v(68SsKWkN!mYh1(Ub8hJ50_%3#SFZlhE4%t2vdDBE-)9m1v zY)!>;WR#<@ey`9Cp#QEaS?J47!yktp7GCP|u{rTfd%58eUGIg5@K}5B{3ufL*y*pM z6?=c?G~Y9y(=*+3E)_PrcTkm5w4Xq#(y!lNPp)xy|9v$%Ibv*M*Z@$w`5Y|K^Ft`i zaVRXJb@pXbn-2IUf47Ly%cY-Q3eu~q-n()4~Q-Dz4y%y6icWYj&|Z2#H# zVL!kCLYCfrqIzhsX~&V#J6=@&jZ^R-%;pB)m+}*eNBKYZ{6~W`4{UDjq>ghEGWrvEpJ^aw1kD+}35K`fG|JP5H&Xt>h~`V^dY=Tcny$g>JeU7T z>CDWB@m@za8X=|qrQ#FreT{8qQ&&fP=#G9ZI17FSr)dmw{0l`FUcE3<`RM(X1kZlZ zDJ4_}^i(A-eOj@`ucfdeGxK1~u6EGw&s{D}Hr8#A3;G_}=IWxrhI2-US$Z%4*@ zj>y4(q$jMtHj*^BOHp-vu^t{}>$7d{)VA~n6^`=E3$|6306!vyy*jFVHQaP&A0xEx z*^&Rg#rBx>RK4vfUn|SYx!u*uNU7)JRxhh*9-`Y{|8@96?faaOW?s5v)`UJ7#0*Md zj{635S%mSMnR7PqZ|3=a?)?#I-Jh~5v&HaoDD3+UyJ)B_XXAR1jXxX#-At7o1x#F4 zgTc>dtcU7qgpPE6XlFObUhPT?JWPq835rIMx%uLcFVqXGm zvU{svHfr@GdcsP1Uk&fyIA68Cso`5@6hQq^cWvnn74viwfSCKkwM*&Dz^mX~@$~sz zt;TV#^{n>l@uNQq*`nR|SiYkuB*hG1otEd-7p-!k{4cY&s2ziw$A92mH8=Gk? zA356t&U%8Vhd9W1Ju94^|HK^*O?mhAWV@)ySpqKdd3l~~GZm1~eHK)tf*fYaQ{5kb)#8d^SA6jIfFr*j~1u&VI@C|1B371dy=0x@vimloA;K5wVX{3JF}#_w(b z3dce5c-!X`)wm#2s8jzd?wnLjSS>V0?E(Ah`rbJ1#1Bat7TMNcs{JY4d^l$0TlJ?q8SBc zPYFyL0@cMr8}wTRy?qRQ@*CTS9uw?be|S3+BpEr!>2-e(slr?hMs-ojS0G0e!UXL7Vp<#2VotmN?9^tsaS8VVu9W@{G)Wp`% zX*Z#YJ4=hoY~K0C9Ky`~n*7&6mrXMjefvWyvGyZEgX+!cu;37{=(E+o3Z*aj$ z7c*GzB&%ZOLt4~yLcfL0VWl7ae59l~&4rbjBWx{}Trm9Xxtjce^mC7hGfbZR1ThgD zAylxU)0ugu)iSnlvEm~CiFpNcL<2I{y=cNdl&YF|^>`ZRO1G{sUla_L(h<`fRrw(EV^GPu$CinweK zpScR2|0ME67dNo*%HBCytVLx*9h()gs3_(^gM zXhJfCLm0~!CaV9PfQ&x6&mTG6E$}h7TMc70tow9AdZ_~_C`|c6|81%Nbm!w)gsl7H zyGAB+J!WJPYiNQM^{j?&KKh^{(-FPi1*v0}H7J+XtLUvZZZ>~L96jU@o)V;X*yM(9 zRVr4?Mw}zo)5a(mEl_jO*o^+WeS5Du>1IkMexqWCe(P97YUc&DsV{2fu_90>1yLn} zX$ZoCA3Wdm^Su?-Dqj>+l5Pgx%%+^yWrwZQ#Zj)671Lww4!>qdz%O>7TyitsUdV(A zyue@&Gm@hXQu4g$47YU8)fL=8wO z%S}a*EUW+~J2bEEHLoruIPK4A?6iR8f8tkLm}QfGN85hWd$}#_+{_EBId;B9IY+j| zl_oc3l;Y~=Y+|jZ#Zg?LJ1NuCUFeIbZbpXU+-v5~T>ACGr^@C~eS7_C_rbmz?bA?P z6y>M9to%AHdpa2OlSez#N@~-yKb`tXGao^e3Qm-G1m4sDBe!Ez(6iYgHZ4JdNBZM&8{sTRgL_iFesI^OTkpsfxrFs);!Ex#UG6+?zRG zXA(Ta@@k*%d0DD8Z{mBAW)QqTSbx)B zY_a=-92q+3$XSa6?^ntk?GjtgE353MVfSeojU)0&l<*Z~19ui$KznImcACfcG65Rl zzncyDvS5tI{|c1p19{e{`WtVkn>X$iR7n5wOB!)4FP+fqEW#$S{<n8?_yN-?a^Xix#8&nu z1?xV(l01a_WtVM-hwzG zR8Jcal2~tha2h+L+$dqT$+5lXI&b{?vfW8Fg6um4lkYo01y@4k~9oT&ZtY8TR| z_Wi5X558K3N@#-Hxiwpa7@YoIR;J`L`+IHWKKH*%!Rbk5f0$nOEI`z4={-Fel@` zgOy3hekbH~qgIhUbAKlC{IW4aG1C19lDvBw#%wEcdu(ptzqy-;yXwn^^M{%s`br@8 z*n3KT887a`41llsuge@kWARObl1tOsEnky=SP!a&VkZ5h7|Q|5n=jHfEBSEdSBUIe zJ}0lD+=JUrh)&o@v}4~=R-T5>7G<-}GpW)u$pc%ocGB6gVJly6K+o?WCMfFixvw!P z$f&CQN&<&=JL2uOn5r5ah?>9EY$X7%G)or6v|MPMTWMHrUZbl-vPZQWX9bqZ3W@Bb zr@3oC=PwB@u-kSbR(Ce^B{4GRQ5cymJD8%%?V1~Jkl$60!BcayLR{(7HYC>eBV~~D zyksk_&#FxaSTvynybmC?z7lYrmt{ZfOvHPAUTLR?Mo2Ogq=~30mP$w)KNTK&1L|2? zZ~yd%^lzLg7(RL?f9-`l2DJo}X$adJpC)t489+;x-pQEq zU;Xz|*=0dt>rAN~p1wabG~tck;mstye+Ta@Z+U1s z-8HP^c%O_s7>+bpkx?8Ga69@Mx^6Dnf%>FL#2^Bb*~u0#!D)0IkOA=TrUUGSc#bQZ zDta3nf1Sct95mCBE5PdSF)MMBjClM^CWJ=m7$(&-z(?%)6)AE5ai~!KX`Sk`2FCTo zy5!%KubmtF@3sLuj=_^bP7mvTyTMj7nQxlM3P;Oo)<)OLh}z(4U`=#p`}63~V^o1l zn;L4l^n}Y9czL$ja&5T%>KE4bGTdozOd}<9uz)I|AdumFkp4vW2{o76_9V5F)>yJ<5j(uLiv!=loInQIQrN^<>1F|+%;yX&<$Lh zN0IGHjpfS4nh6A{_Vdwm@b2?T?eGEZJX9;Mu}*EVK~zW<>(!P&{YihxIOEzBo9d!L zG+yiTFhuU`wH)m|f4C#BTqZo^(cTmDaqRhsbl;~gZS#6D4rf8QBe->bGF&y)`IxJy zW}$(S%IcNyy(^5G2xiSwr`>i@L5_#xqzXaTx)fiW3tI2Q{n(yZu9H`2b83R<0J{#+mI9^0C|kA<}yuU)%&J*i>1X(j0S zA%>yvv6D00=-yY*9Ee!*=a08zcnUG#()<~Xv|qXM8A9&O%xC3JCwp99p60(WUa&{Z z_Ggrs#zd4`Sm>jIuGQ`(ukI#KYDdg#uS{)Z)ZidXl!FP%a`UW2vkN-ePfEAhV#wcc zyR{cvYO#^3;9FXmF&XY~Y$al`Fv3gUW18hKjW3~GewqnI^sI20H+xp1Gn&Mu9TnWM@&>46jUW7^}posbPPE z6x^~;YH9R6i5}b8Dg=iIPw%bN%-3`5oSe5XVNYZ_I2gU1k6BF*BJON`lsE7sx_M1j zh`VhC`}zW|a2lb*OEHl@CsM~zi=#xFd$>B*8Nkd>{wp2@ovGM+TRja?YE^utrLrj+ z@NK@OQ#y+GI|4E~Uc{ZK^J3Xx?7}|#MObBu$$yee)={i%FqzkD+0+YG3IA;t%qph6 zoUI-dHIMVolQ<15vm>`P8}MZ+$gAyAuc)XU;2=jcX8@p>)Y|lFo*+)o1ycNcg2&{~ zRp;REb?Ed2`NNBp5W-6N;bQsImMycE8|1T#8qWf3e#yUDgl9?vnZMsZpGYJ^^He0p zbEo$gDx)eJA{Q;Nx>KyqL5&HCI-b65!1lxOkdvLyb2h3{G!fT@E!uTKgpob+&5HA; z$+b3qslu;wC7FnGuKeZg2WcmP(>79b6KA-$HUfsWg5(Q4bEvF&Rh`1JxuHHB)}21T zG9}!Io}JaTW`6YrV2}10+<>wV?s2fJ1Y|ZY%bW1ahr>I=u$90&Y7#a@S(5&-I?W#t z>L&TJz!pXl{mf=rpz_?HdUx*p{)}aL=+H_PNm(L{x8>_BnSW4(cgpF+;Kkgjtx^_kxAVG=^%2V=bObaFdRN`|mUMqZFP;M9jFf%{Ps0 z>;2T%&=ODCTx-OUs>7Hmi^=-PCj%)D@Z`Fe|(h|LC{8(1cBY zCC?r5-}{pOoNis@9}@N+^POE4546RLeVT$i`-_bPu^Iu|$9=DcO{~P72rJ4aq7bZ_ zV+|aMXs+);4Ni!Ss~Q7P4Wc%O9u{J?4ziyvIEeZzgqE&n$q0e22Yys}nqvt`Fvi{` z&Je1kkF|bVc*nPCDs4y%=97O_j;1RJ3G$;xucwUHplnT+tFZ@5QceOMFoX`#u+Ua< zK{`$VAhm{3shsegrK^^c`=UXRaK-1GsW5}%OMmW}nA#yhJQaxpnSKV}gw#OUK;g_x zl=W>KamE!RQ=vWMJEu3zCbO#EG_}oh{nj6GO~)J1Uiu9T<*7Q%bzSh@kMX+s24GRj z^BrPzdn$h%58;l1+Ik`owefDd?{CrJmhulEPqjO9B00zp{)lcp0aBwu{bwC0auIGp zth+j`Z_Pgio~e4%@%Q3h`(83dw1v3<9faWa28V|_L)(3(#xUi~6DBexegXm1S&EJW zafBPfJ6E=!LD;->^Ii{CW*4h5Yo6pQP1Tbs+;5ZuE#?Tc0d2A&t;ri#y)h|sI4kq_ z_`yEqXn$Al7J3-|`5(hL$*|YLW?(h{uq!5~%R+54Ll|G=ru9Q)yb@A`h5t~}dKQDr zigDe*+h&GROXU5mYG{NOze)(T#6>VE-`c5+8nITV-KsbB22@d+1zpunXdA5f-27cV zq6OH9nXBWH}X_WkQZ`gsw}M%oYX`Y~bex zf~7mXa>lEu&hYYT-w`kGAy%XA%&sCLi{Hrm_IqYa;=Uj9!b^v8n46V_`lUO+JlJK=zHtU~A&TK)&Y=ok$%u9VF zLT_3JwQJxQ0<->|kRiEYZ*VS-@hhCh5+xZ@UD{^sd8shfIDLGDqdApNn@S2rY~YBm zO4335xK+^RNSV>yQme?O(@R~Qf(i9kS8w-Rl=q8j(~Aq?m_KlWtFX?AGOYx)AjjhO zLT%vvR%mguY}5mWB=dd0co@f(oA^EKX{({ukqWNWb%H`CLr>m$rr26@ayoVwMEfY1 zr2|DQG+36b!HECXA->KiJ8zH8*~j6{%PNsp3h>Ua3ph1%l5`Y0pZ7cb^P3>Vw-EBU zKJ{wQ7X2c82Uz;*()EAU=R1||o(98oE{;RZ%p-q#G+MN#!KS$?rWl4{uQULE1+BR= z<&kvqbnwNkr7ieaR$Jg;ZYWe*>Nf-aqq$7exf0y zqKJc-2cCf3tWLYPAXTEGpTig#B7huOMEZB!4hQwgF)mNBW?o)(nl`ve4K`Fm|>gHTWaQ7sY*W09T@_37gTfDxEXkF7N&j4-CcwI4R zSqlAz(GTk(>EQce)s7SOeX|YSB+ekEUP&3fN3*=?1Zt_cf3@KQkVufdo zZlgD_Bw5>I0S>5e97biE_%;1;M|NMb@Lo=18j0_TScwa+IcNc7)h;VCP;)J4y+`k- z=y+96S7CWmaX{HzypQKoK?y*CAc*Uo14pDe=sSAAV)E5G?$Rae;=9{2s~u0=skr%J zZmv@hY3Cn9zvyA^qXgQ$Fzw((;+zjT$XH!TIsBFKl&iNrOq5yO)jH~he!lL-_Z(9^ zG@VC>593+75wAIv!zGGwd9H8$@7TF1yKrJ>DNI_iIL@4$qO-}F5maO8`X=66;kewt zwuU@*UW^I55Dk{Fg-wYE9b+*1kAjugl zIi4CRe3f#VZHfJbnweVo8R{xeqMywAI3I0My0w25MG2dNYVO--n*A{!c`SKaH(JQV zARGr$M5)y+l&X=@)E*1q;jZ!P6Lu#}MXVK(FLh{c(ux6fHX)k3DmF4jbfd0$u%o}s zOc344me^kc7v<<2au@|N2fxZ9wu8{`3pQXMSNh^;(CrI*CKaD{06GuG>lOQSLqBqJ zCc8rc5tf58v49fMqXSlWtM;SiT_L9vlTiMnzFAUlgs1IcPscOm0KvUzdg!&dIvtF) zYqnYBzT4|g@8hQW{6DAX5-8q-n@2u}#Uc^9^fWiN37MJUI1}GnDckM$izObx$$G(;i#cyufbz_Z)zvm z7{A`!vT&4PS7uZ_w4s5JDUa!x?C9OlK+dP4;l6XfR9{{s>}uc`NkiNa@rru(+?;dx zV~c<2g_^Pp_{!gzjsyhTAeXMB61rnpyTOteJ|N zpr9?+2XX=7Q>@b=g6#P04)WT>q|t0lIsB+*5Rr#g5-0op?n;rGxX)H8-Zdj_T%f6& zAcM^Z7k=qzo?gRYhq8$IoKN4+cVBDD2CaEJAE?hltoNU7*!~`G6@iXbW*x`OIa#+z zY&nq*pLAIxY<9WXD)e>^ybRQ_voE$4q^KFo!~kYc;pR({-xf8>og$gqPj{plObKw! zpmwAagj8`#OEJqsj7sHvLlw$5i1O}eo>Oa+@%muOI>kTe1S9SjMLxSFR(>ybnTM7 z!cNQy89v`KUPGhta91zRF>-~kBvHj3cmEgM6gq}PIIbE7&FMVd zkz`nq90wCh-n*}QKr>1%k&|j&Lu}alW1S%n28TYxVCD9H=f=THpLzq%olrWXy@Fa7 zAX)VZBVqKGn@UFggFhi9;8$b3AmdTy;@&pcaV3SSF36uR<3HLh6Z2hV7aqxYW{!Ay zF&>3sEH!^6mo!oOSs#x6URf#TIw;$Q8?E%7A1mY7rqV4#D?crzLHglS zj7-scw+=)hQ=XB0Igp~OEK%pm33PFKusv4=EYGvEga3eu&3mK-2?5$(7(OOg ztzRa8K7`OW%NQXP@MFWGTQIHEnkS_X#dz)J@NvmI0@7^SOIX(cL~2b-C%gH*j;OVgb}GS54ucU^ zD`2kvC@}G&3INnQKAgW{A`LZ`S@Js!&T8mhnD8GHL>(5 zJ=PQOUfMwBWA8jQROMEGu7;zcUu|SbjM4H{Ja_xYO`GU#{<@8rbLV>w^t{la#~F-x zF!izFqiq?lx`GN=Lm2+&!|s2tEcCuXdeimH^(DM=%0sy6LQ%cLt-XI#LVv6PBiDc? zb@g&Zac;p!v0vtU-NN5otIxRWvjzTsXQJIOPMXy5O_FhZ{u^ReRMlidvrOqoDglCC zn-F>@P4!TXH&w=BwHxX)R+ZZV_P*6lG%&6_Sy~}Ebv0ns(vaU(U;*9>Tlj@aY8o?u zqQq7Jfs6DKQY=)IGJ_-;33^kkYzrZ&fN(`rKq)()*Q4)7aUQ*MaOK;ED7D>9PDX3} zA1quU!lYcz{XS);OUmfhe8q%fC|YTMKK976amSL^kO~D)lP`Q-Io@^CEkitCCAEAS^4JvuHKOSTm+(ue)7d zrY<8Vt+3P`>NRbbv3Ii3{m70apr4sM^G9X=p1gFY{M2Ox9XT*xnH~|yX6iYfs3T|? zAZ>ONR-#zd=F;C7VJ)&%`rUXzy?K?!I4h30j^@-OQphZwf$qGtEelBwU}UfEs%P@p z^IPltP_LQj*P`2drMiyfgp!f3fmVk<%50n(&M4C6YfLOxT-<~r)~>Uu&KI>NP2y4p z>Hbx=X=8a1AI8(W2ZLujd}n)pWad(#WTVr$6SOFePMSc4bfTWh=I$Uc&S(of+zw8t zFF+8te{#EMY_R5u*)E2qKpDvLoNCG@sii`M@NcK~)2=($@&gd(e?tS5*tlvZVp=)4 z+SX6{uH;>(iNns_IcniDKARbV;Cj)J)9}@*rEI^yq~+<*)N0wp@D}|WYkOi6H_Ogk z5$!2`Ef7^bKMtRT)$-d#_)C)dQC-A-gm@PCK-Bkr=-wkNG zJ9ku<81@G({KHS{;9_m4Q%Dp}E`R8`UVVm61_o7H8&1m|=5iwB0|^#Ps2A;NUMZ?) zSUCt95&6;Uu+ra9XAVXW%3I(2bL0uQV=QM4qpxP zkbl_`7L@Lr@GApzgAiXAs*IfkU`y23CB6h|mIfi(r(YH|Y3-CW=!lp=&Ys4a1t`WX zsGCswPi6p-*+7`@NF~qA$!lsc^(r#<&n1ko*RdVP!j37I8%H-LK)N4VyzMYefCF7n zzr<}7rqin^rYvf=+Empx(_m&?DAGzro*N2fY*r3Oug}{yj)u&#-z8~;S6mIB$QqFL zPz;OmzA~Er;pAQDSvAxJs!kAS?{mpvLDICDhWJn~J}L=hcz;H)I0;2_w<>@o-sRSL4#0mW`gXh&ax@4W3W z;aB|TN$~fY)sCpO?x*%%?BONhBA?qF>VnWr7O+A;YP4{VdezChP1qusn#VGh?(K; zKcEh4Y)if3PRZyct5b^?T?ww83w!~nGpkPtrno(7Fj^shy-lod09`}tTDwTFiDTM{ zsoc>1y!$#yGy*e|or%Re8wh1cZaQ;-^3O!Fu8?oNH=8Tml`Pp+%-!TA3#?$#TVi8V z_L3#j%u+Qhwf^fU*^^S0__)paz)5dv}7R7N;2;ZGd*I9+7|4$M-VZ?5?!d z`ie^0`}Iugm9rPXANN`NH^T!Fia{k>AT=~cjOnpTyOM7x8O!4ECIa`eC^k9u1e=fq zEqPMoMV)FHZNw7lp@Y|q#@`mp;U5gXFro3oB~i40f3^GfU3Hq^BTyxFa$aqI+xVwT z#wkR<6o?Yl{Oyw$flaB@FBP}L>w!0Sx6fj2@k{s-%3FG9zJnp9tv25e6BPda>hR`F zV&6qr9#%vV7{rvlc^0Q2m_6d6tcLWg*_g?;NBb!tN4tds{OvDJn)cTtXb`(w8{r?N zL32>VL4P416jc1FJVWytvLi8AUmgYhD`i^XGB%V$#kvno&t+prVb(k^uLNja+~@(7 z8vAZsJLD2t4hZa9bS~?JESE13sye)Mf+EJ8({=NW&9;`G#~97+df8fMkm@5#C1u9e z0+uW?8wpy?<@ZSi7fQO9eTZVt3KxX`hK>=VL}q|SOQGmokI_5AOQ z$0DgUHY*~;Q-RGvHgHxyUYvZYxhlhi+Wy`pF*|~oSo&bNIa?dJlh8(YZxWALH&L7D zo;`hJ?oaqrGwkQ7l)(Xr-oRHNsLtUWM$O>V-6~g+*Sw@tFKshHbvCGM(duuu5h^cX zS2Imcu3dfmyeXr8@HZ^4TnxomnI#$EGZ$|QBwWgn03F?oCFve6W*Uu)^@y68O}WKB ze>40U+}bV7nAl3w4n5_3;YA}|-btoR04G;yzVI_sV}QrZq1|p*f>W-X6kre9Q2V+`K6VA^%{?2cZ=h8FK-tM5ndH@tVbtWjPBKi%i%>vP(v=U{6PlH8onJDmEls zI|w14Q~2_YTC*Cp=haoGyLz1>229${g+I|vrBD{vD_NW=Z+hCr}#7BkF?9 z=@uiQqL6422Ye+%9SrJ$2;dviKQ}Rd% zt^T3*%JJTq(hj?*t)#1|$Qg|zUkxWi37W~P%@jBG!@h+Y#CMmEQvi|EQ1dyqqF8w?Ixwzc4 z?+`LBBGzrXuTZFP^DpUXD`n~6!4csK+4JbF!|Z$wfhbDi^|zA;`_ zmoYsY1(ZPS-NY;kn-0cZQkIZ;oU}g>`l0J&6GlExs`*uy!5@MK1FWSc3@waaok4uFQs3lks}nfK?gdiEdo79U+&os> z^pBFXdIzGkf3M7R@1I=$nZ@^AqmgWKI}yhmJ9q=#*tR6GQVTTwa0f?ir4B+oue`2` z1)Di0TbUC$Hr6_*g|4sh9zJva8iLXcAVwl2q7`_L+4E0rfg$Wh=9}r}gOGrb;c}U= zT4ky$O9QyT4|nV{w%1~F5Re2)dW|o@;S5hj@=mSxOpU$j_-;1 zh1!pbcG|(h-qQIma@SPOArg=C$fwIRzN3CfmqW$Rr!nWD6=XHSHY%%HW<8P1m=R9R zo4DIKVLCy6;AG%UtTOho>Xn_w)LJd)t4xrL&8K#Y)7O3Iv|@)0;T|DtC*|c@Uepbq zKQefx->>q|VA}dWb!8^RHYNv!*Tvfp_#i?Z2wV9P;zc**<@4*dkZx(KuPCb6<35Nj z2xS>BcRcXcGm_yD~8d<#(BmgUA5o}Zgd?*s6ywS9;so;S{JV8TY`v{Oe)TOl} ZM+xS>*YW@Vr5J$eKjyc~{&tT3e*n4q5_JFo literal 0 HcmV?d00001 diff --git a/mods/artifact_hud/textures/artifact_radial_cursor.png b/mods/artifact_hud/textures/artifact_radial_cursor.png new file mode 100644 index 0000000000000000000000000000000000000000..cf731b142b4bb6e375fc8937c0bbed7613b2b552 GIT binary patch literal 162 zcmeAS@N?(olHy`uVBq!ia0vp^0wB!61|;P_|4#%`jKx9jP7LeL$-D$|Vmw_OLo9mV zPTDARK!L|e@&EtxB^T$V3m=U;Zj^b3nXS`h(v-8FpA3r+Oubku@la>wt%cm-YJs7} zub3HjFJ?~nP1?WfTK 50 then + r = 50 + m._menu.pos = m._menu.pos:normalize() *50 + end + p:hud_change(m._menu.cursor._id, "offset", m._menu.pos) + if r > 20 then + local angle = minetest.dir_to_yaw(vector.new(m._menu.pos.x, 0, m._menu.pos.y):normalize()) + local idx = math.floor((-angle +math.pi +(m._menu.step /2)) %(math.pi *2) /m._menu.step) +1 + if m._menu.selected and m._menu.selected ~= idx then + m._menu[m._menu.selected]:animate{ + scale = { + value = {x=0.7, y=0.7}, + duration = 0.2 + }, + opacity = { + value = 128, + duration = 0.2 + } + } + end + if m._menu.selected ~= idx and m._menu[idx] then + m._menu.selected = idx + m._menu[m._menu.selected]:animate{ + scale = { + value = {x=1, y=1}, + duration = 0.2 + }, + opacity = { + value = 256, + duration = 0.2 + } + } + end + elseif m._menu.selected then + m._menu[m._menu.selected]:animate{ + scale = { + value = {x=0.7, y=0.7}, + duration = 0.2 + }, + opacity = { + value = 128, + duration = 0.2 + } + } + m._menu.selected = nil + end + end + end m.ctl = ctl - + m.yaw = yaw + m.pitch = pitch end, set_character = function(m, to) m.character = to @@ -162,34 +242,34 @@ Player = setmetatable({ -- If called post-init, make sure we delete the previous HUD. -- This is useful when we want to recreate the HUD in response -- to an event, like freeing Vix. - if m.hud then - for _, x in pairs(m.hud) do - if type(x) == "table" then - for _, y in pairs(x) do - m.object:hud_remove(y) - end - else - m.object:hud_remove(x) - end - end - end - m.hud = { - key_health = m.object:hud_add { - type = "statbar", - position = {x=0.5,y=1}, - offset = {x=-27 *5,y=artifact.debug and -96 or -30}, - scale = {x=4,y=4}, - alignment = {x=-1, y=-1}, - size = {x=27,y=27}, - text = "artifact_heart_vix.png", - text2 = "artifact_heart_bg.png", - number = 20 - } - } - - if artifact.debug or artifact.story.states[artifact.story.get_state()] >= artifact.story.states.main then - - end +-- if m.hud then +-- for _, x in pairs(m.hud) do +-- if type(x) == "table" then +-- for _, y in pairs(x) do +-- m.object:hud_remove(y) +-- end +-- else +-- m.object:hud_remove(x) +-- end +-- end +-- end +-- m.hud = { +-- key_health = m.object:hud_add { +-- type = "statbar", +-- position = {x=0.5,y=1}, +-- offset = {x=-27 *5,y=artifact.debug and -96 or -30}, +-- scale = {x=4,y=4}, +-- alignment = {x=-1, y=-1}, +-- size = {x=27,y=27}, +-- text = "artifact_heart_vix.png", +-- text2 = "artifact_heart_bg.png", +-- number = 20 +-- } +-- } +-- +-- if artifact.debug or artifact.story.states[artifact.story.get_state()] >= artifact.story.states.main then +-- +-- end end, set_hotbar_size = function(m, slots) local p = m.object diff --git a/mods/artifact_player/radial_menu.lua b/mods/artifact_player/radial_menu.lua new file mode 100644 index 0000000..22c9674 --- /dev/null +++ b/mods/artifact_player/radial_menu.lua @@ -0,0 +1,48 @@ +local ns = artifact + +function ns.show_radial_menu(m, menu) + m._menu = { + name = menu.name, + count = #menu, + step = math.pi *2 /#menu, + pos = vector.zero(), + cursor = artifact.hud_add(m, { + type = "image", + pos = {x=0.5, y=0.5}, + offset = {x=0, y=0}, + image = "artifact_radial_cursor.png" + }) + } + for i, x in ipairs(menu) do + local size = 150 + local angle = m._menu.step *(i -1) -math.pi + local el = artifact.hud_add(m, { + name = menu.name.."_"..i, + type = "image", + pos = {x=0.5,y=0.5}, + scale = {x=0.1,y=0.1}, + offset = {x=math.sin(angle) *size,y=math.cos(angle) *size}, + image = "artifact_construct_test_icon.png", + opacity = 128 + }) + el:animate { + scale = { + value = {x=0.7, y=0.7}, + duration = 0.1 + } + } + m._menu[#m._menu +1] = el + end + m.object:hud_set_flags{crosshair = false} +end + +function ns.dismiss_radial_menu(m, name) + -- This is in case we only want to close a specific menu while leaving others intact. + if name and m._menu.name ~= name then return end + for _, x in ipairs(m._menu) do + x:remove(m) + end + m._menu.cursor:remove(m) + m._menu = nil + m.object:hud_set_flags{crosshair = true} +end