Integrate mcl_cozy: add the ability to sit and lay on blocks

This commit is contained in:
Mikita Wiśniewski 2025-03-05 14:19:23 +07:00 committed by the-real-herowl
parent 03f46eceda
commit 33000e5f8c
12 changed files with 450 additions and 0 deletions

View file

@ -0,0 +1,53 @@
unused_args = false
allow_defined_top = true
globals = {
"mcl_player"
}
read_globals = {
"core",
"mcl_tmp_message",
string = {fields = {"split"}},
table = {fields = {"copy", "getn"}},
-- My mod
"mcl_cozy",
-- Builtin
"vector", "ItemStack",
"dump", "DIR_DELIM", "VoxelArea", "Settings",
-- MTG
"default", "sfinv", "creative",
-- Mineclone
"mcl_loot", "tga_encoder", "mcl_util", "flowlib", "mcl_sounds", "mcl_autogroup",
"mcl_events", "biomeinfo", "mcl_damage", "mcl_particles", "mcl_worlds", "mcl_colors",
"mcl_explosions", "mcl_vars", "controls", "walkover", "mcl_meshhand", "mcl_fovapi",
"playerphysics", "mcl_hunger", "mcl_death_drop", "mcl_playerplus",
"mcl_gamemode", "mcl_spawn", "mcl_skins", "mcl_sprint", "mcl_playerinfo",
"mcl_item_id", "tt", "mcl_craftguide", "doc", "mcl_dripping",
"mcl_entity_invs", "mcl_item_entity", "mcl_burning",
"mcl_minecarts", "pillager", "mobs_mc", "sounds",
"textures", "mcl_mobs", "mcl_paintings",
"mcl_grindstone", "mcl_walls", "mcl_bamboo",
"mcl_maps", "mcl_clock", "mcl_end", "mcl_starting_inventory",
"mcl_bows", "mcl_bows_s", "mcl_dye", "mcl_copper",
"mcl_flowerpots", "mcl_furnaces", "mcl_farming",
"mcl_campfires", "mcl_crafting_table", "mcl_doors",
"mcl_jukebox", "screwdriver", "mcl_itemframes",
"mcl_heads", "mcl_beacons", "xpanes", "mcl_enchanting",
"mcl_beds", "mcl_throwing", "mcl_banners", "mcl_mobspawners",
"mcl_cocoas", "mcl_smithing_table", "mcl_flowers",
"mcl_core", "mcl_torches", "mcl_target", "mesecon", "mcl_observers",
"mcl_sculk", "mcl_armor", "mcl_lanterns", "mcl_stairs", "mcl_bells",
"mcl_hamburger", "mcl_signs", "mcl_honey", "mcl_stonecutter", "mcl_fire",
"mcl_compass", "mcl_ocean", "mcl_fences", "mcl_buckets", "mcl_potions",
"tnt", "mcl_cherry_blossom", "mcl_portals", "mcl_chests", "mcl_shields",
"mcl_wip", "mcl_raids", "mcl_moon", "lightning", "mcl_weather",
"mcl_formspec", "mcl_death_messages", "mcl_bossbars", "awards",
"mcl_inventory", "mcl_title", "mcl_offhand", "hb", "mcl_experience",
"mcl_info", "mcl_credits", "tsm_railcorridors", "mcl_mapgen_core",
"mcl_structures", "settlements", "mcl_dungeons", "mcl_colors_official"
}

View file

@ -0,0 +1,65 @@
# VoxeLibre Get Comfortable API
## Actions
The mod provides 2 types of player actions: `"sit"` and `"lay"`. Both attach
players to blocks, but show different animations. Whenever there's a mention
of `action`, it means that it has to either be `"sit"` or `"lay"`.
There is currently no way to register new actions.
## `function mcl_cozy[action](pos, _, player)`
A function provided for every action that applies the action to `player`.
If `pos` is nil, the player will be mounted to their current position.
```lua
core.register_node("mymod:sit_on_me", {
description = "Sit on me!",
-- ...
on_rightclick = mcl_cozy.sit
})
```
## `function mcl_cozy.stand_up(player)`
Stands `player` up, unmounting them, resetting their eye offset and setting
their animation back to standing.
## `function mcl_cozy.print_action(name, action)`
Outputs the `action` of `name`, if the corresponding setting is enabled.
```
<coolguy> I'm sleepy
* coolguy lies
<coolguy> Zzz
```
## `function mcl_cozy.actionbar_show_status(player, message)`
Shows `message` on the actionbar of `player`. If `message` is nil, it defaults
to `S("Move to stand up")`.
Historically, this wrapper was needed because Mineclonia had `mcl_tmp_message`
instead of `mcl_title`. Nowadays, it exists purely for convenience and
simplicity.
## `def._mcl_cozy_offset = vector.new(x, y, z)`
It is possible to offset the body point for a player to be mounted to. Usually,
this is a vector with very small values, just enough to make the player feel
comfortable.
* `+x` and `-x` move the player left and right
* `+y` and `-y` move the player up and down
* `+z` and `-z` move the player front and back
```lua
core.register_node("mymod:lay_on_me", {
description = "Lay on me!",
on_rightclick = mcl_cozy.lay,
-- ...
_mcl_cozy_offset = vector.new(0, 0.1, -0.2) -- a bit higher and to the back
})
```

View file

@ -0,0 +1,10 @@
Permission to use, copy, modify, and/or distribute this software for any
purpose with or without fee is hereby granted.
THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH
REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT,
INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR
OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
PERFORMANCE OF THIS SOFTWARE.

View file

@ -0,0 +1,21 @@
# VoxeLibre Get Comfortable
Adds commands to /lay or /sit to VoxeLibre (ex. MineClone2), as well as the
ability to sit on some blocks (stairs and slabs out-of-the-box) by
right-clicking them.
Port of the good old [cozy mod](https://forum.luanti.org/viewtopic.php?t=14143)
(originally made for MTG by everamzah) to work with VL properly and even
provide some nice new features!
## License
The source code is licensed under 0BSD ("public domain"). See `LICENSE` file
for details.
## API
This mod provides its own APIs for other mods to use. See [`API.md`](API.md)
for details.

View file

@ -0,0 +1,233 @@
local S = core.get_translator(core.get_current_modname())
local mcl_cozy_sit_on_stairs = core.settings:get_bool("mcl_cozy_sit_on_stairs", true)
local mcl_cozy_print_actions = core.settings:get_bool("mcl_cozy_print_actions", false)
local SIT_EYE_OFFSET = vector.new(0, -7, 2)
local LAY_EYE_OFFSET = vector.new(0, -13, -5)
local DISTANCE_THRESHOLD = 3
local VELOCITY_THRESHOLD = 0.125
local PLAYERSP_THRESHOLD = 0.1
-- used to partially avoid a race condition(?) causing the player animation to not be set properly when mounting onto
-- a block (e.g. stair)
local ACTION_APPLY_DELAY = 0.05
mcl_cozy = {}
mcl_cozy.pos = {}
-- TODO: when the API is more polished and there is demand, un-internalize this
local actions = {
["sit"] = {
description = S("Sit down"),
message = {
chat = "@1 sits",
actionbar = {
distance_fail = S("You can't sit, the block's too far away!"),
movement_fail = S("You have to stop moving before sitting down!"),
},
},
eye_offset = {SIT_EYE_OFFSET, SIT_EYE_OFFSET},
on_apply = function(player)
mcl_player.player_set_animation(player, "sit", 30)
end,
},
["lay"] = {
description = S("Lie down"),
message = {
chat = "@1 lies",
actionbar = {
distance_fail = S("You can't lay, the block's too far away!"),
movement_fail = S("You have to stop moving before lying down!"),
},
},
eye_offset = {LAY_EYE_OFFSET, LAY_EYE_OFFSET},
on_apply = function(player)
mcl_player.player_set_animation(player, "lay", 30)
end,
},
}
local function is_attached(player)
return mcl_player.player_attached[player:get_player_name()]
end
local function set_attach(player, bool)
mcl_player.player_attached[player:get_player_name()] = bool
end
local function is_air_below(player)
return mcl_playerinfo[player:get_player_name()].node_stand == "air"
end
--[[if mcl_player.players then
is_attached = function(player)
return mcl_player.players[player].attached
end
set_attach = function(player, bool)
mcl_player.players[player].attached = bool
end
is_air_below = function(player)
return mcl_player.players[player].nodes.stand == "air"
end
end]]
function mcl_cozy.print_action(name, action)
if not mcl_cozy_print_actions then return end
local msg = "@1 stands up"
if actions[action] then
msg = actions[action].message.chat
end
core.chat_send_all("* "..S(msg, name))
end
function mcl_cozy.actionbar_show_status(player, message)
if not message then message = S("Move to stand up") end
mcl_title.set(player, "actionbar", {text=message, color="white", stay=60})
end
function mcl_cozy.stand_up(player)
local name = player:get_player_name()
player:set_eye_offset(vector.zero(), vector.zero())
playerphysics.remove_physics_factor(player, "speed", "mcl_cozy:attached")
playerphysics.remove_physics_factor(player, "jump", "mcl_cozy:attached")
set_attach(player, false)
mcl_player.player_set_animation(player, "stand", 30)
mcl_cozy.pos[name] = nil
mcl_cozy.print_action(name, "stand")
end
-- register actions
for action, def in pairs(actions) do
if not def or type(def) ~= "table" then return end
mcl_cozy[action] = function(pos, _, player)
if not player or not player:is_player() then return end
local name = player:get_player_name()
local ppos = player:get_pos()
-- check attachment
if is_attached(player) then
mcl_cozy.pos[name] = nil
mcl_cozy.stand_up(player)
return
end
local delay = 0
if pos then
-- check distance
if vector.distance(pos, ppos) > DISTANCE_THRESHOLD then
mcl_cozy.actionbar_show_status(player, def.message.actionbar.distance_fail)
return
end
-- check movement
if vector.length(player:get_velocity()) > VELOCITY_THRESHOLD then
mcl_cozy.actionbar_show_status(player, def.message.actionbar.movement_fail)
return
end
-- check if occupied
for _, other_pos in pairs(mcl_cozy.pos) do
if vector.distance(pos, other_pos) < PLAYERSP_THRESHOLD then
mcl_cozy.actionbar_show_status(player,
def.message.actionbar.occupancy_fail or S("This block is already occupied!"))
return
end
end
-- all checks pass
local node = core.get_node(pos)
local param2 = node.param2
local ndef = core.registered_nodes[node.name]
local rot
if ndef.paramtype2:find("dir") then
local dir = core.facedir_to_dir(param2)
rot = vector.dir_to_rotation(dir)
-- set player's yaw to match the direction of the block they mount onto
local yaw = rot.y
if param2 % 2 == 0 then
yaw = -yaw
end
if yaw == 0 then
yaw = -math.pi
elseif yaw == math.pi then
yaw = 0
end
player:set_look_horizontal(-yaw)
else
rot = vector.zero()
end
if ndef._mcl_cozy_offset then -- account for the body point offset
local off = ndef._mcl_cozy_offset
pos = pos + vector.rotate(off, rot)
end
delay = ACTION_APPLY_DELAY
player:move_to(pos)
else
pos = ppos
end
player:set_eye_offset(unpack(def.eye_offset))
playerphysics.add_physics_factor(player, "speed", "mcl_cozy:attached", 0)
playerphysics.add_physics_factor(player, "jump", "mcl_cozy:attached", 0)
set_attach(player, true)
mcl_cozy.pos[name] = pos
core.after(delay, function()
if player then
def.on_apply(player)
end
end)
mcl_cozy.print_action(name, action)
mcl_cozy.actionbar_show_status(player)
end
core.register_chatcommand(action, {
description = def.description,
func = function(name)
local player = core.get_player_by_name(name)
-- check the node below player (and if it's air, just don't sit)
if is_air_below(player) then return end
mcl_cozy[action](nil, nil, player)
end
})
end
core.register_globalstep(function(dtime)
for _, player in ipairs(core.get_connected_players()) do
local name = player:get_player_name()
local ctrl = player:get_player_control()
if mcl_cozy.pos[name] then
-- unmount when player tries to move
if (ctrl.up == true or ctrl.down == true or
ctrl.left == true or ctrl.right == true or
ctrl.jump == true or ctrl.sneak == true) then
mcl_cozy.stand_up(player)
-- check the node below player (and if it's air, just unmount)
elseif is_air_below(player) then
mcl_cozy.stand_up(player)
end
end
end
end)
-- fix players getting stuck after they leave while still sitting
core.register_on_joinplayer(function(player)
local name = player:get_player_name()
playerphysics.remove_physics_factor(player, "speed", "mcl_cozy:attached")
playerphysics.remove_physics_factor(player, "jump", "mcl_cozy:attached")
mcl_cozy.pos[name] = nil
end)
if core.get_modpath("mcl_stairs") and mcl_cozy_sit_on_stairs then
dofile(core.get_modpath("mcl_cozy") .. "/stairs_slabs.lua")
end

View file

@ -0,0 +1,8 @@
# textdomain: mcl_cozy
Sit down=S'assoir
Lay down=Se coucher
Move to stand up=Se déplacer pour se lever
This spot is already occupied!=Cette place est déjà occupée !
@1 sits=@1 s'assoit
@1 lies=@1 se couche
@1 stands up=@1 se lève

View file

@ -0,0 +1,8 @@
# textdomain: mcl_cozy
Sit down=Usiądź
Lay down=Połóź się
Move to stand up=Porusz się aby wstać
This spot is already occupied!=To miejsce jest już zajęte!
@1 sits=@1 siedzi
@1 lies=@1 leży
@1 stands up=@1 wstaje

View file

@ -0,0 +1,8 @@
# textdomain: mcl_cozy
Sit down=Сесть
Lay down=Лечь
Move to stand up=Двиньтесь чтобы встать
This spot is already occupied!=Это место уже занято!
@1 sits=@1 сел(а)
@1 lies=@1 лег(ла)
@1 stands up=@1 встал(а)

View file

@ -0,0 +1,8 @@
# textdomain: mcl_cozy
Sit down=
Lay down=
Move to stand up=
This spot is already occupied!=
@1 sits=
@1 lies=
@1 stands up=

View file

@ -0,0 +1,5 @@
name = mcl_cozy
author = rudzik8
description = Sit and lay using chat command and by right-clicking blocks
depends = mcl_player, playerphysics
optional_depends = mcl_title, mcl_stairs, mcl_playerinfo

View file

@ -0,0 +1,23 @@
local function check_param2_and_sit(pos, ...)
local param2 = core.get_node(pos).param2
-- avoid inverted stairs
if param2 >= 20 then return end
return mcl_cozy.sit(pos, ...)
end
core.register_on_mods_loaded(function()
for name, def in pairs(core.registered_nodes) do
-- bottom slabs
if name:find("^mcl_stairs:slab") and not (name:find("_top$")) then
core.override_item(name, {
on_rightclick = mcl_cozy.sit,
})
-- stairs
elseif name:find("^mcl_stairs:stair") then
core.override_item(name, {
on_rightclick = check_param2_and_sit,
_mcl_cozy_offset = vector.new(0, 0, -0.15),
})
end
end
end)

View file

@ -87,6 +87,14 @@ mcl_craftguide_progressive_mode (Learn crafting recipes progressively) bool true
# If disabled, the skin of all players will be character.png
mcl_enable_skin_customization (Enable player skin customization) bool true
# If enabled, players will be able to sit on stairs and slabs by right-clicking
# them
mcl_cozy_sit_on_stairs (Sit on stairs & slabs) bool true
# If enabled, messages like "* player stands up" (in style of /me) will be sent
# each time a player sits, lays or stands up
mcl_cozy_print_actions (Print actions) bool false
# How far the player hand can reach
# Default:4.5
mcl_hand_range (Hand range) float 4.5 1 128