Repixture/mods/rp_bed/init.lua

808 lines
24 KiB
Lua
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

--
-- Bed mod
--
local S = minetest.get_translator("rp_bed")
bed = {}
local DEFAULT_BED_COLOR = rp_paint.COLOR_AZURE_BLUE
-- Per-user data table
local bed_userdata = {}
bed_userdata.saved = {}
bed_userdata.temp = {}
-- List of occupied beds, indexed by node position hash
local occupied_beds = {}
-- Returns <spawn position> of `player` or
-- nil if if there is no spawn active
bed.get_spawn = function(player)
local name = player:get_player_name()
local spawn
if bed_userdata.saved[name].spawn_pos then
spawn = bed_userdata.saved[name].spawn_pos
end
return spawn
end
-- Sets the bed spawn position for `player`.
-- Returns true if spawn position was set and changed.
-- Returns false if spawn position was not changed because
-- it's already used by the player.
bed.set_spawn = function(player, spawn_pos)
local name = player:get_player_name()
local old_spawn_pos = bed_userdata.saved[name].spawn_pos
if old_spawn_pos and vector.equals(spawn_pos, old_spawn_pos) then
return false
end
bed_userdata.saved[name].spawn_pos = table.copy(spawn_pos)
minetest.log("action", "[rp_bed] Respawn position of "..name.." set to "..minetest.pos_to_string(spawn_pos, 1))
return true
end
-- Unsets the bed spawn position of `player`
bed.unset_spawn = function(player)
local name = player:get_player_name()
bed_userdata.saved[name].spawn_pos = nil
end
-- Returns true if pos has a valid bed
bed.is_valid_bed = function(pos)
local node = minetest.get_node(pos)
local dir = minetest.fourdir_to_dir(node.param2)
if node.name == "rp_bed:bed_head" then
local neighbor = vector.subtract(pos, dir)
local nnode = minetest.get_node(neighbor)
if nnode.name == "rp_bed:bed_foot" and nnode.param2 == node.param2 then
return true
end
elseif node.name == "rp_bed:bed_foot" then
local neighbor = vector.add(pos, dir)
local nnode = minetest.get_node(neighbor)
if nnode.name == "rp_bed:bed_head" and nnode.param2 == node.param2 then
return true
end
end
return false
end
-- Savefile
local bed_file = minetest.get_worldpath() .. "/bed.dat"
local saving = false
-- Timer
local timer_interval = 1
local timer = 0
local delay_daytime = false
local function is_bed_node(pos)
if pos == nil then
return false
end
local node = minetest.get_node(pos)
if node.name == "rp_bed:bed_foot" then
return true
end
return false
end
-- Returns name of player in bed at pos or nil if not occupied
local function get_player_in_bed(pos)
local hash = minetest.hash_node_position(pos)
local playername = occupied_beds[hash]
return playername
end
-- Assign a player to the bed at pos.
-- If playername==nil, bed will be unassigned.
local function set_bed_occupier(pos, playername)
local hash = minetest.hash_node_position(pos)
occupied_beds[hash] = playername
end
local function put_player_in_bed(player)
if player == nil then
return
end
local name = player:get_player_name()
if not is_bed_node(bed_userdata.temp[name].node_pos) then
return
end
player:set_pos(bed_userdata.temp[name].sleep_pos)
player_effects.apply_effect(player, "inbed")
player:set_eye_offset(vector.new(0, -13, 0), vector.new(0, -13, 0))
player:set_local_animation(
{x=162, y=166},
{x=162, y=166},
{x=162, y=166},
{x=162, y=168},
rp_player.player_animation_speed)
rp_player.player_set_animation(player, "lay", rp_player.player_animation_speed)
rp_player.player_attached[name] = true
minetest.log("action", "[rp_bed] "..name.." was put into bed")
end
local function clear_bed_status(player)
if player == nil then
return
end
local name = player:get_player_name()
bed_userdata.temp[name].in_bed = false
if bed_userdata.temp[name].node_pos then
set_bed_occupier(bed_userdata.temp[name].node_pos, nil)
end
bed_userdata.temp[name].node_pos = nil
player_effects.remove_effect(player, "inbed")
player:set_eye_offset(vector.new(0, 0, 0), vector.new(0, 0, 0))
player:set_local_animation(
{x=0, y=79},
{x=168, y=187},
{x=189, y=198},
{x=200, y=219},
rp_player.player_animation_speed)
rp_player.player_set_animation(player, "stand", rp_player.player_animation_speed)
rp_player.player_attached[name] = false
end
local function take_player_from_bed(player)
if player == nil then
return
end
local name = player:get_player_name()
local was_in_bed = bed_userdata.temp[name].in_bed == true
if was_in_bed then
minetest.log("action", "[rp_bed] "..name.." was taken from bed")
end
local spawn_pos = bed.get_spawn(player)
if spawn_pos then
player:set_pos(spawn_pos)
end
clear_bed_status(player)
end
local function save_bed()
local f = io.open(bed_file, "w")
f:write(minetest.serialize(bed_userdata.saved))
io.close(f)
saving = false
end
local function delayed_save()
if not saving then
saving = true
minetest.after(40, save_bed)
end
end
local function load_bed()
local f = io.open(bed_file, "r")
if f then
bed_userdata.saved = minetest.deserialize(f:read("*all"))
io.close(f)
else
save_bed()
end
end
-- Server start
local function on_load()
load_bed()
end
-- Server shutdown
local function on_shutdown()
save_bed()
end
-- Joining player
local function on_joinplayer(player)
local name = player:get_player_name()
if not bed_userdata.saved[name] then
bed_userdata.saved[name] = {
spawn_pos = nil,
}
end
bed_userdata.temp[name] = {
in_bed = false,
node_pos = nil,
sleep_pos = nil,
}
delayed_save()
end
-- Leaving player
local function on_leaveplayer(player)
local name = player:get_player_name()
if bed_userdata.temp[name] then
bed_userdata.temp[name].in_bed = false
if bed_userdata.temp[name].node_pos then
set_bed_occupier(bed_userdata.temp[name].node_pos, nil)
end
bed_userdata.temp[name].node_pos = nil
end
end
-- Returns true if players can spawn into the given node safely.
local function node_is_spawnable_in(node, is_upper)
-- All non-walkable, non-damaging, non-drowning nodes are safe.
-- Also the bed as a special case for the lower check.
if not node then
return false, "no_node"
end
if not is_upper and minetest.get_item_group(node.name, "bed") ~= 0 then
return true
end
local def = minetest.registered_nodes[node.name]
if not def.walkable and def.drowning <= 0 and def.damage_per_second <= 0 then
return true
end
local fail_reason
if def.walkable then
fail_reason = "blocked"
elseif def.damage_per_second > 0 then
fail_reason = "damage"
elseif def.drowning > 0 then
fail_reason = "drowning"
else
fail_reason = "blocked"
end
return false, fail_reason
end
-- Returns true if players can spawn on given node safely (without falling).
local function node_is_spawnable_on(node)
-- All walkable full cube nodes that don't disable jump are accepted
if not node then
return false
end
local def = minetest.registered_nodes[node.name]
if def.walkable and
((def.collision_box == nil and def.node_box == nil) or
(not def.collision_box and def.node_box and def.node_box.type == "regular") or
(not def.node_box and def.collision_box and def.collision_box.type == "regular")) and
(minetest.get_item_group(node.name, "disable_jump") == 0) then
return true
end
return false
end
local respawn_check_posses = {
vector.new(0, 0, 0 ),
vector.new(0, 0, -1 ),
vector.new(0, 0, 1 ),
vector.new(-1,0, 0 ),
vector.new(1, 0, 0 ),
vector.new(-1,0, -1 ),
vector.new(-1,0, 1 ),
vector.new(1, 0, -1 ),
vector.new(1, 0, 1 ),
}
local attempt_bed_respawn = function(player)
-- Place player on respawn position if set
local name = player:get_player_name()
local pos = bed.get_spawn(player)
if pos then
-- Load area around spawn pos to make sure
-- we don't get ignore nodes.
local load_offset = vector.new(1,1,1)
local load_min = vector.subtract(pos, load_offset)
local load_max = vector.add(pos, load_offset)
minetest.load_area(load_min, load_max)
-- Check if position is safe, if not, try to spawn to one of the
-- neighbor blocks
for n=1, #respawn_check_posses do
local cpos = vector.add(pos, respawn_check_posses[n])
local node = minetest.get_node(cpos)
if node_is_spawnable_in(node, false) then
local is_bed = minetest.get_item_group(node.name, "bed") ~= 0
-- Check posses above (must be free)
-- and below (must be walkable)
-- If bed, 2 posses must be free above
local acpos = { x=cpos.x, y=cpos.y+1, z=cpos.z }
local aacpos = { x=cpos.x, y=cpos.y+2, z=cpos.z }
local abpos = { x=cpos.x, y=cpos.y-1, z=cpos.z }
local anode = minetest.get_node(acpos)
local aanode = minetest.get_node(aacpos)
local bnode = minetest.get_node(abpos)
if node_is_spawnable_in(anode, true) and
((n == 1 and is_bed) or node_is_spawnable_on(bnode)) and
(not is_bed or node_is_spawnable_in(aanode, true)) then
local spos = cpos
if not is_bed then
spos.y = spos.y - 0.5
end
player:set_pos(spos)
return true
end
end
end
bed.unset_spawn(player)
minetest.chat_send_player(name, minetest.colorize("#FFFF00", S("Your respawn position was blocked or dangerous. Youve lost your old respawn position.")))
return false
end
return false
end
local on_respawnplayer = function(player)
clear_bed_status(player)
return attempt_bed_respawn(player)
end
local function on_dieplayer(player)
local name = player:get_player_name()
if bed_userdata.temp[name] then
bed_userdata.temp[name].in_bed = false
if bed_userdata.temp[name].node_pos then
set_bed_occupier(bed_userdata.temp[name].node_pos, nil)
end
bed_userdata.temp[name].node_pos = nil
end
end
-- Update function
local function on_globalstep(dtime)
timer = timer + dtime
if timer < timer_interval then
return
end
timer = 0
local sleeping_players = 0
local in_bed = {}
for name, data in pairs(bed_userdata.temp) do
if data.in_bed then
local player = minetest.get_player_by_name(name)
if player then
table.insert(in_bed, name)
sleeping_players = sleeping_players + 1
end
end
end
local players = minetest.get_connected_players()
local player_count = #players
if player_count > 0 and (player_count / 2.0) < sleeping_players then
if minetest.get_timeofday() < 0.2 or minetest.get_timeofday() > 0.8 then
if not delay_daytime then
delay_daytime = true
minetest.after(
2,
function()
minetest.set_timeofday(0.23)
delay_daytime = false
local players = minetest.get_connected_players()
local msg
if #players == 1 then
msg = S("You have slept, rise and shine!")
else
msg = S("Players have slept, rise and shine!")
end
minetest.chat_send_all(minetest.colorize("#0ff", "*** " .. msg))
minetest.log("action", "[rp_bed] Players have slept; the night was skipped")
end)
delayed_save()
end
end
end
end
-- Force player to wake up when punched
local function on_punchplayer(player)
if player:get_hp() <= 0 then
return
end
local name = player:get_player_name()
if bed_userdata.temp[name].in_bed then
take_player_from_bed(player)
end
end
-- Force player to wake up when taking damage
local function on_player_hpchange(player, hp_change)
if player:get_hp() <= 0 or hp_change >= 0 then
return
end
local name = player:get_player_name()
if bed_userdata.temp[name].in_bed then
take_player_from_bed(player)
end
end
minetest.register_on_mods_loaded(on_load)
minetest.register_on_shutdown(on_shutdown)
minetest.register_on_joinplayer(on_joinplayer)
minetest.register_on_leaveplayer(on_joinplayer)
minetest.register_on_respawnplayer(on_respawnplayer)
minetest.register_on_dieplayer(on_dieplayer)
minetest.register_on_punchplayer(on_punchplayer)
minetest.register_on_player_hpchange(on_player_hpchange)
minetest.register_globalstep(on_globalstep)
-- Nodes
local sounds = rp_sounds.node_sound_planks_defaults({
footstep = {name="rp_sounds_footstep_fuzzy", gain=0.7},
dug = {name="rp_sounds_dug_planks", gain=1.0, pitch=0.8},
place = {name="rp_sounds_place_planks", gain=1.0, pitch=0.8},
})
minetest.register_node(
"rp_bed:bed_foot",
{
description = S("Bed"),
_tt_help = S("Sets the respawn position and allows to pass the night"),
drawtype = "nodebox",
paramtype = "light",
paramtype2 = "color4dir",
palette = "bed_palette.png",
sunlight_propagates = true,
wield_image = "bed_bed_inventory.png",
inventory_image = "bed_bed_inventory.png",
tiles = {
"bed_foot.png",
{name="default_wood.png",color="white"},
"bed_side_l.png",
"bed_side_r.png",
"bed_inside.png",
"bed_back.png",
},
overlay_tiles = {
{name="bed_foot_overlay.png",color="white"},
"",
{name="bed_side_l_overlay.png",color="white"},
{name="bed_side_r_overlay.png",color="white"},
{name="bed_inside_overlay.png",color="white"},
{name="bed_back_overlay.png",color="white"},
},
use_texture_alpha = "clip",
groups = {choppy = 2, oddly_breakable_by_hand = 2, flammable = 3, bed = 1, fall_damage_add_percent = -15, creative_decoblock = 1, interactive_node = 1, paintable = 1 },
is_ground_content = false,
sounds = sounds,
node_box = {
type = "fixed",
fixed = {-0.5, -0.5, -0.5, 0.5, 2/16, 0.5}
},
selection_box = {
type = "fixed",
fixed = {-0.5, -0.5, -0.5, 0.5, 2/16, 1.5}
},
node_placement_prediction = "",
on_place = function(itemstack, placer, pointed_thing)
local under = pointed_thing.under
-- Use pointed node's on_rightclick function first, if present
local node = minetest.get_node(under)
if placer and not placer:get_player_control().sneak then
if minetest.registered_nodes[node.name] and minetest.registered_nodes[node.name].on_rightclick then
return minetest.registered_nodes[node.name].on_rightclick(pointed_thing.under, node, placer, itemstack, pointed_thing) or itemstack
end
end
local pos
local undername = minetest.get_node(under).name
if minetest.registered_items[undername] and minetest.registered_items[undername].buildable_to then
pos = under
else
pos = pointed_thing.above
end
if minetest.is_protected(pos, placer:get_player_name()) and
not minetest.check_player_privs(placer, "protection_bypass") then
minetest.record_protection_violation(pos, placer:get_player_name())
return itemstack
end
local node_def = minetest.registered_nodes[minetest.get_node(pos).name]
if not node_def or not node_def.buildable_to then
rp_sounds.play_place_failed_sound(placer)
return itemstack
end
local dir = minetest.dir_to_fourdir(placer:get_look_dir())
local botpos = vector.add(pos, minetest.fourdir_to_dir(dir))
if minetest.is_protected(botpos, placer:get_player_name()) and
not minetest.check_player_privs(placer, "protection_bypass") then
minetest.record_protection_violation(botpos, placer:get_player_name())
return itemstack
end
local bot = minetest.get_node(botpos)
local botdef = minetest.registered_nodes[bot.name]
-- Check if the 2nd node for the bed is free or already a bed head.
if not (bot.name == "rp_bed:bed_head" and bot.param2 == dir) and (not botdef or not botdef.buildable_to) then
rp_sounds.play_place_failed_sound(placer)
return itemstack
end
local param2 = dir + (DEFAULT_BED_COLOR - 1) * 4
local footnode = {name = "rp_bed:bed_foot", param2 = param2}
local headnode = {name = "rp_bed:bed_head", param2 = param2}
minetest.set_node(pos, footnode)
minetest.set_node(botpos, headnode)
rp_sounds.play_node_sound(pos, footnode, "place")
if not minetest.is_creative_enabled(placer:get_player_name()) then
itemstack:take_item()
end
return itemstack
end,
on_destruct = function(pos)
local player_name = get_player_in_bed(pos)
if player_name then
local player = minetest.get_player_by_name(player_name)
take_player_from_bed(player)
end
set_bed_occupier(pos, nil)
local node = minetest.get_node(pos)
local dir = minetest.fourdir_to_dir(node.param2)
local head_pos = vector.add(pos, dir)
if minetest.get_node(head_pos).name == "rp_bed:bed_head" then
minetest.remove_node(head_pos)
minetest.check_for_falling({x=head_pos.x, y=head_pos.y+1, z=head_pos.z})
end
end,
on_blast = function(pos)
-- Needed to force on_destruct to be called
minetest.remove_node(pos)
minetest.check_for_falling({x=pos.x, y=pos.y+1, z=pos.z})
end,
on_rightclick = function(pos, node, clicker, itemstack)
if not clicker:is_player() then
return itemstack
end
local clicker_name = clicker:get_player_name()
local sleeper_name = get_player_in_bed(pos)
if clicker_name == sleeper_name then
take_player_from_bed(clicker)
elseif sleeper_name == nil and not rp_player.player_attached[clicker_name]
and bed_userdata.temp[clicker_name].in_bed == false then
if not minetest.settings:get_bool("bed_enable", true) then
minetest.chat_send_player(clicker_name, minetest.colorize("#FFFF00", S("Sleeping is disabled.")))
return itemstack
end
local dir = minetest.fourdir_to_dir(node.param2)
local above_posses = {
{x=pos.x, y=pos.y+1, z=pos.z},
vector.add({x=pos.x, y=pos.y+1, z=pos.z}, dir),
{x=pos.x, y=pos.y+2, z=pos.z},
vector.add({x=pos.x, y=pos.y+2, z=pos.z}, dir),
}
for a=1,#above_posses do
local apos = above_posses[a]
local anode = minetest.get_node(apos)
local is_spawnable, fail_reason = node_is_spawnable_in(anode, true)
if not is_spawnable then
local msg
if fail_reason == "damage" then
msg = S("Its too painful to sleep here!")
elseif fail_reason == "drowning" then
msg = S("You cant sleep while holding your breath!")
elseif fail_reason == "blocked" then
msg = S("Not enough space to sleep!")
else
msg = S("You cant sleep here!")
end
minetest.chat_send_player(clicker_name, minetest.colorize("#FFFF00", msg))
return itemstack
end
end
-- No sleeping while moving
if vector.length(clicker:get_velocity()) > 0.001 then
minetest.chat_send_player(clicker_name, minetest.colorize("#FFFF00", S("You have to stop moving before going to bed!")))
return itemstack
end
local put_pos = table.copy(pos)
local yaw = (-(node.param2 / 2.0) * math.pi) + math.pi
bed_userdata.temp[clicker_name].in_bed = true
local changed = bed.set_spawn(clicker, put_pos)
if changed then
minetest.chat_send_player(clicker_name, minetest.colorize("#00FFFF", S("Respawn position set!")))
end
bed_userdata.temp[clicker_name].node_pos = pos
local sleep_pos = vector.add(pos, vector.divide(minetest.fourdir_to_dir(node.param2), 2))
bed_userdata.temp[clicker_name].sleep_pos = sleep_pos
set_bed_occupier(pos, clicker_name)
put_player_in_bed(clicker)
end
return itemstack
end,
can_dig = function(pos)
local sleeper = get_player_in_bed(pos)
return sleeper == nil
end,
-- Paint support for rp_paint mod
_on_paint = function(pos, new_param2)
local node = minetest.get_node(pos)
local dir = minetest.fourdir_to_dir(node.param2)
local head_pos = vector.add(pos, dir)
if minetest.get_node(head_pos).name == "rp_bed:bed_head" then
minetest.swap_node(head_pos, {name = "rp_bed:bed_head", param2=new_param2})
end
return true
end,
-- Drop itself, but without metadata
drop = "rp_bed:bed_foot",
})
minetest.register_node(
"rp_bed:bed_head",
{
drawtype = "nodebox",
paramtype = "light",
paramtype2 = "color4dir",
palette = "bed_palette.png",
is_ground_content = false,
pointable = false,
diggable = false,
tiles = {
"bed_head.png",
{name="default_wood.png",color="white"},
"bed_side_r.png",
"bed_side_l.png",
"bed_front.png",
"bed_inside.png",
},
overlay_tiles = {
{name="bed_head_overlay.png",color="white"},
"",
{name="bed_side_r_overlay.png",color="white"},
{name="bed_side_l_overlay.png",color="white"},
{name="bed_front_overlay.png",color="white"},
{name="bed_inside_overlay.png",color="white"},
},
use_texture_alpha = "clip",
groups = {choppy = 2, oddly_breakable_by_hand = 2, flammable = 3, bed = 1, fall_damage_add_percent = -15, not_in_creative_inventory = 1 },
sounds = sounds,
node_box = {
type = "fixed",
fixed = {-0.5, -0.5, -0.5, 0.5, 2/16, 0.5}
},
on_blast = function() end,
drop = "",
})
minetest.register_alias("rp_bed:bed", "rp_bed:bed_foot")
-- Crafting
crafting.register_craft(
{
output = "rp_bed:bed",
items = {
"group:fuzzy 3",
"group:planks 3",
}
})
minetest.register_craft({
type = "fuel",
recipe = "rp_bed:bed_foot",
burntime = 30,
})
-- Player effects
player_effects.register_effect(
"inbed",
{
title = S("In bed"),
description = S("You're in a bed"),
duration = -1,
physics = {
speed = 0,
jump = 0,
},
save = false,
icon = "bed_effect.png",
})
-- Achievements
achievements.register_achievement(
"bedtime",
{
title = S("Bed Time"),
description = S("Craft a bed."),
times = 1,
craftitem = "rp_bed:bed_foot",
difficulty = 4.1,
})
minetest.register_lbm({
label = "Clear legacy bed meta and initialize color param2",
name = "rp_bed:reset_beds_v3_10_0",
nodenames = {"rp_bed:bed_foot", "rp_bed:bed_head"},
action = function(pos, node)
-- Clear meta
if node.name == "rp_bed:bed_foot" then
local meta = minetest.get_meta(pos)
meta:set_string("player", "")
end
-- Set default color
if node.param2 <= 3 then
node.param2 = node.param2 + (DEFAULT_BED_COLOR - 1) * 4
minetest.swap_node(pos, node)
end
end,
})
-- Aliases
minetest.register_alias("bed:bed", "rp_bed:bed_foot")
minetest.register_alias("bed:bed_foot", "rp_bed:bed_foot")
minetest.register_alias("bed:bed_head", "rp_bed:bed_head")