VoxeLibre/mods/ITEMS/mcl_beds/functions.lua
Elias Fleckenstein 0b27b6bec3 Mob API: Merge mobs_mc and mcl_mobs into one mod
DO NOT USE IN PRODUCTION, DO NOT START OLD WORLDS WITHOUT A BACKUP
These are the first steps of the new mob API. The game does actually start, but mobs do not work yet.
You will also get some warnings about mob spawners, but don't worry about that.
This is really just some 'first impression' of how the mob API is gonna look like. Some things are already complete, like the agression system.
AI and attacking have not been worked on yet.
mobs_mc and mcl_mobs have actually been merged into one piece but I will probably change that again in the future actually, and split the different mobs into different mods.
There are also a few usefull things like the universal mount API and a more general purpose smoke API, but all of this is still far from complete.
I'll put some work into the API this week but probably not next week, then I'll see but don't expect this to be done before 2022.
I'll work on it, but I'll do it slowly and progressively to not get burned out again and to still have enough time to graduate from school in the meantime.
2021-09-01 23:27:47 +02:00

405 lines
12 KiB
Lua

local S = minetest.get_translator("mcl_beds")
local F = minetest.formspec_escape
local pi = math.pi
local player_in_bed = 0
local is_sp = minetest.is_singleplayer()
local weather_mod = minetest.get_modpath("mcl_weather") ~= nil
local explosions_mod = minetest.get_modpath("mcl_explosions") ~= nil
local spawn_mod = minetest.get_modpath("mcl_spawn")
local worlds_mod = minetest.get_modpath("mcl_worlds")
-- Helper functions
local function get_look_yaw(pos)
local n = minetest.get_node(pos)
if n.param2 == 1 then
return pi / 2, n.param2
elseif n.param2 == 3 then
return -pi / 2, n.param2
elseif n.param2 == 0 then
return pi, n.param2
else
return 0, n.param2
end
end
local function is_night_skip_enabled()
local enable_night_skip = minetest.settings:get_bool("enable_bed_night_skip")
if enable_night_skip == nil then
enable_night_skip = true
end
return enable_night_skip
end
local function check_in_beds(players)
local in_bed = mcl_beds.player
if not players then
players = minetest.get_connected_players()
end
for n, player in pairs(players) do
local name = player:get_player_name()
if not in_bed[name] then
return false
end
end
return #players > 0
end
-- These monsters do not prevent sleep
local monster_exceptions = {
["mcl_mobs:ghast"] = true,
["mcl_mobs:enderdragon"] = true,
["mcl_mobs:killer_bunny"] = true,
["mcl_mobs:slime_big"] = true,
["mcl_mobs:slime_small"] = true,
["mcl_mobs:slime_tiny"] = true,
["mcl_mobs:magma_cube_big"] = true,
["mcl_mobs:magma_cube_small"] = true,
["mcl_mobs:magma_cube_tiny"] = true,
["mcl_mobs:shulker"] = true,
}
local function lay_down(player, pos, bed_pos, state, skip)
local name = player:get_player_name()
local hud_flags = player:hud_get_flags()
if not player or not name then
return false
end
local yaw, param2, dir, bed_pos2, bed_center
if bed_pos then
yaw, param2 = get_look_yaw(bed_pos)
dir = minetest.facedir_to_dir(param2)
bed_pos2 = {x = bed_pos.x - dir.x, y = bed_pos.y, z = bed_pos.z - dir.z}
bed_center = {x = bed_pos.x - dir.x/2, y = bed_pos.y + 0.1, z = bed_pos.z - dir.z/2}
-- save respawn position when entering bed
if spawn_mod and mcl_spawn.set_spawn_pos(player, bed_pos, nil) then
minetest.chat_send_player(name, S("New respawn position set!"))
end
-- No sleeping if too far away
if vector.distance(bed_pos, pos) > 2 and vector.distance(bed_pos2, pos) > 2 then
return false, S("You can't sleep, the bed's too far away!")
end
for _, other_pos in pairs(mcl_beds.bed_pos) do
if vector.distance(bed_pos2, other_pos) < 0.1 then
return false, S("This bed is already occupied!")
end
end
-- No sleeping while moving. Slightly different behaviour than in MC.
-- FIXME: Velocity threshold should be 0.01 but Minetest 5.3.0
-- sometimes reports incorrect Y speed. A velocity threshold
-- of 0.125 still seems good enough.
if vector.length(player:get_velocity() or player:get_player_velocity()) > 0.125 then
return false, S("You have to stop moving before going to bed!")
end
-- No sleeping if monsters nearby.
-- The exceptions above apply.
-- Zombie pigmen only prevent sleep while they are hostle.
for _, obj in pairs(minetest.get_objects_inside_radius(bed_pos, 8)) do
if obj ~= nil and not obj:is_player() then
local ent = obj:get_luaentity()
local mobname = ent.name
local def = minetest.registered_entities[mobname]
-- Approximation of monster detection range
if def.is_mob and ((mobname ~= "mcl_mobs:pigman" and def.type == "monster" and not monster_exceptions[mobname]) or (mobname == "mcl_mobs:pigman" and ent.state == "attack")) then
if math.abs(bed_pos.y - obj:get_pos().y) <= 5 then
return false, S("You can't sleep now, monsters are nearby!")
end
end
end
end
end
-- stand up
if state ~= nil and not state then
local p = mcl_beds.pos[name] or nil
if mcl_beds.player[name] ~= nil then
mcl_beds.player[name] = nil
player_in_bed = player_in_bed - 1
end
mcl_beds.pos[name] = nil
mcl_beds.bed_pos[name] = nil
if p then
player:set_pos(p)
end
-- skip here to prevent sending player specific changes (used for leaving players)
if skip then
return false
end
-- physics, eye_offset, etc
player:set_eye_offset({x = 0, y = 0, z = 0}, {x = 0, y = 0, z = 0})
if player:get_look_vertical() > 0 then
player:set_look_vertical(0)
end
mcl_player.player_attached[name] = false
playerphysics.remove_physics_factor(player, "speed", "mcl_beds:sleeping")
playerphysics.remove_physics_factor(player, "jump", "mcl_beds:sleeping")
player:get_meta():set_string("mcl_beds:sleeping", "false")
hud_flags.wielditem = true
mcl_player.player_set_animation(player, "stand" , 30)
-- lay down
else
local n1 = minetest.get_node({x = bed_pos.x, y = bed_pos.y + 1, z = bed_pos.z})
local n2 = minetest.get_node({x = bed_pos2.x, y = bed_pos2.y + 1, z = bed_pos2.z})
local def1 = minetest.registered_nodes[n1.name]
local def2 = minetest.registered_nodes[n2.name]
if def1.walkable or def2.walkable then
return false, S("You can't sleep, the bed is obstructed!")
elseif (def1.damage_per_second ~= nil and def1.damage_per_second > 0) or (def2.damage_per_second ~= nil and def2.damage_per_second > 0) then
return false, S("It's too dangerous to sleep here!")
end
-- Check day of time and weather
local tod = minetest.get_timeofday() * 24000
-- Values taken from Minecraft Wiki with offset of +6000
if tod < 18541 and tod > 5458 and (not weather_mod or (mcl_weather.get_weather() ~= "thunder")) then
return false, S("You can only sleep at night or during a thunderstorm.")
end
mcl_beds.player[name] = 1
mcl_beds.pos[name] = pos
mcl_beds.bed_pos[name] = bed_pos2
player_in_bed = player_in_bed + 1
-- physics, eye_offset, etc
player:set_eye_offset({x = 0, y = -13, z = 0}, {x = 0, y = 0, z = 0})
player:set_look_horizontal(yaw)
-- With head tracking:
player:set_look_vertical(0)
-- Without head tracking:
-- player:set_look_vertical(-(math.pi/2))
player:get_meta():set_string("mcl_beds:sleeping", "true")
playerphysics.add_physics_factor(player, "speed", "mcl_beds:sleeping", 0)
playerphysics.add_physics_factor(player, "jump", "mcl_beds:sleeping", 0)
player:set_pos(bed_center)
mcl_player.player_attached[name] = true
hud_flags.wielditem = false
mcl_player.player_set_animation(player, "lay" , 0)
end
player:hud_set_flags(hud_flags)
return true
end
local function update_formspecs(finished, ges)
local ges = ges or #minetest.get_connected_players()
local form_n = "size[12,5;true]"
local all_in_bed = ges == player_in_bed
local night_skip = is_night_skip_enabled()
local button_leave = "button_exit[4,3;4,0.75;leave;"..F(S("Leave bed")).."]"
local button_abort = "button_exit[4,3;4,0.75;leave;"..F(S("Abort sleep")).."]"
local bg_presleep = "bgcolor[#00000080;true]"
local bg_sleep = "bgcolor[#000000FF;true]"
if finished then
for name,_ in pairs(mcl_beds.player) do
minetest.close_formspec(name, "mcl_beds_form")
end
return
elseif not is_sp then
local text = S("Players in bed: @1/@2", player_in_bed, ges)
if not night_skip then
text = text .. "\n" .. S("Note: Night skip is disabled.")
form_n = form_n .. bg_presleep
form_n = form_n .. button_leave
elseif all_in_bed then
text = text .. "\n" .. S("You're sleeping.")
form_n = form_n .. bg_sleep
form_n = form_n .. button_abort
else
text = text .. "\n" .. S("You will fall asleep when all players are in bed.")
form_n = form_n .. bg_presleep
form_n = form_n .. button_leave
end
form_n = form_n .. "label[0.5,1;"..F(text).."]"
else
local text
if night_skip then
text = S("You're sleeping.")
form_n = form_n .. bg_sleep
form_n = form_n .. button_abort
else
text = S("You're in bed.") .. "\n" .. S("Note: Night skip is disabled.")
form_n = form_n .. bg_presleep
form_n = form_n .. button_leave
end
form_n = form_n .. "label[0.5,1;"..F(text).."]"
end
for name,_ in pairs(mcl_beds.player) do
minetest.show_formspec(name, "mcl_beds_form", form_n)
end
end
-- Public functions
-- Handle environment stuff related to sleeping: skip night and thunderstorm
function mcl_beds.sleep()
local storm_skipped = mcl_beds.skip_thunderstorm()
-- Always clear weather
if weather_mod then
mcl_weather.change_weather("none")
end
if is_night_skip_enabled() then
if not storm_skipped then
mcl_beds.skip_night()
end
mcl_beds.kick_players()
end
end
-- Throw all players out of bed
function mcl_beds.kick_players()
for name, _ in pairs(mcl_beds.player) do
local player = minetest.get_player_by_name(name)
lay_down(player, nil, nil, false)
end
update_formspecs(false)
end
-- Throw a player out of bed
function mcl_beds.kick_player(player)
local name = player:get_player_name()
if mcl_beds.player[name] ~= nil then
lay_down(player, nil, nil, false)
update_formspecs(false)
minetest.close_formspec(name, "mcl_beds_form")
end
end
function mcl_beds.skip_night()
minetest.set_timeofday(0.25) -- tod = 6000
end
function mcl_beds.skip_thunderstorm()
-- Skip thunderstorm
if weather_mod and mcl_weather.get_weather() == "thunder" then
-- Sleep for a half day (=minimum thunderstorm duration)
minetest.set_timeofday((minetest.get_timeofday() + 0.5) % 1)
return true
end
return false
end
function mcl_beds.on_rightclick(pos, player, is_top)
-- Anti-Inception: Don't allow to sleep while you're sleeping
if player:get_meta():get_string("mcl_beds:sleeping") == "true" then
return
end
if worlds_mod then
local dim = mcl_worlds.pos_to_dimension(pos)
if dim == "nether" or dim == "end" then
-- Bed goes BOOM in the Nether or End.
local node = minetest.get_node(pos)
local dir = minetest.facedir_to_dir(node.param2)
minetest.remove_node(pos)
minetest.remove_node(string.sub(node.name, -4) == "_top" and vector.subtract(pos, dir) or vector.add(pos, dir))
if explosions_mod then
mcl_explosions.explode(pos, 5, {drop_chance = 1.0, fire = true})
end
return
end
end
local name = player:get_player_name()
local ppos = player:get_pos()
-- move to bed
if not mcl_beds.player[name] then
local success, message
if is_top then
success, message = lay_down(player, ppos, pos)
else
local node = minetest.get_node(pos)
local dir = minetest.facedir_to_dir(node.param2)
local other = vector.add(pos, dir)
success, message = lay_down(player, ppos, other)
end
if message then
mcl_tmp_message.message(player, message)
end
else
lay_down(player, nil, nil, false)
end
update_formspecs(false)
-- skip the night and let all players stand up
if check_in_beds() then
minetest.after(5, function()
if check_in_beds() then
update_formspecs(is_night_skip_enabled())
mcl_beds.sleep()
end
end)
end
end
-- Callbacks
minetest.register_on_joinplayer(function(player)
local meta = player:get_meta()
if meta:get_string("mcl_beds:sleeping") == "true" then
-- Make player awake on joining server
meta:set_string("mcl_beds:sleeping", "false")
end
playerphysics.remove_physics_factor(player, "speed", "mcl_beds:sleeping")
playerphysics.remove_physics_factor(player, "jump", "mcl_beds:sleeping")
update_formspecs(false)
end)
minetest.register_on_leaveplayer(function(player)
lay_down(player, nil, nil, false, true)
local players = minetest.get_connected_players()
local name = player:get_player_name()
for n, player in ipairs(players) do
if player:get_player_name() == name then
players[n] = nil
break
end
end
if check_in_beds(players) then
minetest.after(5, function()
if check_in_beds() then
update_formspecs(is_night_skip_enabled())
mcl_beds.sleep()
end
end)
end
update_formspecs(false, #players)
end)
minetest.register_on_player_receive_fields(function(player, formname, fields)
if formname ~= "mcl_beds_form" then
return
end
if fields.quit or fields.leave then
lay_down(player, nil, nil, false)
update_formspecs(false)
end
if fields.force then
update_formspecs(is_night_skip_enabled())
mcl_beds.sleep()
end
end)
minetest.register_on_player_hpchange(function(player, hp_change)
if hp_change < 0 then
mcl_beds.kick_player(player)
end
end)