add back edited character_anim
This commit is contained in:
parent
2535c14394
commit
ea5bfccf6f
192
mods/character_anim/Readme.md
Normal file
192
mods/character_anim/Readme.md
Normal file
@ -0,0 +1,192 @@
|
||||
# Character Animations (`character_anim`)
|
||||
|
||||
Animates the character. Resembles [`playeranim`](https://github.com/minetest-mods/playeranim) and [`headanim`](https://github.com/LoneWolfHT/headanim).
|
||||
|
||||
## About
|
||||
|
||||
Depends on [`modlib`](https://github.com/appgurueu/modlib). Code written by Lars Mueller aka LMD or appguru(eu) and licensed under the MIT license. Media (player model) was created by [MTG contributors](https://github.com/minetest/minetest_game/blob/master/mods/player_api/README.txt) (MirceaKitsune, stujones11 and An0n3m0us) and is licensed under the CC BY-SA 3.0 license, as must be its derivatives (`skinsdb` and `3d_armor` variants).
|
||||
|
||||
## Screenshot
|
||||
|
||||

|
||||
|
||||
## Links
|
||||
|
||||
* [GitHub](https://github.com/appgurueu/character_anim) - sources, issue tracking, contributing
|
||||
* [Discord](https://discordapp.com/invite/ysP74by) - discussion, chatting
|
||||
* [Minetest Forum](https://forum.minetest.net/viewtopic.php?f=9&t=25385) - (more organized) discussion
|
||||
* [ContentDB](https://content.minetest.net/packages/LMD/character_anim) - releases (cloning from GitHub is recommended)
|
||||
|
||||
## Features
|
||||
|
||||
* Animates head, right arm & body
|
||||
* Advantages over `playeranim`:
|
||||
* Extracts exact animations and bone positions from glTF models
|
||||
* Also animates attached players (with restrictions on angles)
|
||||
* Advantages over `headanim`:
|
||||
* Provides compatibility for Minetest 5.2.0 and lower
|
||||
* Head angles are clamped, head can tilt sideways
|
||||
* Animates right arm & body as well
|
||||
|
||||
## Instructions
|
||||
|
||||
0. If you want to use a custom model, install [`binarystream`](https://luarocks.org/modules/Tarik02/binarystream) from LuaRocks:
|
||||
1. `sudo luarocks install binarystream` on many UNIX-systems
|
||||
2. `sudo luarocks install luabitop` if you're not using LuaJIT
|
||||
3. Disable mod security. **Make sure you trust all your mods! Ideally import models with all other mods disabled.**
|
||||
4. Export the model as `glTF` and save it under `models/modelname.extension.gltf`
|
||||
5. Do `/ca_import modelname.extension`
|
||||
1. Install and use `character_anim` like any other mod
|
||||
|
||||
## Configuration
|
||||
|
||||
<!--modlib:conf:2-->
|
||||
### `default`
|
||||
|
||||
#### `arm_right`
|
||||
|
||||
##### `radius`
|
||||
|
||||
Right arm spin radius
|
||||
|
||||
* Type: number
|
||||
* Default: `10`
|
||||
* >= -180
|
||||
* <= 180
|
||||
|
||||
##### `speed`
|
||||
|
||||
Right arm spin speed
|
||||
|
||||
* Type: number
|
||||
* Default: `1000`
|
||||
* > 0
|
||||
* <= 10000
|
||||
|
||||
##### `yaw`
|
||||
|
||||
###### `max`
|
||||
|
||||
Right arm yaw (max)
|
||||
|
||||
* Type: number
|
||||
* Default: `160`
|
||||
* >= -180
|
||||
* <= 180
|
||||
|
||||
###### `min`
|
||||
|
||||
Right arm yaw (min)
|
||||
|
||||
* Type: number
|
||||
* Default: `-30`
|
||||
* >= -180
|
||||
* <= 180
|
||||
|
||||
|
||||
|
||||
#### `body`
|
||||
|
||||
##### `turn_speed`
|
||||
|
||||
Body turn speed
|
||||
|
||||
* Type: number
|
||||
* Default: `0.2`
|
||||
* > 0
|
||||
* <= 1000
|
||||
|
||||
|
||||
#### `head`
|
||||
|
||||
##### `pitch`
|
||||
|
||||
###### `max`
|
||||
|
||||
Head pitch (max)
|
||||
|
||||
* Type: number
|
||||
* Default: `80`
|
||||
* >= -180
|
||||
* <= 180
|
||||
|
||||
###### `min`
|
||||
|
||||
Head pitch (min)
|
||||
|
||||
* Type: number
|
||||
* Default: `-60`
|
||||
* >= -180
|
||||
* <= 180
|
||||
|
||||
|
||||
##### `yaw`
|
||||
|
||||
###### `max`
|
||||
|
||||
Head yaw (max)
|
||||
|
||||
* Type: number
|
||||
* Default: `90`
|
||||
* >= -180
|
||||
* <= 180
|
||||
|
||||
###### `min`
|
||||
|
||||
Head yaw (min)
|
||||
|
||||
* Type: number
|
||||
* Default: `-90`
|
||||
* >= -180
|
||||
* <= 180
|
||||
|
||||
|
||||
##### `yaw_restricted`
|
||||
|
||||
###### `max`
|
||||
|
||||
Head yaw restricted (max)
|
||||
|
||||
* Type: number
|
||||
* Default: `45`
|
||||
* >= -180
|
||||
* <= 180
|
||||
|
||||
###### `min`
|
||||
|
||||
Head yaw restricted (min)
|
||||
|
||||
* Type: number
|
||||
* Default: `0`
|
||||
* >= -180
|
||||
* <= 180
|
||||
|
||||
|
||||
##### `yaw_restriction`
|
||||
|
||||
Head yaw restriction
|
||||
|
||||
* Type: number
|
||||
* Default: `60`
|
||||
* >= -180
|
||||
* <= 180
|
||||
|
||||
|
||||
|
||||
### `models`
|
||||
|
||||
Other models, same format as `default` model
|
||||
<!--modlib:conf-->
|
||||
|
||||
## API
|
||||
|
||||
Minetest's `player:set_bone_position` is overridden so that it still works as expected.
|
||||
|
||||
### `character_anim.set_bone_override(player, bonename, position, rotation)`
|
||||
|
||||
The signature resembles that of `set_bone_position`. `bonename` must be a string. The following additional features are provided:
|
||||
|
||||
* Using it like `set_bone_position` by setting `rotation` and `position` to non-`nil` values and using `""` to set the root bone
|
||||
* *Setting only the bone position* by setting `rotation` to `nil` - bone rotation will then be model-animation-determined
|
||||
* *Setting only the bone rotation* by setting `position` to `nil` - bone position will then be model-animation-determined
|
||||
* *Clearing the override* by setting both `rotation` and `position` to `nil` ("unset_bone_position")
|
167
mods/character_anim/importer.lua
Normal file
167
mods/character_anim/importer.lua
Normal file
@ -0,0 +1,167 @@
|
||||
local BinaryStream
|
||||
local previous_require = require
|
||||
rawset(_G, "require", insecure_environment.require)
|
||||
pcall(function()
|
||||
-- Lua 5.1 compatibility
|
||||
rawset(_G, "bit", rawget(_G, "bit") or require"bit")
|
||||
BinaryStream = require"binarystream"
|
||||
end)
|
||||
rawset(_G, "require", previous_require)
|
||||
local io = insecure_environment.io
|
||||
insecure_environment = nil
|
||||
if not BinaryStream then return end
|
||||
|
||||
local data_uri_start = "data:application/octet-stream;base64,"
|
||||
function read_bonedata(path)
|
||||
local gltf = minetest.parse_json(modlib.file.read(path))
|
||||
local buffers = {}
|
||||
for index, buffer in ipairs(gltf.buffers) do
|
||||
buffer = buffer.uri
|
||||
assert(modlib.text.starts_with(buffer, data_uri_start))
|
||||
-- Trim padding characters, see https://github.com/minetest/minetest/commit/f34abaedd2b9277c1862cd9b82ca3338747f104e
|
||||
buffers[index] = assert(minetest.decode_base64(modlib.text.trim_right(buffer:sub((data_uri_start):len()+1), "=")) or nil, "base64 decoding failed, upgrade to Minetest 5.4 or newer")
|
||||
end
|
||||
local accessors = gltf.accessors
|
||||
local function read_accessor(accessor)
|
||||
local buffer_view = gltf.bufferViews[accessor.bufferView + 1]
|
||||
local buffer = assert(buffers[buffer_view.buffer + 1])
|
||||
local binary_stream = BinaryStream(buffer, buffer:len())
|
||||
-- See https://github.com/KhronosGroup/glTF/tree/master/specification/2.0#animations
|
||||
local component_readers = {
|
||||
[5120] = function()
|
||||
return math.max(binary_stream:readS8() / 127, -1)
|
||||
end,
|
||||
[5121] = function()
|
||||
return binary_stream:readU8() / 255
|
||||
end,
|
||||
[5122] = function()
|
||||
return math.max(binary_stream:readS16() / 32767, -1)
|
||||
end,
|
||||
[5123] = function()
|
||||
return binary_stream:readU16() / 65535
|
||||
end,
|
||||
[5126] = function()
|
||||
return binary_stream:readF32()
|
||||
end
|
||||
}
|
||||
local accessor_type = accessor.type
|
||||
local component_reader = component_readers[accessor.componentType]
|
||||
binary_stream:skip(buffer_view.byteOffset)
|
||||
local values = {}
|
||||
for index = 1, accessor.count do
|
||||
if accessor_type == "SCALAR" then
|
||||
values[index] = component_reader()
|
||||
elseif accessor_type == "VEC3" then
|
||||
values[index] = {
|
||||
x = component_reader(),
|
||||
y = component_reader(),
|
||||
z = component_reader()
|
||||
}
|
||||
elseif accessor_type == "VEC4" then
|
||||
values[index] = {
|
||||
component_reader(),
|
||||
component_reader(),
|
||||
component_reader(),
|
||||
component_reader()
|
||||
}
|
||||
end
|
||||
end
|
||||
return values
|
||||
end
|
||||
local nodes = gltf.nodes
|
||||
local animation = gltf.animations[1]
|
||||
local channels, samplers = animation.channels, animation.samplers
|
||||
local animations_by_nodename = {}
|
||||
for _, node in pairs(nodes) do
|
||||
animations_by_nodename[node.name] = {
|
||||
default_translation = node.translation,
|
||||
default_rotation = node.rotation
|
||||
}
|
||||
end
|
||||
for _, channel in ipairs(channels) do
|
||||
local path, node_index, sampler = channel.target.path, channel.target.node, samplers[channel.sampler + 1]
|
||||
assert(sampler.interpolation == "LINEAR")
|
||||
if path == "translation" or path == "rotation" then
|
||||
local time_accessor = accessors[sampler.input + 1]
|
||||
local time, transform = read_accessor(time_accessor), read_accessor(accessors[sampler.output + 1])
|
||||
local min_time, max_time = time_accessor.min and time_accessor.min[1] or modlib.table.min(time), time_accessor.max and time_accessor.max[1] or modlib.table.max(time)
|
||||
local nodename = nodes[node_index + 1].name
|
||||
assert(not animations_by_nodename[nodename][path])
|
||||
animations_by_nodename[nodename][path] = {
|
||||
start_time = min_time,
|
||||
end_time = max_time,
|
||||
keyframes = time,
|
||||
values = transform
|
||||
}
|
||||
end
|
||||
end
|
||||
-- HACK to remove unanimated bones (technically invalid, but only proper way to remove Armature / Player / Camera / Suns)
|
||||
for bone, animation in pairs(animations_by_nodename) do
|
||||
if not(animation.translation or animation.rotation) then
|
||||
animations_by_nodename[bone] = nil
|
||||
end
|
||||
end
|
||||
local is_root, is_child = {}, {}
|
||||
for index, node in pairs(nodes) do
|
||||
if animations_by_nodename[node.name] then
|
||||
local children = node.children
|
||||
if children and #children > 0 then
|
||||
is_root[index] = node
|
||||
for _, child_index in pairs(children) do
|
||||
child_index = child_index + 1
|
||||
assert(not is_child[child_index])
|
||||
is_child[child_index] = true
|
||||
is_root[child_index] = nil
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
local order = {}
|
||||
local insert = modlib.func.curry(table.insert, order)
|
||||
for node_index in pairs(is_root) do
|
||||
local node = nodes[node_index]
|
||||
insert(node.name)
|
||||
local function insert_children(parent, children)
|
||||
for _, child_index in ipairs(children) do
|
||||
local child = nodes[child_index + 1]
|
||||
local name = child.name
|
||||
animations_by_nodename[name].parent = parent
|
||||
insert(name)
|
||||
if child.children then
|
||||
insert_children(name, child.children)
|
||||
end
|
||||
end
|
||||
end
|
||||
insert_children(node.name, node.children)
|
||||
end
|
||||
for index, node in ipairs(nodes) do
|
||||
if animations_by_nodename[node.name] and not(is_root[index] or is_child[index]) then
|
||||
insert(node.name)
|
||||
end
|
||||
end
|
||||
return {order = order, animations_by_nodename = animations_by_nodename}
|
||||
end
|
||||
|
||||
local basepath = modlib.mod.get_resource""
|
||||
|
||||
function import_model(filename)
|
||||
local path = basepath .. "models/".. filename .. ".gltf"
|
||||
if not modlib.file.exists(path) then
|
||||
return false
|
||||
end
|
||||
modeldata[filename] = read_bonedata(path)
|
||||
local file = io.open(basepath .. "modeldata.lua", "w")
|
||||
file:write(minetest.serialize(modeldata))
|
||||
file:close()
|
||||
return true
|
||||
end
|
||||
|
||||
minetest.register_chatcommand("ca_import", {
|
||||
params = "<filename>",
|
||||
description = "Imports a model for use with character_anim",
|
||||
privs = {server = true},
|
||||
func = function(_, filename)
|
||||
local success = import_model(filename)
|
||||
return success, (success and "Model %s imported successfully" or "File %s does not exist"):format(filename)
|
||||
end
|
||||
})
|
7
mods/character_anim/init.lua
Normal file
7
mods/character_anim/init.lua
Normal file
@ -0,0 +1,7 @@
|
||||
local mod = modlib.mod
|
||||
local namespace = mod.create_namespace()
|
||||
namespace.quaternion = modlib.quaternion
|
||||
namespace.conf = mod.configuration()
|
||||
namespace.insecure_environment = minetest.request_insecure_environment() or _G
|
||||
mod.extend"importer"
|
||||
mod.extend"main"
|
243
mods/character_anim/main.lua
Normal file
243
mods/character_anim/main.lua
Normal file
@ -0,0 +1,243 @@
|
||||
modeldata = minetest.deserialize(modlib.file.read(modlib.mod.get_resource"modeldata.lua"))
|
||||
|
||||
function get_animation_value(animation, keyframe_index, is_rotation)
|
||||
local values = animation.values
|
||||
assert(keyframe_index >= 1 and keyframe_index <= #values, keyframe_index)
|
||||
local ratio = keyframe_index % 1
|
||||
if ratio == 0 then
|
||||
return values[keyframe_index]
|
||||
end
|
||||
assert(ratio > 0 and ratio < 1)
|
||||
local prev_value, next_value = values[math.floor(keyframe_index)], values[math.ceil(keyframe_index)]
|
||||
assert(next_value)
|
||||
if is_rotation then
|
||||
return quaternion.slerp(prev_value, next_value, ratio)
|
||||
end
|
||||
return modlib.vector.interpolate(prev_value, next_value, ratio)
|
||||
end
|
||||
|
||||
function is_interacting(player)
|
||||
local control = player:get_player_control()
|
||||
return minetest.check_player_privs(player, "interact") and (control.RMB or control.LMB)
|
||||
end
|
||||
|
||||
local function disable_local_animation(player)
|
||||
return player:set_local_animation(nil, nil, nil, nil, 0)
|
||||
end
|
||||
|
||||
local function get_look_horizontal(player)
|
||||
return 180-math.deg(player:get_look_horizontal())
|
||||
end
|
||||
|
||||
players = {}
|
||||
|
||||
function set_bone_override(player, bonename, position, rotation)
|
||||
local name = player:get_player_name()
|
||||
local value = {
|
||||
position = position,
|
||||
euler_rotation = rotation
|
||||
}
|
||||
-- TODO consider setting empty overrides to nil
|
||||
players[name].bone_positions[bonename] = value
|
||||
end
|
||||
|
||||
-- Raw PlayerRef.set_bone_position
|
||||
local set_bone_position
|
||||
minetest.register_on_joinplayer(function(player)
|
||||
local name = player:get_player_name()
|
||||
disable_local_animation(player)
|
||||
players[name] = {
|
||||
interaction_time = 0,
|
||||
animation_time = 0,
|
||||
animation = {},
|
||||
look_horizontal = get_look_horizontal(player),
|
||||
bone_positions = {}
|
||||
}
|
||||
if not set_bone_position then
|
||||
local PlayerRef = getmetatable(player)
|
||||
set_bone_position = PlayerRef.set_bone_position
|
||||
function PlayerRef:set_bone_position(bonename, position, rotation)
|
||||
if self:is_player() then
|
||||
set_bone_override(self, bonename or "", position or {x = 0, y = 0, z = 0}, rotation or {x = 0, y = 0, z = 0})
|
||||
end
|
||||
return set_bone_position(self, bonename, position, rotation)
|
||||
end
|
||||
end
|
||||
end)
|
||||
|
||||
minetest.register_on_leaveplayer(function(player) players[player:get_player_name()] = nil end)
|
||||
|
||||
local function disable_animation(player)
|
||||
return player:set_animation({x = 0, y = 0}, 0, 0, false)
|
||||
end
|
||||
|
||||
local function clamp(value, range)
|
||||
if value > range.max then
|
||||
return range.max
|
||||
end
|
||||
if value < range.min then
|
||||
return range.min
|
||||
end
|
||||
return value
|
||||
end
|
||||
|
||||
local function normalize_angle(angle)
|
||||
return ((angle + 180) % 360) - 180
|
||||
end
|
||||
|
||||
local function normalize_rotation(euler_rotation)
|
||||
return vector.apply(euler_rotation, normalize_angle)
|
||||
end
|
||||
|
||||
local function handle_player_animations(dtime, player)
|
||||
local mesh = player:get_properties().mesh
|
||||
local modeldata = modeldata[mesh]
|
||||
if not modeldata then
|
||||
return
|
||||
end
|
||||
local conf = conf.models[mesh] or conf.default
|
||||
local name = player:get_player_name()
|
||||
local range, frame_speed, frame_blend, frame_loop = player:get_animation()
|
||||
disable_animation(player)
|
||||
local player_animation = players[name]
|
||||
local anim = {range, frame_speed, frame_blend, frame_loop}
|
||||
local animation_time = player_animation.animation_time
|
||||
if (range.x == 0 and range.y == 0 and frame_speed == 0 and frame_blend == 0 and frame_loop == false) or modlib.table.equals_noncircular(anim, player_animation.animation) then
|
||||
range, frame_speed, frame_blend, frame_loop = unpack(player_animation.animation)
|
||||
animation_time = animation_time + dtime
|
||||
else
|
||||
player_animation.animation = anim
|
||||
animation_time = 0
|
||||
end
|
||||
player_animation.animation_time = animation_time
|
||||
local range_min, range_max = range.x + 1, range.y + 1
|
||||
local keyframe
|
||||
if range_min == range_max then
|
||||
keyframe = range_min
|
||||
elseif frame_loop then
|
||||
keyframe = range_min + ((animation_time * frame_speed) % (range_max - range_min))
|
||||
else
|
||||
keyframe = math.min(range_max, range_min + animation_time * frame_speed)
|
||||
end
|
||||
local bone_positions = {}
|
||||
for _, bone in ipairs(modeldata.order) do
|
||||
local animation = modeldata.animations_by_nodename[bone]
|
||||
local position, rotation = animation.default_translation, animation.default_rotation
|
||||
if animation.translation then
|
||||
position = get_animation_value(animation.translation, keyframe)
|
||||
end
|
||||
position = {x = -position.x, y = position.y, z = -position.z}
|
||||
if animation.rotation then
|
||||
-- rotation override instead of additional rotation (quaternion.multiply(animated_rotation, rotation))
|
||||
rotation = get_animation_value(animation.rotation, keyframe, true)
|
||||
end
|
||||
rotation = {unpack(rotation)}
|
||||
rotation[1] = -rotation[1]
|
||||
local euler_rotation
|
||||
local parent = animation.parent
|
||||
if parent then
|
||||
rotation[4] = -rotation[4]
|
||||
local values = bone_positions[parent]
|
||||
local absolute_rotation = quaternion.multiply(values.rotation, rotation)
|
||||
euler_rotation = vector.subtract(quaternion.to_euler_rotation(absolute_rotation), values.euler_rotation)
|
||||
else
|
||||
euler_rotation = quaternion.to_euler_rotation(rotation)
|
||||
end
|
||||
bone_positions[bone] = {position = position, rotation = rotation, euler_rotation = euler_rotation}
|
||||
end
|
||||
local Body, Head, Arm_Right = bone_positions.Body.euler_rotation, bone_positions.Head.euler_rotation, bone_positions.Arm_Right.euler_rotation
|
||||
local look_vertical = -math.deg(player:get_look_vertical())
|
||||
Head.x = look_vertical
|
||||
local interacting = is_interacting(player)
|
||||
if interacting then
|
||||
local interaction_time = player_animation.interaction_time
|
||||
-- note: +90 instead +Arm_Right.x because it looks better
|
||||
Arm_Right.x = 90 + look_vertical - math.sin(-interaction_time) * conf.arm_right.radius
|
||||
Arm_Right.y = Arm_Right.y + math.cos(-interaction_time) * conf.arm_right.radius
|
||||
player_animation.interaction_time = interaction_time + dtime * math.rad(conf.arm_right.speed)
|
||||
else
|
||||
player_animation.interaction_time = 0
|
||||
end
|
||||
local look_horizontal = get_look_horizontal(player)
|
||||
local diff = look_horizontal - player_animation.look_horizontal
|
||||
if math.abs(diff) > 180 then
|
||||
diff = math.sign(-diff) * 360 + diff
|
||||
end
|
||||
local moving_diff = math.sign(diff) * math.abs(diff) * math.min(1, dtime / conf.body.turn_speed)
|
||||
player_animation.look_horizontal = player_animation.look_horizontal + moving_diff
|
||||
if math.abs(moving_diff) < 1e-6 then
|
||||
player_animation.look_horizontal = look_horizontal
|
||||
end
|
||||
local lag_behind = diff - moving_diff
|
||||
local attach_parent, _, _, attach_rotation = player:get_attach()
|
||||
-- TODO properly handle eye offset & height vs. actual head position
|
||||
if attach_parent then
|
||||
local parent_rotation = attach_parent:get_rotation()
|
||||
if attach_rotation and parent_rotation then
|
||||
parent_rotation = vector.apply(parent_rotation, math.deg)
|
||||
local total_rotation = normalize_rotation(vector.subtract(parent_rotation, attach_rotation))
|
||||
local function rotate_relative(euler_rotation)
|
||||
-- HACK +180
|
||||
euler_rotation.y = euler_rotation.y + look_horizontal + 180
|
||||
local new_rotation = normalize_rotation(vector.add(euler_rotation, total_rotation))
|
||||
modlib.table.add_all(euler_rotation, new_rotation)
|
||||
end
|
||||
|
||||
rotate_relative(Head)
|
||||
if interacting then rotate_relative(Arm_Right) end
|
||||
end
|
||||
elseif not player_api.player_attached[name] then
|
||||
Body.y = Body.y - lag_behind
|
||||
Head.y = Head.y + lag_behind
|
||||
if interacting then Arm_Right.y = Arm_Right.y + lag_behind end
|
||||
end
|
||||
|
||||
-- HACK assumes that Body is root & parent bone of Head, only takes rotation around X-axis into consideration
|
||||
Head.x = normalize_angle(Head.x + Body.x)
|
||||
if interacting then Arm_Right.x = normalize_angle(Arm_Right.x + Body.x) end
|
||||
|
||||
Head.x = clamp(Head.x, conf.head.pitch)
|
||||
Head.y = clamp(Head.y, conf.head.yaw)
|
||||
if math.abs(Head.y) > conf.head.yaw_restriction then
|
||||
Head.x = clamp(Head.x, conf.head.yaw_restricted)
|
||||
end
|
||||
Arm_Right.y = clamp(Arm_Right.y, conf.arm_right.yaw)
|
||||
|
||||
if spriteguns and spriteguns.is_wielding_gun(name) then
|
||||
local tempvertlook = math.rad(look_vertical)
|
||||
local Rightval = vector.multiply(vector.dir_to_rotation(vector.rotate({x=0,y=0,z=1}, {x=tempvertlook,y=0,z=0})), 180/math.pi)
|
||||
Rightval.x = Rightval.x + 85
|
||||
bone_positions.Arm_Right.euler_rotation = Rightval
|
||||
bone_positions.Arm_Right.position.x = bone_positions.Arm_Right.position.x + .9
|
||||
local Leftval = vector.multiply(vector.dir_to_rotation(vector.rotate({x=-.8,y=0,z=1}, {x=tempvertlook,y=0,z=0})), 180/math.pi)
|
||||
Leftval.x = Leftval.x + 85
|
||||
bone_positions.Arm_Left.euler_rotation = Leftval
|
||||
bone_positions.Arm_Left.position.x = bone_positions.Arm_Left.position.x - .9
|
||||
end
|
||||
|
||||
for bone, values in pairs(bone_positions) do
|
||||
local overridden_values = player_animation.bone_positions[bone]
|
||||
overridden_values = overridden_values or {}
|
||||
set_bone_position(player, bone, overridden_values.position or values.position, overridden_values.euler_rotation or values.euler_rotation)
|
||||
end
|
||||
end
|
||||
|
||||
if player_api then
|
||||
-- TODO prevent player_api from using player:set_animation
|
||||
local set_animation = player_api.set_animation
|
||||
player_api.set_animation = function(player, ...)
|
||||
local player_animation = players[player:get_player_name()]
|
||||
if not player_animation then
|
||||
return
|
||||
end
|
||||
local ret = {set_animation(player, ...)}
|
||||
handle_player_animations(0, player)
|
||||
return unpack(ret)
|
||||
end
|
||||
end
|
||||
|
||||
minetest.register_globalstep(function(dtime)
|
||||
for _, player in pairs(minetest.get_connected_players()) do
|
||||
handle_player_animations(dtime, player)
|
||||
end
|
||||
end)
|
6
mods/character_anim/mod.conf
Normal file
6
mods/character_anim/mod.conf
Normal file
@ -0,0 +1,6 @@
|
||||
name = character_anim
|
||||
title = Character Animations
|
||||
description = Animates the character
|
||||
author = LMD aka appguru(eu)
|
||||
depends = modlib
|
||||
optional_depends = player_api, 3d_armor, skinsdb
|
1
mods/character_anim/modeldata.lua
Normal file
1
mods/character_anim/modeldata.lua
Normal file
File diff suppressed because one or more lines are too long
BIN
mods/character_anim/models/3d_armor_character.b3d
Normal file
BIN
mods/character_anim/models/3d_armor_character.b3d
Normal file
Binary file not shown.
1021
mods/character_anim/models/3d_armor_character.b3d.gltf
Normal file
1021
mods/character_anim/models/3d_armor_character.b3d.gltf
Normal file
File diff suppressed because one or more lines are too long
BIN
mods/character_anim/models/3d_armor_character.blend
Normal file
BIN
mods/character_anim/models/3d_armor_character.blend
Normal file
Binary file not shown.
BIN
mods/character_anim/models/character.b3d
Normal file
BIN
mods/character_anim/models/character.b3d
Normal file
Binary file not shown.
759
mods/character_anim/models/character.b3d.gltf
Normal file
759
mods/character_anim/models/character.b3d.gltf
Normal file
File diff suppressed because one or more lines are too long
BIN
mods/character_anim/models/character.blend
Normal file
BIN
mods/character_anim/models/character.blend
Normal file
Binary file not shown.
BIN
mods/character_anim/models/skinsdb_3d_armor_character_5.b3d
Normal file
BIN
mods/character_anim/models/skinsdb_3d_armor_character_5.b3d
Normal file
Binary file not shown.
1120
mods/character_anim/models/skinsdb_3d_armor_character_5.b3d.gltf
Normal file
1120
mods/character_anim/models/skinsdb_3d_armor_character_5.b3d.gltf
Normal file
File diff suppressed because one or more lines are too long
BIN
mods/character_anim/models/skinsdb_3d_armor_character_5.blend
Normal file
BIN
mods/character_anim/models/skinsdb_3d_armor_character_5.blend
Normal file
Binary file not shown.
66
mods/character_anim/schema.lua
Normal file
66
mods/character_anim/schema.lua
Normal file
@ -0,0 +1,66 @@
|
||||
local function angle(description, default)
|
||||
return { type = "number", range = { min = -180, max = 180 }, description = description, default = default }
|
||||
end
|
||||
local function range(description, default_min, default_max)
|
||||
return {
|
||||
type = "table",
|
||||
entries = {
|
||||
min = angle(description .. " (min)", default_min),
|
||||
max = angle(description .. " (max)", default_max)
|
||||
},
|
||||
func = function(range)
|
||||
if range.max < range.min then return "Minimum range value is not <= maximum range value" end
|
||||
end
|
||||
}
|
||||
end
|
||||
local model = {
|
||||
type = "table",
|
||||
entries = {
|
||||
body = {
|
||||
type = "table",
|
||||
entries = {
|
||||
turn_speed = {
|
||||
type = "number",
|
||||
range = { min_exclusive = 0, max = 1e3 },
|
||||
description = "Body turn speed",
|
||||
default = 0.2
|
||||
}
|
||||
}
|
||||
},
|
||||
head = {
|
||||
type = "table",
|
||||
entries = {
|
||||
pitch = range("Head pitch", -60, 80),
|
||||
yaw = range("Head yaw", -90, 90),
|
||||
yaw_restricted = range("Head yaw restricted", 0, 45),
|
||||
yaw_restriction = angle("Head yaw restriction", 60)
|
||||
}
|
||||
},
|
||||
arm_right = {
|
||||
type = "table",
|
||||
entries = {
|
||||
radius = angle("Right arm spin radius", 10),
|
||||
speed = {
|
||||
type = "number",
|
||||
range = { min_exclusive = 0, max = 1e4 },
|
||||
description = "Right arm spin speed",
|
||||
default = 1e3
|
||||
},
|
||||
yaw = range("Right arm yaw", -30, 160)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
type = "table",
|
||||
entries = {
|
||||
default = model,
|
||||
models = {
|
||||
type = "table",
|
||||
keys = { type = "string" },
|
||||
values = model,
|
||||
description = "Other models, same format as `default` model"
|
||||
}
|
||||
}
|
||||
}
|
BIN
mods/character_anim/screenshot.png
Normal file
BIN
mods/character_anim/screenshot.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 27 KiB |
33
mods/character_anim/settingtypes.txt
Normal file
33
mods/character_anim/settingtypes.txt
Normal file
@ -0,0 +1,33 @@
|
||||
[*character_anim.default]
|
||||
[**character_anim.default.arm_right]
|
||||
# Right arm spin radius
|
||||
character_anim.default.arm_right.radius (Character anim Default Arm right Radius) float 10
|
||||
# Right arm spin speed
|
||||
character_anim.default.arm_right.speed (Character anim Default Arm right Speed) float 1000
|
||||
[***character_anim.default.arm_right.yaw]
|
||||
# Right arm yaw (max)
|
||||
character_anim.default.arm_right.yaw.max (Character anim Default Arm right Yaw Max) float 160
|
||||
# Right arm yaw (min)
|
||||
character_anim.default.arm_right.yaw.min (Character anim Default Arm right Yaw Min) float -30
|
||||
[**character_anim.default.body]
|
||||
# Body turn speed
|
||||
character_anim.default.body.turn_speed (Character anim Default Body Turn speed) float 0.2
|
||||
[**character_anim.default.head]
|
||||
[***character_anim.default.head.pitch]
|
||||
# Head pitch (max)
|
||||
character_anim.default.head.pitch.max (Character anim Default Head Pitch Max) float 80
|
||||
# Head pitch (min)
|
||||
character_anim.default.head.pitch.min (Character anim Default Head Pitch Min) float -60
|
||||
[***character_anim.default.head.yaw]
|
||||
# Head yaw (max)
|
||||
character_anim.default.head.yaw.max (Character anim Default Head Yaw Max) float 90
|
||||
# Head yaw (min)
|
||||
character_anim.default.head.yaw.min (Character anim Default Head Yaw Min) float -90
|
||||
[***character_anim.default.head.yaw_restricted]
|
||||
# Head yaw restricted (max)
|
||||
character_anim.default.head.yaw_restricted.max (Character anim Default Head Yaw restricted Max) float 45
|
||||
# Head yaw restricted (min)
|
||||
character_anim.default.head.yaw_restricted.min (Character anim Default Head Yaw restricted Min) float 0
|
||||
# Head yaw restriction
|
||||
character_anim.default.head.yaw_restriction (Character anim Default Head Yaw restriction) float 60
|
||||
[*character_anim.models]
|
Loading…
x
Reference in New Issue
Block a user