558 lines
15 KiB
Lua
Raw Permalink Normal View History

--[[
2021-01-12 14:50:57 -06:00
An API framework for mounting objects.
Copyright (C) 2016 blert2112 and contributors
2023-04-04 11:33:27 -06:00
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
--]]
2016-06-24 01:50:41 -04:00
lib_mount = {
passengers = { }
}
local crash_threshold = 6.5 -- ignored if enable_crash is disabled
------------------------------------------------------------------------------
2016-06-24 01:50:41 -04:00
local mobs_redo = false
if minetest.get_modpath("mobs") then
if mobs.mod and mobs.mod == "redo" then
mobs_redo = true
end
2016-06-24 01:50:41 -04:00
end
2016-06-11 02:06:23 -04:00
--
-- 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"
2016-06-11 02:06:23 -04:00
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
2023-04-04 11:33:27 -06:00
return vector.new(x, y, z)
2016-06-11 02:06:23 -04:00
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
2016-06-11 02:06:23 -04:00
local function force_detach(player)
local attached_to = player:get_attach()
2016-08-28 02:50:56 -04:00
if attached_to then
2016-06-11 02:06:23 -04:00
local entity = attached_to:get_luaentity()
2016-08-28 02:50:56 -04:00
if entity.driver and entity.driver == player then
2016-06-11 02:06:23 -04:00
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
2016-06-11 02:06:23 -04:00
end
player:set_detach()
player_api.player_attached[player:get_player_name()] = false
2023-04-04 11:33:27 -06:00
player:set_eye_offset(vector.new(0,0,0), vector.new(0,0,0))
2016-06-11 02:06:23 -04: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)
-------------------------------------------------------------------------------
function lib_mount.attach(entity, player, is_passenger, passenger_number)
2016-08-28 02:50:56 -04:00
local attach_at, eye_offset = {}, {}
if not is_passenger then
passenger_number = nil
end
if not entity.player_rotation then
2023-04-04 11:33:27 -06:00
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
2023-04-04 11:33:27 -06:00
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
2023-04-04 11:33:27 -06:00
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)
2016-08-28 02:50:56 -04:00
else
if not entity.driver_attach_at then
2023-04-04 11:33:27 -06:00
entity.driver_attach_at = vector.new(0,0,0)
2016-08-28 02:50:56 -04:00
end
if not entity.driver_eye_offset then
2023-04-04 11:33:27 -06:00
entity.driver_eye_offset = vector.new(0,0,0)
end
2016-08-28 02:50:56 -04:00
attach_at = entity.driver_attach_at
eye_offset = entity.driver_eye_offset
entity.driver = player
end
2016-06-11 02:06:23 -04:00
force_detach(player)
2016-08-28 02:50:56 -04:00
player:set_attach(entity.object, "", attach_at, entity.player_rotation)
player_api.player_attached[player:get_player_name()] = true
2023-04-04 11:33:27 -06:00
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))
2016-06-11 02:06:23 -04:00
end
function lib_mount.detach(player, offset)
force_detach(player)
player_api.set_animation(player, "stand", 30)
local pos = player:get_pos()
2016-06-11 02:06:23 -04:00
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)
2016-06-11 02:06:23 -04:00
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)
2021-02-01 13:10:50 -06:00
-- 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
2021-02-01 13:10:50 -06:00
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
2016-06-11 02:06:23 -04:00
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
2016-06-11 02:06:23 -04:00
end
end
-- if not moving then set animation and return
2016-06-11 02:06:23 -04:00
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
2016-06-24 01:50:41 -04:00
end
2016-06-11 02:06:23 -04:00
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
2016-06-24 01:50:41 -04:00
end
-- Stop!
2016-06-11 02:06:23 -04:00
local s = get_sign(entity.v)
entity.v = entity.v - 0.02 * s
if s ~= get_sign(entity.v) then
2023-04-04 11:33:27 -06:00
entity.object:set_velocity(vector.new(0,0,0))
2016-06-11 02:06:23 -04:00
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
2023-04-04 11:33:27 -06:00
entity.object:set_velocity(vector.new(0,0,0))
velo.y = 0
return
end
if s3 ~= get_sign(acce_y) then
2023-04-04 11:33:27 -06:00
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)
2016-06-11 02:06:23 -04:00
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()
2023-04-04 11:33:27 -06:00
local new_velo = vector.new(0,0,0)
local new_acce = vector.new(0,-9.8,0)
2016-06-11 02:06:23 -04: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
acce_y = acce_y - get_sign(acce_y) -- When going down, this will prevent from exceeding the maximum speed.
2016-06-11 02:06:23 -04: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
2016-06-11 02:06:23 -04:00
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
2016-06-11 02:06:23 -04:00
end
else
v = v*0.25
2016-06-11 02:06:23 -04:00
end
-- elseif ni == "walkable" then
-- v = 0
-- new_acce.y = 1
2016-06-11 02:06:23 -04:00
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
2023-04-04 11:33:27 -06:00
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
2023-04-04 11:33:27 -06:00
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()
2020-06-04 12:19:53 -05:00
------------------
-- 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.
2020-06-04 12:19:53 -05:00
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])
2020-06-04 12:19:53 -05:00
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
2016-06-11 02:06:23 -04:00
end
Print when the mod is loaded (#7) * amend load print Add a final **print to log** at the end of `init.lua` to indicate the mod was loaded successfully. This idea was derived from other mods which are specifically aimed at MT servers. This could be useful not only for player support (e.g. in the MT forum) but would certainly be useful for MT server admins using the CLI and reading their server logs. * amend load print with extra compatibilty Unfortunately I do not know how to resolve a PR conversation with changing my own commit accordingly (yet). My prior PR was aiming at compatibilty with legacy clients and servers. If you scan the MT forum you will become aware that there seem to be quite many MT 0.4 servers around which are actively used by many players. I would like to continue in using the one load print for better compatibilty with legacy MT software in the field and would use the minetest.log for all other cases where applicable. However, the best solution may be this new piece of improved code, if I understand the MT Lua code correctly. * amend load print with improved compatibility This should be the more correct code now. Unfortunately I do not know how to resolve a PR conversation with changing my own commit accordingly (yet) or how to revoke a commit be GitHub web GUI. My prior PR was aiming at compatibilty with legacy clients and servers. If you scan the MT forum you will become aware that there seem to be quite many MT 0.4 servers around which are actively used by many players. I would like to continue in using the one load print for better compatibilty with legacy MT software in the field and would use the minetest.log for all other cases where applicable. However, the best solution may be this new piece of improved code, if I understand the MT Lua code correctly. * Apply suggestions from code review Co-authored-by: David Leal <halfpacho@gmail.com>
2022-12-21 20:05:56 +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