557 lines
15 KiB
Lua
557 lines
15 KiB
Lua
--[[
|
|
An API framework for mounting objects.
|
|
|
|
Copyright (C) 2016 blert2112 and contributors
|
|
Copyright (C) 2019-2023 David Leal (halfpacho@gmail.com) and contributors
|
|
|
|
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 = {
|
|
passengers = { }
|
|
}
|
|
|
|
local crash_threshold = 6.5 -- ignored if enable_crash is disabled
|
|
|
|
------------------------------------------------------------------------------
|
|
|
|
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
|
|
return vector.new(x, y, z)
|
|
end
|
|
|
|
local function get_v(v)
|
|
return math.sqrt(v.x ^ 2 + v.z ^ 2)
|
|
end
|
|
|
|
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
|
|
|
|
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
|
|
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
|
|
end
|
|
player:set_detach()
|
|
player_api.player_attached[player:get_player_name()] = false
|
|
player:set_eye_offset(vector.new(0,0,0), vector.new(0,0,0))
|
|
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)
|
|
|
|
-------------------------------------------------------------------------------
|
|
|
|
function lib_mount.attach(entity, player, is_passenger, passenger_number)
|
|
local attach_at, eye_offset = {}, {}
|
|
|
|
if not is_passenger then
|
|
passenger_number = nil
|
|
end
|
|
|
|
if not entity.player_rotation then
|
|
entity.player_rotation = vector.new(0,0,0)
|
|
end
|
|
|
|
if is_passenger == true then
|
|
-- Legacy support
|
|
ensure_passengers_exists(entity)
|
|
|
|
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
|
|
end
|
|
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
|
|
end
|
|
|
|
attach_at = entity.passengers[passenger_number].attach_at
|
|
eye_offset = entity.passengers[passenger_number].eye_offset
|
|
|
|
entity.passengers[passenger_number].player = player
|
|
lib_mount.passengers[player] = player
|
|
|
|
-- Legacy support
|
|
old_copy_passenger(entity,passenger_number,true,attach_updated,eye_updated)
|
|
else
|
|
if not entity.driver_attach_at then
|
|
entity.driver_attach_at = vector.new(0,0,0)
|
|
end
|
|
if not entity.driver_eye_offset then
|
|
entity.driver_eye_offset = vector.new(0,0,0)
|
|
end
|
|
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)
|
|
player_api.player_attached[player:get_player_name()] = true
|
|
player:set_eye_offset(eye_offset, vector.new(0,0,0))
|
|
minetest.after(0.2, function()
|
|
player_api.set_animation(player, "sit", 30)
|
|
end)
|
|
player:set_look_horizontal(entity.object:get_yaw() + math.rad(entity.player_rotation.y or 90))
|
|
end
|
|
|
|
function lib_mount.detach(player, offset)
|
|
force_detach(player)
|
|
player_api.set_animation(player, "stand", 30)
|
|
local pos = player:get_pos()
|
|
pos = {x = pos.x + offset.x, y = pos.y + 0.2 + offset.y, z = pos.z + offset.z}
|
|
minetest.after(0.1, function()
|
|
player:set_pos(pos)
|
|
end)
|
|
end
|
|
|
|
local aux_timer = 0
|
|
|
|
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)
|
|
-- Sanity checks
|
|
if entity.driver and not entity.driver:get_attach() then entity.driver = nil end
|
|
|
|
-- 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
|
|
end
|
|
|
|
aux_timer = aux_timer + dtime
|
|
|
|
if can_fly and can_fly == true then
|
|
jump_height = 0
|
|
end
|
|
|
|
local rot_steer, rot_view = math.pi/2, 0 -- luacheck: ignore
|
|
if entity.player_rotation.y == 90 then
|
|
rot_steer, rot_view = 0, math.pi/2 -- luacheck: ignore
|
|
end
|
|
|
|
local acce_y = 0
|
|
|
|
local velo = entity.object:get_velocity()
|
|
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
|
|
entity.object:set_yaw(entity.object:get_yaw()+get_sign(entity.v)*math.rad(1+dtime)*entity.turn_spd)
|
|
elseif ctrl.right then
|
|
entity.object:set_yaw(entity.object:get_yaw()-get_sign(entity.v)*math.rad(1+dtime)*entity.turn_spd)
|
|
end
|
|
else
|
|
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
|
|
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
|
|
if can_go_up and can_fly and can_fly == true then
|
|
velo.y = velo.y + 1
|
|
acce_y = acce_y + 1
|
|
end
|
|
end
|
|
if ctrl.sneak then
|
|
if can_go_down and can_fly and can_fly == true then
|
|
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
|
|
|
|
-- 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
|
|
entity.object:set_velocity(vector.new(0,0,0))
|
|
entity.v = 0
|
|
return
|
|
end
|
|
|
|
-- 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
|
|
entity.object:set_velocity(vector.new(0,0,0))
|
|
velo.y = 0
|
|
return
|
|
end
|
|
if s3 ~= get_sign(acce_y) then
|
|
entity.object:set_velocity(vector.new(0,0,0))
|
|
acce_y = 0 -- luacheck: ignore
|
|
return
|
|
end
|
|
end
|
|
|
|
-- 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
|
|
|
|
-- 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
|
|
|
|
-- Set position, velocity and acceleration
|
|
local p = entity.object:get_pos()
|
|
local new_velo = vector.new(0,0,0)
|
|
local new_acce = vector.new(0,-9.8,0)
|
|
|
|
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
|
|
acce_y = acce_y - get_sign(acce_y) -- When going down, this will prevent from exceeding the maximum speed.
|
|
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
|
|
local pos = entity.object:get_pos()
|
|
pos.y = math.floor(pos.y) + 0.5
|
|
entity.object:set_pos(pos)
|
|
velo.y = 0
|
|
end
|
|
end
|
|
else
|
|
v = v*0.25
|
|
end
|
|
-- elseif ni == "walkable" then
|
|
-- v = 0
|
|
-- new_acce.y = 1
|
|
end
|
|
|
|
new_velo = get_velocity(v, entity.object:get_yaw() - rot_view, velo.y)
|
|
new_acce.y = new_acce.y + acce_y
|
|
|
|
entity.object:set_velocity(new_velo)
|
|
entity.object:set_acceleration(new_acce)
|
|
|
|
-- 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
|
|
lib_mount.detach(drvr, vector.new(0,0,0))
|
|
drvr:set_velocity(new_velo)
|
|
drvr:set_hp(drvr:get_hp() - intensity)
|
|
end
|
|
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
|
|
end
|
|
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
|
|
|
|
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
|
|
|
|
-- 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
|