memebuilder/mods/railbuilder/railbuilder_ui.lua
2022-08-16 16:12:37 -04:00

355 lines
12 KiB
Lua

-- HUD stuff and marker entities
-- first_person_offset from API is 0...
local FIRST_PERSON_EYE_OFFSET = vector.new(0, 1.5, 0)
local SelectionEntity = {
conquer = true,
initial_properties = {
physical = false,
collide_with_objects = false,
pointable = false,
collisionbox = {-0.5, 0.05, -0.5, 0.5, 0.1, 0.5},
visual_size = {x = 8, y = 2 },
mesh = "conquer_selection_disc.obj",
textures = { "conquer_selection_disc.png" },
visual = "mesh",
static_save = false,
use_texture_alpha = true,
},
}
function SelectionEntity:set_color(color, ratio)
self.object:set_properties({
textures = {
("conquer_selection_disc.png^[colorize:%s:%d"):format(color, ratio or 200),
},
})
end
function SelectionEntity:on_activate()
self.object:set_armor_groups({ immortal = 1 })
end
function SelectionEntity:on_detach()
self.object:remove()
end
local function show_possible_endpoints(player, pos, color)
return player:hud_add {
hud_elem_type = "image_waypoint",
name = name,
scale={x=4,y=4},
text = ("conquer_selection_disc.png^[colorize:%s:%d"):format(color, ratio or 200),
world_pos = pos,
z_index = -300,
}
end
local function draw_debug_point(player, pos, color)
return player:hud_add {
hud_elem_type = "image_waypoint",
name = name,
scale={x=0.5,y=0.5},
text = ("conquer_selection_disc.png^[colorize:%s:%d"):format(color, ratio or 200),
world_pos = pos,
z_index = -300,
}
end
-- for vectors p, q, v, w find scalars r, s so that p + r*v - (q + s*w) takes the smallest value possible
local function min_distance_lines(p, v, q, w)
local a = vector.dot(v, v)
local b = vector.dot(v, w)
local c = vector.dot(v, vector.subtract(q, p))
local d = vector.dot(v, w)
local e = vector.dot(w, w)
local f = vector.dot(w, vector.subtract(q, p))
return (b*f-c*e)/(b*d-a*e),(a*f-c*d)/(b*d-a*e)
end
-- for vectors p, q, v, w find scalars r, s so that p + r*v - (q + s*w) takes the smallest value possible
-- return p + r*v, q + s*w
local function closest_points_on_lines(p, v, q, w)
local r, s = min_distance_lines(p, v, q, w)
return vector.add( p, vector.multiply(v, r) ), vector.add( q, vector.multiply(w, s) )
end
local function trace_player_view(player, directions)
local player_data = railbuilder.datastore.get_data(player)
local start_pos = player_data.railbuilder_start_pos
if start_pos and directions then
local min_dist_found = 1E99
local min_dist_point = vector.new(0, 0, 0)
local first_person_eye_offset, third_person_offset = player:get_eye_offset()
first_person_eye_offset = FIRST_PERSON_EYE_OFFSET
eye_pos = vector.add(player:get_pos(), first_person_eye_offset)
look_dir = player:get_look_dir()
for _, direction in ipairs(directions) do
local dir_vector = advtrain_helpers.rotation_index_and_vertical_direction_to_advtrains_dir(direction, player_data.railbuilder_desired_vertical_direction or 0)
if dir_vector then
-- hud marker is at the center of the node
local p, q = closest_points_on_lines(start_pos, dir_vector, eye_pos, look_dir)
local r, s = min_distance_lines(start_pos, dir_vector, eye_pos, look_dir)
dist = vector.length( vector.subtract(q, p) )
if r > 0 and s > 0 and dist < min_dist_found then
min_dist_found = dist
min_dist_point = p
end
end
end
if player_data.ui.hud_track_preview_points.selected ~= nil then
player:hud_change(player_data.ui.hud_track_preview_points.selected.id, "text", ("conquer_selection_disc.png^[colorize:%s:%d"):format("#44FF44", ratio or 200))
player_data.ui.hud_track_preview_points.selected = nil
end
for _, point in ipairs(player_data.ui.hud_track_preview_points) do
if vector.distance(point.pos, min_dist_point) < 0.5 then
player:hud_change(point.id, "text", ("conquer_selection_disc.png^[colorize:%s:%d"):format("#FF0000", ratio or 200))
player_data.ui.hud_track_preview_points.selected = point
end
end
end
end
local function length_2d(v)
return math.sqrt(v.x * v.x + v.z * v.z)
end
local function update_hud_track_preview_points(player, rail_start_pos, directions)
local player_data = railbuilder.datastore.get_data(player)
player_data.ui.hud_update_last_player_pos = player:get_pos()
if #directions == 0 then return end
local delta_pos = vector.subtract(player:get_pos(), rail_start_pos)
local distance_to_start_pos_2d = length_2d(delta_pos)
local normalized_delta_2d = vector.normalize(vector.new(delta_pos.x, 0, delta_pos.z))
for _, direction in ipairs(directions) do
dir_step = advtrain_helpers.rotation_index_and_vertical_direction_to_advtrains_dir(direction, player_data.railbuilder_desired_vertical_direction or 0)
if dir_step then
if distance_to_start_pos_2d < 5 or vector.dot(normalized_delta_2d, dir_step) > 0 then
step_size = vector.length(dir_step)
end_i = 9 / step_size
step_multiplier = math.max(math.floor(distance_to_start_pos_2d / step_size - 1/2*end_i), 1)
for i=0,end_i do
point_pos = vector.add(rail_start_pos, vector.multiply(dir_step, step_multiplier + i))
table.insert(player_data.ui.hud_track_preview_points,
{
id = show_possible_endpoints(player, point_pos, "#44FF44"),
pos = point_pos
})
end
end
end
end
end
function update_hud(player, force)
local player_data = railbuilder.datastore.get_data(player)
local start_pos = player_data.railbuilder_start_pos
if player:get_player_control().dig then
force = update_slope_selection_ui(player) or force
else
hide_slope_selection_ui(player)
end
-- nothing to update if player did not move
if force or vector.distance(player_data.ui.hud_update_last_player_pos, player:get_pos()) > 1 then
remove_hud_track_preview_points(player)
if is_start_marker_valid(player) then
local directions = advtrain_helpers.get_advtrains_dirs(start_pos, player_data.railbuilder_last_direction, player_data.railbuilder_last_vertical_direction) or
advtrain_helpers.get_closest_directions(player, start_pos)
update_hud_track_preview_points(player, start_pos, directions)
player_data.ui.hud_track_preview_directions = directions
end
end
if is_start_marker_valid(player) then
trace_player_view(player, player_data.ui.hud_track_preview_directions)
end
end
local function remove_hud_points(player, hud_points)
for _, hud_point in ipairs(hud_points) do
player:hud_remove(hud_point.id)
end
end
function remove_hud_track_preview_points(player)
local player_data = railbuilder.datastore.get_data(player)
if #player_data.ui.hud_track_preview_points > 0 then
remove_hud_points(player, player_data.ui.hud_track_preview_points)
player_data.ui.hud_track_preview_points = {}
end
end
function highlight_point(player, directions)
-- deselect last marked
if last_marked_node then
end
end
function get_selected_position(player)
local player_data = railbuilder.datastore.get_data(player)
if player_data.ui.hud_track_preview_points.selected then
return player_data.ui.hud_track_preview_points.selected.pos
end
return nil
end
local function draw_slope_selection_point(player, pos, color)
return player:hud_add {
hud_elem_type = "image_waypoint",
name = name,
scale={x=2,y=2},
text = ("conquer_selection_disc.png^[colorize:%s:%d"):format(color, ratio or 200),
world_pos = pos,
z_index = -300,
}
end
local function init_slope_selection_ui(player)
local player_data = railbuilder.datastore.get_data(player)
-- store initial look_dir
player_data.ui.slope_selection_initial_look_yaw = player:get_look_horizontal()
player_data.ui.slope_selection_initial_look_pitch = player:get_look_vertical()
player_data.ui.slope_selection_is_showing = true
end
local function slope_to_enum(slope)
if slope == -1/2 then return 1
elseif slope == -1/3 then return 2
elseif slope == 1/3 then return 3
elseif slope == 1/2 then return 4
end
return 0
end
-- update the nth point
local function update_slope_selection_nth_point(player, n)
local player_data = railbuilder.datastore.get_data(player)
for i, point in ipairs(player_data.ui.hud_slope_selection_points) do
if i == n then
player:hud_change(point.id, "text", ("conquer_selection_disc.png^[colorize:%s:%d"):format("#FF0000", ratio or 200))
else
player:hud_change(point.id, "text", ("conquer_selection_disc.png^[colorize:%s:%d"):format("#FFFFFF", ratio or 200))
end
end
end
function show_slope_selection_ui(player)
init_slope_selection_ui(player)
local player_data = railbuilder.datastore.get_data(player)
local start_pos = player_data.railbuilder_start_pos
if start_pos then
table.insert(player_data.ui.hud_slope_selection_points,
{
id = draw_slope_selection_point(player, vector.add(start_pos, vector.new(0, -0.5, 0)), "#FFFFFF"),
pos = point_pos
})
table.insert(player_data.ui.hud_slope_selection_points,
{
id = draw_slope_selection_point(player, vector.add(start_pos, vector.new(0, -1/3, 0)), "#FFFFFF"),
pos = point_pos
})
table.insert(player_data.ui.hud_slope_selection_points,
{
id = draw_slope_selection_point(player, vector.add(start_pos, vector.new(0, 1/3, 0)), "#FFFFFF"),
pos = point_pos
})
table.insert(player_data.ui.hud_slope_selection_points,
{
id = draw_slope_selection_point(player, vector.add(start_pos, vector.new(0, 0.5, 0)), "#FFFFFF"),
pos = point_pos
})
end
end
-- update the hud points for slope selection
-- return true if selected slope changed
function update_slope_selection_ui(player)
local player_data = railbuilder.datastore.get_data(player)
if not player_data.ui.slope_selection_is_showing then
return
end
local current_yaw = player:get_look_horizontal()
local current_pitch = player:get_look_vertical()
local desired_vertical_direction = player_data.railbuilder_desired_vertical_direction
-- abort if too much to the side
local yaw_diff_abs = math.abs(player_data.ui.slope_selection_initial_look_yaw - current_yaw)
if yaw_diff_abs < 0.3 or yaw_diff_abs > 2 * math.pi - 0.3 then
local pitch_diff = player_data.ui.slope_selection_initial_look_pitch - current_pitch
if pitch_diff < -0.15 then
desired_vertical_direction = -1/2
elseif pitch_diff < -0.07 then
desired_vertical_direction = -1/3
elseif pitch_diff < 0.07 then
desired_vertical_direction = 0
elseif pitch_diff < 0.15 then
desired_vertical_direction = 1/3
else
desired_vertical_direction = 1/2
end
end
if player_data.railbuilder_desired_vertical_direction ~= desired_vertical_direction then
player_data.railbuilder_desired_vertical_direction = desired_vertical_direction
update_slope_selection_nth_point(player, slope_to_enum(desired_vertical_direction))
return true
end
return false
end
function hide_slope_selection_ui(player)
local player_data = railbuilder.datastore.get_data(player)
if #player_data.ui.hud_slope_selection_points > 0 then
remove_hud_points(player, player_data.ui.hud_slope_selection_points)
player_data.ui.hud_slope_selection_points = {}
end
player_data.ui.slope_selection_is_showing = false
end
function set_start_marker(player, position, direction_delta)
local player_data = railbuilder.datastore.get_data(player)
local railbuilder_start_marker_pos = vector.new(position.x, position.y, position.z)
-- for positive slope, move the start marker a little bit down
if direction_delta and direction_delta.y > 0 then railbuilder_start_marker_pos.y = railbuilder_start_marker_pos.y - direction_delta.y end
player_data.ui.railbuilder_start_marker = minetest.add_entity(railbuilder_start_marker_pos, "railbuilder:selection")
end
function remove_start_marker(player)
local player_data = railbuilder.datastore.get_data(player)
local old_start_marker = player_data.ui.railbuilder_start_marker
player_data.ui.railbuilder_start_marker = nil
old_start_marker:remove()
end
function is_start_marker_valid(player)
local player_data = railbuilder.datastore.get_data(player)
return player_data.ui.railbuilder_start_marker and player_data.ui.railbuilder_start_marker:get_luaentity() ~= nil
end
-- returns true if the start marker exists but is unloaded because it is too far away
function is_start_marker_lost(player)
local player_data = railbuilder.datastore.get_data(player)
return player_data.ui.railbuilder_start_marker and not player_data.ui.railbuilder_start_marker:get_luaentity()
end
minetest.register_entity("railbuilder:selection", SelectionEntity)