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 } p:hud_set_flags { healthbar = false, breathbar = false, hotbar = artifact.debug, minimap = false, basic_debug = artifact.debug, crosshair = false, -- It gets set to true once we can play. wielditem = false, -- Ditto. chat = false, -- We provide our own implementation of the chat HUD. } -- The following exists to make sure that whatever physics -- settings the server may have set are transparently ignored. local defaults = { speed_walk = 4, speed_crouch = 1.35, speed_fast = 20, speed_climb = 3, speed_jump = 6.5, gravity = 9.81, liquid_fluidity = 1, liquid_fluidity_smooth = 0.5, liquid_sink = 10, acceleration_default = 3, acceleration_air = 2, acceleration_fast = 10, } local override = { speed = 1, sneak = true, sneak_glitch = false, new_move = true, } for key, def_value in pairs(defaults) do local setting_name = "movement_"..key local current = tonumber(minetest.settings:get(setting_name)) or def_value override[key] = def_value /current end p:set_physics_override(override) -- No unreasonable FOV settings here. p:set_fov(72) m.hud = {} m.poi = {} m.chat = {} if not artifact.debug then p:set_inventory_formspec "" end if m.character == "vix" then artifact.apply_vix(m) else artifact.apply_key(m) end -- Let us build in debug mode, but ensure we always wield the hand item otherwise. m:set_hotbar_size(artifact.debug and 8 or 1) 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 local state = artifact.story.get_state() -- Sleep if we are not yet ready for the player to do things. if not artifact.debug and state <= artifact.story.states.init then return end -- 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 -- This should only work once we have Vix, since we can't use it without her. if state >= artifact.story.states.main and 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 -- MARK: Health regen if m.next_regen and time -m.next_regen >= 0 then m.object:set_hp(m.object:get_hp() +1) end m.ctl = ctl m.yaw = yaw m.pitch = pitch m.dir = dir end, set_character = function(m, to) m.character = to m.meta:set_string("character", to) end, add_health_bar = 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 -40}, scale = {x=4,y=4}, alignment = {x=-1, y=-1}, size = {x=27,y=27}, text = m.character == "vix" and "artifact_heart_vix.png" or "artifact_heart.png", text2 = "artifact_heart_bg.png", number = 20, item = 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 }) -- Mirror the player's HP in our custom HUD. -- (We need a custom HUD so that we can change its appearance dynamically.) minetest.register_on_player_hpchange(function(p, delta) local m = artifact.players[p:get_player_name()] local hp = p:get_hp() +delta if m.healthbar then p:hud_change(m.healthbar, "number", hp) end if hp < 20 then m.next_regen = minetest.get_us_time() +5000000 else m.next_regen = nil end 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 artifact.story.get_state() > artifact.story.states.pre_vix then artifact.swap_character(m) end return s end, on_use = function(s, p) local m = artifact.players[p:get_player_name()] if m.character == "vix" then artifact.do_shoot(m) else artifact.do_whack(m) end 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)