playeranim/init.lua

475 lines
14 KiB
Lua

if not minetest.settings then
error("Mod playeranim requires Minetest 0.4.16 or newer")
end
playeranim = {}
local ANIMATION_SPEED = tonumber(minetest.settings:get("playeranim.animation_speed")) or 2.4
local ANIMATION_SPEED_SNEAK = tonumber(minetest.settings:get("playeranim.animation_speed_sneak")) or 0.8
local BODY_ROTATION_DELAY = math.max(math.floor(tonumber(minetest.settings:get("playeranim.body_rotation_delay")) or 7), 1)
local BODY_X_ROTATION_SNEAK = tonumber(minetest.settings:get("playeranim.body_x_rotation_sneak")) or 6.0
local ROTATE_ON_SNEAK = minetest.settings:get_bool("playeranim.rotation_sneak", true)
local BONE_POSITION, BONE_ROTATION = (function()
local modname = minetest.get_current_modname()
local modpath = minetest.get_modpath(modname)
return dofile(modpath .. "/model.lua")
end)()
local vector_add, vector_equals = vector.add, vector.equals
local math_sin, math_cos, math_pi, math_deg = math.sin, math.cos, math.pi, math.deg
local table_remove = table.remove
local get_animation = minetest.global_exists("player_api")
and player_api.get_animation or default.player_get_animation
if not get_animation then
error("player_api.get_animation or default.player_get_animation is not found")
end
-- stop player_api from messing stuff up (since 5.3)
if minetest.global_exists("player_api") then
minetest.register_on_mods_loaded(function()
for _, model in pairs(player_api.registered_models) do
if model.animations then
for _, animation in pairs(model.animations) do
animation.x = 0
animation.y = 0
end
end
end
end)
minetest.register_on_joinplayer(function(player)
player:set_local_animation(nil, nil, nil, nil, 0)
end)
end
local function get_pitch_deg(player)
return math_deg(player:get_look_vertical())
end
local players_animation_data = setmetatable({}, {
__index = {
init_player = function(self, player)
self[player] = {
time = 0,
yaw_history = {},
bone_rotations = {},
bone_positions = {},
static_rotations = {},
static_positions = {},
previous_animations = {},
assigned_animations = {},
animation_speed = nil
}
end,
-- time
get_time = function(self, player)
return self[player].time
end,
increment_time = function(self, player, dtime)
self[player].time = self:get_time(player) + dtime
end,
reset_time = function(self, player)
self[player].time = 0
end,
-- yaw_history
get_yaw_history = function(self, player)
return self[player].yaw_history -- Return mutable reference
end,
add_yaw_to_history = function(self, player)
local yaw = player:get_look_horizontal()
local history = self:get_yaw_history(player)
history[#history + 1] = yaw
end,
clear_yaw_history = function(self, player)
if #self[player].yaw_history > 0 then
self[player].yaw_history = {}
end
end,
-- bone_rotations
get_bone_rotation = function(self, player, bone)
return self[player].bone_rotations[bone]
end,
set_bone_rotation = function(self, player, bone, rotation)
self[player].bone_rotations[bone] = rotation
end,
-- bone_positions
get_bone_position = function(self, player, bone)
return self[player].bone_positions[bone]
end,
set_bone_position = function(self, player, bone, position)
self[player].bone_positions[bone] = position
end,
-- static_rotations
get_static_rotations = function(self, player)
return self[player].static_rotations
end,
set_static_rotations = function(self, player, rotations)
self[player].static_rotations = rotations
end,
-- static_positions
get_static_positions = function(self, player)
return self[player].static_positions
end,
set_static_positions = function(self, player, positions)
self[player].static_positions = positions
end,
-- previous_animations
get_previous_animations = function(self, player)
return self[player].previous_animations
end,
set_previous_animations = function(self, player, animations)
self[player].previous_animations = animations
end,
-- assigned_animations
get_assigned_animations = function(self, player)
return self[player].assigned_animations
end,
assign_animation = function(self, player, animation)
self[player].assigned_animations[animation] = true
end,
unassign_animation = function(self, player, animation)
self[player].assigned_animations[animation] = nil
end,
-- animation_speed
get_animation_speed = function(self, player)
return self[player].animation_speed
end,
set_animation_speed = function(self, player, speed)
self[player].animation_speed = speed
end,
}
})
minetest.register_on_joinplayer(function(player)
players_animation_data:init_player(player)
end)
local function rotate_bone(player, bone, rotation, position)
local previous_rotation = players_animation_data:get_bone_rotation(player, bone)
local previous_position = players_animation_data:get_bone_position(player, bone)
if not previous_rotation
or not previous_position
or not vector_equals(rotation, previous_rotation)
or not vector_equals(position, previous_position)
then
player:set_bone_position(bone, position, rotation)
players_animation_data:set_bone_rotation(player, bone, rotation)
players_animation_data:set_bone_position(player, bone, position)
end
end
-- Bone alias
local BODY = "Body"
local HEAD = "Head"
local CAPE = "Cape"
local LARM = "Arm_Left"
local RARM = "Arm_Right"
local LLEG = "Leg_Left"
local RLEG = "Leg_Right"
local ANIMATIONS = {}
local ORDERED_STATIC_ANIMATIONS = {}
local ORDERED_MOVING_ANIMATIONS = {}
local function update_reference_caches()
ORDERED_STATIC_ANIMATIONS = {}
ORDERED_MOVING_ANIMATIONS = {}
local ordered_static = {}
local ordered_moving = {}
for animation, config in pairs(ANIMATIONS) do
if config.options.moving then
table.insert(ordered_moving, { order=config.options.order or 0, name=animation})
else
table.insert(ordered_static, { order=config.options.order or 0, name=animation})
end
end
table.sort(ordered_static, function (a,b) return a.order < b.order end)
for i,animation in ipairs(ordered_static) do
table.insert(ORDERED_STATIC_ANIMATIONS, animation.name)
end
table.sort(ordered_moving, function (a,b) return a.order < b.order end)
for i,animation in ipairs(ordered_moving) do
table.insert(ORDERED_MOVING_ANIMATIONS, animation.name)
end
end
function playeranim.register_animation(name, options, apply)
if type(options) ~= 'table' then options = {} end
ANIMATIONS[name] = {
apply=apply,
options = options
}
update_reference_caches()
end
playeranim.bones = {
BODY = BODY,
HEAD = HEAD,
CAPE = CAPE,
LARM = LARM,
RARM = RARM,
LLEG = LLEG,
RLEG = RLEG
}
function playeranim.assign_animation (player, animation)
return players_animation_data:assign_animation(player, animation)
end
function playeranim.unassign_animation (player, animation)
return players_animation_data:unassign_animation(player, animation)
end
function playeranim.set_animation_speed(player, speed)
return players_animation_data:set_animation_speed(player, speed)
end
function playeranim.set_rotate_on_sneak(newval)
ROTATE_ON_SNEAK = newval
end
playeranim.register_animation("stand", {looking=true,facing=true})
playeranim.register_animation("lay", nil, function(player, _time)
anim.rotations[BODY] = vector.add(anim.rotations[BODY], anim.rotations.body_lay)
anim.positions[BODY] = vector.add(anim.positions[BODY], anim.positions.body_lay)
end)
playeranim.register_animation("sit", {looking=true}, function(player, _time, anim)
anim.rotations[LLEG].x = anim.rotations[LLEG].x+90
anim.rotations[RLEG].x = anim.rotations[RLEG].x+90
anim.rotations[BODY] = vector.add(anim.rotations[BODY], anim.rotations.body_sit)
anim.positions[BODY] = vector.add(anim.positions[BODY], anim.positions.body_sit)
end)
playeranim.register_animation("walk", {moving=true,looking=true,facing=true}, function(player, time, anim)
local sin = math_sin(time * anim.speed * math_pi)
anim.rotations[CAPE].x = anim.rotations[CAPE].x+(-35 * sin - 35)
anim.rotations[LARM].x = anim.rotations[LARM].x+(-55 * sin)
anim.rotations[LLEG].x = anim.rotations[LLEG].x+( 55 * sin)
anim.rotations[RLEG].x = anim.rotations[RLEG].x+(-55 * sin)
if not anim.current_animations.mine then
anim.rotations[RARM].x = anim.rotations[RARM].x+(55 * sin)
end
end)
playeranim.register_animation("mine", {moving=true}, function(player, time, anim)
local cape_sin = math_sin(time * anim.speed * math_pi)
local rarm_sin = math_sin(2 * time * anim.speed * math_pi)
local rarm_cos = -math_cos(2 * time * anim.speed * math_pi)
local pitch = 90 - get_pitch_deg(player)
anim.rotations[CAPE].x = anim.rotations[CAPE].x+(-5 * cape_sin - 5)
anim.rotations[RARM].x = anim.rotations[RARM].x+( 10 * rarm_sin + pitch)
anim.rotations[RARM].y = anim.rotations[RARM].y+( 10 * rarm_cos)
end)
local function rotate_head(player, anim)
local head_x_rotation = -get_pitch_deg(player)
anim.rotations[HEAD].x = anim.rotations[HEAD].x+head_x_rotation
end
local function rotate_body(player, anim)
local body_x_rotation = (function()
local sneak = player:get_player_control().sneak
return sneak and ROTATE_ON_SNEAK and BODY_X_ROTATION_SNEAK or 0
end)()
local body_y_rotation = (function()
local yaw_history = players_animation_data:get_yaw_history(player)
if #yaw_history > BODY_ROTATION_DELAY then
local body_yaw = table_remove(yaw_history, 1)
local player_yaw = player:get_look_horizontal()
return math_deg(player_yaw - body_yaw)
end
return 0
end)()
anim.rotations[BODY].x = anim.rotations[BODY].x+body_x_rotation
anim.rotations[BODY].y = anim.rotations[BODY].y+body_y_rotation
anim.rotations[HEAD].y = anim.rotations[HEAD].y-body_y_rotation
end
local function get_animation_speed(player)
local assigned_speed = players_animation_data:get_animation_speed(player)
if assigned_speed then
return assigned_speed
end
if player:get_player_control().sneak then
return ANIMATION_SPEED_SNEAK
end
return ANIMATION_SPEED
end
local function static_animations_changed(previous_animations, current_animations)
for animation in pairs(previous_animations) do
if not ANIMATIONS[animation].options.moving and not current_animations[animation] then
return true
end
end
for animation in pairs(current_animations) do
if not ANIMATIONS[animation].options.moving and not previous_animations[animation] then
return true
end
end
return false
end
local function any_animation_has_property(animations, property)
for animation in pairs(animations) do
if ANIMATIONS[animation] and ANIMATIONS[animation].options[property] then
return true
end
end
return false
end
local function animate_player(player, dtime)
local animation = get_animation(player).animation
-- Combine manually set animations and system defined animations
local animations = table.copy(players_animation_data:get_assigned_animations(player))
if animation == "walk_mine" then
animations.walk = true
animations.mine = true
else
animations[animation] = true
end
-- Set animation
local animation_speed = get_animation_speed(player)
local moving_animation = any_animation_has_property(animations, 'moving')
local facing_animation = any_animation_has_property(animations, 'facing')
local looking_animation = any_animation_has_property(animations, 'looking')
-- Increment animation time
if moving_animation then
players_animation_data:increment_time(player, dtime)
else
players_animation_data:reset_time(player)
end
-- Yaw history
if facing_animation then
players_animation_data:add_yaw_to_history(player)
else
players_animation_data:clear_yaw_history(player)
end
local previous_animations = players_animation_data:get_previous_animations(player)
local changes = false
local time = players_animation_data:get_time(player)
-- Apply any static animations to the base if they have changed, retrieve them if not
local rotations
local positions
if static_animations_changed(previous_animations, animations) then
rotations = table.copy(BONE_ROTATION.default)
positions = table.copy(BONE_POSITION.default)
for _,animation in ipairs(ORDERED_STATIC_ANIMATIONS) do
if animations[animation] then
local apply = ANIMATIONS[animation] and ANIMATIONS[animation].apply
if apply then
apply(player, time, {
rotations = rotations,
positions = positions,
current_animations = animations,
previous_animations = previous_animations,
speed = animation_speed
})
end
end
end
players_animation_data:set_static_rotations(player, table.copy(rotations))
players_animation_data:set_static_positions(player, table.copy(positions))
changes = true
else
rotations = table.copy(players_animation_data:get_static_rotations(player))
positions = table.copy(players_animation_data:get_static_positions(player))
end
-- Apply any animations that are moving
for _,animation in ipairs(ORDERED_MOVING_ANIMATIONS) do
if animations[animation] then
if not previous_animations[animation] then
changes = true
end
local apply = ANIMATIONS[animation] and ANIMATIONS[animation].apply
if apply then
apply(player, time, {
rotations = rotations,
positions = positions,
current_animations = animations,
previous_animations = previous_animations,
speed = animation_speed
})
end
end
end
-- Head looks around
if looking_animation then
rotate_head(player, {
rotations = rotations,
positions = positions,
current_animations = animations,
previous_animations = previous_animations,
speed = animation_speed
})
end
-- Body follows head
if facing_animation then
rotate_body(player, {
rotations = rotations,
positions = positions,
current_animations = animations,
previous_animations = previous_animations,
speed = animation_speed
})
end
for name, bone in pairs(playeranim.bones) do
rotate_bone(player, bone, rotations[bone], positions[bone])
end
if changes then
players_animation_data:set_previous_animations(player, animations)
end
end
local minetest_get_connected_players = minetest.get_connected_players
minetest.register_globalstep(function(dtime)
for _, player in ipairs(minetest_get_connected_players()) do
animate_player(player, dtime)
end
end)