2020-10-26 17:38:53 +01:00
--[[
2021-01-23 12:36:50 +01:00
An API framework for mounting objects .
Copyright ( C ) 2016 blert2112 and contributors
2024-12-19 12:55:40 +01:00
Copyright ( C ) 2019 - 2023 David Leal ( halfpacho @ gmail.com ) and contributors
2020-10-26 17:38:53 +01:00
This library is free software ; you can redistribute it and / or
modify it under the terms of the GNU Lesser General Public
License as published by the Free Software Foundation ; either
version 2.1 of the License , or ( at your option ) any later version .
This library is distributed in the hope that it will be useful ,
but WITHOUT ANY WARRANTY ; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE . See the GNU
Lesser General Public License for more details .
You should have received a copy of the GNU Lesser General Public
License along with this library ; if not , write to the Free Software
Foundation , Inc . , 51 Franklin Street , Fifth Floor , Boston , MA 02110 - 1301
USA
--]]
lib_mount = {
2024-12-19 12:55:40 +01:00
passengers = { }
2020-10-26 17:38:53 +01:00
}
2020-10-25 19:05:08 +01:00
2020-11-17 16:23:21 +01:00
local crash_threshold = 6.5 -- ignored if enable_crash is disabled
2020-10-25 19:05:08 +01:00
------------------------------------------------------------------------------
local mobs_redo = false
if minetest.get_modpath ( " mobs " ) then
if mobs.mod and mobs.mod == " redo " then
mobs_redo = true
end
end
--
-- Helper functions
--
--local function is_group(pos, group)
-- local nn = minetest.get_node(pos).name
-- return minetest.get_item_group(nn, group) ~= 0
--end
local function node_is ( pos )
local node = minetest.get_node ( pos )
if node.name == " air " then
return " air "
end
if minetest.get_item_group ( node.name , " liquid " ) ~= 0 then
return " liquid "
end
if minetest.get_item_group ( node.name , " walkable " ) ~= 0 then
return " walkable "
end
return " other "
end
local function get_sign ( i )
i = i or 0
if i == 0 then
return 0
else
return i / math.abs ( i )
end
end
local function get_velocity ( v , yaw , y )
local x = - math.sin ( yaw ) * v
local z = math.cos ( yaw ) * v
2024-12-19 12:55:40 +01:00
return vector.new ( x , y , z )
2020-10-25 19:05:08 +01:00
end
local function get_v ( v )
return math.sqrt ( v.x ^ 2 + v.z ^ 2 )
end
2024-12-19 12:55:40 +01:00
local function ensure_passengers_exists ( entity )
if entity.passengers ~= nil then
return
end
entity.passengers = { }
if entity.passenger ~= nil or
entity.passenger_attach_at ~= nil or
entity.passenger_eye_offset ~= nil then
table.insert ( entity.passengers , {
player = entity.passenger ,
attach_at = entity.passenger_attach_at ,
eye_offset = entity.passenger_eye_offset
} )
else
return
end
if entity.passenger2 ~= nil or
entity.passenger2_attach_at ~= nil or
entity.passenger2_eye_offset ~= nil then
table.insert ( entity.passengers , {
player = entity.passenger2 ,
attach_at = entity.passenger2_attach_at ,
eye_offset = entity.passenger2_eye_offset
} )
else
return
end
if entity.passenger3 ~= nil or
entity.passenger3_attach_at ~= nil or
entity.passenger3_eye_offset ~= nil then
table.insert ( entity.passengers , {
player = entity.passenger3 ,
attach_at = entity.passenger3_attach_at ,
eye_offset = entity.passenger3_eye_offset
} )
end
end
-- Copies the specified passenger to the older api. Note that this is one-directional.
-- If something changed in the old api before this is called it is lost.
-- In code you control be sure to always use the newer API and to call this function on every change.
-- If you would like to improove preformance (memory & CPU) by not updating the old API, set
-- entity.new_api to true. This will return from the funciton instead of doing anything.
local function old_copy_passenger ( entity , index , player , attach , eye )
if entity.new_api then
return
end
ensure_passengers_exists ( entity )
if index == 1 then -- Don't forget! Lua indexes start at 1
if player then
entity.passenger = entity.passengers [ index ] . player
end
if attach then
entity.passenger_attach_at = entity.passengers [ index ] . attach_at
end
if eye then
entity.passenger_eye_offset = entity.passengers [ index ] . eye_offset
end
elseif index == 2 then
if player then
entity.passenger2 = entity.passengers [ index ] . player
end
if attach then
entity.passenger2_attach_at = entity.passengers [ index ] . attach_at
end
if eye then
entity.passenger2_eye_offset = entity.passengers [ index ] . eye_offset
end
elseif index == 3 then
if player then
entity.passenger3 = entity.passengers [ index ] . player
end
if attach then
entity.passenger3_attach_at = entity.passengers [ index ] . attach_at
end
if eye then
entity.passenger3_eye_offset = entity.passengers [ index ] . eye_offset
end
end
end
2020-10-25 19:05:08 +01:00
local function force_detach ( player )
local attached_to = player : get_attach ( )
if attached_to then
local entity = attached_to : get_luaentity ( )
if entity.driver and entity.driver == player then
entity.driver = nil
2024-12-19 12:55:40 +01:00
else
ensure_passengers_exists ( entity )
for i , passenger in ipairs ( entity.passengers ) do
if passenger.player == player then -- If it's nil it won't match
entity.passengers [ i ] . player = nil -- This maintains the behavior where you could have passenger 1 leave but passenger 2 is still there, and they don't move
lib_mount.passengers [ player ] = nil
-- Legacy support
old_copy_passenger ( entity , i , true , false , false )
break -- No need to continue looping. We found them.
end
end
2020-10-25 19:05:08 +01:00
end
player : set_detach ( )
2020-10-26 17:38:53 +01:00
player_api.player_attached [ player : get_player_name ( ) ] = false
2024-12-19 12:55:40 +01:00
player : set_eye_offset ( vector.new ( 0 , 0 , 0 ) , vector.new ( 0 , 0 , 0 ) )
2020-10-25 19:05:08 +01:00
end
end
-------------------------------------------------------------------------------
minetest.register_on_leaveplayer ( function ( player )
force_detach ( player )
end )
minetest.register_on_shutdown ( function ( )
local players = minetest.get_connected_players ( )
for i = 1 , # players do
force_detach ( players [ i ] )
end
end )
minetest.register_on_dieplayer ( function ( player )
force_detach ( player )
return true
end )
-------------------------------------------------------------------------------
2020-10-26 17:38:53 +01:00
function lib_mount . attach ( entity , player , is_passenger , passenger_number )
local attach_at , eye_offset = { } , { }
2020-10-25 19:05:08 +01:00
2020-10-26 17:38:53 +01:00
if not is_passenger then
passenger_number = nil
end
2020-10-25 19:05:08 +01:00
if not entity.player_rotation then
2024-12-19 12:55:40 +01:00
entity.player_rotation = vector.new ( 0 , 0 , 0 )
2020-10-25 19:05:08 +01:00
end
2020-10-26 17:38:53 +01:00
2024-12-19 12:55:40 +01:00
if is_passenger == true then
-- Legacy support
ensure_passengers_exists ( entity )
2020-10-26 17:38:53 +01:00
2024-12-19 12:55:40 +01:00
local attach_updated = false
if not entity.passengers [ passenger_number ] . attach_at then
entity.passengers [ passenger_number ] . attach_at = vector.new ( 0 , 0 , 0 )
attach_updated = true
2020-10-26 17:38:53 +01:00
end
2024-12-19 12:55:40 +01:00
local eye_updated = false
if not entity.passengers [ passenger_number ] . eye_offset then
entity.passengers [ passenger_number ] . eye_offset = vector.new ( 0 , 0 , 0 )
eye_updated = true
2020-10-26 17:38:53 +01:00
end
2024-12-19 12:55:40 +01:00
attach_at = entity.passengers [ passenger_number ] . attach_at
eye_offset = entity.passengers [ passenger_number ] . eye_offset
2020-10-26 17:38:53 +01:00
2024-12-19 12:55:40 +01:00
entity.passengers [ passenger_number ] . player = player
lib_mount.passengers [ player ] = player
2020-10-26 17:38:53 +01:00
2024-12-19 12:55:40 +01:00
-- Legacy support
old_copy_passenger ( entity , passenger_number , true , attach_updated , eye_updated )
2020-10-25 19:05:08 +01:00
else
if not entity.driver_attach_at then
2024-12-19 12:55:40 +01:00
entity.driver_attach_at = vector.new ( 0 , 0 , 0 )
2020-10-25 19:05:08 +01:00
end
if not entity.driver_eye_offset then
2024-12-19 12:55:40 +01:00
entity.driver_eye_offset = vector.new ( 0 , 0 , 0 )
2020-10-26 17:38:53 +01:00
end
2020-10-25 19:05:08 +01:00
attach_at = entity.driver_attach_at
eye_offset = entity.driver_eye_offset
entity.driver = player
end
force_detach ( player )
player : set_attach ( entity.object , " " , attach_at , entity.player_rotation )
2020-10-26 17:38:53 +01:00
player_api.player_attached [ player : get_player_name ( ) ] = true
2024-12-19 12:55:40 +01:00
player : set_eye_offset ( eye_offset , vector.new ( 0 , 0 , 0 ) )
2020-10-25 19:05:08 +01:00
minetest.after ( 0.2 , function ( )
2020-10-26 17:38:53 +01:00
player_api.set_animation ( player , " sit " , 30 )
2020-10-25 19:05:08 +01:00
end )
2024-12-19 12:55:40 +01:00
player : set_look_horizontal ( entity.object : get_yaw ( ) + math.rad ( entity.player_rotation . y or 90 ) )
2020-10-25 19:05:08 +01:00
end
function lib_mount . detach ( player , offset )
force_detach ( player )
2020-10-26 17:38:53 +01:00
player_api.set_animation ( player , " stand " , 30 )
local pos = player : get_pos ( )
2020-10-25 19:05:08 +01:00
pos = { x = pos.x + offset.x , y = pos.y + 0.2 + offset.y , z = pos.z + offset.z }
minetest.after ( 0.1 , function ( )
2020-10-26 17:38:53 +01:00
player : set_pos ( pos )
2020-10-25 19:05:08 +01:00
end )
end
local aux_timer = 0
2024-12-19 12:55:40 +01:00
function lib_mount . drive ( entity , dtime , is_mob , moving_anim , stand_anim , jump_height , can_fly , can_go_down , can_go_up , enable_crash , moveresult )
2021-02-02 10:09:27 +01:00
-- Sanity checks
if entity.driver and not entity.driver : get_attach ( ) then entity.driver = nil end
2024-12-19 12:55:40 +01:00
-- Legacy support
ensure_passengers_exists ( entity )
for i , passenger in ipairs ( entity.passengers ) do
if passenger.player and not passenger.player : get_attach ( ) then
entity.passengers [ i ] . player = nil
-- Legacy support
old_copy_passenger ( entity , i , true , false , false )
end
2021-02-02 10:09:27 +01:00
end
2020-10-25 19:05:08 +01:00
aux_timer = aux_timer + dtime
2020-10-26 17:38:53 +01:00
2020-10-25 19:05:08 +01:00
if can_fly and can_fly == true then
jump_height = 0
end
2024-12-19 12:55:40 +01:00
local rot_steer , rot_view = math.pi / 2 , 0 -- luacheck: ignore
2020-10-25 19:05:08 +01:00
if entity.player_rotation . y == 90 then
2024-12-19 12:55:40 +01:00
rot_steer , rot_view = 0 , math.pi / 2 -- luacheck: ignore
2020-10-25 19:05:08 +01:00
end
local acce_y = 0
2020-10-26 17:38:53 +01:00
local velo = entity.object : get_velocity ( )
2020-10-25 19:05:08 +01:00
entity.v = get_v ( velo ) * get_sign ( entity.v )
-- process controls
if entity.driver then
local ctrl = entity.driver : get_player_control ( )
if ctrl.aux1 then
if aux_timer >= 0.2 then
entity.mouselook = not entity.mouselook
aux_timer = 0
end
end
if ctrl.up then
if get_sign ( entity.v ) >= 0 then
entity.v = entity.v + entity.accel / 10
else
entity.v = entity.v + entity.braking / 10
end
elseif ctrl.down then
if entity.max_speed_reverse == 0 and entity.v == 0 then return end
if get_sign ( entity.v ) < 0 then
entity.v = entity.v - entity.accel / 10
else
entity.v = entity.v - entity.braking / 10
end
end
if entity.mouselook then
if ctrl.left then
2020-10-26 17:38:53 +01:00
entity.object : set_yaw ( entity.object : get_yaw ( ) + get_sign ( entity.v ) * math.rad ( 1 + dtime ) * entity.turn_spd )
2020-10-25 19:05:08 +01:00
elseif ctrl.right then
2020-10-26 17:38:53 +01:00
entity.object : set_yaw ( entity.object : get_yaw ( ) - get_sign ( entity.v ) * math.rad ( 1 + dtime ) * entity.turn_spd )
2020-10-25 19:05:08 +01:00
end
else
2024-12-19 12:55:40 +01:00
if minetest.settings : get_bool ( " lib_mount.limited_turn_speed " ) then
-- WIP and may contain bugs.
local yaw = entity.object : get_yaw ( )
local yaw_delta = entity.driver : get_look_horizontal ( ) - yaw + math.rad ( entity.player_rotation . y or 90 )
if yaw_delta > math.pi then
yaw_delta = yaw_delta - math.pi * 2
elseif yaw_delta < - math.pi then
yaw_delta = yaw_delta + math.pi * 2
end
local yaw_sign = get_sign ( yaw_delta )
if yaw_sign == 0 then
yaw_sign = 1
end
yaw_delta = math.abs ( yaw_delta )
if yaw_delta > math.pi / 2 then
yaw_delta = math.pi / 2
end
local yaw_speed = yaw_delta * entity.turn_spd
yaw_speed = yaw_speed * dtime
entity.object : set_yaw ( yaw + yaw_sign * yaw_speed )
else
entity.object : set_yaw ( entity.driver : get_look_horizontal ( ) + math.rad ( entity.player_rotation . y or 90 ) )
end
2020-10-25 19:05:08 +01:00
end
if ctrl.jump then
if jump_height > 0 and velo.y == 0 then
velo.y = velo.y + ( jump_height * 3 ) + 1
acce_y = acce_y + ( acce_y * 3 ) + 1
end
2021-03-25 16:46:51 +01:00
if can_go_up and can_fly and can_fly == true then
2020-10-25 19:05:08 +01:00
velo.y = velo.y + 1
acce_y = acce_y + 1
end
end
if ctrl.sneak then
2021-03-25 16:46:51 +01:00
if can_go_down and can_fly and can_fly == true then
2020-10-25 19:05:08 +01:00
velo.y = velo.y - 1
acce_y = acce_y - 1
end
end
end
-- if not moving then set animation and return
if entity.v == 0 and velo.x == 0 and velo.y == 0 and velo.z == 0 then
if is_mob and mobs_redo == true then
if stand_anim and stand_anim ~= nil then
set_animation ( entity , stand_anim )
end
end
return
end
2020-10-26 17:38:53 +01:00
2020-10-25 19:05:08 +01:00
-- set animation
if is_mob and mobs_redo == true then
if moving_anim and moving_anim ~= nil then
set_animation ( entity , moving_anim )
end
end
-- Stop!
local s = get_sign ( entity.v )
entity.v = entity.v - 0.02 * s
if s ~= get_sign ( entity.v ) then
2024-12-19 12:55:40 +01:00
entity.object : set_velocity ( vector.new ( 0 , 0 , 0 ) )
2020-10-25 19:05:08 +01:00
entity.v = 0
return
end
2021-03-25 16:46:51 +01:00
-- Stop! (upwards and downwards; applies only if `can_fly` is enabled)
if can_fly == true then
local s2 = get_sign ( velo.y )
local s3 = get_sign ( acce_y )
velo.y = velo.y - 0.02 * s2
acce_y = acce_y - 0.02 * s3
if s2 ~= get_sign ( velo.y ) then
2024-12-19 12:55:40 +01:00
entity.object : set_velocity ( vector.new ( 0 , 0 , 0 ) )
2021-03-25 16:46:51 +01:00
velo.y = 0
return
end
if s3 ~= get_sign ( acce_y ) then
2024-12-19 12:55:40 +01:00
entity.object : set_velocity ( vector.new ( 0 , 0 , 0 ) )
2021-03-25 16:46:51 +01:00
acce_y = 0 -- luacheck: ignore
return
end
end
2020-10-25 19:05:08 +01:00
-- enforce speed limit forward and reverse
local max_spd = entity.max_speed_reverse
if get_sign ( entity.v ) >= 0 then
max_spd = entity.max_speed_forward
end
if math.abs ( entity.v ) > max_spd then
entity.v = entity.v - get_sign ( entity.v )
end
2021-03-25 16:46:51 +01:00
-- Enforce speed limit when going upwards or downwards (applies only if `can_fly` is enabled)
if can_fly == true then
local max_spd_flying = entity.max_speed_downwards
if get_sign ( velo.y ) >= 0 or get_sign ( acce_y ) >= 0 then
max_spd_flying = entity.max_speed_upwards
end
if math.abs ( velo.y ) > max_spd_flying then
velo.y = velo.y - get_sign ( velo.y )
end
if velo.y > max_spd_flying then -- This check is to prevent exceeding the maximum speed; but the above check also prevents that.
velo.y = velo.y - get_sign ( velo.y )
end
if math.abs ( acce_y ) > max_spd_flying then
acce_y = acce_y - get_sign ( acce_y )
end
end
2020-10-26 17:38:53 +01:00
-- Set position, velocity and acceleration
local p = entity.object : get_pos ( )
2024-12-19 12:55:40 +01:00
local new_velo = vector.new ( 0 , 0 , 0 )
local new_acce = vector.new ( 0 , - 9.8 , 0 )
2020-10-25 19:05:08 +01:00
p.y = p.y - 0.5
local ni = node_is ( p )
local v = entity.v
if ni == " air " then
if can_fly == true then
new_acce.y = 0
2021-03-25 16:46:51 +01:00
acce_y = acce_y - get_sign ( acce_y ) -- When going down, this will prevent from exceeding the maximum speed.
2020-10-25 19:05:08 +01:00
end
elseif ni == " liquid " then
if entity.terrain_type == 2 or entity.terrain_type == 3 then
new_acce.y = 0
p.y = p.y + 1
if node_is ( p ) == " liquid " then
if velo.y >= 5 then
velo.y = 5
elseif velo.y < 0 then
new_acce.y = 20
else
new_acce.y = 5
end
else
if math.abs ( velo.y ) < 1 then
2020-10-26 17:38:53 +01:00
local pos = entity.object : get_pos ( )
2020-10-25 19:05:08 +01:00
pos.y = math.floor ( pos.y ) + 0.5
2020-10-26 17:38:53 +01:00
entity.object : set_pos ( pos )
2020-10-25 19:05:08 +01:00
velo.y = 0
end
end
else
v = v * 0.25
end
-- elseif ni == "walkable" then
-- v = 0
-- new_acce.y = 1
end
2020-10-26 17:38:53 +01:00
new_velo = get_velocity ( v , entity.object : get_yaw ( ) - rot_view , velo.y )
2020-10-25 19:05:08 +01:00
new_acce.y = new_acce.y + acce_y
2020-10-26 17:38:53 +01:00
entity.object : set_velocity ( new_velo )
entity.object : set_acceleration ( new_acce )
2020-10-25 19:05:08 +01:00
-- CRASH!
if enable_crash then
local intensity = entity.v2 - v
if intensity >= crash_threshold then
if is_mob then
entity.object : set_hp ( entity.object : get_hp ( ) - intensity )
else
if entity.driver then
local drvr = entity.driver
2024-12-19 12:55:40 +01:00
lib_mount.detach ( drvr , vector.new ( 0 , 0 , 0 ) )
2020-10-26 17:38:53 +01:00
drvr : set_velocity ( new_velo )
2020-10-25 19:05:08 +01:00
drvr : set_hp ( drvr : get_hp ( ) - intensity )
end
2024-12-19 12:55:40 +01:00
ensure_passengers_exists ( entity ) -- Legacy support
for _ , passenger in ipairs ( entity.passengers ) do
if passenger.player then
local pass = passenger.player
lib_mount.detach ( pass , vector.new ( 0 , 0 , 0 ) ) -- This function already copies to old API
pass : set_velocity ( new_velo )
pass : set_hp ( pass : get_hp ( ) - intensity )
end
2020-10-25 19:05:08 +01:00
end
2020-10-26 17:38:53 +01:00
local pos = entity.object : get_pos ( )
------------------
-- Handle drops --
------------------
-- `entity.drop_on_destory` is table which stores all the items that will be dropped on destroy.
-- It will drop one of those items, from `1` to the length, or the end of the table.
local i = math.random ( 1 , # entity.drop_on_destroy )
local j = math.random ( 2 , # entity.drop_on_destroy )
minetest.add_item ( pos , entity.drop_on_destroy [ i ] )
if i ~= j then
minetest.add_item ( pos , entity.drop_on_destroy [ j ] )
end
2020-10-25 19:05:08 +01:00
entity.removed = true
-- delay remove to ensure player is detached
minetest.after ( 0.1 , function ( )
entity.object : remove ( )
end )
end
end
end
entity.v2 = v
end
2024-12-19 12:55:40 +01:00
-- Print after the mod was loaded successfully
local load_message = " [MOD] Library Mount loaded! "
if minetest.log then
minetest.log ( " info " , load_message ) -- Aims at state of the MT software art
else
print ( load_message ) -- Aims at legacy MT software used in the field
end