Add MultiCraft builtin improvements
@ -2,10 +2,10 @@
|
|||||||
-- handled by the engine.
|
-- handled by the engine.
|
||||||
|
|
||||||
core.register_on_death(function()
|
core.register_on_death(function()
|
||||||
core.display_chat_message("You died.")
|
core.display_chat_message(fgettext("You died."))
|
||||||
local formspec = "size[11,5.5]bgcolor[#320000b4;true]" ..
|
local formspec = "size[11,5.5]bgcolor[#320000b4;true]" ..
|
||||||
"label[4.85,1.35;" .. fgettext("You died") ..
|
"label[5,2;" .. fgettext("You died.") ..
|
||||||
"]button_exit[4,3;3,0.5;btn_respawn;".. fgettext("Respawn") .."]"
|
"]button_exit[3.5,3;4,0.5;btn_respawn;".. fgettext("Respawn") .."]"
|
||||||
core.show_formspec("bultin:death", formspec)
|
core.show_formspec("bultin:death", formspec)
|
||||||
end)
|
end)
|
||||||
|
|
||||||
|
@ -3,7 +3,7 @@
|
|||||||
--
|
--
|
||||||
--This program is free software; you can redistribute it and/or modify
|
--This program is free software; you can redistribute it and/or modify
|
||||||
--it under the terms of the GNU Lesser General Public License as published by
|
--it under the terms of the GNU Lesser General Public License as published by
|
||||||
--the Free Software Foundation; either version 2.1 of the License, or
|
--the Free Software Foundation; either version 3.0 of the License, or
|
||||||
--(at your option) any later version.
|
--(at your option) any later version.
|
||||||
--
|
--
|
||||||
--This program is distributed in the hope that it will be useful,
|
--This program is distributed in the hope that it will be useful,
|
||||||
|
@ -16,7 +16,10 @@ local LIST_FORMSPEC_DESCRIPTION = [[
|
|||||||
tablecolumns[color;tree;text;text]
|
tablecolumns[color;tree;text;text]
|
||||||
table[0,0.5;12.8,4.8;list;%s;%i]
|
table[0,0.5;12.8,4.8;list;%s;%i]
|
||||||
box[0,5.5;12.8,1.5;#000]
|
box[0,5.5;12.8,1.5;#000]
|
||||||
textarea[0.3,5.5;13.05,1.9;;;%s]
|
]] ..
|
||||||
|
-- textarea[0.3,5.5;13.05,1.9;;;%s] -- for compatibility with 0.4
|
||||||
|
[[
|
||||||
|
label[0.3,5.5;%s]
|
||||||
button_exit[5,7;3,1;quit;%s]
|
button_exit[5,7;3,1;quit;%s]
|
||||||
]]
|
]]
|
||||||
|
|
||||||
@ -149,4 +152,3 @@ help_command.func = function(name, param)
|
|||||||
|
|
||||||
return old_help_func(name, param)
|
return old_help_func(name, param)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@ -280,7 +280,7 @@ end
|
|||||||
--------------------------------------------------------------------------------
|
--------------------------------------------------------------------------------
|
||||||
|
|
||||||
if INIT == "game" then
|
if INIT == "game" then
|
||||||
local dirs1 = {9, 18, 7, 12}
|
local dirs1 = {10, 19, 4, 13}
|
||||||
local dirs2 = {20, 23, 22, 21}
|
local dirs2 = {20, 23, 22, 21}
|
||||||
|
|
||||||
function core.rotate_and_place(itemstack, placer, pointed_thing,
|
function core.rotate_and_place(itemstack, placer, pointed_thing,
|
||||||
@ -292,8 +292,7 @@ if INIT == "game" then
|
|||||||
return
|
return
|
||||||
end
|
end
|
||||||
local undef = core.registered_nodes[unode.name]
|
local undef = core.registered_nodes[unode.name]
|
||||||
local sneaking = placer and placer:get_player_control().sneak
|
if undef and undef.on_rightclick then
|
||||||
if undef and undef.on_rightclick and not sneaking then
|
|
||||||
return undef.on_rightclick(pointed_thing.under, unode, placer,
|
return undef.on_rightclick(pointed_thing.under, unode, placer,
|
||||||
itemstack, pointed_thing)
|
itemstack, pointed_thing)
|
||||||
end
|
end
|
||||||
|
@ -115,6 +115,7 @@ function core.serialize(x)
|
|||||||
function dump_val(x)
|
function dump_val(x)
|
||||||
local tp = type(x)
|
local tp = type(x)
|
||||||
if x == nil then return "nil"
|
if x == nil then return "nil"
|
||||||
|
elseif tp == "userdata" then return "nil"
|
||||||
elseif tp == "string" then return string.format("%q", x)
|
elseif tp == "string" then return string.format("%q", x)
|
||||||
elseif tp == "boolean" then return x and "true" or "false"
|
elseif tp == "boolean" then return x and "true" or "false"
|
||||||
elseif tp == "function" then
|
elseif tp == "function" then
|
||||||
|
@ -1,6 +1,8 @@
|
|||||||
|
|
||||||
vector = {}
|
vector = {}
|
||||||
|
|
||||||
|
local floor, hypot = math.floor, math.hypot
|
||||||
|
local min, max, pi = math.min, math.max, math.pi
|
||||||
|
|
||||||
function vector.new(a, b, c)
|
function vector.new(a, b, c)
|
||||||
if type(a) == "table" then
|
if type(a) == "table" then
|
||||||
assert(a.x and a.y and a.z, "Invalid vector passed to vector.new()")
|
assert(a.x and a.y and a.z, "Invalid vector passed to vector.new()")
|
||||||
@ -19,7 +21,7 @@ function vector.equals(a, b)
|
|||||||
end
|
end
|
||||||
|
|
||||||
function vector.length(v)
|
function vector.length(v)
|
||||||
return math.hypot(v.x, math.hypot(v.y, v.z))
|
return hypot(v.x, hypot(v.y, v.z))
|
||||||
end
|
end
|
||||||
|
|
||||||
function vector.normalize(v)
|
function vector.normalize(v)
|
||||||
@ -33,17 +35,17 @@ end
|
|||||||
|
|
||||||
function vector.floor(v)
|
function vector.floor(v)
|
||||||
return {
|
return {
|
||||||
x = math.floor(v.x),
|
x = floor(v.x),
|
||||||
y = math.floor(v.y),
|
y = floor(v.y),
|
||||||
z = math.floor(v.z)
|
z = floor(v.z)
|
||||||
}
|
}
|
||||||
end
|
end
|
||||||
|
|
||||||
function vector.round(v)
|
function vector.round(v)
|
||||||
return {
|
return {
|
||||||
x = math.floor(v.x + 0.5),
|
x = floor(v.x + 0.5),
|
||||||
y = math.floor(v.y + 0.5),
|
y = floor(v.y + 0.5),
|
||||||
z = math.floor(v.z + 0.5)
|
z = floor(v.z + 0.5)
|
||||||
}
|
}
|
||||||
end
|
end
|
||||||
|
|
||||||
@ -59,7 +61,7 @@ function vector.distance(a, b)
|
|||||||
local x = a.x - b.x
|
local x = a.x - b.x
|
||||||
local y = a.y - b.y
|
local y = a.y - b.y
|
||||||
local z = a.z - b.z
|
local z = a.z - b.z
|
||||||
return math.hypot(x, math.hypot(y, z))
|
return hypot(x, hypot(y, z))
|
||||||
end
|
end
|
||||||
|
|
||||||
function vector.direction(pos1, pos2)
|
function vector.direction(pos1, pos2)
|
||||||
@ -138,12 +140,12 @@ function vector.divide(a, b)
|
|||||||
end
|
end
|
||||||
|
|
||||||
function vector.sort(a, b)
|
function vector.sort(a, b)
|
||||||
return {x = math.min(a.x, b.x), y = math.min(a.y, b.y), z = math.min(a.z, b.z)},
|
return {x = min(a.x, b.x), y = min(a.y, b.y), z = min(a.z, b.z)},
|
||||||
{x = math.max(a.x, b.x), y = math.max(a.y, b.y), z = math.max(a.z, b.z)}
|
{x = max(a.x, b.x), y = max(a.y, b.y), z = max(a.z, b.z)}
|
||||||
end
|
end
|
||||||
|
|
||||||
local function sin(x)
|
local function sin(x)
|
||||||
if x % math.pi == 0 then
|
if x % pi == 0 then
|
||||||
return 0
|
return 0
|
||||||
else
|
else
|
||||||
return math.sin(x)
|
return math.sin(x)
|
||||||
@ -151,7 +153,7 @@ local function sin(x)
|
|||||||
end
|
end
|
||||||
|
|
||||||
local function cos(x)
|
local function cos(x)
|
||||||
if x % math.pi == math.pi / 2 then
|
if x % pi == pi / 2 then
|
||||||
return 0
|
return 0
|
||||||
else
|
else
|
||||||
return math.cos(x)
|
return math.cos(x)
|
||||||
|
@ -138,10 +138,10 @@ core.register_on_prejoinplayer(function(name, ip)
|
|||||||
local name_lower = name:lower()
|
local name_lower = name:lower()
|
||||||
for k in core.builtin_auth_handler.iterate() do
|
for k in core.builtin_auth_handler.iterate() do
|
||||||
if k:lower() == name_lower then
|
if k:lower() == name_lower then
|
||||||
return string.format("\nCannot create new player called '%s'. "..
|
return ("\nYou can not register as \"%s\"! "..
|
||||||
"Another account called '%s' is already registered. "..
|
"Another player called \"%s\" is already registered. " ..
|
||||||
"Please check the spelling if it's your account "..
|
"Please check the spelling if it's your account " ..
|
||||||
"or use a different nickname.", name, k)
|
"or use a different name."):format(name, k)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end)
|
end)
|
||||||
|
@ -105,6 +105,15 @@ local function parse_range_str(player_name, str)
|
|||||||
return p1, p2
|
return p1, p2
|
||||||
end
|
end
|
||||||
|
|
||||||
|
-- Chatcommand aliases (by @luk3yx)
|
||||||
|
local function register_chatcommand_alias(new, old)
|
||||||
|
local def = assert(core.registered_chatcommands[old])
|
||||||
|
assert(not core.registered_chatcommands[new])
|
||||||
|
|
||||||
|
core.registered_chatcommands[new] = def
|
||||||
|
end
|
||||||
|
core.register_chatcommand_alias = register_chatcommand_alias
|
||||||
|
|
||||||
--
|
--
|
||||||
-- Chat commands
|
-- Chat commands
|
||||||
--
|
--
|
||||||
@ -436,7 +445,6 @@ core.register_chatcommand("teleport", {
|
|||||||
p.y = tonumber(p.y)
|
p.y = tonumber(p.y)
|
||||||
p.z = tonumber(p.z)
|
p.z = tonumber(p.z)
|
||||||
if p.x and p.y and p.z then
|
if p.x and p.y and p.z then
|
||||||
|
|
||||||
local lm = 31000
|
local lm = 31000
|
||||||
if p.x < -lm or p.x > lm or p.y < -lm or p.y > lm or p.z < -lm or p.z > lm then
|
if p.x < -lm or p.x > lm or p.y < -lm or p.y > lm or p.z < -lm or p.z > lm then
|
||||||
return false, "Cannot teleport out of map bounds!"
|
return false, "Cannot teleport out of map bounds!"
|
||||||
@ -447,7 +455,7 @@ core.register_chatcommand("teleport", {
|
|||||||
return false, "Can't teleport, you're attached to an object!"
|
return false, "Can't teleport, you're attached to an object!"
|
||||||
end
|
end
|
||||||
teleportee:set_pos(p)
|
teleportee:set_pos(p)
|
||||||
return true, "Teleporting to "..core.pos_to_string(p)
|
return true, "Teleporting to " .. core.pos_to_string(p, 1)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
@ -469,7 +477,7 @@ core.register_chatcommand("teleport", {
|
|||||||
p = find_free_position_near(p)
|
p = find_free_position_near(p)
|
||||||
teleportee:set_pos(p)
|
teleportee:set_pos(p)
|
||||||
return true, "Teleporting to " .. target_name
|
return true, "Teleporting to " .. target_name
|
||||||
.. " at "..core.pos_to_string(p)
|
.. " at " .. core.pos_to_string(p, 1)
|
||||||
end
|
end
|
||||||
|
|
||||||
if not core.check_player_privs(name, {bring=true}) then
|
if not core.check_player_privs(name, {bring=true}) then
|
||||||
@ -491,7 +499,7 @@ core.register_chatcommand("teleport", {
|
|||||||
end
|
end
|
||||||
teleportee:set_pos(p)
|
teleportee:set_pos(p)
|
||||||
return true, "Teleporting " .. teleportee_name
|
return true, "Teleporting " .. teleportee_name
|
||||||
.. " to " .. core.pos_to_string(p)
|
.. " to " .. core.pos_to_string(p, 1)
|
||||||
end
|
end
|
||||||
|
|
||||||
teleportee = nil
|
teleportee = nil
|
||||||
@ -514,13 +522,14 @@ core.register_chatcommand("teleport", {
|
|||||||
teleportee:set_pos(p)
|
teleportee:set_pos(p)
|
||||||
return true, "Teleporting " .. teleportee_name
|
return true, "Teleporting " .. teleportee_name
|
||||||
.. " to " .. target_name
|
.. " to " .. target_name
|
||||||
.. " at " .. core.pos_to_string(p)
|
.. " at " .. core.pos_to_string(p, 1)
|
||||||
end
|
end
|
||||||
|
|
||||||
return false, 'Invalid parameters ("' .. param
|
return false, 'Invalid parameters ("' .. param
|
||||||
.. '") or player not found (see /help teleport)'
|
.. '") or player not found (see /help teleport)'
|
||||||
end,
|
end,
|
||||||
})
|
})
|
||||||
|
register_chatcommand_alias("tp", "teleport")
|
||||||
|
|
||||||
core.register_chatcommand("set", {
|
core.register_chatcommand("set", {
|
||||||
params = "([-n] <name> <value>) | <name>",
|
params = "([-n] <name> <value>) | <name>",
|
||||||
@ -649,7 +658,7 @@ core.register_chatcommand("fixlight", {
|
|||||||
core.register_chatcommand("mods", {
|
core.register_chatcommand("mods", {
|
||||||
params = "",
|
params = "",
|
||||||
description = "List mods installed on the server",
|
description = "List mods installed on the server",
|
||||||
privs = {},
|
privs = {server = true},
|
||||||
func = function(name, param)
|
func = function(name, param)
|
||||||
return true, table.concat(core.get_modnames(), ", ")
|
return true, table.concat(core.get_modnames(), ", ")
|
||||||
end,
|
end,
|
||||||
@ -658,6 +667,18 @@ core.register_chatcommand("mods", {
|
|||||||
local function handle_give_command(cmd, giver, receiver, stackstring)
|
local function handle_give_command(cmd, giver, receiver, stackstring)
|
||||||
core.log("action", giver .. " invoked " .. cmd
|
core.log("action", giver .. " invoked " .. cmd
|
||||||
.. ', stackstring="' .. stackstring .. '"')
|
.. ', stackstring="' .. stackstring .. '"')
|
||||||
|
local ritems = core.registered_items
|
||||||
|
if not stackstring:match(":") and not ritems[stackstring] then
|
||||||
|
local modslist = core.get_modnames()
|
||||||
|
table.insert(modslist, 1, "default")
|
||||||
|
for _, modname in pairs(modslist) do
|
||||||
|
local namecheck = modname .. ":" .. stackstring:match("%S*")
|
||||||
|
if ritems[namecheck] then
|
||||||
|
stackstring = modname .. ":" .. stackstring
|
||||||
|
break
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
local itemstack = ItemStack(stackstring)
|
local itemstack = ItemStack(stackstring)
|
||||||
if itemstack:is_empty() then
|
if itemstack:is_empty() then
|
||||||
return false, "Cannot give an empty item"
|
return false, "Cannot give an empty item"
|
||||||
@ -926,6 +947,7 @@ core.register_chatcommand("time", {
|
|||||||
return true, "Time of day changed."
|
return true, "Time of day changed."
|
||||||
end,
|
end,
|
||||||
})
|
})
|
||||||
|
register_chatcommand_alias("settime", "time")
|
||||||
|
|
||||||
core.register_chatcommand("days", {
|
core.register_chatcommand("days", {
|
||||||
description = "Show day count since world creation",
|
description = "Show day count since world creation",
|
||||||
@ -1030,8 +1052,7 @@ core.register_chatcommand("clearobjects", {
|
|||||||
core.log("action", name .. " clears all objects ("
|
core.log("action", name .. " clears all objects ("
|
||||||
.. options.mode .. " mode).")
|
.. options.mode .. " mode).")
|
||||||
core.chat_send_all("Clearing all objects. This may take a long time."
|
core.chat_send_all("Clearing all objects. This may take a long time."
|
||||||
.. " You may experience a timeout. (by "
|
.. " You may experience a timeout.")
|
||||||
.. name .. ")")
|
|
||||||
core.clear_objects(options)
|
core.clear_objects(options)
|
||||||
core.log("action", "Object clearing done.")
|
core.log("action", "Object clearing done.")
|
||||||
core.chat_send_all("*** Cleared all objects.")
|
core.chat_send_all("*** Cleared all objects.")
|
||||||
@ -1041,7 +1062,7 @@ core.register_chatcommand("clearobjects", {
|
|||||||
|
|
||||||
core.register_chatcommand("msg", {
|
core.register_chatcommand("msg", {
|
||||||
params = "<name> <message>",
|
params = "<name> <message>",
|
||||||
description = "Send a direct message to a player",
|
description = "Send a private message to a player",
|
||||||
privs = {shout=true},
|
privs = {shout=true},
|
||||||
func = function(name, param)
|
func = function(name, param)
|
||||||
local sendto, message = param:match("^(%S+)%s(.+)$")
|
local sendto, message = param:match("^(%S+)%s(.+)$")
|
||||||
@ -1052,13 +1073,15 @@ core.register_chatcommand("msg", {
|
|||||||
return false, "The player " .. sendto
|
return false, "The player " .. sendto
|
||||||
.. " is not online."
|
.. " is not online."
|
||||||
end
|
end
|
||||||
core.log("action", "DM from " .. name .. " to " .. sendto
|
core.log("action", "PM from " .. name .. " to " .. sendto
|
||||||
.. ": " .. message)
|
.. ": " .. message)
|
||||||
core.chat_send_player(sendto, "DM from " .. name .. ": "
|
core.chat_send_player(sendto, minetest.colorize("green",
|
||||||
.. message)
|
"PM from " .. name .. ": " .. message))
|
||||||
return true, "Message sent."
|
return true, "Message sent."
|
||||||
end,
|
end,
|
||||||
})
|
})
|
||||||
|
register_chatcommand_alias("m", "msg")
|
||||||
|
register_chatcommand_alias("pm", "msg")
|
||||||
|
|
||||||
core.register_chatcommand("last-login", {
|
core.register_chatcommand("last-login", {
|
||||||
params = "[<name>]",
|
params = "[<name>]",
|
||||||
@ -1135,3 +1158,34 @@ core.register_chatcommand("kill", {
|
|||||||
return handle_kill_command(name, param == "" and name or param)
|
return handle_kill_command(name, param == "" and name or param)
|
||||||
end,
|
end,
|
||||||
})
|
})
|
||||||
|
|
||||||
|
core.register_chatcommand("spawn", {
|
||||||
|
description = "Teleport to the spawn point",
|
||||||
|
func = function(name)
|
||||||
|
local player = core.get_player_by_name(name)
|
||||||
|
if not player then
|
||||||
|
return false
|
||||||
|
end
|
||||||
|
local spawnpoint = core.setting_get_pos("static_spawnpoint")
|
||||||
|
if spawnpoint then
|
||||||
|
player:set_pos(spawnpoint)
|
||||||
|
return true, "Teleporting to spawn..."
|
||||||
|
else
|
||||||
|
return false, "The spawn point is not set!"
|
||||||
|
end
|
||||||
|
end
|
||||||
|
})
|
||||||
|
|
||||||
|
core.register_chatcommand("setspawn", {
|
||||||
|
description = "Sets the spawn point to your current position",
|
||||||
|
privs = {server = true},
|
||||||
|
func = function(name)
|
||||||
|
local player = core.get_player_by_name(name)
|
||||||
|
if not player then
|
||||||
|
return false
|
||||||
|
end
|
||||||
|
local pos = core.pos_to_string(player:get_pos(), 1)
|
||||||
|
core.settings:set("static_spawnpoint", pos)
|
||||||
|
return true, "The spawn point are set to " .. pos
|
||||||
|
end
|
||||||
|
})
|
||||||
|
@ -1,33 +1,36 @@
|
|||||||
-- Minetest: builtin/item.lua
|
-- Minetest: builtin/item.lua
|
||||||
|
|
||||||
|
local random, pi = math.random, math.pi
|
||||||
|
local vnew, vround, vadd, vapply = vector.new, vector.round, vector.add, vector.apply
|
||||||
|
|
||||||
local builtin_shared = ...
|
local builtin_shared = ...
|
||||||
local SCALE = 0.667
|
local SCALE = 0.667
|
||||||
|
|
||||||
local facedir_to_euler = {
|
local facedir_to_euler = {
|
||||||
{y = 0, x = 0, z = 0},
|
{y = 0, x = 0, z = 0},
|
||||||
{y = -math.pi/2, x = 0, z = 0},
|
{y = -pi/2, x = 0, z = 0},
|
||||||
{y = math.pi, x = 0, z = 0},
|
{y = pi, x = 0, z = 0},
|
||||||
{y = math.pi/2, x = 0, z = 0},
|
{y = pi/2, x = 0, z = 0},
|
||||||
{y = math.pi/2, x = -math.pi/2, z = math.pi/2},
|
{y = pi/2, x = -pi/2, z = pi/2},
|
||||||
{y = math.pi/2, x = math.pi, z = math.pi/2},
|
{y = pi/2, x = pi, z = pi/2},
|
||||||
{y = math.pi/2, x = math.pi/2, z = math.pi/2},
|
{y = pi/2, x = pi/2, z = pi/2},
|
||||||
{y = math.pi/2, x = 0, z = math.pi/2},
|
{y = pi/2, x = 0, z = pi/2},
|
||||||
{y = -math.pi/2, x = math.pi/2, z = math.pi/2},
|
{y = -pi/2, x = pi/2, z = pi/2},
|
||||||
{y = -math.pi/2, x = 0, z = math.pi/2},
|
{y = -pi/2, x = 0, z = pi/2},
|
||||||
{y = -math.pi/2, x = -math.pi/2, z = math.pi/2},
|
{y = -pi/2, x = -pi/2, z = pi/2},
|
||||||
{y = -math.pi/2, x = math.pi, z = math.pi/2},
|
{y = -pi/2, x = pi, z = pi/2},
|
||||||
{y = 0, x = 0, z = math.pi/2},
|
{y = 0, x = 0, z = pi/2},
|
||||||
{y = 0, x = -math.pi/2, z = math.pi/2},
|
{y = 0, x = -pi/2, z = pi/2},
|
||||||
{y = 0, x = math.pi, z = math.pi/2},
|
{y = 0, x = pi, z = pi/2},
|
||||||
{y = 0, x = math.pi/2, z = math.pi/2},
|
{y = 0, x = pi/2, z = pi/2},
|
||||||
{y = math.pi, x = math.pi, z = math.pi/2},
|
{y = pi, x = pi, z = pi/2},
|
||||||
{y = math.pi, x = math.pi/2, z = math.pi/2},
|
{y = pi, x = pi/2, z = pi/2},
|
||||||
{y = math.pi, x = 0, z = math.pi/2},
|
{y = pi, x = 0, z = pi/2},
|
||||||
{y = math.pi, x = -math.pi/2, z = math.pi/2},
|
{y = pi, x = -pi/2, z = pi/2},
|
||||||
{y = math.pi, x = math.pi, z = 0},
|
{y = pi, x = pi, z = 0},
|
||||||
{y = -math.pi/2, x = math.pi, z = 0},
|
{y = -pi/2, x = pi, z = 0},
|
||||||
{y = 0, x = math.pi, z = 0},
|
{y = 0, x = pi, z = 0},
|
||||||
{y = math.pi/2, x = math.pi, z = 0}
|
{y = pi/2, x = pi, z = 0}
|
||||||
}
|
}
|
||||||
|
|
||||||
local gravity = tonumber(core.settings:get("movement_gravity")) or 9.81
|
local gravity = tonumber(core.settings:get("movement_gravity")) or 9.81
|
||||||
@ -36,6 +39,8 @@ local gravity = tonumber(core.settings:get("movement_gravity")) or 9.81
|
|||||||
-- Falling stuff
|
-- Falling stuff
|
||||||
--
|
--
|
||||||
|
|
||||||
|
local drop_attached_node
|
||||||
|
|
||||||
core.register_entity(":__builtin:falling_node", {
|
core.register_entity(":__builtin:falling_node", {
|
||||||
initial_properties = {
|
initial_properties = {
|
||||||
visual = "item",
|
visual = "item",
|
||||||
@ -50,8 +55,10 @@ core.register_entity(":__builtin:falling_node", {
|
|||||||
node = {},
|
node = {},
|
||||||
meta = {},
|
meta = {},
|
||||||
floats = false,
|
floats = false,
|
||||||
|
timer = 0,
|
||||||
|
|
||||||
set_node = function(self, node, meta)
|
set_node = function(self, node, meta)
|
||||||
|
node.param2 = node.param2 or 0
|
||||||
self.node = node
|
self.node = node
|
||||||
meta = meta or {}
|
meta = meta or {}
|
||||||
if type(meta.to_table) == "function" then
|
if type(meta.to_table) == "function" then
|
||||||
@ -143,7 +150,7 @@ core.register_entity(":__builtin:falling_node", {
|
|||||||
|
|
||||||
-- Rotate entity
|
-- Rotate entity
|
||||||
if def.drawtype == "torchlike" then
|
if def.drawtype == "torchlike" then
|
||||||
self.object:set_yaw(math.pi*0.25)
|
self.object:set_yaw(pi*0.25)
|
||||||
elseif (node.param2 ~= 0 and (def.wield_image == ""
|
elseif (node.param2 ~= 0 and (def.wield_image == ""
|
||||||
or def.wield_image == nil))
|
or def.wield_image == nil))
|
||||||
or def.drawtype == "signlike"
|
or def.drawtype == "signlike"
|
||||||
@ -161,28 +168,28 @@ core.register_entity(":__builtin:falling_node", {
|
|||||||
local rot = node.param2 % 8
|
local rot = node.param2 % 8
|
||||||
local pitch, yaw, roll = 0, 0, 0
|
local pitch, yaw, roll = 0, 0, 0
|
||||||
if rot == 1 then
|
if rot == 1 then
|
||||||
pitch, yaw = math.pi, math.pi
|
pitch, yaw = pi, pi
|
||||||
elseif rot == 2 then
|
elseif rot == 2 then
|
||||||
pitch, yaw = math.pi/2, math.pi/2
|
pitch, yaw = pi/2, pi/2
|
||||||
elseif rot == 3 then
|
elseif rot == 3 then
|
||||||
pitch, yaw = math.pi/2, -math.pi/2
|
pitch, yaw = pi/2, -pi/2
|
||||||
elseif rot == 4 then
|
elseif rot == 4 then
|
||||||
pitch, yaw = math.pi/2, math.pi
|
pitch, yaw = pi/2, pi
|
||||||
elseif rot == 5 then
|
elseif rot == 5 then
|
||||||
pitch, yaw = math.pi/2, 0
|
pitch, yaw = pi/2, 0
|
||||||
end
|
end
|
||||||
if def.drawtype == "signlike" then
|
if def.drawtype == "signlike" then
|
||||||
pitch = pitch - math.pi/2
|
pitch = pitch - pi/2
|
||||||
if rot == 0 then
|
if rot == 0 then
|
||||||
yaw = yaw + math.pi/2
|
yaw = yaw + pi/2
|
||||||
elseif rot == 1 then
|
elseif rot == 1 then
|
||||||
yaw = yaw - math.pi/2
|
yaw = yaw - pi/2
|
||||||
end
|
end
|
||||||
elseif def.drawtype == "mesh" or def.drawtype == "normal" then
|
elseif def.drawtype == "mesh" or def.drawtype == "normal" then
|
||||||
if rot >= 0 and rot <= 1 then
|
if rot >= 0 and rot <= 1 then
|
||||||
roll = roll + math.pi
|
roll = roll + pi
|
||||||
else
|
else
|
||||||
yaw = yaw + math.pi
|
yaw = yaw + pi
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
self.object:set_rotation({x=pitch, y=yaw, z=roll})
|
self.object:set_rotation({x=pitch, y=yaw, z=roll})
|
||||||
@ -217,9 +224,9 @@ core.register_entity(":__builtin:falling_node", {
|
|||||||
-- Add levels if dropped on same leveled node
|
-- Add levels if dropped on same leveled node
|
||||||
if bcd and bcd.paramtype2 == "leveled" and
|
if bcd and bcd.paramtype2 == "leveled" and
|
||||||
bcn.name == self.node.name then
|
bcn.name == self.node.name then
|
||||||
local addlevel = self.node.level
|
local addlevel = self.node.level or 0
|
||||||
if (addlevel or 0) <= 0 then
|
if addlevel <= 0 then
|
||||||
addlevel = bcd.leveled
|
addlevel = bcd.leveled or 0
|
||||||
end
|
end
|
||||||
if core.add_node_level(bcp, addlevel) < addlevel then
|
if core.add_node_level(bcp, addlevel) < addlevel then
|
||||||
return true
|
return true
|
||||||
@ -230,7 +237,7 @@ core.register_entity(":__builtin:falling_node", {
|
|||||||
end
|
end
|
||||||
|
|
||||||
-- Decide if we're replacing the node or placing on top
|
-- Decide if we're replacing the node or placing on top
|
||||||
local np = vector.new(bcp)
|
local np = vnew(bcp)
|
||||||
if bcd and bcd.buildable_to and
|
if bcd and bcd.buildable_to and
|
||||||
(not self.floats or bcd.liquidtype == "none") then
|
(not self.floats or bcd.liquidtype == "none") then
|
||||||
core.remove_node(bcp)
|
core.remove_node(bcp)
|
||||||
@ -262,6 +269,12 @@ core.register_entity(":__builtin:falling_node", {
|
|||||||
if self.meta then
|
if self.meta then
|
||||||
core.get_meta(np):from_table(self.meta)
|
core.get_meta(np):from_table(self.meta)
|
||||||
end
|
end
|
||||||
|
-- Drop node if falls into a protected area
|
||||||
|
if core.is_protected(np, "") then
|
||||||
|
self.object:remove()
|
||||||
|
drop_attached_node(np)
|
||||||
|
return true
|
||||||
|
end
|
||||||
if def.sounds and def.sounds.place then
|
if def.sounds and def.sounds.place then
|
||||||
core.sound_play(def.sounds.place, {pos = np}, true)
|
core.sound_play(def.sounds.place, {pos = np}, true)
|
||||||
end
|
end
|
||||||
@ -276,7 +289,7 @@ core.register_entity(":__builtin:falling_node", {
|
|||||||
if self.floats then
|
if self.floats then
|
||||||
local pos = self.object:get_pos()
|
local pos = self.object:get_pos()
|
||||||
|
|
||||||
local bcp = vector.round({x = pos.x, y = pos.y - 0.7, z = pos.z})
|
local bcp = vround({x = pos.x, y = pos.y - 0.7, z = pos.z})
|
||||||
local bcn = core.get_node(bcp)
|
local bcn = core.get_node(bcp)
|
||||||
|
|
||||||
local bcd = core.registered_nodes[bcn.name]
|
local bcd = core.registered_nodes[bcn.name]
|
||||||
@ -322,7 +335,7 @@ core.register_entity(":__builtin:falling_node", {
|
|||||||
y = player_collision.old_velocity.y,
|
y = player_collision.old_velocity.y,
|
||||||
z = vel.z
|
z = vel.z
|
||||||
})
|
})
|
||||||
self.object:set_pos(vector.add(self.object:get_pos(),
|
self.object:set_pos(vadd(self.object:get_pos(),
|
||||||
{x = 0, y = -0.5, z = 0}))
|
{x = 0, y = -0.5, z = 0}))
|
||||||
end
|
end
|
||||||
return
|
return
|
||||||
@ -335,7 +348,7 @@ core.register_entity(":__builtin:falling_node", {
|
|||||||
local failure = false
|
local failure = false
|
||||||
|
|
||||||
local pos = self.object:get_pos()
|
local pos = self.object:get_pos()
|
||||||
local distance = vector.apply(vector.subtract(pos, bcp), math.abs)
|
local distance = vapply(vector.subtract(pos, bcp), math.abs)
|
||||||
if distance.x >= 1 or distance.z >= 1 then
|
if distance.x >= 1 or distance.z >= 1 then
|
||||||
-- We're colliding with some part of a node that's sticking out
|
-- We're colliding with some part of a node that's sticking out
|
||||||
-- Since we don't want to visually teleport, drop as item
|
-- Since we don't want to visually teleport, drop as item
|
||||||
@ -353,6 +366,14 @@ core.register_entity(":__builtin:falling_node", {
|
|||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
-- Drop node if does not fall within 5 seconds
|
||||||
|
self.timer = self.timer + dtime
|
||||||
|
if self.timer > 5 then
|
||||||
|
core.add_item(pos, self.node)
|
||||||
|
self.object:remove()
|
||||||
|
return
|
||||||
|
end
|
||||||
|
|
||||||
-- Try to actually place ourselves
|
-- Try to actually place ourselves
|
||||||
if not failure then
|
if not failure then
|
||||||
failure = not self:try_place(bcp, bcn)
|
failure = not self:try_place(bcp, bcn)
|
||||||
@ -396,7 +417,7 @@ function core.spawn_falling_node(pos)
|
|||||||
return convert_to_falling_node(pos, node)
|
return convert_to_falling_node(pos, node)
|
||||||
end
|
end
|
||||||
|
|
||||||
local function drop_attached_node(p)
|
function drop_attached_node(p)
|
||||||
local n = core.get_node(p)
|
local n = core.get_node(p)
|
||||||
local drops = core.get_node_drops(n, "")
|
local drops = core.get_node_drops(n, "")
|
||||||
local def = core.registered_items[n.name]
|
local def = core.registered_items[n.name]
|
||||||
@ -418,9 +439,9 @@ local function drop_attached_node(p)
|
|||||||
core.remove_node(p)
|
core.remove_node(p)
|
||||||
for _, item in pairs(drops) do
|
for _, item in pairs(drops) do
|
||||||
local pos = {
|
local pos = {
|
||||||
x = p.x + math.random()/2 - 0.25,
|
x = p.x + random()/2 - 0.25,
|
||||||
y = p.y + math.random()/2 - 0.25,
|
y = p.y + random()/2 - 0.25,
|
||||||
z = p.z + math.random()/2 - 0.25,
|
z = p.z + random()/2 - 0.25,
|
||||||
}
|
}
|
||||||
core.add_item(pos, item)
|
core.add_item(pos, item)
|
||||||
end
|
end
|
||||||
@ -439,7 +460,7 @@ function builtin_shared.check_attached_node(p, n)
|
|||||||
else
|
else
|
||||||
d.y = -1
|
d.y = -1
|
||||||
end
|
end
|
||||||
local p2 = vector.add(p, d)
|
local p2 = vadd(p, d)
|
||||||
local nn = core.get_node(p2).name
|
local nn = core.get_node(p2).name
|
||||||
local def2 = core.registered_nodes[nn]
|
local def2 = core.registered_nodes[nn]
|
||||||
if def2 and not def2.walkable then
|
if def2 and not def2.walkable then
|
||||||
@ -486,6 +507,33 @@ function core.check_single_for_falling(p)
|
|||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
-- Attached, but not wallmounted. Yes, no one thought about it.
|
||||||
|
-- This is an alternative to function 'check_attached_node', so it seems too complicated.
|
||||||
|
local check_connected = {
|
||||||
|
{x = -1, y = 0, z = 0},
|
||||||
|
{x = 1, y = 0, z = 0},
|
||||||
|
{x = 0, y = 0, z = 1},
|
||||||
|
{x = 0, y = 0, z = -1}
|
||||||
|
}
|
||||||
|
|
||||||
|
for i = 1, 4 do
|
||||||
|
local pa = vadd(p, check_connected[i])
|
||||||
|
local nc = core.get_node(pa)
|
||||||
|
if core.get_item_group(nc.name, "attached_node2") ~= 0 then
|
||||||
|
for j = 1, 4 do
|
||||||
|
local ptwo = vadd(pa, check_connected[j])
|
||||||
|
local ntwo = core.get_node(ptwo)
|
||||||
|
local def = core.registered_nodes[ntwo.name]
|
||||||
|
if def and def.walkable then
|
||||||
|
return false
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
drop_attached_node(pa)
|
||||||
|
return true
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
return false
|
return false
|
||||||
end
|
end
|
||||||
|
|
||||||
@ -509,7 +557,7 @@ local check_for_falling_neighbors = {
|
|||||||
|
|
||||||
function core.check_for_falling(p)
|
function core.check_for_falling(p)
|
||||||
-- Round p to prevent falling entities to get stuck.
|
-- Round p to prevent falling entities to get stuck.
|
||||||
p = vector.round(p)
|
p = vround(p)
|
||||||
|
|
||||||
-- We make a stack, and manually maintain size for performance.
|
-- We make a stack, and manually maintain size for performance.
|
||||||
-- Stored in the stack, we will maintain tables with pos, and
|
-- Stored in the stack, we will maintain tables with pos, and
|
||||||
@ -526,7 +574,7 @@ function core.check_for_falling(p)
|
|||||||
n = n + 1
|
n = n + 1
|
||||||
s[n] = {p = p, v = v}
|
s[n] = {p = p, v = v}
|
||||||
-- Select next node from neighbor list.
|
-- Select next node from neighbor list.
|
||||||
p = vector.add(p, check_for_falling_neighbors[v])
|
p = vadd(p, check_for_falling_neighbors[v])
|
||||||
-- Now we check out the node. If it is in need of an update,
|
-- Now we check out the node. If it is in need of an update,
|
||||||
-- it will let us know in the return value (true = updated).
|
-- it will let us know in the return value (true = updated).
|
||||||
if not core.check_single_for_falling(p) then
|
if not core.check_single_for_falling(p) then
|
||||||
|
167
builtin/game/hud.lua
Normal file
@ -0,0 +1,167 @@
|
|||||||
|
--[[
|
||||||
|
From Better HUD mod
|
||||||
|
Copyright (C) BlockMen (2013-2016)
|
||||||
|
Copyright (C) MultiCraft Development Team (2019-2020)
|
||||||
|
|
||||||
|
This program is free software; you can redistribute it and/or modify
|
||||||
|
it under the terms of the GNU Lesser General Public License as published by
|
||||||
|
the Free Software Foundation; either version 3.0 of the License, or
|
||||||
|
(at your option) any later version.
|
||||||
|
|
||||||
|
This program is distributed in the hope that it will be useful,
|
||||||
|
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
GNU Lesser General Public License for more details.
|
||||||
|
|
||||||
|
You should have received a copy of the GNU Lesser General Public License along
|
||||||
|
with this program; if not, write to the Free Software Foundation, Inc.,
|
||||||
|
51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||||
|
]]
|
||||||
|
|
||||||
|
hud, hud_id = {}, {}
|
||||||
|
|
||||||
|
local items, sb_bg = {}, {}
|
||||||
|
|
||||||
|
local function throw_error(msg)
|
||||||
|
core.log("error", "HUD: " .. msg)
|
||||||
|
end
|
||||||
|
|
||||||
|
--
|
||||||
|
-- API
|
||||||
|
--
|
||||||
|
|
||||||
|
function hud.register(name, def)
|
||||||
|
if not name or not def then
|
||||||
|
throw_error("Not enough parameters given")
|
||||||
|
return false
|
||||||
|
end
|
||||||
|
|
||||||
|
if items[name] then
|
||||||
|
throw_error("A statbar with name " .. name .. " already exists")
|
||||||
|
return false
|
||||||
|
end
|
||||||
|
|
||||||
|
-- actually register
|
||||||
|
-- add background first since draworder is based on id
|
||||||
|
if def.hud_elem_type == "statbar" and def.background then
|
||||||
|
sb_bg[name] = table.copy(def)
|
||||||
|
sb_bg[name].text = def.background
|
||||||
|
if not def.autohide_bg and def.max then
|
||||||
|
sb_bg[name].number = def.max
|
||||||
|
end
|
||||||
|
end
|
||||||
|
-- add item itself
|
||||||
|
items[name] = def
|
||||||
|
|
||||||
|
return true
|
||||||
|
end
|
||||||
|
|
||||||
|
function hud.change_item(player, name, def)
|
||||||
|
if not player or not player:is_player() then
|
||||||
|
return false
|
||||||
|
end
|
||||||
|
|
||||||
|
if not name or not def then
|
||||||
|
throw_error("Not enough parameters given")
|
||||||
|
return false
|
||||||
|
end
|
||||||
|
|
||||||
|
local i_name = player:get_player_name() .. "_" .. name
|
||||||
|
local elem = hud_id[i_name]
|
||||||
|
local item = items[name]
|
||||||
|
|
||||||
|
if not elem then
|
||||||
|
-- throw_error("Given HUD element " .. dump(name) .. " does not exist")
|
||||||
|
return false
|
||||||
|
end
|
||||||
|
|
||||||
|
if def.number then
|
||||||
|
if item.hud_elem_type ~= "statbar" then
|
||||||
|
throw_error("Attempted to change an statbar HUD parameter for text HUD element " .. dump(name))
|
||||||
|
return false
|
||||||
|
end
|
||||||
|
|
||||||
|
if item.max and def.number > item.max then
|
||||||
|
def.number = item.max
|
||||||
|
end
|
||||||
|
player:hud_change(elem.id, "number", def.number)
|
||||||
|
|
||||||
|
-- hide background when set
|
||||||
|
if item.autohide_bg then
|
||||||
|
local bg = hud_id[i_name .. "_bg"]
|
||||||
|
if not bg then
|
||||||
|
throw_error("Given HUD element " .. dump(name) .. "_bg does not exist")
|
||||||
|
return false
|
||||||
|
end
|
||||||
|
|
||||||
|
local num = def.number ~= 0 and (item.max or item.number) or 0
|
||||||
|
player:hud_change(bg.id, "number", num)
|
||||||
|
end
|
||||||
|
elseif def.text then
|
||||||
|
player:hud_change(elem.id, "text", def.text)
|
||||||
|
elseif def.offset then
|
||||||
|
player:hud_change(elem.id, "offset", def.offset)
|
||||||
|
end
|
||||||
|
|
||||||
|
return true
|
||||||
|
end
|
||||||
|
|
||||||
|
function hud.remove_item(player, name)
|
||||||
|
if not player or not player:is_player() then
|
||||||
|
return false
|
||||||
|
end
|
||||||
|
|
||||||
|
if not name then
|
||||||
|
throw_error("Not enough parameters given")
|
||||||
|
return false
|
||||||
|
end
|
||||||
|
|
||||||
|
local i_name = player:get_player_name() .. "_" .. name
|
||||||
|
local elem = hud_id[i_name]
|
||||||
|
|
||||||
|
if not elem then
|
||||||
|
throw_error("Given HUD element " .. dump(name) .. " does not exist")
|
||||||
|
return false
|
||||||
|
end
|
||||||
|
|
||||||
|
player:hud_remove(elem.id)
|
||||||
|
|
||||||
|
return true
|
||||||
|
end
|
||||||
|
|
||||||
|
--
|
||||||
|
-- Add registered HUD items to joining players
|
||||||
|
--
|
||||||
|
|
||||||
|
-- Following code is placed here to keep HUD ids internal
|
||||||
|
local function add_hud_item(player, name, def)
|
||||||
|
if not player or not name or not def then
|
||||||
|
throw_error("Not enough parameters given")
|
||||||
|
return false
|
||||||
|
end
|
||||||
|
|
||||||
|
local i_name = player:get_player_name() .. "_" .. name
|
||||||
|
hud_id[i_name] = {}
|
||||||
|
hud_id[i_name].id = player:hud_add(def)
|
||||||
|
end
|
||||||
|
|
||||||
|
core.register_on_joinplayer(function(player)
|
||||||
|
-- add the backgrounds for statbars
|
||||||
|
for name, item in pairs(sb_bg) do
|
||||||
|
add_hud_item(player, name .. "_bg", item)
|
||||||
|
end
|
||||||
|
-- and finally the actual HUD items
|
||||||
|
for name, item in pairs(items) do
|
||||||
|
add_hud_item(player, name, item)
|
||||||
|
end
|
||||||
|
end)
|
||||||
|
|
||||||
|
core.register_on_leaveplayer(function(player)
|
||||||
|
local player_name = player:get_player_name()
|
||||||
|
for name, _ in pairs(sb_bg) do
|
||||||
|
sb_bg[player_name .. "_" .. name] = nil
|
||||||
|
end
|
||||||
|
for name, _ in pairs(items) do
|
||||||
|
hud_id[player_name .. "_" .. name] = nil
|
||||||
|
end
|
||||||
|
end)
|
376
builtin/game/hunger.lua
Normal file
@ -0,0 +1,376 @@
|
|||||||
|
--[[
|
||||||
|
From Stamina mod
|
||||||
|
Copyright (C) BlockMen (2013-2015)
|
||||||
|
Copyright (C) Auke Kok <sofar@foo-projects.org> (2016)
|
||||||
|
Copyright (C) Minetest Mods Team (2016-2019)
|
||||||
|
Copyright (C) MultiCraft Development Team (2016-2020)
|
||||||
|
|
||||||
|
This program is free software; you can redistribute it and/or modify
|
||||||
|
it under the terms of the GNU Lesser General Public License as published by
|
||||||
|
the Free Software Foundation; either version 3.0 of the License, or
|
||||||
|
(at your option) any later version.
|
||||||
|
|
||||||
|
This program is distributed in the hope that it will be useful,
|
||||||
|
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
GNU Lesser General Public License for more details.
|
||||||
|
|
||||||
|
You should have received a copy of the GNU Lesser General Public License along
|
||||||
|
with this program; if not, write to the Free Software Foundation, Inc.,
|
||||||
|
51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||||
|
]]
|
||||||
|
|
||||||
|
if not core.settings:get_bool("enable_damage")
|
||||||
|
or not core.settings:get_bool("enable_hunger") then
|
||||||
|
return
|
||||||
|
end
|
||||||
|
|
||||||
|
hunger = {}
|
||||||
|
|
||||||
|
local function get_setting(key, default)
|
||||||
|
local setting = core.settings:get("hunger." .. key)
|
||||||
|
return tonumber(setting) or default
|
||||||
|
end
|
||||||
|
|
||||||
|
hunger.settings = {
|
||||||
|
-- see settingtypes.txt for descriptions
|
||||||
|
tick = get_setting("tick", 600),
|
||||||
|
tick_min = get_setting("tick_min", 4),
|
||||||
|
health_tick = get_setting("health_tick", 4),
|
||||||
|
move_tick = get_setting("move_tick", 0.5),
|
||||||
|
poison_tick = get_setting("poison_tick", 1),
|
||||||
|
exhaust_dig = get_setting("exhaust_dig", 2),
|
||||||
|
exhaust_place = get_setting("exhaust_place", 1),
|
||||||
|
exhaust_move = get_setting("exhaust_move", 2),
|
||||||
|
exhaust_jump = get_setting("exhaust_jump", 4),
|
||||||
|
exhaust_craft = get_setting("exhaust_craft", 2),
|
||||||
|
exhaust_punch = get_setting("exhaust_punch", 5),
|
||||||
|
exhaust_lvl = get_setting("exhaust_lvl", 192),
|
||||||
|
heal = get_setting("heal", 1),
|
||||||
|
heal_lvl = get_setting("heal_lvl", 5),
|
||||||
|
starve = get_setting("starve", 1),
|
||||||
|
starve_lvl = get_setting("starve_lvl", 2),
|
||||||
|
level_max = get_setting("level_max", 21),
|
||||||
|
visual_max = get_setting("visual_max", 20)
|
||||||
|
}
|
||||||
|
local settings = hunger.settings
|
||||||
|
|
||||||
|
local min, max = math.min, math.max
|
||||||
|
local vlength = vector.length
|
||||||
|
|
||||||
|
local attribute = {
|
||||||
|
saturation = "hunger:level",
|
||||||
|
poisoned = "hunger:poisoned",
|
||||||
|
exhaustion = "hunger:exhaustion"
|
||||||
|
}
|
||||||
|
|
||||||
|
local function is_player(player)
|
||||||
|
return (
|
||||||
|
minetest.is_player(player) and
|
||||||
|
not player.is_fake_player)
|
||||||
|
end
|
||||||
|
|
||||||
|
local function get_int_attribute(player, key)
|
||||||
|
local level = player:get_attribute(key)
|
||||||
|
if level then
|
||||||
|
return tonumber(level)
|
||||||
|
else
|
||||||
|
return nil
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
--- SATURATION API ---
|
||||||
|
function hunger.get_saturation(player)
|
||||||
|
return get_int_attribute(player, attribute.saturation)
|
||||||
|
end
|
||||||
|
|
||||||
|
function hunger.set_saturation(player, level)
|
||||||
|
player:set_attribute(attribute.saturation, level)
|
||||||
|
hud.change_item(player, "hunger", {number = min(settings.visual_max, level)})
|
||||||
|
end
|
||||||
|
|
||||||
|
hunger.registered_on_update_saturations = {}
|
||||||
|
function hunger.register_on_update_saturation(fun)
|
||||||
|
local saturations = hunger.registered_on_update_saturations
|
||||||
|
saturations[#saturations+1] = fun
|
||||||
|
end
|
||||||
|
|
||||||
|
function hunger.update_saturation(player, level)
|
||||||
|
for _, callback in pairs(hunger.registered_on_update_saturations) do
|
||||||
|
local result = callback(player, level)
|
||||||
|
if result then
|
||||||
|
return result
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
local old = hunger.get_saturation(player)
|
||||||
|
|
||||||
|
if not old or old == level then -- To suppress HUD update
|
||||||
|
return
|
||||||
|
end
|
||||||
|
|
||||||
|
-- players without interact priv cannot eat
|
||||||
|
if old < settings.heal_lvl and not core.check_player_privs(player, {interact=true}) then
|
||||||
|
return
|
||||||
|
end
|
||||||
|
|
||||||
|
hunger.set_saturation(player, level)
|
||||||
|
end
|
||||||
|
|
||||||
|
function hunger.change_saturation(player, change)
|
||||||
|
if not is_player(player) or not change or change == 0 then
|
||||||
|
return false
|
||||||
|
end
|
||||||
|
local level = hunger.get_saturation(player) + change or 0
|
||||||
|
level = max(level, 0)
|
||||||
|
level = min(level, settings.level_max)
|
||||||
|
hunger.update_saturation(player, level)
|
||||||
|
return true
|
||||||
|
end
|
||||||
|
|
||||||
|
hunger.change = hunger.change_saturation -- for backwards compatablity
|
||||||
|
--- END SATURATION API ---
|
||||||
|
|
||||||
|
--- POISON API ---
|
||||||
|
function hunger.is_poisoned(player)
|
||||||
|
return player:get_attribute(attribute.poisoned) == "yes"
|
||||||
|
end
|
||||||
|
|
||||||
|
function hunger.set_poisoned(player, poisoned)
|
||||||
|
if poisoned then
|
||||||
|
hud.change_item(player, "hunger", {text = "hunger_poisen.png"})
|
||||||
|
player:set_attribute(attribute.poisoned, "yes")
|
||||||
|
else
|
||||||
|
hud.change_item(player, "hunger", {text = "hunger.png"})
|
||||||
|
player:set_attribute(attribute.poisoned, "no")
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
local function poison_tick(player, ticks, interval, elapsed)
|
||||||
|
if not hunger.is_poisoned(player) then
|
||||||
|
return
|
||||||
|
elseif elapsed > ticks then
|
||||||
|
hunger.set_poisoned(player, false)
|
||||||
|
else
|
||||||
|
local hp = player:get_hp() - 1
|
||||||
|
if hp > 0 then
|
||||||
|
player:set_hp(hp)
|
||||||
|
end
|
||||||
|
core.after(interval, poison_tick, player, ticks, interval, elapsed + 1)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
hunger.registered_on_poisons = {}
|
||||||
|
function hunger.register_on_poison(fun)
|
||||||
|
local poison = hunger.registered_on_poisons
|
||||||
|
poison[#poison+1] = fun
|
||||||
|
end
|
||||||
|
|
||||||
|
function hunger.poison(player, ticks, interval)
|
||||||
|
for _, fun in pairs(hunger.registered_on_poisons) do
|
||||||
|
local rv = fun(player, ticks, interval)
|
||||||
|
if rv == true then
|
||||||
|
return
|
||||||
|
end
|
||||||
|
end
|
||||||
|
if not is_player(player) then
|
||||||
|
return
|
||||||
|
end
|
||||||
|
hunger.set_poisoned(player, true)
|
||||||
|
poison_tick(player, ticks, interval, 0)
|
||||||
|
end
|
||||||
|
--- END POISON API ---
|
||||||
|
|
||||||
|
--- EXHAUSTION API ---
|
||||||
|
hunger.exhaustion_reasons = {
|
||||||
|
craft = "craft",
|
||||||
|
dig = "dig",
|
||||||
|
heal = "heal",
|
||||||
|
jump = "jump",
|
||||||
|
move = "move",
|
||||||
|
place = "place",
|
||||||
|
punch = "punch"
|
||||||
|
}
|
||||||
|
|
||||||
|
function hunger.get_exhaustion(player)
|
||||||
|
return get_int_attribute(player, attribute.exhaustion)
|
||||||
|
end
|
||||||
|
|
||||||
|
function hunger.set_exhaustion(player, exhaustion)
|
||||||
|
player:set_attribute(attribute.exhaustion, exhaustion)
|
||||||
|
end
|
||||||
|
|
||||||
|
hunger.registered_on_exhaust_players = {}
|
||||||
|
function hunger.register_on_exhaust_player(fun)
|
||||||
|
local exhaust = hunger.registered_on_exhaust_players
|
||||||
|
exhaust[#exhaust+1] = fun
|
||||||
|
end
|
||||||
|
|
||||||
|
function hunger.exhaust_player(player, change, cause)
|
||||||
|
for _, callback in pairs(hunger.registered_on_exhaust_players) do
|
||||||
|
local result = callback(player, change, cause)
|
||||||
|
if result then
|
||||||
|
return result
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
if not is_player(player) then
|
||||||
|
return
|
||||||
|
end
|
||||||
|
|
||||||
|
local exhaustion = hunger.get_exhaustion(player) or 0
|
||||||
|
exhaustion = exhaustion + change
|
||||||
|
|
||||||
|
if exhaustion >= settings.exhaust_lvl then
|
||||||
|
exhaustion = exhaustion - settings.exhaust_lvl
|
||||||
|
hunger.change(player, -1)
|
||||||
|
end
|
||||||
|
|
||||||
|
hunger.set_exhaustion(player, exhaustion)
|
||||||
|
end
|
||||||
|
--- END EXHAUSTION API ---
|
||||||
|
|
||||||
|
-- Time based hunger functions
|
||||||
|
local connected_players = core.get_connected_players
|
||||||
|
|
||||||
|
local function move_tick()
|
||||||
|
for _, player in pairs(connected_players()) do
|
||||||
|
local controls = player:get_player_control()
|
||||||
|
local is_moving = controls.up or controls.down or controls.left or controls.right
|
||||||
|
local velocity = player:get_player_velocity()
|
||||||
|
velocity.y = 0
|
||||||
|
local horizontal_speed = vlength(velocity)
|
||||||
|
local has_velocity = horizontal_speed > 0.05
|
||||||
|
|
||||||
|
if controls.jump then
|
||||||
|
hunger.exhaust_player(player, settings.exhaust_jump, hunger.exhaustion_reasons.jump)
|
||||||
|
elseif is_moving and has_velocity then
|
||||||
|
hunger.exhaust_player(player, settings.exhaust_move, hunger.exhaustion_reasons.move)
|
||||||
|
end
|
||||||
|
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
local function hunger_tick()
|
||||||
|
-- lower saturation by 1 point after settings.tick seconds
|
||||||
|
for _, player in pairs(connected_players()) do
|
||||||
|
local saturation = hunger.get_saturation(player) or 0
|
||||||
|
if saturation > settings.tick_min then
|
||||||
|
hunger.update_saturation(player, saturation - 1)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
local function health_tick()
|
||||||
|
-- heal or damage player, depending on saturation
|
||||||
|
for _, player in pairs(connected_players()) do
|
||||||
|
local air = player:get_breath() or 0
|
||||||
|
local hp = player:get_hp() or 0
|
||||||
|
local saturation = hunger.get_saturation(player) or 0
|
||||||
|
|
||||||
|
-- don't heal if dead, drowning, or poisoned
|
||||||
|
local should_heal = (
|
||||||
|
saturation >= settings.heal_lvl and
|
||||||
|
hp > 0 and
|
||||||
|
hp < 20 and
|
||||||
|
air > 0
|
||||||
|
and not hunger.is_poisoned(player)
|
||||||
|
)
|
||||||
|
-- or damage player by 1 hp if saturation is < 2 (of 21)
|
||||||
|
local is_starving = (
|
||||||
|
saturation < settings.starve_lvl and
|
||||||
|
hp > 0
|
||||||
|
)
|
||||||
|
|
||||||
|
if should_heal then
|
||||||
|
player:set_hp(hp + settings.heal)
|
||||||
|
elseif is_starving then
|
||||||
|
player:set_hp(hp - settings.starve)
|
||||||
|
hud.change_item(player, "hunger", {number = 0})
|
||||||
|
core.after(0.5, function()
|
||||||
|
hud.change_item(player, "hunger", {number = min(settings.visual_max, saturation)})
|
||||||
|
end)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
local hunger_timer = 0
|
||||||
|
local health_timer = 0
|
||||||
|
local action_timer = 0
|
||||||
|
|
||||||
|
core.register_globalstep(function(dtime)
|
||||||
|
hunger_timer = hunger_timer + dtime
|
||||||
|
health_timer = health_timer + dtime
|
||||||
|
action_timer = action_timer + dtime
|
||||||
|
|
||||||
|
if action_timer > settings.move_tick then
|
||||||
|
action_timer = 0
|
||||||
|
move_tick()
|
||||||
|
end
|
||||||
|
|
||||||
|
if hunger_timer > settings.tick then
|
||||||
|
hunger_timer = 0
|
||||||
|
hunger_tick()
|
||||||
|
end
|
||||||
|
|
||||||
|
if health_timer > settings.health_tick then
|
||||||
|
health_timer = 0
|
||||||
|
health_tick()
|
||||||
|
end
|
||||||
|
end)
|
||||||
|
|
||||||
|
function hunger.item_eat(hp_change, user, poison)
|
||||||
|
if not poison then
|
||||||
|
hunger.change_saturation(user, hp_change)
|
||||||
|
hunger.set_exhaustion(user, 0)
|
||||||
|
else
|
||||||
|
hunger.change_saturation(user, hp_change)
|
||||||
|
hunger.poison(user, -poison, settings.poison_tick)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
hud.register("hunger", {
|
||||||
|
hud_elem_type = "statbar",
|
||||||
|
position = {x = 0.5, y = 1},
|
||||||
|
alignment = {x = -1, y = -1},
|
||||||
|
offset = {x = 8, y = -94},
|
||||||
|
size = {x = 24, y = 24},
|
||||||
|
text = "hunger.png",
|
||||||
|
background = "hunger_gone.png",
|
||||||
|
number = settings.visual_max
|
||||||
|
})
|
||||||
|
|
||||||
|
core.register_on_joinplayer(function(player)
|
||||||
|
local level = hunger.get_saturation(player) or settings.level_max
|
||||||
|
|
||||||
|
-- reset poisoned
|
||||||
|
hunger.set_poisoned(player, false)
|
||||||
|
-- set saturation
|
||||||
|
player:set_attribute(attribute.saturation, level)
|
||||||
|
|
||||||
|
-- we must manually update the HUD
|
||||||
|
if level and (level < settings.visual_max) then
|
||||||
|
core.after(1, function()
|
||||||
|
hud.change_item(player, "hunger", {number = min(settings.visual_max, level)})
|
||||||
|
end)
|
||||||
|
end
|
||||||
|
end)
|
||||||
|
|
||||||
|
local exhaust = hunger.exhaust_player
|
||||||
|
core.register_on_placenode(function(_, _, player)
|
||||||
|
exhaust(player, settings.exhaust_place, hunger.exhaustion_reasons.place)
|
||||||
|
end)
|
||||||
|
core.register_on_dignode(function(_, _, player)
|
||||||
|
exhaust(player, settings.exhaust_dig, hunger.exhaustion_reasons.dig)
|
||||||
|
end)
|
||||||
|
core.register_on_craft(function(_, player)
|
||||||
|
exhaust(player, settings.exhaust_craft, hunger.exhaustion_reasons.craft)
|
||||||
|
end)
|
||||||
|
core.register_on_punchplayer(function(_, hitter)
|
||||||
|
exhaust(hitter, settings.exhaust_punch, hunger.exhaustion_reasons.punch)
|
||||||
|
end)
|
||||||
|
core.register_on_respawnplayer(function(player)
|
||||||
|
hunger.set_saturation(player, settings.level_max)
|
||||||
|
hunger.set_exhaustion(player, 0)
|
||||||
|
hunger.set_poisoned(player, false)
|
||||||
|
end)
|
@ -1,4 +1,3 @@
|
|||||||
|
|
||||||
local scriptpath = core.get_builtin_path()
|
local scriptpath = core.get_builtin_path()
|
||||||
local commonpath = scriptpath .. "common" .. DIR_DELIM
|
local commonpath = scriptpath .. "common" .. DIR_DELIM
|
||||||
local gamepath = scriptpath .. "game".. DIR_DELIM
|
local gamepath = scriptpath .. "game".. DIR_DELIM
|
||||||
@ -32,8 +31,10 @@ assert(loadfile(gamepath .. "falling.lua"))(builtin_shared)
|
|||||||
dofile(gamepath .. "features.lua")
|
dofile(gamepath .. "features.lua")
|
||||||
dofile(gamepath .. "voxelarea.lua")
|
dofile(gamepath .. "voxelarea.lua")
|
||||||
dofile(gamepath .. "forceloading.lua")
|
dofile(gamepath .. "forceloading.lua")
|
||||||
|
dofile(gamepath .. "hud.lua")
|
||||||
dofile(gamepath .. "statbars.lua")
|
dofile(gamepath .. "statbars.lua")
|
||||||
dofile(gamepath .. "knockback.lua")
|
dofile(gamepath .. "knockback.lua")
|
||||||
|
dofile(gamepath .. "hunger.lua")
|
||||||
dofile(gamepath .. "sscsm" .. DIR_DELIM .. "init.lua")
|
dofile(gamepath .. "sscsm" .. DIR_DELIM .. "init.lua")
|
||||||
|
|
||||||
profiler = nil
|
profiler = nil
|
||||||
|
@ -2,11 +2,19 @@
|
|||||||
|
|
||||||
local builtin_shared = ...
|
local builtin_shared = ...
|
||||||
|
|
||||||
|
local abs, atan2, cos, floor, max, sin, random =
|
||||||
|
math.abs, math.atan2, math.cos, math.floor, math.max, math.sin, math.random
|
||||||
|
local vadd, vnew, vmultiply, vnormalize, vsubtract =
|
||||||
|
vector.add, vector.new, vector.multiply, vector.normalize, vector.subtract
|
||||||
|
|
||||||
|
local creative_mode = core.settings:get_bool("creative_mode")
|
||||||
|
local node_drop = core.settings:get_bool("node_drop", true)
|
||||||
|
|
||||||
local function copy_pointed_thing(pointed_thing)
|
local function copy_pointed_thing(pointed_thing)
|
||||||
return {
|
return {
|
||||||
type = pointed_thing.type,
|
type = pointed_thing.type,
|
||||||
above = vector.new(pointed_thing.above),
|
above = vnew(pointed_thing.above),
|
||||||
under = vector.new(pointed_thing.under),
|
under = vnew(pointed_thing.under),
|
||||||
ref = pointed_thing.ref,
|
ref = pointed_thing.ref,
|
||||||
}
|
}
|
||||||
end
|
end
|
||||||
@ -39,11 +47,11 @@ end
|
|||||||
|
|
||||||
function core.dir_to_facedir(dir, is6d)
|
function core.dir_to_facedir(dir, is6d)
|
||||||
--account for y if requested
|
--account for y if requested
|
||||||
if is6d and math.abs(dir.y) > math.abs(dir.x) and math.abs(dir.y) > math.abs(dir.z) then
|
if is6d and abs(dir.y) > abs(dir.x) and abs(dir.y) > abs(dir.z) then
|
||||||
|
|
||||||
--from above
|
--from above
|
||||||
if dir.y < 0 then
|
if dir.y < 0 then
|
||||||
if math.abs(dir.x) > math.abs(dir.z) then
|
if abs(dir.x) > abs(dir.z) then
|
||||||
if dir.x < 0 then
|
if dir.x < 0 then
|
||||||
return 19
|
return 19
|
||||||
else
|
else
|
||||||
@ -59,7 +67,7 @@ function core.dir_to_facedir(dir, is6d)
|
|||||||
|
|
||||||
--from below
|
--from below
|
||||||
else
|
else
|
||||||
if math.abs(dir.x) > math.abs(dir.z) then
|
if abs(dir.x) > abs(dir.z) then
|
||||||
if dir.x < 0 then
|
if dir.x < 0 then
|
||||||
return 15
|
return 15
|
||||||
else
|
else
|
||||||
@ -75,7 +83,7 @@ function core.dir_to_facedir(dir, is6d)
|
|||||||
end
|
end
|
||||||
|
|
||||||
--otherwise, place horizontally
|
--otherwise, place horizontally
|
||||||
elseif math.abs(dir.x) > math.abs(dir.z) then
|
elseif abs(dir.x) > abs(dir.z) then
|
||||||
if dir.x < 0 then
|
if dir.x < 0 then
|
||||||
return 3
|
return 3
|
||||||
else
|
else
|
||||||
@ -113,13 +121,13 @@ function core.facedir_to_dir(facedir)
|
|||||||
end
|
end
|
||||||
|
|
||||||
function core.dir_to_wallmounted(dir)
|
function core.dir_to_wallmounted(dir)
|
||||||
if math.abs(dir.y) > math.max(math.abs(dir.x), math.abs(dir.z)) then
|
if abs(dir.y) > max(abs(dir.x), abs(dir.z)) then
|
||||||
if dir.y < 0 then
|
if dir.y < 0 then
|
||||||
return 1
|
return 1
|
||||||
else
|
else
|
||||||
return 0
|
return 0
|
||||||
end
|
end
|
||||||
elseif math.abs(dir.x) > math.abs(dir.z) then
|
elseif abs(dir.x) > abs(dir.z) then
|
||||||
if dir.x < 0 then
|
if dir.x < 0 then
|
||||||
return 3
|
return 3
|
||||||
else
|
else
|
||||||
@ -148,11 +156,11 @@ function core.wallmounted_to_dir(wallmounted)
|
|||||||
end
|
end
|
||||||
|
|
||||||
function core.dir_to_yaw(dir)
|
function core.dir_to_yaw(dir)
|
||||||
return -math.atan2(dir.x, dir.z)
|
return -atan2(dir.x, dir.z)
|
||||||
end
|
end
|
||||||
|
|
||||||
function core.yaw_to_dir(yaw)
|
function core.yaw_to_dir(yaw)
|
||||||
return {x = -math.sin(yaw), y = 0, z = math.cos(yaw)}
|
return {x = -sin(yaw), y = 0, z = cos(yaw)}
|
||||||
end
|
end
|
||||||
|
|
||||||
function core.is_colored_paramtype(ptype)
|
function core.is_colored_paramtype(ptype)
|
||||||
@ -165,9 +173,9 @@ function core.strip_param2_color(param2, paramtype2)
|
|||||||
return nil
|
return nil
|
||||||
end
|
end
|
||||||
if paramtype2 == "colorfacedir" then
|
if paramtype2 == "colorfacedir" then
|
||||||
param2 = math.floor(param2 / 32) * 32
|
param2 = floor(param2 / 32) * 32
|
||||||
elseif paramtype2 == "colorwallmounted" then
|
elseif paramtype2 == "colorwallmounted" then
|
||||||
param2 = math.floor(param2 / 8) * 8
|
param2 = floor(param2 / 8) * 8
|
||||||
end
|
end
|
||||||
-- paramtype2 == "color" requires no modification.
|
-- paramtype2 == "color" requires no modification.
|
||||||
return param2
|
return param2
|
||||||
@ -210,7 +218,7 @@ function core.get_node_drops(node, toolname)
|
|||||||
local good_rarity = true
|
local good_rarity = true
|
||||||
local good_tool = true
|
local good_tool = true
|
||||||
if item.rarity ~= nil then
|
if item.rarity ~= nil then
|
||||||
good_rarity = item.rarity < 1 or math.random(item.rarity) == 1
|
good_rarity = item.rarity < 1 or random(item.rarity) == 1
|
||||||
end
|
end
|
||||||
if item.tools ~= nil then
|
if item.tools ~= nil then
|
||||||
good_tool = false
|
good_tool = false
|
||||||
@ -347,7 +355,7 @@ function core.item_place_node(itemstack, placer, pointed_thing, param2,
|
|||||||
color_divisor = 32
|
color_divisor = 32
|
||||||
end
|
end
|
||||||
if color_divisor then
|
if color_divisor then
|
||||||
local color = math.floor(metatable.palette_index / color_divisor)
|
local color = floor(metatable.palette_index / color_divisor)
|
||||||
local other = newnode.param2 % color_divisor
|
local other = newnode.param2 % color_divisor
|
||||||
newnode.param2 = color * color_divisor + other
|
newnode.param2 = color * color_divisor + other
|
||||||
end
|
end
|
||||||
@ -406,16 +414,6 @@ function core.item_place_node(itemstack, placer, pointed_thing, param2,
|
|||||||
return itemstack, place_to
|
return itemstack, place_to
|
||||||
end
|
end
|
||||||
|
|
||||||
-- deprecated, item_place does not call this
|
|
||||||
function core.item_place_object(itemstack, placer, pointed_thing)
|
|
||||||
local pos = core.get_pointed_thing_position(pointed_thing, true)
|
|
||||||
if pos ~= nil then
|
|
||||||
local item = itemstack:take_item()
|
|
||||||
core.add_item(pos, item)
|
|
||||||
end
|
|
||||||
return itemstack
|
|
||||||
end
|
|
||||||
|
|
||||||
function core.item_place(itemstack, placer, pointed_thing, param2)
|
function core.item_place(itemstack, placer, pointed_thing, param2)
|
||||||
-- Call on_rightclick if the pointed node defines it
|
-- Call on_rightclick if the pointed node defines it
|
||||||
if pointed_thing.type == "node" and placer and
|
if pointed_thing.type == "node" and placer and
|
||||||
@ -439,10 +437,103 @@ function core.item_secondary_use(itemstack, placer)
|
|||||||
return itemstack
|
return itemstack
|
||||||
end
|
end
|
||||||
|
|
||||||
|
local function item_throw_step(entity, dtime)
|
||||||
|
entity.throw_timer = entity.throw_timer + dtime
|
||||||
|
if entity.throw_timer > 20 then
|
||||||
|
entity.object:remove()
|
||||||
|
return
|
||||||
|
end
|
||||||
|
if not entity.thrower then
|
||||||
|
return
|
||||||
|
end
|
||||||
|
local pos = entity.object:get_pos()
|
||||||
|
if not core.is_valid_pos(pos) then
|
||||||
|
entity.object:remove()
|
||||||
|
return
|
||||||
|
end
|
||||||
|
local hit_object
|
||||||
|
local dir = vnormalize(entity.object:get_velocity())
|
||||||
|
local pos2 = vadd(pos, vmultiply(dir, 3))
|
||||||
|
local _, node_pos = core.line_of_sight(pos, pos2)
|
||||||
|
if node_pos then
|
||||||
|
local def = core.get_node(node_pos)
|
||||||
|
if def then
|
||||||
|
pos = vsubtract(node_pos, vmultiply(dir, 1.5))
|
||||||
|
entity.object:move_to(pos)
|
||||||
|
else
|
||||||
|
node_pos = nil
|
||||||
|
end
|
||||||
|
end
|
||||||
|
local objs = core.get_objects_inside_radius(pos, 1.5)
|
||||||
|
for _, obj in pairs(objs) do
|
||||||
|
if obj:is_player() then
|
||||||
|
local name = obj:get_player_name()
|
||||||
|
if name ~= entity.thrower then
|
||||||
|
hit_object = obj
|
||||||
|
break
|
||||||
|
end
|
||||||
|
elseif obj:get_luaentity() ~= nil and
|
||||||
|
obj:get_luaentity().name ~= "__builtin:throwing_item" and
|
||||||
|
obj:get_luaentity().name ~= "__builtin:item" then
|
||||||
|
hit_object = obj
|
||||||
|
break
|
||||||
|
end
|
||||||
|
end
|
||||||
|
if hit_object or node_pos then
|
||||||
|
local player = core.get_player_by_name(entity.thrower)
|
||||||
|
entity.on_impact(player, pos, entity.throw_direction, hit_object)
|
||||||
|
entity.object:remove()
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
function core.item_throw(name, thrower, speed, accel, on_impact)
|
||||||
|
if not thrower or not thrower:is_player() then
|
||||||
|
return
|
||||||
|
end
|
||||||
|
local pos = thrower:get_pos()
|
||||||
|
if not core.is_valid_pos(pos) then
|
||||||
|
return
|
||||||
|
end
|
||||||
|
pos.y = pos.y + 1.5
|
||||||
|
local obj
|
||||||
|
local properties = {is_visible=true}
|
||||||
|
if core.registered_entities[name] then
|
||||||
|
obj = core.add_entity(pos, name)
|
||||||
|
elseif core.registered_items[name] then
|
||||||
|
obj = core.add_entity(pos, "__builtin:throwing_item")
|
||||||
|
properties.textures = {name}
|
||||||
|
else
|
||||||
|
return
|
||||||
|
end
|
||||||
|
if obj then
|
||||||
|
local ent = obj:get_luaentity()
|
||||||
|
if ent then
|
||||||
|
local s = speed or 19 -- default speed
|
||||||
|
local a = accel or -3 -- default acceleration
|
||||||
|
local dir = thrower:get_look_dir()
|
||||||
|
local gravity = tonumber(core.settings:get("movement_gravity")) or 9.81
|
||||||
|
ent.thrower = thrower:get_player_name()
|
||||||
|
ent.throw_timer = 0
|
||||||
|
ent.throw_direction = dir
|
||||||
|
ent.on_step = item_throw_step
|
||||||
|
ent.on_impact = on_impact and on_impact or function() end
|
||||||
|
obj:set_properties(properties)
|
||||||
|
obj:set_velocity({x=dir.x * s, y=dir.y * s, z=dir.z * s})
|
||||||
|
obj:set_acceleration({x=dir.x * a, y=-gravity, z=dir.z * a})
|
||||||
|
return obj
|
||||||
|
else
|
||||||
|
obj:remove()
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
function core.item_drop(itemstack, dropper, pos)
|
function core.item_drop(itemstack, dropper, pos)
|
||||||
local dropper_is_player = dropper and dropper:is_player()
|
local dropper_is_player = dropper and dropper:is_player()
|
||||||
local p = table.copy(pos)
|
local p = table.copy(pos)
|
||||||
local cnt = itemstack:get_count()
|
local cnt = itemstack:get_count()
|
||||||
|
if not core.is_valid_pos(p) then
|
||||||
|
return
|
||||||
|
end
|
||||||
if dropper_is_player then
|
if dropper_is_player then
|
||||||
p.y = p.y + 1.2
|
p.y = p.y + 1.2
|
||||||
end
|
end
|
||||||
@ -450,12 +541,19 @@ function core.item_drop(itemstack, dropper, pos)
|
|||||||
local obj = core.add_item(p, item)
|
local obj = core.add_item(p, item)
|
||||||
if obj then
|
if obj then
|
||||||
if dropper_is_player then
|
if dropper_is_player then
|
||||||
|
local vel = dropper:get_player_velocity()
|
||||||
local dir = dropper:get_look_dir()
|
local dir = dropper:get_look_dir()
|
||||||
dir.x = dir.x * 2.9
|
dir.x = vel.x + dir.x * 4
|
||||||
dir.y = dir.y * 2.9 + 2
|
dir.y = vel.y + dir.y * 4 + 2
|
||||||
dir.z = dir.z * 2.9
|
dir.z = vel.z + dir.z * 4
|
||||||
obj:set_velocity(dir)
|
obj:set_velocity(dir)
|
||||||
obj:get_luaentity().dropped_by = dropper:get_player_name()
|
obj:get_luaentity().dropped_by = dropper:get_player_name()
|
||||||
|
else
|
||||||
|
obj:set_velocity({
|
||||||
|
x = random(-2, 2),
|
||||||
|
y = random(2, 4),
|
||||||
|
z = random(-2, 2)
|
||||||
|
})
|
||||||
end
|
end
|
||||||
return itemstack
|
return itemstack
|
||||||
end
|
end
|
||||||
@ -463,7 +561,8 @@ function core.item_drop(itemstack, dropper, pos)
|
|||||||
-- environment failed
|
-- environment failed
|
||||||
end
|
end
|
||||||
|
|
||||||
function core.do_item_eat(hp_change, replace_with_item, itemstack, user, pointed_thing)
|
local enable_hunger = core.settings:get_bool("enable_damage") and core.settings:get_bool("enable_hunger")
|
||||||
|
function core.do_item_eat(hp_change, replace_with_item, itemstack, user, pointed_thing, poison)
|
||||||
for _, callback in pairs(core.registered_on_item_eats) do
|
for _, callback in pairs(core.registered_on_item_eats) do
|
||||||
local result = callback(hp_change, replace_with_item, itemstack, user, pointed_thing)
|
local result = callback(hp_change, replace_with_item, itemstack, user, pointed_thing)
|
||||||
if result then
|
if result then
|
||||||
@ -472,15 +571,47 @@ function core.do_item_eat(hp_change, replace_with_item, itemstack, user, pointed
|
|||||||
end
|
end
|
||||||
local def = itemstack:get_definition()
|
local def = itemstack:get_definition()
|
||||||
if itemstack:take_item() ~= nil then
|
if itemstack:take_item() ~= nil then
|
||||||
|
if enable_hunger then
|
||||||
|
hunger.item_eat(hp_change, user, poison)
|
||||||
|
else
|
||||||
user:set_hp(user:get_hp() + hp_change)
|
user:set_hp(user:get_hp() + hp_change)
|
||||||
|
end
|
||||||
|
|
||||||
|
local pos = user:get_pos()
|
||||||
|
if not core.is_valid_pos(pos) then
|
||||||
|
return itemstack
|
||||||
|
end
|
||||||
|
|
||||||
if def and def.sound and def.sound.eat then
|
if def and def.sound and def.sound.eat then
|
||||||
core.sound_play(def.sound.eat, {
|
core.sound_play(def.sound.eat, {
|
||||||
pos = user:get_pos(),
|
pos = pos,
|
||||||
max_hear_distance = 16
|
max_hear_distance = 16})
|
||||||
}, true)
|
else
|
||||||
|
core.sound_play("player_eat", {
|
||||||
|
pos = pos,
|
||||||
|
max_hear_distance = 10,
|
||||||
|
gain = 0.3})
|
||||||
end
|
end
|
||||||
|
|
||||||
|
local dir = user:get_look_dir()
|
||||||
|
local ppos = {x = pos.x, y = pos.y + 1.3, z = pos.z}
|
||||||
|
core.add_particlespawner({
|
||||||
|
amount = 20,
|
||||||
|
time = 0.1,
|
||||||
|
minpos = ppos,
|
||||||
|
maxpos = ppos,
|
||||||
|
minvel = {x = dir.x - 1, y = 2, z = dir.z - 1},
|
||||||
|
maxvel = {x = dir.x + 1, y = 2, z = dir.z + 1},
|
||||||
|
minacc = {x = 0, y = -5, z = 0},
|
||||||
|
maxacc = {x = 0, y = -9, z = 0},
|
||||||
|
minexptime = 1,
|
||||||
|
maxexptime = 1,
|
||||||
|
minsize = 1,
|
||||||
|
maxsize = 1,
|
||||||
|
vertical = false,
|
||||||
|
texture = def.inventory_image
|
||||||
|
})
|
||||||
|
|
||||||
if replace_with_item then
|
if replace_with_item then
|
||||||
if itemstack:is_empty() then
|
if itemstack:is_empty() then
|
||||||
itemstack:add_item(replace_with_item)
|
itemstack:add_item(replace_with_item)
|
||||||
@ -490,8 +621,7 @@ function core.do_item_eat(hp_change, replace_with_item, itemstack, user, pointed
|
|||||||
if inv and inv:room_for_item("main", {name=replace_with_item}) then
|
if inv and inv:room_for_item("main", {name=replace_with_item}) then
|
||||||
inv:add_item("main", replace_with_item)
|
inv:add_item("main", replace_with_item)
|
||||||
else
|
else
|
||||||
local pos = user:get_pos()
|
pos.y = pos.y + 0.5
|
||||||
pos.y = math.floor(pos.y + 0.5)
|
|
||||||
core.add_item(pos, replace_with_item)
|
core.add_item(pos, replace_with_item)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
@ -500,10 +630,10 @@ function core.do_item_eat(hp_change, replace_with_item, itemstack, user, pointed
|
|||||||
return itemstack
|
return itemstack
|
||||||
end
|
end
|
||||||
|
|
||||||
function core.item_eat(hp_change, replace_with_item)
|
function core.item_eat(hp_change, replace_with_item, poison)
|
||||||
return function(itemstack, user, pointed_thing) -- closure
|
return function(itemstack, user, pointed_thing) -- closure
|
||||||
if user then
|
if user then
|
||||||
return core.do_item_eat(hp_change, replace_with_item, itemstack, user, pointed_thing)
|
return core.do_item_eat(hp_change, replace_with_item, itemstack, user, pointed_thing, poison)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
@ -512,7 +642,7 @@ function core.node_punch(pos, node, puncher, pointed_thing)
|
|||||||
-- Run script hook
|
-- Run script hook
|
||||||
for _, callback in ipairs(core.registered_on_punchnodes) do
|
for _, callback in ipairs(core.registered_on_punchnodes) do
|
||||||
-- Copy pos and node because callback can modify them
|
-- Copy pos and node because callback can modify them
|
||||||
local pos_copy = vector.new(pos)
|
local pos_copy = vnew(pos)
|
||||||
local node_copy = {name=node.name, param1=node.param1, param2=node.param2}
|
local node_copy = {name=node.name, param1=node.param1, param2=node.param2}
|
||||||
local pointed_thing_copy = pointed_thing and copy_pointed_thing(pointed_thing) or nil
|
local pointed_thing_copy = pointed_thing and copy_pointed_thing(pointed_thing) or nil
|
||||||
callback(pos_copy, node_copy, puncher, pointed_thing_copy)
|
callback(pos_copy, node_copy, puncher, pointed_thing_copy)
|
||||||
@ -523,7 +653,7 @@ function core.handle_node_drops(pos, drops, digger)
|
|||||||
-- Add dropped items to object's inventory
|
-- Add dropped items to object's inventory
|
||||||
local inv = digger and digger:get_inventory()
|
local inv = digger and digger:get_inventory()
|
||||||
local give_item
|
local give_item
|
||||||
if inv then
|
if (not node_drop or creative_mode) and inv then
|
||||||
give_item = function(item)
|
give_item = function(item)
|
||||||
return inv:add_item("main", item)
|
return inv:add_item("main", item)
|
||||||
end
|
end
|
||||||
@ -537,12 +667,7 @@ function core.handle_node_drops(pos, drops, digger)
|
|||||||
for _, dropped_item in pairs(drops) do
|
for _, dropped_item in pairs(drops) do
|
||||||
local left = give_item(dropped_item)
|
local left = give_item(dropped_item)
|
||||||
if not left:is_empty() then
|
if not left:is_empty() then
|
||||||
local p = {
|
core.item_drop(left, nil, pos)
|
||||||
x = pos.x + math.random()/2-0.25,
|
|
||||||
y = pos.y + math.random()/2-0.25,
|
|
||||||
z = pos.z + math.random()/2-0.25,
|
|
||||||
}
|
|
||||||
core.add_item(p, left)
|
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
@ -675,7 +800,7 @@ end
|
|||||||
-- Item definition defaults
|
-- Item definition defaults
|
||||||
--
|
--
|
||||||
|
|
||||||
local default_stack_max = tonumber(minetest.settings:get("default_stack_max")) or 99
|
local default_stack_max = tonumber(minetest.settings:get("default_stack_max")) or 64
|
||||||
|
|
||||||
core.nodedef_default = {
|
core.nodedef_default = {
|
||||||
-- Item properties
|
-- Item properties
|
||||||
|
@ -1,5 +1,7 @@
|
|||||||
-- Minetest: builtin/item_entity.lua
|
-- Minetest: builtin/item_entity.lua
|
||||||
|
|
||||||
|
local abs, min, floor, random, pi = math.abs, math.min, math.floor, math.random, math.pi
|
||||||
|
local vnormalize = vector.normalize
|
||||||
function core.spawn_item(pos, item)
|
function core.spawn_item(pos, item)
|
||||||
-- Take item in any format
|
-- Take item in any format
|
||||||
local stack = ItemStack(item)
|
local stack = ItemStack(item)
|
||||||
@ -14,9 +16,53 @@ end
|
|||||||
-- If item_entity_ttl is not set, enity will have default life time
|
-- If item_entity_ttl is not set, enity will have default life time
|
||||||
-- Setting it to -1 disables the feature
|
-- Setting it to -1 disables the feature
|
||||||
|
|
||||||
local time_to_live = tonumber(core.settings:get("item_entity_ttl")) or 900
|
local time_to_live = tonumber(core.settings:get("item_entity_ttl")) or 600
|
||||||
local gravity = tonumber(core.settings:get("movement_gravity")) or 9.81
|
local gravity = tonumber(core.settings:get("movement_gravity")) or 9.81
|
||||||
|
local collection = core.settings:get_bool("item_collection", true)
|
||||||
|
local water_flow = core.settings:get_bool("item_water_flow", true)
|
||||||
|
local lava_destroy = core.settings:get_bool("item_lava_destroy", true)
|
||||||
|
|
||||||
|
-- Water flow functions, based on QwertyMine3 (WTFPL), and TenPlus1 (MIT) mods
|
||||||
|
local function quick_flow_logic(node, pos_testing, dir)
|
||||||
|
local node_testing = core.get_node_or_nil(pos_testing)
|
||||||
|
if not node_testing then return 0 end
|
||||||
|
local liquid = core.registered_nodes[node_testing.name] and core.registered_nodes[node_testing.name].liquidtype
|
||||||
|
|
||||||
|
if not liquid or liquid ~= "flowing" and liquid ~= "source" then
|
||||||
|
return 0
|
||||||
|
end
|
||||||
|
|
||||||
|
local sum = node.param2 - node_testing.param2
|
||||||
|
|
||||||
|
return (sum < -6 or (sum < 6 and sum > 0) or sum == 0) and dir or -dir
|
||||||
|
end
|
||||||
|
|
||||||
|
local function quick_flow(pos, node)
|
||||||
|
local x, z = 0, 0
|
||||||
|
|
||||||
|
x = x + quick_flow_logic(node, {x = pos.x - 1.01, y = pos.y, z = pos.z}, -1)
|
||||||
|
x = x + quick_flow_logic(node, {x = pos.x + 1.01, y = pos.y, z = pos.z}, 1)
|
||||||
|
z = z + quick_flow_logic(node, {x = pos.x, y = pos.y, z = pos.z - 1.01}, -1)
|
||||||
|
z = z + quick_flow_logic(node, {x = pos.x, y = pos.y, z = pos.z + 1.01}, 1)
|
||||||
|
return vnormalize({x = x, y = 0, z = z})
|
||||||
|
end
|
||||||
|
|
||||||
|
core.register_entity(":__builtin:throwing_item", {
|
||||||
|
physical = false,
|
||||||
|
visual = "wielditem",
|
||||||
|
collisionbox = {0, 0, 0, 0, 0, 0},
|
||||||
|
textures = {""},
|
||||||
|
visual_size = {x = 0.4, y = 0.4},
|
||||||
|
is_visible = false,
|
||||||
|
on_activate = function(self, staticdata)
|
||||||
|
if staticdata == "expired" then
|
||||||
|
self.object:remove()
|
||||||
|
end
|
||||||
|
end,
|
||||||
|
get_staticdata = function()
|
||||||
|
return "expired"
|
||||||
|
end
|
||||||
|
})
|
||||||
|
|
||||||
core.register_entity(":__builtin:item", {
|
core.register_entity(":__builtin:item", {
|
||||||
initial_properties = {
|
initial_properties = {
|
||||||
@ -52,10 +98,10 @@ core.register_entity(":__builtin:item", {
|
|||||||
local itemname = stack:is_known() and stack:get_name() or "unknown"
|
local itemname = stack:is_known() and stack:get_name() or "unknown"
|
||||||
|
|
||||||
local max_count = stack:get_stack_max()
|
local max_count = stack:get_stack_max()
|
||||||
local count = math.min(stack:get_count(), max_count)
|
local count = min(stack:get_count(), max_count)
|
||||||
local size = 0.2 + 0.1 * (count / max_count) ^ (1 / 3)
|
local size = 0.2 + 0.1 * (count / max_count) ^ (1 / 3)
|
||||||
local def = core.registered_nodes[itemname]
|
local def = core.registered_nodes[itemname]
|
||||||
local glow = def and math.floor(def.light_source / 2 + 0.5)
|
local glow = def and floor(def.light_source / 2 + 0.5)
|
||||||
|
|
||||||
self.object:set_properties({
|
self.object:set_properties({
|
||||||
is_visible = true,
|
is_visible = true,
|
||||||
@ -63,9 +109,10 @@ core.register_entity(":__builtin:item", {
|
|||||||
textures = {itemname},
|
textures = {itemname},
|
||||||
visual_size = {x = size, y = size},
|
visual_size = {x = size, y = size},
|
||||||
collisionbox = {-size, -size, -size, size, size, size},
|
collisionbox = {-size, -size, -size, size, size, size},
|
||||||
automatic_rotate = math.pi * 0.5 * 0.2 / size,
|
automatic_rotate = pi * 0.5 * 0.15 / size,
|
||||||
wield_item = self.itemstring,
|
wield_item = self.itemstring,
|
||||||
glow = glow,
|
glow = glow,
|
||||||
|
infotext = core.registered_items[itemname].description
|
||||||
})
|
})
|
||||||
|
|
||||||
end,
|
end,
|
||||||
@ -79,7 +126,7 @@ core.register_entity(":__builtin:item", {
|
|||||||
end,
|
end,
|
||||||
|
|
||||||
on_activate = function(self, staticdata, dtime_s)
|
on_activate = function(self, staticdata, dtime_s)
|
||||||
if string.sub(staticdata, 1, string.len("return")) == "return" then
|
if staticdata:sub(1, 6) == "return" then
|
||||||
local data = core.deserialize(staticdata)
|
local data = core.deserialize(staticdata)
|
||||||
if data and type(data) == "table" then
|
if data and type(data) == "table" then
|
||||||
self.itemstring = data.itemstring
|
self.itemstring = data.itemstring
|
||||||
@ -89,7 +136,7 @@ core.register_entity(":__builtin:item", {
|
|||||||
else
|
else
|
||||||
self.itemstring = staticdata
|
self.itemstring = staticdata
|
||||||
end
|
end
|
||||||
self.object:set_armor_groups({immortal = 1})
|
self.object:set_armor_groups({immortal = 1, silent = 1})
|
||||||
self.object:set_velocity({x = 0, y = 2, z = 0})
|
self.object:set_velocity({x = 0, y = 2, z = 0})
|
||||||
self.object:set_acceleration({x = 0, y = -gravity, z = 0})
|
self.object:set_acceleration({x = 0, y = -gravity, z = 0})
|
||||||
self:set_item()
|
self:set_item()
|
||||||
@ -165,6 +212,7 @@ core.register_entity(":__builtin:item", {
|
|||||||
y = pos.y + self.object:get_properties().collisionbox[2] - 0.05,
|
y = pos.y + self.object:get_properties().collisionbox[2] - 0.05,
|
||||||
z = pos.z
|
z = pos.z
|
||||||
})
|
})
|
||||||
|
local node_inside = core.get_node_or_nil(pos)
|
||||||
-- Delete in 'ignore' nodes
|
-- Delete in 'ignore' nodes
|
||||||
if node and node.name == "ignore" then
|
if node and node.name == "ignore" then
|
||||||
self.itemstring = ""
|
self.itemstring = ""
|
||||||
@ -264,16 +312,64 @@ core.register_entity(":__builtin:item", {
|
|||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
-- Slide on slippery nodes
|
local vel = self.object:get_velocity()
|
||||||
local def = node and core.registered_nodes[node.name]
|
local def = node and core.registered_nodes[node.name]
|
||||||
|
local def_inside = node_inside and core.registered_nodes[node_inside.name]
|
||||||
|
-- local is_moving = (def and not def.walkable) or
|
||||||
|
-- vel.x ~= 0 or vel.y ~= 0 or vel.z ~= 0
|
||||||
|
-- local is_slippery = false
|
||||||
|
|
||||||
|
-- Destroy item when dropped into lava
|
||||||
|
if lava_destroy and def_inside
|
||||||
|
and def_inside.groups and def_inside.groups.lava then
|
||||||
|
core.sound_play("default_cool_lava", {
|
||||||
|
pos = pos, max_hear_distance = 10})
|
||||||
|
self.object:remove()
|
||||||
|
core.add_particlespawner({
|
||||||
|
amount = 3,
|
||||||
|
time = 0.1,
|
||||||
|
minpos = {x = pos.x - 0.1, y = pos.y + 0.1, z = pos.z - 0.1},
|
||||||
|
maxpos = {x = pos.x + 0.1, y = pos.y + 0.2, z = pos.z + 0.1},
|
||||||
|
minvel = {x = 0, y = 2.5, z = 0},
|
||||||
|
maxvel = {x = 0, y = 2.5, z = 0},
|
||||||
|
minacc = {x = -0.15, y = -0.02, z = -0.15},
|
||||||
|
maxacc = {x = 0.15, y = -0.01, z = 0.15},
|
||||||
|
minexptime = 4,
|
||||||
|
maxexptime = 6,
|
||||||
|
minsize = 2,
|
||||||
|
maxsize = 4,
|
||||||
|
texture = "item_smoke.png"
|
||||||
|
})
|
||||||
|
return
|
||||||
|
end
|
||||||
|
|
||||||
|
-- Moving items in the water flow (TenPlus1, MIT)
|
||||||
|
if water_flow and def_inside and def_inside.liquidtype == "flowing" then
|
||||||
|
local vec = quick_flow(pos, node_inside)
|
||||||
|
self.object:set_velocity({x = vec.x, y = vel.y, z = vec.z})
|
||||||
|
return
|
||||||
|
end
|
||||||
|
|
||||||
|
-- Move item inside node to free space (TenPlus1, MIT)
|
||||||
|
--[[if not self.stuck and def_inside and def_inside.walkable and
|
||||||
|
not def_inside.liquid and node_inside.name ~= "air" and
|
||||||
|
def_inside.drawtype == "normal" then
|
||||||
|
local npos = core.find_node_near(pos, 1, "air")
|
||||||
|
if npos then
|
||||||
|
self.object:move_to(npos)
|
||||||
|
else
|
||||||
|
self.stuck = true
|
||||||
|
end
|
||||||
|
end]]
|
||||||
|
|
||||||
|
-- Slide on slippery nodes
|
||||||
local keep_movement = false
|
local keep_movement = false
|
||||||
|
|
||||||
if def then
|
if def then
|
||||||
local slippery = core.get_item_group(node.name, "slippery")
|
local slippery = core.get_item_group(node.name, "slippery")
|
||||||
local vel = self.object:get_velocity()
|
if slippery ~= 0 and (abs(vel.x) > 0.1 or abs(vel.z) > 0.1) then
|
||||||
if slippery ~= 0 and (math.abs(vel.x) > 0.1 or math.abs(vel.z) > 0.1) then
|
|
||||||
-- Horizontal deceleration
|
-- Horizontal deceleration
|
||||||
local factor = math.min(4 / (slippery + 4) * dtime, 1)
|
local factor = min(4 / (slippery + 4) * dtime, 1)
|
||||||
self.object:set_velocity({
|
self.object:set_velocity({
|
||||||
x = vel.x * (1 - factor),
|
x = vel.x * (1 - factor),
|
||||||
y = 0,
|
y = 0,
|
||||||
@ -302,7 +398,7 @@ core.register_entity(":__builtin:item", {
|
|||||||
if own_stack:get_free_space() == 0 then
|
if own_stack:get_free_space() == 0 then
|
||||||
return
|
return
|
||||||
end
|
end
|
||||||
local objects = core.get_objects_inside_radius(pos, 1.0)
|
local objects = core.get_objects_inside_radius(pos, 0.5)
|
||||||
for k, obj in pairs(objects) do
|
for k, obj in pairs(objects) do
|
||||||
local entity = obj:get_luaentity()
|
local entity = obj:get_luaentity()
|
||||||
if entity and entity.name == "__builtin:item" then
|
if entity and entity.name == "__builtin:item" then
|
||||||
@ -329,3 +425,57 @@ core.register_entity(":__builtin:item", {
|
|||||||
self.object:remove()
|
self.object:remove()
|
||||||
end,
|
end,
|
||||||
})
|
})
|
||||||
|
|
||||||
|
-- Item Collection
|
||||||
|
if collection then
|
||||||
|
local function collect_items(player)
|
||||||
|
local ppos = player:get_pos()
|
||||||
|
ppos.y = ppos.y + 1.3
|
||||||
|
if not core.is_valid_pos(ppos) then
|
||||||
|
return
|
||||||
|
end
|
||||||
|
-- Detect
|
||||||
|
local objects = core.get_objects_inside_radius(ppos, 2)
|
||||||
|
for _, obj in pairs(objects) do
|
||||||
|
local entity = obj:get_luaentity()
|
||||||
|
if entity and entity.name == "__builtin:item" and
|
||||||
|
not entity.collectioner and
|
||||||
|
entity.age and entity.age > 0.5 then
|
||||||
|
local item = ItemStack(entity.itemstring)
|
||||||
|
local inv = player:get_inventory()
|
||||||
|
if item:get_name() ~= "" and
|
||||||
|
inv and inv:room_for_item("main", item) then
|
||||||
|
-- Magnet
|
||||||
|
obj:move_to(ppos)
|
||||||
|
entity.collectioner = true
|
||||||
|
-- Collect
|
||||||
|
if entity.collectioner == true then
|
||||||
|
core.after(0.05, function()
|
||||||
|
core.sound_play("item_drop_pickup", {
|
||||||
|
pos = ppos,
|
||||||
|
max_hear_distance = 10,
|
||||||
|
gain = 0.2,
|
||||||
|
pitch = random(60,100)/100
|
||||||
|
})
|
||||||
|
entity.itemstring = ""
|
||||||
|
obj:remove()
|
||||||
|
item = inv:add_item("main", item)
|
||||||
|
if not item:is_empty() then
|
||||||
|
core.item_drop(item, player, ppos)
|
||||||
|
end
|
||||||
|
end)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
core.register_playerstep(function(dtime, playernames)
|
||||||
|
for _, name in pairs(playernames) do
|
||||||
|
local player = core.get_player_by_name(name)
|
||||||
|
if player and player:is_player() and player:get_hp() > 0 then
|
||||||
|
collect_items(player)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end, core.is_singleplayer()) -- Force step in singlplayer mode only
|
||||||
|
end
|
||||||
|
@ -17,14 +17,15 @@ function core.calculate_knockback(player, hitter, time_from_last_punch, tool_cap
|
|||||||
return res
|
return res
|
||||||
end
|
end
|
||||||
|
|
||||||
|
local max, abs = math.max, math.abs
|
||||||
local function vector_absmax(v)
|
local function vector_absmax(v)
|
||||||
local max, abs = math.max, math.abs
|
|
||||||
return max(max(abs(v.x), abs(v.y)), abs(v.z))
|
return max(max(abs(v.x), abs(v.y)), abs(v.z))
|
||||||
end
|
end
|
||||||
|
|
||||||
core.register_on_punchplayer(function(player, hitter, time_from_last_punch, tool_capabilities, unused_dir, damage)
|
core.register_on_punchplayer(function(player, hitter, time_from_last_punch, tool_capabilities, unused_dir, damage)
|
||||||
if player:get_hp() == 0 then
|
if player:get_hp() == 0
|
||||||
return -- RIP
|
or player:get_animation().range ~= "stand" then
|
||||||
|
return -- RIP or attached
|
||||||
end
|
end
|
||||||
|
|
||||||
-- Server::handleCommand_Interact() adds eye offset to one but not the other
|
-- Server::handleCommand_Interact() adds eye offset to one but not the other
|
||||||
|
@ -42,13 +42,13 @@ end
|
|||||||
|
|
||||||
function core.send_join_message(player_name)
|
function core.send_join_message(player_name)
|
||||||
if not core.is_singleplayer() then
|
if not core.is_singleplayer() then
|
||||||
core.chat_send_all("*** " .. player_name .. " joined the game.")
|
core.chat_send_all("=> " .. player_name .. " has joined the server")
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
||||||
function core.send_leave_message(player_name, timed_out)
|
function core.send_leave_message(player_name, timed_out)
|
||||||
local announcement = "*** " .. player_name .. " left the game."
|
local announcement = "<= " .. player_name .. " left the server"
|
||||||
if timed_out then
|
if timed_out then
|
||||||
announcement = announcement .. " (timed out)"
|
announcement = announcement .. " (timed out)"
|
||||||
end
|
end
|
||||||
@ -109,6 +109,22 @@ function core.get_player_radius_area(player_name, radius)
|
|||||||
end
|
end
|
||||||
|
|
||||||
|
|
||||||
|
local mapgen_limit = tonumber(core.settings:get("mapgen_limit"))
|
||||||
|
function core.is_valid_pos(pos)
|
||||||
|
if not pos or type(pos) ~= "table" then
|
||||||
|
return false
|
||||||
|
end
|
||||||
|
for _, v in pairs({"x", "y", "z"}) do
|
||||||
|
if not pos[v] or pos[v] ~= pos[v] or
|
||||||
|
pos[v] < -mapgen_limit or pos[v] > mapgen_limit then
|
||||||
|
return false
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
return true
|
||||||
|
end
|
||||||
|
|
||||||
|
|
||||||
function core.hash_node_position(pos)
|
function core.hash_node_position(pos)
|
||||||
return (pos.z + 32768) * 65536 * 65536
|
return (pos.z + 32768) * 65536 * 65536
|
||||||
+ (pos.y + 32768) * 65536
|
+ (pos.y + 32768) * 65536
|
||||||
@ -157,6 +173,9 @@ function core.is_protected(pos, name)
|
|||||||
return false
|
return false
|
||||||
end
|
end
|
||||||
|
|
||||||
|
function core.is_protected_action()
|
||||||
|
return false
|
||||||
|
end
|
||||||
|
|
||||||
function core.record_protection_violation(pos, name)
|
function core.record_protection_violation(pos, name)
|
||||||
for _, func in pairs(core.registered_on_protection_violation) do
|
for _, func in pairs(core.registered_on_protection_violation) do
|
||||||
|
@ -28,6 +28,11 @@ function core.register_privilege(name, param)
|
|||||||
core.registered_privileges[name] = def
|
core.registered_privileges[name] = def
|
||||||
end
|
end
|
||||||
|
|
||||||
|
local creative = false
|
||||||
|
if core.settings:get_bool("creative_mode") then
|
||||||
|
creative = true
|
||||||
|
end
|
||||||
|
|
||||||
core.register_privilege("interact", "Can interact with things and modify the world")
|
core.register_privilege("interact", "Can interact with things and modify the world")
|
||||||
core.register_privilege("shout", "Can speak in chat")
|
core.register_privilege("shout", "Can speak in chat")
|
||||||
core.register_privilege("basic_privs", "Can modify 'shout' and 'interact' privileges")
|
core.register_privilege("basic_privs", "Can modify 'shout' and 'interact' privileges")
|
||||||
@ -35,7 +40,7 @@ core.register_privilege("privs", "Can modify privileges")
|
|||||||
|
|
||||||
core.register_privilege("teleport", {
|
core.register_privilege("teleport", {
|
||||||
description = "Can teleport self",
|
description = "Can teleport self",
|
||||||
give_to_singleplayer = false,
|
give_to_singleplayer = creative,
|
||||||
})
|
})
|
||||||
core.register_privilege("bring", {
|
core.register_privilege("bring", {
|
||||||
description = "Can teleport other players",
|
description = "Can teleport other players",
|
||||||
@ -43,7 +48,7 @@ core.register_privilege("bring", {
|
|||||||
})
|
})
|
||||||
core.register_privilege("settime", {
|
core.register_privilege("settime", {
|
||||||
description = "Can set the time of day using /time",
|
description = "Can set the time of day using /time",
|
||||||
give_to_singleplayer = false,
|
give_to_singleplayer = creative,
|
||||||
})
|
})
|
||||||
core.register_privilege("server", {
|
core.register_privilege("server", {
|
||||||
description = "Can do server maintenance stuff",
|
description = "Can do server maintenance stuff",
|
||||||
@ -75,11 +80,11 @@ core.register_privilege("password", {
|
|||||||
})
|
})
|
||||||
core.register_privilege("fly", {
|
core.register_privilege("fly", {
|
||||||
description = "Can use fly mode",
|
description = "Can use fly mode",
|
||||||
give_to_singleplayer = false,
|
give_to_singleplayer = creative,
|
||||||
})
|
})
|
||||||
core.register_privilege("fast", {
|
core.register_privilege("fast", {
|
||||||
description = "Can use fast mode",
|
description = "Can use fast mode",
|
||||||
give_to_singleplayer = false,
|
give_to_singleplayer = creative,
|
||||||
})
|
})
|
||||||
core.register_privilege("noclip", {
|
core.register_privilege("noclip", {
|
||||||
description = "Can fly through solid nodes using noclip mode",
|
description = "Can fly through solid nodes using noclip mode",
|
||||||
|
@ -107,6 +107,9 @@ function core.register_entity(name, prototype)
|
|||||||
prototype.mod_origin = core.get_current_modname() or "??"
|
prototype.mod_origin = core.get_current_modname() or "??"
|
||||||
end
|
end
|
||||||
|
|
||||||
|
-- Intllib
|
||||||
|
Sl = intllib.make_gettext_pair("locales")
|
||||||
|
|
||||||
function core.register_item(name, itemdef)
|
function core.register_item(name, itemdef)
|
||||||
-- Check name
|
-- Check name
|
||||||
if name == nil then
|
if name == nil then
|
||||||
@ -134,6 +137,9 @@ function core.register_item(name, itemdef)
|
|||||||
core.log("warning", "Node 'light_source' value exceeds maximum," ..
|
core.log("warning", "Node 'light_source' value exceeds maximum," ..
|
||||||
" limiting to maximum: " ..name)
|
" limiting to maximum: " ..name)
|
||||||
end
|
end
|
||||||
|
if itemdef.light_source == nil then
|
||||||
|
itemdef.light_source = 0
|
||||||
|
end
|
||||||
setmetatable(itemdef, {__index = core.nodedef_default})
|
setmetatable(itemdef, {__index = core.nodedef_default})
|
||||||
core.registered_nodes[itemdef.name] = itemdef
|
core.registered_nodes[itemdef.name] = itemdef
|
||||||
elseif itemdef.type == "craft" then
|
elseif itemdef.type == "craft" then
|
||||||
@ -153,6 +159,12 @@ function core.register_item(name, itemdef)
|
|||||||
itemdef.paramtype2 = "flowingliquid"
|
itemdef.paramtype2 = "flowingliquid"
|
||||||
end
|
end
|
||||||
|
|
||||||
|
-- Intllib
|
||||||
|
if itemdef.description and itemdef.description ~= "" then
|
||||||
|
itemdef.description = Sl(itemdef.description:gsub("@", "\001"))
|
||||||
|
:gsub("\001", "@")
|
||||||
|
end
|
||||||
|
|
||||||
-- BEGIN Legacy stuff
|
-- BEGIN Legacy stuff
|
||||||
if itemdef.cookresult_itemstring ~= nil and itemdef.cookresult_itemstring ~= "" then
|
if itemdef.cookresult_itemstring ~= nil and itemdef.cookresult_itemstring ~= "" then
|
||||||
core.register_craft({
|
core.register_craft({
|
||||||
@ -344,8 +356,8 @@ core.register_item(":unknown", {
|
|||||||
|
|
||||||
core.register_node(":air", {
|
core.register_node(":air", {
|
||||||
description = "Air",
|
description = "Air",
|
||||||
inventory_image = "air.png",
|
inventory_image = "blank.png",
|
||||||
wield_image = "air.png",
|
wield_image = "blank.png",
|
||||||
drawtype = "airlike",
|
drawtype = "airlike",
|
||||||
paramtype = "light",
|
paramtype = "light",
|
||||||
sunlight_propagates = true,
|
sunlight_propagates = true,
|
||||||
@ -356,6 +368,7 @@ core.register_node(":air", {
|
|||||||
floodable = true,
|
floodable = true,
|
||||||
air_equivalent = true,
|
air_equivalent = true,
|
||||||
drop = "",
|
drop = "",
|
||||||
|
drowning = 0,
|
||||||
groups = {not_in_creative_inventory=1},
|
groups = {not_in_creative_inventory=1},
|
||||||
})
|
})
|
||||||
|
|
||||||
@ -372,6 +385,7 @@ core.register_node(":ignore", {
|
|||||||
buildable_to = true, -- A way to remove accidentally placed ignores
|
buildable_to = true, -- A way to remove accidentally placed ignores
|
||||||
air_equivalent = true,
|
air_equivalent = true,
|
||||||
drop = "",
|
drop = "",
|
||||||
|
drowning = 0,
|
||||||
groups = {not_in_creative_inventory=1},
|
groups = {not_in_creative_inventory=1},
|
||||||
on_place = function(itemstack, placer, pointed_thing)
|
on_place = function(itemstack, placer, pointed_thing)
|
||||||
core.chat_send_player(
|
core.chat_send_player(
|
||||||
@ -385,7 +399,11 @@ core.register_node(":ignore", {
|
|||||||
-- The hand (bare definition)
|
-- The hand (bare definition)
|
||||||
core.register_item(":", {
|
core.register_item(":", {
|
||||||
type = "none",
|
type = "none",
|
||||||
wield_image = "wieldhand.png",
|
wield_image = "blank.png",
|
||||||
|
tool_capabilities = {
|
||||||
|
full_punch_interval = 0.5,
|
||||||
|
damage_groups = {fleshy = core.settings:get_bool("creative_mode") and 5 or 1}
|
||||||
|
},
|
||||||
groups = {not_in_creative_inventory=1},
|
groups = {not_in_creative_inventory=1},
|
||||||
})
|
})
|
||||||
|
|
||||||
@ -408,6 +426,15 @@ function core.override_item(name, redefinition)
|
|||||||
end
|
end
|
||||||
|
|
||||||
|
|
||||||
|
function core.add_group(name, adding)
|
||||||
|
local addgroup = table.copy(core.registered_items[name].groups) or {}
|
||||||
|
for k, v in pairs(adding) do
|
||||||
|
addgroup[k] = v
|
||||||
|
end
|
||||||
|
core.override_item(name, {groups = addgroup})
|
||||||
|
end
|
||||||
|
|
||||||
|
|
||||||
core.callback_origins = {}
|
core.callback_origins = {}
|
||||||
|
|
||||||
function core.run_callbacks(callbacks, mode, ...)
|
function core.run_callbacks(callbacks, mode, ...)
|
||||||
@ -613,6 +640,102 @@ core.registered_on_modchannel_message, core.register_on_modchannel_message = mak
|
|||||||
core.registered_on_player_inventory_actions, core.register_on_player_inventory_action = make_registration()
|
core.registered_on_player_inventory_actions, core.register_on_player_inventory_action = make_registration()
|
||||||
core.registered_allow_player_inventory_actions, core.register_allow_player_inventory_action = make_registration()
|
core.registered_allow_player_inventory_actions, core.register_allow_player_inventory_action = make_registration()
|
||||||
|
|
||||||
|
|
||||||
|
--
|
||||||
|
-- Player step iteration
|
||||||
|
--
|
||||||
|
|
||||||
|
players_per_step = tonumber(core.settings:get("players_per_globalstep")) or 20
|
||||||
|
|
||||||
|
local player_iter
|
||||||
|
local player_iter_forced
|
||||||
|
local playerstep_iter
|
||||||
|
local playerstep_funcs = {}
|
||||||
|
local playerstep_funcs_forced = {}
|
||||||
|
local playernames = {}
|
||||||
|
local playernames_forced = {}
|
||||||
|
|
||||||
|
core.register_playerstep = function(func, force)
|
||||||
|
local funcs = force and playerstep_funcs_forced or playerstep_funcs
|
||||||
|
funcs[#funcs + 1] = func
|
||||||
|
end
|
||||||
|
|
||||||
|
local function table_iter(t)
|
||||||
|
local i = 0
|
||||||
|
local n = table.getn(t)
|
||||||
|
return function ()
|
||||||
|
i = i + 1
|
||||||
|
if i <= n then
|
||||||
|
return t[i]
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
function core.get_player_iter()
|
||||||
|
local names = {}
|
||||||
|
for player in table_iter(core.get_connected_players()) do
|
||||||
|
local name = player:get_player_name()
|
||||||
|
if name then
|
||||||
|
names[#names + 1] = name
|
||||||
|
end
|
||||||
|
end
|
||||||
|
return table_iter(names)
|
||||||
|
end
|
||||||
|
|
||||||
|
local function get_playerstep_func()
|
||||||
|
if playerstep_iter == nil then
|
||||||
|
playerstep_iter = table_iter(playerstep_funcs)
|
||||||
|
playernames = {}
|
||||||
|
for _ = 1, players_per_step do
|
||||||
|
if player_iter == nil then
|
||||||
|
player_iter = core.get_player_iter()
|
||||||
|
end
|
||||||
|
local name = player_iter()
|
||||||
|
if not name then
|
||||||
|
player_iter = nil
|
||||||
|
break
|
||||||
|
end
|
||||||
|
playernames[#playernames + 1] = name
|
||||||
|
end
|
||||||
|
end
|
||||||
|
local func = playerstep_iter()
|
||||||
|
playerstep_iter = func and playerstep_iter
|
||||||
|
return func or get_playerstep_func()
|
||||||
|
end
|
||||||
|
|
||||||
|
-- Run playerstep callbacks
|
||||||
|
|
||||||
|
core.register_globalstep(function(dtime)
|
||||||
|
-- Run forced callbacks
|
||||||
|
if #playerstep_funcs_forced ~= 0 then
|
||||||
|
playernames_forced = {}
|
||||||
|
for _ = 1, players_per_step do
|
||||||
|
if player_iter_forced == nil then
|
||||||
|
player_iter_forced = core.get_player_iter()
|
||||||
|
end
|
||||||
|
local name = player_iter_forced()
|
||||||
|
if not name then
|
||||||
|
player_iter_forced = nil
|
||||||
|
break
|
||||||
|
end
|
||||||
|
playernames_forced[#playernames_forced + 1] = name
|
||||||
|
end
|
||||||
|
for func in table_iter(playerstep_funcs_forced) do
|
||||||
|
if type(func) == "function" then
|
||||||
|
func(dtime, playernames_forced)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
if #playerstep_funcs ~= 0 then
|
||||||
|
-- Run single step callbacks
|
||||||
|
local func = get_playerstep_func()
|
||||||
|
if type(func) == "function" then
|
||||||
|
func(dtime, playernames)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end)
|
||||||
|
|
||||||
|
|
||||||
--
|
--
|
||||||
-- Compatibility for on_mapgen_init()
|
-- Compatibility for on_mapgen_init()
|
||||||
--
|
--
|
||||||
|
@ -1,41 +1,30 @@
|
|||||||
-- cache setting
|
-- cache setting
|
||||||
local enable_damage = core.settings:get_bool("enable_damage")
|
local enable_damage = core.settings:get_bool("enable_damage")
|
||||||
|
local disable_health = false
|
||||||
|
|
||||||
local health_bar_definition = {
|
local health_bar_definition = {
|
||||||
hud_elem_type = "statbar",
|
hud_elem_type = "statbar",
|
||||||
position = {x = 0.5, y = 1},
|
position = {x = 0.5, y = 1},
|
||||||
text = "heart.png",
|
alignment = {x = -1, y = -1},
|
||||||
text2 = "heart_gone.png",
|
offset = {x = -247, y = -94},
|
||||||
number = core.PLAYER_MAX_HP_DEFAULT,
|
|
||||||
item = core.PLAYER_MAX_HP_DEFAULT,
|
|
||||||
direction = 0,
|
|
||||||
size = {x = 24, y = 24},
|
size = {x = 24, y = 24},
|
||||||
offset = {x = (-10 * 24) - 25, y = -(48 + 24 + 16)},
|
text = "heart.png",
|
||||||
|
background = "heart_gone.png",
|
||||||
|
number = 20
|
||||||
}
|
}
|
||||||
|
|
||||||
local breath_bar_definition = {
|
local breath_bar_definition = {
|
||||||
hud_elem_type = "statbar",
|
hud_elem_type = "statbar",
|
||||||
position = {x = 0.5, y = 1},
|
position = {x = 0.5, y = 1},
|
||||||
text = "bubble.png",
|
alignment = {x = -1, y = -1},
|
||||||
text2 = "bubble_gone.png",
|
offset = {x = 8, y = -120},
|
||||||
number = core.PLAYER_MAX_BREATH_DEFAULT,
|
|
||||||
item = core.PLAYER_MAX_BREATH_DEFAULT * 2,
|
|
||||||
direction = 0,
|
|
||||||
size = {x = 24, y = 24},
|
size = {x = 24, y = 24},
|
||||||
offset = {x = 25, y= -(48 + 24 + 16)},
|
text = "bubble.png",
|
||||||
|
number = 20
|
||||||
}
|
}
|
||||||
|
|
||||||
local hud_ids = {}
|
local hud_ids = {}
|
||||||
|
|
||||||
local function scaleToDefault(player, field)
|
|
||||||
-- Scale "hp" or "breath" to the default dimensions
|
|
||||||
local current = player["get_" .. field](player)
|
|
||||||
local nominal = core["PLAYER_MAX_" .. field:upper() .. "_DEFAULT"]
|
|
||||||
local max_display = math.max(nominal,
|
|
||||||
math.max(player:get_properties()[field .. "_max"], current))
|
|
||||||
return current / max_display * nominal
|
|
||||||
end
|
|
||||||
|
|
||||||
local function update_builtin_statbars(player)
|
local function update_builtin_statbars(player)
|
||||||
local name = player:get_player_name()
|
local name = player:get_player_name()
|
||||||
|
|
||||||
@ -50,47 +39,24 @@ local function update_builtin_statbars(player)
|
|||||||
-- our current flags are transmitted by sending them actively
|
-- our current flags are transmitted by sending them actively
|
||||||
player:hud_set_flags(flags)
|
player:hud_set_flags(flags)
|
||||||
end
|
end
|
||||||
local hud = hud_ids[name]
|
local hud_id = hud_ids[name]
|
||||||
|
|
||||||
local immortal = player:get_armor_groups().immortal == 1
|
if flags.healthbar and not disable_health then
|
||||||
|
hud.change_item(player, "health", {number = player:get_hp()})
|
||||||
if flags.healthbar and enable_damage and not immortal then
|
|
||||||
local number = scaleToDefault(player, "hp")
|
|
||||||
if hud.id_healthbar == nil then
|
|
||||||
local hud_def = table.copy(health_bar_definition)
|
|
||||||
hud_def.number = number
|
|
||||||
hud.id_healthbar = player:hud_add(hud_def)
|
|
||||||
else
|
|
||||||
player:hud_change(hud.id_healthbar, "number", number)
|
|
||||||
end
|
|
||||||
elseif hud.id_healthbar then
|
|
||||||
player:hud_remove(hud.id_healthbar)
|
|
||||||
hud.id_healthbar = nil
|
|
||||||
end
|
end
|
||||||
|
|
||||||
local show_breathbar = flags.breathbar and enable_damage and not immortal
|
if flags.breathbar and player:get_breath() < 10 then
|
||||||
|
local number = player:get_breath() * 2
|
||||||
local breath = player:get_breath()
|
if hud_id.id_breathbar == nil then
|
||||||
local breath_max = player:get_properties().breath_max
|
|
||||||
if show_breathbar and breath <= breath_max then
|
|
||||||
local number = 2 * scaleToDefault(player, "breath")
|
|
||||||
if not hud.id_breathbar and breath < breath_max then
|
|
||||||
local hud_def = table.copy(breath_bar_definition)
|
local hud_def = table.copy(breath_bar_definition)
|
||||||
hud_def.number = number
|
hud_def.number = number
|
||||||
hud.id_breathbar = player:hud_add(hud_def)
|
hud_id.id_breathbar = player:hud_add(hud_def)
|
||||||
elseif hud.id_breathbar then
|
else
|
||||||
player:hud_change(hud.id_breathbar, "number", number)
|
player:hud_change(hud_id.id_breathbar, "number", number)
|
||||||
end
|
end
|
||||||
end
|
elseif hud_id.id_breathbar then
|
||||||
|
player:hud_remove(hud_id.id_breathbar)
|
||||||
if hud.id_breathbar and (not show_breathbar or breath == breath_max) then
|
hud_id.id_breathbar = nil
|
||||||
minetest.after(1, function(player_name, breath_bar)
|
|
||||||
local player = minetest.get_player_by_name(player_name)
|
|
||||||
if player then
|
|
||||||
player:hud_remove(breath_bar)
|
|
||||||
end
|
|
||||||
end, name, hud.id_breathbar)
|
|
||||||
hud.id_breathbar = nil
|
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
@ -109,7 +75,7 @@ local function player_event_handler(player,eventname)
|
|||||||
|
|
||||||
local name = player:get_player_name()
|
local name = player:get_player_name()
|
||||||
|
|
||||||
if name == "" or not hud_ids[name] then
|
if name == "" then
|
||||||
return
|
return
|
||||||
end
|
end
|
||||||
|
|
||||||
@ -138,26 +104,16 @@ local function player_event_handler(player,eventname)
|
|||||||
end
|
end
|
||||||
|
|
||||||
function core.hud_replace_builtin(hud_name, definition)
|
function core.hud_replace_builtin(hud_name, definition)
|
||||||
|
if hud_name == "health" then
|
||||||
|
disable_health = true
|
||||||
|
return true
|
||||||
|
end
|
||||||
|
|
||||||
if type(definition) ~= "table" or
|
if type(definition) ~= "table" or
|
||||||
definition.hud_elem_type ~= "statbar" then
|
definition.hud_elem_type ~= "statbar" then
|
||||||
return false
|
return false
|
||||||
end
|
end
|
||||||
|
|
||||||
if hud_name == "health" then
|
|
||||||
health_bar_definition = definition
|
|
||||||
|
|
||||||
for name, ids in pairs(hud_ids) do
|
|
||||||
local player = core.get_player_by_name(name)
|
|
||||||
if player and ids.id_healthbar then
|
|
||||||
player:hud_remove(ids.id_healthbar)
|
|
||||||
ids.id_healthbar = nil
|
|
||||||
update_builtin_statbars(player)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
return true
|
|
||||||
end
|
|
||||||
|
|
||||||
if hud_name == "breath" then
|
if hud_name == "breath" then
|
||||||
breath_bar_definition = definition
|
breath_bar_definition = definition
|
||||||
|
|
||||||
@ -177,8 +133,13 @@ end
|
|||||||
|
|
||||||
-- Append "update_builtin_statbars" as late as possible
|
-- Append "update_builtin_statbars" as late as possible
|
||||||
-- This ensures that the HUD is hidden when the flags are updated in this callback
|
-- This ensures that the HUD is hidden when the flags are updated in this callback
|
||||||
core.register_on_mods_loaded(function()
|
if enable_damage then
|
||||||
|
core.register_on_mods_loaded(function()
|
||||||
|
if not disable_health then
|
||||||
|
hud.register("health", health_bar_definition)
|
||||||
core.register_on_joinplayer(update_builtin_statbars)
|
core.register_on_joinplayer(update_builtin_statbars)
|
||||||
end)
|
end
|
||||||
core.register_on_leaveplayer(cleanup_builtin_statbars)
|
end)
|
||||||
core.register_playerevent(player_event_handler)
|
core.register_on_leaveplayer(cleanup_builtin_statbars)
|
||||||
|
core.register_playerevent(player_event_handler)
|
||||||
|
end
|
||||||
|
@ -29,12 +29,14 @@ local gamepath = scriptdir .. "game" .. DIR_DELIM
|
|||||||
local clientpath = scriptdir .. "client" .. DIR_DELIM
|
local clientpath = scriptdir .. "client" .. DIR_DELIM
|
||||||
local commonpath = scriptdir .. "common" .. DIR_DELIM
|
local commonpath = scriptdir .. "common" .. DIR_DELIM
|
||||||
local asyncpath = scriptdir .. "async" .. DIR_DELIM
|
local asyncpath = scriptdir .. "async" .. DIR_DELIM
|
||||||
|
local intlpath = scriptdir .. "intllib" .. DIR_DELIM
|
||||||
|
|
||||||
dofile(commonpath .. "strict.lua")
|
dofile(commonpath .. "strict.lua")
|
||||||
dofile(commonpath .. "serialize.lua")
|
dofile(commonpath .. "serialize.lua")
|
||||||
dofile(commonpath .. "misc_helpers.lua")
|
dofile(commonpath .. "misc_helpers.lua")
|
||||||
|
|
||||||
if INIT == "game" then
|
if INIT == "game" then
|
||||||
|
dofile(intlpath .. "init.lua")
|
||||||
dofile(gamepath .. "init.lua")
|
dofile(gamepath .. "init.lua")
|
||||||
assert(not core.get_http_api)
|
assert(not core.get_http_api)
|
||||||
elseif INIT == "mainmenu" then
|
elseif INIT == "mainmenu" then
|
||||||
|
27
builtin/intllib/LICENSE.md
Normal file
@ -0,0 +1,27 @@
|
|||||||
|
By Diego Martínez (kaeza).
|
||||||
|
Released under Unlicense.
|
||||||
|
|
||||||
|
This is free and unencumbered software released into the public domain.
|
||||||
|
|
||||||
|
Anyone is free to copy, modify, publish, use, compile, sell, or
|
||||||
|
distribute this software, either in source code form or as a compiled
|
||||||
|
binary, for any purpose, commercial or non-commercial, and by any
|
||||||
|
means.
|
||||||
|
|
||||||
|
In jurisdictions that recognize copyright laws, the author or authors
|
||||||
|
of this software dedicate any and all copyright interest in the
|
||||||
|
software to the public domain. We make this dedication for the benefit
|
||||||
|
of the public at large and to the detriment of our heirs and
|
||||||
|
successors. We intend this dedication to be an overt act of
|
||||||
|
relinquishment in perpetuity of all present and future rights to this
|
||||||
|
software under copyright law.
|
||||||
|
|
||||||
|
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
||||||
|
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
||||||
|
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
|
||||||
|
IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR
|
||||||
|
OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
|
||||||
|
ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
|
||||||
|
OTHER DEALINGS IN THE SOFTWARE.
|
||||||
|
|
||||||
|
For more information, please refer to <http://unlicense.org/>
|
225
builtin/intllib/gettext.lua
Normal file
@ -0,0 +1,225 @@
|
|||||||
|
local strsub, strrep = string.sub, string.rep
|
||||||
|
local strmatch, strgsub = string.match, string.gsub
|
||||||
|
|
||||||
|
local function trim(str)
|
||||||
|
return strmatch(str, "^%s*(.-)%s*$")
|
||||||
|
end
|
||||||
|
|
||||||
|
local escapes = { n="\n", r="\r", t="\t" }
|
||||||
|
|
||||||
|
local function unescape(str)
|
||||||
|
return (strgsub(str, "(\\+)([nrt]?)", function(bs, c)
|
||||||
|
local bsl = #bs
|
||||||
|
local realbs = strrep("\\", bsl/2)
|
||||||
|
if bsl%2 == 1 then
|
||||||
|
c = escapes[c] or c
|
||||||
|
end
|
||||||
|
return realbs..c
|
||||||
|
end))
|
||||||
|
end
|
||||||
|
|
||||||
|
local function parse_po(str)
|
||||||
|
local state, msgid, msgid_plural, msgstrind
|
||||||
|
local texts = { }
|
||||||
|
local lineno = 0
|
||||||
|
local function perror(msg)
|
||||||
|
return error(msg.." at line "..lineno)
|
||||||
|
end
|
||||||
|
for _, line in ipairs(str:split("\n")) do repeat
|
||||||
|
lineno = lineno + 1
|
||||||
|
line = trim(line)
|
||||||
|
|
||||||
|
if line == "" or strmatch(line, "^#") then
|
||||||
|
state, msgid, msgid_plural = nil, nil, nil
|
||||||
|
break -- continue
|
||||||
|
end
|
||||||
|
|
||||||
|
local mid = strmatch(line, "^%s*msgid%s*\"(.*)\"%s*$")
|
||||||
|
if mid then
|
||||||
|
if state == "id" then
|
||||||
|
return perror("unexpected msgid")
|
||||||
|
end
|
||||||
|
state, msgid = "id", unescape(mid)
|
||||||
|
break -- continue
|
||||||
|
end
|
||||||
|
|
||||||
|
mid = strmatch(line, "^%s*msgid_plural%s*\"(.*)\"%s*$")
|
||||||
|
if mid then
|
||||||
|
if state ~= "id" then
|
||||||
|
return perror("unexpected msgid_plural")
|
||||||
|
end
|
||||||
|
state, msgid_plural = "idp", unescape(mid)
|
||||||
|
break -- continue
|
||||||
|
end
|
||||||
|
|
||||||
|
local ind, mstr = strmatch(line,
|
||||||
|
"^%s*msgstr([0-9%[%]]*)%s*\"(.*)\"%s*$")
|
||||||
|
if ind then
|
||||||
|
if not msgid then
|
||||||
|
return perror("missing msgid")
|
||||||
|
elseif ind == "" then
|
||||||
|
msgstrind = 0
|
||||||
|
elseif strmatch(ind, "%[[0-9]+%]") then
|
||||||
|
msgstrind = tonumber(strsub(ind, 2, -2))
|
||||||
|
else
|
||||||
|
return perror("malformed msgstr")
|
||||||
|
end
|
||||||
|
texts[msgid] = texts[msgid] or { }
|
||||||
|
if msgid_plural then
|
||||||
|
texts[msgid_plural] = texts[msgid]
|
||||||
|
end
|
||||||
|
texts[msgid][msgstrind] = unescape(mstr)
|
||||||
|
state = "str"
|
||||||
|
break -- continue
|
||||||
|
end
|
||||||
|
|
||||||
|
mstr = strmatch(line, "^%s*\"(.*)\"%s*$")
|
||||||
|
if mstr then
|
||||||
|
if state == "id" then
|
||||||
|
msgid = msgid..unescape(mstr)
|
||||||
|
break -- continue
|
||||||
|
elseif state == "idp" then
|
||||||
|
msgid_plural = msgid_plural..unescape(mstr)
|
||||||
|
break -- continue
|
||||||
|
elseif state == "str" then
|
||||||
|
local text = texts[msgid][msgstrind]
|
||||||
|
texts[msgid][msgstrind] = text..unescape(mstr)
|
||||||
|
break -- continue
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
return perror("malformed line")
|
||||||
|
|
||||||
|
-- luacheck: ignore
|
||||||
|
until true end -- end for
|
||||||
|
|
||||||
|
return texts
|
||||||
|
end
|
||||||
|
|
||||||
|
local M = { }
|
||||||
|
|
||||||
|
local function warn(msg)
|
||||||
|
core.log("warning", "[intllib] "..msg)
|
||||||
|
end
|
||||||
|
|
||||||
|
-- hax!
|
||||||
|
-- This function converts a C expression to an equivalent Lua expression.
|
||||||
|
-- It handles enough stuff to parse the `Plural-Forms` header correctly.
|
||||||
|
-- Note that it assumes the C expression is valid to begin with.
|
||||||
|
local function compile_plural_forms(str)
|
||||||
|
local plural = strmatch(str, "plural=([^;]+);?$")
|
||||||
|
local function replace_ternary(s)
|
||||||
|
local c, t, f = strmatch(s, "^(.-)%?(.-):(.*)")
|
||||||
|
if c then
|
||||||
|
return ("__if("
|
||||||
|
..replace_ternary(c)
|
||||||
|
..","..replace_ternary(t)
|
||||||
|
..","..replace_ternary(f)
|
||||||
|
..")")
|
||||||
|
end
|
||||||
|
return s
|
||||||
|
end
|
||||||
|
plural = replace_ternary(plural)
|
||||||
|
plural = strgsub(plural, "&&", " and ")
|
||||||
|
plural = strgsub(plural, "||", " or ")
|
||||||
|
plural = strgsub(plural, "!=", "~=")
|
||||||
|
plural = strgsub(plural, "!", " not ")
|
||||||
|
local f, err = loadstring([[
|
||||||
|
local function __if(c, t, f)
|
||||||
|
if c and c~=0 then return t else return f end
|
||||||
|
end
|
||||||
|
local function __f(n)
|
||||||
|
return (]]..plural..[[)
|
||||||
|
end
|
||||||
|
return (__f(...))
|
||||||
|
]])
|
||||||
|
if not f then return nil, err end
|
||||||
|
local env = { }
|
||||||
|
env._ENV, env._G = env, env
|
||||||
|
setfenv(f, env)
|
||||||
|
return function(n)
|
||||||
|
local v = f(n)
|
||||||
|
if type(v) == "boolean" then
|
||||||
|
-- Handle things like a plain `n != 1`
|
||||||
|
v = v and 1 or 0
|
||||||
|
end
|
||||||
|
return v
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
local function parse_headers(str)
|
||||||
|
local headers = { }
|
||||||
|
for _, line in ipairs(str:split("\n")) do
|
||||||
|
local k, v = strmatch(line, "^([^:]+):%s*(.*)")
|
||||||
|
if k then
|
||||||
|
headers[k] = v
|
||||||
|
end
|
||||||
|
end
|
||||||
|
return headers
|
||||||
|
end
|
||||||
|
|
||||||
|
local function load_catalog(filename)
|
||||||
|
local f, data, err
|
||||||
|
|
||||||
|
local function bail(msg)
|
||||||
|
warn(msg..(err and ": " or "")..(err or ""))
|
||||||
|
return nil
|
||||||
|
end
|
||||||
|
|
||||||
|
f, err = io.open(filename, "rb")
|
||||||
|
if not f then
|
||||||
|
return --bail("failed to open catalog")
|
||||||
|
end
|
||||||
|
|
||||||
|
data, err = f:read("*a")
|
||||||
|
|
||||||
|
f:close()
|
||||||
|
|
||||||
|
if not data then
|
||||||
|
return bail("failed to read catalog")
|
||||||
|
end
|
||||||
|
|
||||||
|
data, err = parse_po(data)
|
||||||
|
if not data then
|
||||||
|
return bail("failed to parse catalog")
|
||||||
|
end
|
||||||
|
|
||||||
|
err = nil
|
||||||
|
local hdrs = data[""]
|
||||||
|
if not (hdrs and hdrs[0]) then
|
||||||
|
return bail("catalog has no headers")
|
||||||
|
end
|
||||||
|
|
||||||
|
hdrs = parse_headers(hdrs[0])
|
||||||
|
|
||||||
|
local pf = hdrs["Plural-Forms"]
|
||||||
|
if not pf then
|
||||||
|
-- XXX: Is this right? Gettext assumes this if header not present.
|
||||||
|
pf = "nplurals=2; plural=n != 1"
|
||||||
|
end
|
||||||
|
|
||||||
|
data.plural_index, err = compile_plural_forms(pf)
|
||||||
|
if not data.plural_index then
|
||||||
|
return bail("failed to compile plural forms")
|
||||||
|
end
|
||||||
|
|
||||||
|
--warn("loaded: "..filename)
|
||||||
|
|
||||||
|
return data
|
||||||
|
end
|
||||||
|
|
||||||
|
function M.load_catalogs(path)
|
||||||
|
local langs = intllib.get_detected_languages()
|
||||||
|
|
||||||
|
local cats = { }
|
||||||
|
for _, lang in ipairs(langs) do
|
||||||
|
local cat = load_catalog(path.."/"..lang..".po")
|
||||||
|
if cat then
|
||||||
|
cats[#cats+1] = cat
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
return cats
|
||||||
|
end
|
||||||
|
|
||||||
|
return M
|
209
builtin/intllib/init.lua
Normal file
@ -0,0 +1,209 @@
|
|||||||
|
intllib = {
|
||||||
|
getters = {},
|
||||||
|
strings = {},
|
||||||
|
}
|
||||||
|
|
||||||
|
local path = core.get_builtin_path() .. DIR_DELIM .. "intllib" .. DIR_DELIM
|
||||||
|
dofile(path .. "lib.lua")
|
||||||
|
|
||||||
|
local LANG = core.settings:get("language")
|
||||||
|
if not (LANG and (LANG ~= "")) then LANG = os.getenv("LANG") end
|
||||||
|
if not (LANG and (LANG ~= "")) then LANG = "en" end
|
||||||
|
|
||||||
|
|
||||||
|
local INS_CHAR = intllib.INSERTION_CHAR
|
||||||
|
local insertion_pattern = "("..INS_CHAR.."?)"..INS_CHAR.."(%(?)(%d+)(%)?)"
|
||||||
|
|
||||||
|
local function do_replacements(str, ...)
|
||||||
|
local args = {...}
|
||||||
|
-- Outer parens discard extra return values
|
||||||
|
return (str:gsub(insertion_pattern, function(escape, open, num, close)
|
||||||
|
if escape == "" then
|
||||||
|
local replacement = tostring(args[tonumber(num)])
|
||||||
|
if open == "" then
|
||||||
|
replacement = replacement..close
|
||||||
|
end
|
||||||
|
return replacement
|
||||||
|
else
|
||||||
|
return INS_CHAR..open..num..close
|
||||||
|
end
|
||||||
|
end))
|
||||||
|
end
|
||||||
|
|
||||||
|
local function make_getter(msgstrs)
|
||||||
|
return function(s, ...)
|
||||||
|
local str
|
||||||
|
if msgstrs then
|
||||||
|
str = msgstrs[s]
|
||||||
|
end
|
||||||
|
if not str or str == "" then
|
||||||
|
str = s
|
||||||
|
end
|
||||||
|
if select("#", ...) == 0 then
|
||||||
|
return str
|
||||||
|
end
|
||||||
|
return do_replacements(str, ...)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
|
||||||
|
local function Getter(modname)
|
||||||
|
modname = modname or core.get_current_modname()
|
||||||
|
if not intllib.getters[modname] then
|
||||||
|
local msgstr = intllib.get_strings(modname)
|
||||||
|
intllib.getters[modname] = make_getter(msgstr)
|
||||||
|
end
|
||||||
|
return intllib.getters[modname]
|
||||||
|
end
|
||||||
|
|
||||||
|
|
||||||
|
function intllib.Getter(modname)
|
||||||
|
local info = debug and debug.getinfo and debug.getinfo(2)
|
||||||
|
local loc = info and info.short_src..":"..info.currentline
|
||||||
|
core.log("deprecated", "intllib.Getter is deprecated."
|
||||||
|
.." Please use intllib.make_gettext_pair instead."
|
||||||
|
..(info and " (called from "..loc..")" or ""))
|
||||||
|
return Getter(modname)
|
||||||
|
end
|
||||||
|
|
||||||
|
|
||||||
|
local strfind, strsub = string.find, string.sub
|
||||||
|
local langs
|
||||||
|
|
||||||
|
local function split(str, sep)
|
||||||
|
local pos, endp = 1, #str+1
|
||||||
|
return function()
|
||||||
|
if (not pos) or pos > endp then return end
|
||||||
|
local s, e = strfind(str, sep, pos, true)
|
||||||
|
local part = strsub(str, pos, s and s-1)
|
||||||
|
pos = e and e + 1
|
||||||
|
return part
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
function intllib.get_detected_languages()
|
||||||
|
if langs then return langs end
|
||||||
|
|
||||||
|
langs = { }
|
||||||
|
|
||||||
|
local function addlang(l)
|
||||||
|
local sep
|
||||||
|
langs[#langs+1] = l
|
||||||
|
sep = strfind(l, ".", 1, true)
|
||||||
|
if sep then
|
||||||
|
l = strsub(l, 1, sep-1)
|
||||||
|
langs[#langs+1] = l
|
||||||
|
end
|
||||||
|
sep = strfind(l, "_", 1, true)
|
||||||
|
if sep then
|
||||||
|
langs[#langs+1] = strsub(l, 1, sep-1)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
local v
|
||||||
|
|
||||||
|
v = core.settings:get("language")
|
||||||
|
if v and v~="" then
|
||||||
|
addlang(v)
|
||||||
|
end
|
||||||
|
|
||||||
|
v = os.getenv("LANGUAGE")
|
||||||
|
if v then
|
||||||
|
for item in split(v, ":") do
|
||||||
|
langs[#langs+1] = item
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
v = os.getenv("LANG")
|
||||||
|
if v then
|
||||||
|
addlang(v)
|
||||||
|
end
|
||||||
|
|
||||||
|
langs[#langs+1] = "en"
|
||||||
|
|
||||||
|
return langs
|
||||||
|
end
|
||||||
|
|
||||||
|
|
||||||
|
local gettext = dofile(path .. "gettext.lua")
|
||||||
|
|
||||||
|
|
||||||
|
local function catgettext(catalogs, msgid)
|
||||||
|
for _, cat in ipairs(catalogs) do
|
||||||
|
local msgstr = cat and cat[msgid]
|
||||||
|
if msgstr and msgstr~="" then
|
||||||
|
local msg = msgstr[0]
|
||||||
|
return msg~="" and msg or nil
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
local function catngettext(catalogs, msgid, msgid_plural, n)
|
||||||
|
n = math.floor(n)
|
||||||
|
for _, cat in ipairs(catalogs) do
|
||||||
|
local msgstr = cat and cat[msgid]
|
||||||
|
if msgstr then
|
||||||
|
local index = cat.plural_index(n)
|
||||||
|
local msg = msgstr[index]
|
||||||
|
return msg~="" and msg or nil
|
||||||
|
end
|
||||||
|
end
|
||||||
|
return n==1 and msgid or msgid_plural
|
||||||
|
end
|
||||||
|
|
||||||
|
|
||||||
|
local gettext_getters = { }
|
||||||
|
function intllib.make_gettext_pair(modname)
|
||||||
|
modname = modname or core.get_current_modname()
|
||||||
|
if gettext_getters[modname] then
|
||||||
|
return unpack(gettext_getters[modname])
|
||||||
|
end
|
||||||
|
local modpath = core.get_modpath(modname) and core.get_modpath(modname)
|
||||||
|
local localedir = modpath and modpath.."/locale"
|
||||||
|
local catalogs = localedir and gettext.load_catalogs(localedir) or {}
|
||||||
|
local getter = Getter(modname)
|
||||||
|
local function gettext_func(msgid, ...)
|
||||||
|
local msgstr = (catgettext(catalogs, msgid)
|
||||||
|
or getter(msgid))
|
||||||
|
return do_replacements(msgstr, ...)
|
||||||
|
end
|
||||||
|
local function ngettext_func(msgid, msgid_plural, n, ...)
|
||||||
|
local msgstr = (catngettext(catalogs, msgid, msgid_plural, n)
|
||||||
|
or getter(msgid))
|
||||||
|
return do_replacements(msgstr, ...)
|
||||||
|
end
|
||||||
|
gettext_getters[modname] = { gettext_func, ngettext_func }
|
||||||
|
return gettext_func, ngettext_func
|
||||||
|
end
|
||||||
|
|
||||||
|
|
||||||
|
local function get_locales(code)
|
||||||
|
local ll, cc = code:match("^(..)_(..)")
|
||||||
|
if ll then
|
||||||
|
return { ll.."_"..cc, ll, ll~="en" and "en" or nil }
|
||||||
|
else
|
||||||
|
return { code, code~="en" and "en" or nil }
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
|
||||||
|
function intllib.get_strings(modname, langcode)
|
||||||
|
langcode = langcode or LANG
|
||||||
|
modname = modname or core.get_current_modname()
|
||||||
|
local msgstr = intllib.strings[modname]
|
||||||
|
if not msgstr then
|
||||||
|
local modpath = core.get_modpath(modname) and core.get_modpath(modname)
|
||||||
|
msgstr = { }
|
||||||
|
if modpath then
|
||||||
|
for _, l in ipairs(get_locales(langcode)) do
|
||||||
|
local t = intllib.load_strings(modpath.."/locale/"..modname.."."..l..".tr")
|
||||||
|
or intllib.load_strings(modpath.."/locale/"..l..".txt") or { }
|
||||||
|
for k, v in pairs(t) do
|
||||||
|
msgstr[k] = msgstr[k] or v
|
||||||
|
end
|
||||||
|
end
|
||||||
|
intllib.strings[modname] = msgstr
|
||||||
|
end
|
||||||
|
end
|
||||||
|
return msgstr
|
||||||
|
end
|
72
builtin/intllib/lib.lua
Normal file
@ -0,0 +1,72 @@
|
|||||||
|
intllib = intllib or {}
|
||||||
|
|
||||||
|
local INS_CHAR = "@"
|
||||||
|
intllib.INSERTION_CHAR = INS_CHAR
|
||||||
|
|
||||||
|
local escapes = {
|
||||||
|
["\\"] = "\\",
|
||||||
|
["n"] = "\n",
|
||||||
|
["s"] = " ",
|
||||||
|
["t"] = "\t",
|
||||||
|
["r"] = "\r",
|
||||||
|
["f"] = "\f",
|
||||||
|
[INS_CHAR] = INS_CHAR..INS_CHAR,
|
||||||
|
}
|
||||||
|
|
||||||
|
local function unescape(str)
|
||||||
|
local parts = {}
|
||||||
|
local n = 1
|
||||||
|
local function add(s)
|
||||||
|
parts[n] = s
|
||||||
|
n = n + 1
|
||||||
|
end
|
||||||
|
|
||||||
|
local start = 1
|
||||||
|
while true do
|
||||||
|
local pos = str:find("[\\@]", start)
|
||||||
|
if pos then
|
||||||
|
add(str:sub(start, pos - 1))
|
||||||
|
else
|
||||||
|
add(str:sub(start))
|
||||||
|
break
|
||||||
|
end
|
||||||
|
local c = str:sub(pos + 1, pos + 1)
|
||||||
|
if escapes[c] then
|
||||||
|
add(escapes[c])
|
||||||
|
elseif str:sub(pos, pos) == "@" then
|
||||||
|
add("@" .. c)
|
||||||
|
else
|
||||||
|
add(c)
|
||||||
|
end
|
||||||
|
start = pos + 2
|
||||||
|
end
|
||||||
|
return table.concat(parts)
|
||||||
|
end
|
||||||
|
|
||||||
|
local function find_eq(s)
|
||||||
|
for slashes, pos in s:gmatch("([\\]*)=()") do
|
||||||
|
if (slashes:len() % 2) == 0 then
|
||||||
|
return pos - 1
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
function intllib.load_strings(filename)
|
||||||
|
local file, err = io.open(filename, "r")
|
||||||
|
if not file then
|
||||||
|
return nil, err
|
||||||
|
end
|
||||||
|
local strings = {}
|
||||||
|
for line in file:lines() do
|
||||||
|
line = line:trim()
|
||||||
|
if line ~= "" and line:sub(1, 1) ~= "#" then
|
||||||
|
local pos = find_eq(line)
|
||||||
|
if pos then
|
||||||
|
local msgid = unescape(line:sub(1, pos - 1):trim())
|
||||||
|
strings[msgid] = unescape(line:sub(pos + 1):trim())
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
file:close()
|
||||||
|
return strings
|
||||||
|
end
|
@ -3,7 +3,7 @@
|
|||||||
--
|
--
|
||||||
--This program is free software; you can redistribute it and/or modify
|
--This program is free software; you can redistribute it and/or modify
|
||||||
--it under the terms of the GNU Lesser General Public License as published by
|
--it under the terms of the GNU Lesser General Public License as published by
|
||||||
--the Free Software Foundation; either version 2.1 of the License, or
|
--the Free Software Foundation; either version 3.0 of the License, or
|
||||||
--(at your option) any later version.
|
--(at your option) any later version.
|
||||||
--
|
--
|
||||||
--This program is distributed in the hope that it will be useful,
|
--This program is distributed in the hope that it will be useful,
|
||||||
|
@ -3,7 +3,7 @@
|
|||||||
--
|
--
|
||||||
--This program is free software; you can redistribute it and/or modify
|
--This program is free software; you can redistribute it and/or modify
|
||||||
--it under the terms of the GNU Lesser General Public License as published by
|
--it under the terms of the GNU Lesser General Public License as published by
|
||||||
--the Free Software Foundation; either version 2.1 of the License, or
|
--the Free Software Foundation; either version 3.0 of the License, or
|
||||||
--(at your option) any later version.
|
--(at your option) any later version.
|
||||||
--
|
--
|
||||||
--This program is distributed in the hope that it will be useful,
|
--This program is distributed in the hope that it will be useful,
|
||||||
|
@ -3,7 +3,7 @@
|
|||||||
--
|
--
|
||||||
--This program is free software; you can redistribute it and/or modify
|
--This program is free software; you can redistribute it and/or modify
|
||||||
--it under the terms of the GNU Lesser General Public License as published by
|
--it under the terms of the GNU Lesser General Public License as published by
|
||||||
--the Free Software Foundation; either version 2.1 of the License, or
|
--the Free Software Foundation; either version 3.0 of the License, or
|
||||||
--(at your option) any later version.
|
--(at your option) any later version.
|
||||||
--
|
--
|
||||||
--This program is distributed in the hope that it will be useful,
|
--This program is distributed in the hope that it will be useful,
|
||||||
@ -73,7 +73,7 @@ local Formatter = {
|
|||||||
end
|
end
|
||||||
}
|
}
|
||||||
|
|
||||||
local widths = { 55, 9, 9, 9, 5, 5, 5 }
|
local widths = { 50, 8, 8, 8, 5, 5, 5 }
|
||||||
local txt_row_format = sprintf(" %%-%ds | %%%ds | %%%ds | %%%ds | %%%ds | %%%ds | %%%ds", unpack(widths))
|
local txt_row_format = sprintf(" %%-%ds | %%%ds | %%%ds | %%%ds | %%%ds | %%%ds | %%%ds", unpack(widths))
|
||||||
|
|
||||||
local HR = {}
|
local HR = {}
|
||||||
|
@ -3,7 +3,7 @@
|
|||||||
--
|
--
|
||||||
--This program is free software; you can redistribute it and/or modify
|
--This program is free software; you can redistribute it and/or modify
|
||||||
--it under the terms of the GNU Lesser General Public License as published by
|
--it under the terms of the GNU Lesser General Public License as published by
|
||||||
--the Free Software Foundation; either version 2.1 of the License, or
|
--the Free Software Foundation; either version 3.0 of the License, or
|
||||||
--(at your option) any later version.
|
--(at your option) any later version.
|
||||||
--
|
--
|
||||||
--This program is distributed in the hope that it will be useful,
|
--This program is distributed in the hope that it will be useful,
|
||||||
|
Before Width: | Height: | Size: 255 B After Width: | Height: | Size: 344 B |
Before Width: | Height: | Size: 68 B After Width: | Height: | Size: 212 B |
BIN
textures/base/pack/hunger.png
Normal file
After Width: | Height: | Size: 366 B |
BIN
textures/base/pack/hunger_gone.png
Normal file
After Width: | Height: | Size: 226 B |
BIN
textures/base/pack/hunger_poisen.png
Normal file
After Width: | Height: | Size: 424 B |