local ns = artifact ns.players = {} local db = minetest.get_mod_storage() include "radial_menu.lua" Player = setmetatable({ new = function(p) local m = setmetatable({ object = p, pitch = 0, yaw = 0 }, {__index = Player}) m.name = p:get_player_name() m.meta = p:get_meta() m.character = m.meta:get("character") or "key" m.inv = p:get_inventory() m.inv:set_stack("main", 1, ItemStack("input_"..m.character)) -- Generic black sky, since the whole game takes place underground. p:set_sky{ type = "basic", base_color = "#000", clouds = false } p:set_sun{visible = false} p:set_moon{visible = false} p:set_stars{visible = false} p:set_properties { visual = "mesh", mesh = "artifact_character.gltf", shaded = false } if m.character == "vix" then artifact.apply_vix(m) else artifact.apply_key(m) end p:hud_set_flags { healthbar = false, breathbar = false, hotbar = artifact.debug, minimap = false, basic_debug = false } m.hud = {} m.poi = {} m:create_hud() m:set_hotbar_size(8) m.ctl = p:get_player_control() return m end, tick = function(m) local time = minetest.get_us_time() local p = m.object local pos = p:get_pos() local yaw = p:get_look_horizontal() local pitch = p:get_look_vertical() local dir = p:get_look_dir() local vel = p:get_velocity() local speed = vel:length() m.pos = pos m.pos.y = m.pos.y +m.eye_height -- MARK: Pointing callbacks local pointed_found = nil m.pointed_node = nil for x in minetest.raycast(m.pos, m.pos +(dir *5)) do if x and x.type == "object" then local e = x.ref:get_luaentity() -- Ignore players. if e then local names_match = m.pointed_obj and (m.pointed_obj._name or m.pointed_obj.name) == (e._name or e.name) if m.pointed_obj and not names_match then if m.pointed_obj.on_unhover then m.pointed_obj:on_unhover(m) end if m.pointed_obj.on_interact and m.interaction_marker then m.object:hud_remove(m.interaction_marker) m.interaction_marker = nil m.interaction_start = nil end end if e.on_interact and not e._no_interact and (not names_match or names_match and not m.interaction_marker) then if m.interaction_marker then m.object:hud_remove(m.interaction_marker) end local dst = e.object:get_pos() if e._interact_marker_offset then dst = dst +e:_interact_marker_offset() end m.interaction_marker = m.object:hud_add { type = "image_waypoint", world_pos = dst, scale = {x=3, y=3}, text = "artifact_rmb.png" } end if (m.pointed_obj and not names_match and e.on_hover) or not m.pointed_obj then if e.on_hover then e:on_hover(m) end pointed_found = true m.pointed_obj = e break elseif m.pointed_obj and names_match then pointed_found = true break end end elseif x and x.type == "node" then m.pointed_node = x if m.pointed_obj then if m.pointed_obj.on_unhover then m.pointed_obj:on_unhover(m) end if m.pointed_obj.on_interact and m.interaction_marker then m.object:hud_remove(m.interaction_marker) m.interaction_marker = nil m.interaction_start = nil end m.pointed_obj = nil end break end end if not pointed_found and m.pointed_obj then if m.pointed_obj.on_unhover then m.pointed_obj:on_unhover(m) end if m.pointed_obj.on_interact and m.interaction_marker then m.object:hud_remove(m.interaction_marker) m.interaction_marker = nil m.interaction_start = nil end m.pointed_obj = nil end local ctl = m.object:get_player_control() -- MARK: Animations local moving = (ctl.up or ctl.down or ctl.left or ctl.right) and speed > 0.1 if moving then m.moving = true if ctl.aux1 and ctl.up then if p:get_animation().y ~= 2 then p:set_animation({x=1, y=2}, 1.5, 0.2, true) end p:set_physics_override{ speed = 1.5 } else if p:get_animation().y ~= 1 then p:set_animation({x=0, y=1}, 1.5, 0.2, true) end p:set_physics_override{ speed = 1 } end else m.moving = false if p:get_animation().y ~= 0 then p:set_animation({x=0, y=0}) end end if not m.rot then m.rot = 0 end if moving then local fac = 0 if ctl.left then fac = 30 elseif ctl.right then fac = -30 end m.rot = yaw +math.rad(fac) elseif math.abs(yaw -m.rot) > math.rad(40) then m.rot = m.rot +(yaw -(m.yaw or 0)) end m.rot = m.rot %(math.pi *2) p:set_bone_override("Head", { rotation = {vec = vector.new(math.min(math.max(pitch, math.rad(-60)), math.rad(60)),-(yaw -m.rot),0), interpolation = 0.1, absolute = true} }) p:set_bone_override("root", { rotation = {vec = vector.new(0,yaw -m.rot,0), interpolation = 0.1, absolute = true} }) -- MARK: Progressive interaction if ctl.place and m.ctl.place and m.pointed_obj and m.pointed_obj.on_interact and not m.pointed_obj._no_interact then if not m.interaction_start then m.interaction_start = time else local duration = (m.pointed_obj._interact_time or 1) *1000000 local progress = (time -m.interaction_start) /duration if progress > 1.1 then m.pointed_obj:on_interact(m) m.interaction_start = nil m.object:hud_remove(m.interaction_marker) m.interaction_marker = nil elseif progress > 1 then m.object:hud_change(m.interaction_marker, "text", "artifact_rmb_100.png") elseif progress > 0.75 then m.object:hud_change(m.interaction_marker, "text", "artifact_rmb_75.png") elseif progress > 0.5 then m.object:hud_change(m.interaction_marker, "text", "artifact_rmb_50.png") elseif progress > 0.25 then m.object:hud_change(m.interaction_marker, "text", "artifact_rmb_25.png") end end elseif not ctl.place and m.interaction_start and (not m.pointed_obj or not m.pointed_obj._no_interact) then m.interaction_start = nil if m.interaction_marker then m.object:hud_change(m.interaction_marker, "text", "artifact_rmb.png") end end local wi = p:get_wielded_item() m.wielded_item = wi -- MARK: Radial menu handling if ctl.place and not m.ctl.place and wi:get_name():find "artifact:input" and (not m.pointed_obj or m.pointed_obj._no_interact) then artifact.show_radial_menu(m, { name = "construct", "test", "test2", "test3", "test4", "test5" }) elseif m._menu and not (ctl.place and wi:get_name():find "artifact:input") or (m.pointed_obj and not m.pointed_obj._no_interact) then artifact.dismiss_radial_menu(m, "construct") elseif m._menu then local dx = m.yaw -yaw local dy = m.pitch -pitch if dx ~= 0 and dy ~= 0 then m._menu.pos.x = m._menu.pos.x +dx *200 m._menu.pos.y = m._menu.pos.y -dy *200 local r = m._menu.pos:distance(vector.zero()) if r > 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 m.meta:set_string("character", to) end, -- Initialize the player's primary HUD display based on saved state. create_hud = function(m) m.healthbar = 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 } end, set_hotbar_size = function(m, slots) local p = m.object p:hud_set_hotbar_itemcount(slots) local list = "" for i = 0, slots do list = list..":"..(21*i)..",0=artifact_hotbar_bg.png" end p:hud_set_hotbar_image("[combine:"..(21 *slots +1).."x22"..list) p:hud_set_hotbar_selected_image("artifact_hotbar_selected_bg.png") end, }, { __call = function(_, ...) return Player.new(...) end }) local _hand = minetest.registered_items[""] function artifact.register_input(name) artifact.register_node("input_"..name, { inventory_image = "artifact_rmb_100.png", description = "", paramtype = "light", drawtype = "mesh", mesh = "artifact_hand.gltf", tiles = {"artifact_"..name..".png"}, use_texture_alpha = "opaque", visual_scale = 1, wield_scale = vector.new(2,2,2), node_placement_prediction = "", on_construct = function(pos) minetest.remove_node(pos) end, drop = "", range = 0, on_drop = function(s, p, pos) local m = artifact.players[p:get_player_name()] if artifact.debug or artifat.story.state > artifact.story.states.pre_vix then artifact.swap_character(m) end return s end }) end artifact.register_input "key" artifact.register_input "vix" minetest.register_globalstep(function() for _, m in pairs(artifact.players) do m:tick() end end) minetest.register_on_joinplayer(function(p) artifact.players[p:get_player_name()] = Player(p) if artifact.debug then -- Make sure we don't have to `/grantme` a million times while testing. minetest.registered_chatcommands.grantme.func(p:get_player_name(), "all") end end) minetest.register_on_leaveplayer(function(p) artifact.players[p:get_player_name()] = nil end)