Add MultiCraft builtin improvements
@ -2,10 +2,10 @@
|
||||
-- handled by the engine.
|
||||
|
||||
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]" ..
|
||||
"label[4.85,1.35;" .. fgettext("You died") ..
|
||||
"]button_exit[4,3;3,0.5;btn_respawn;".. fgettext("Respawn") .."]"
|
||||
"label[5,2;" .. fgettext("You died.") ..
|
||||
"]button_exit[3.5,3;4,0.5;btn_respawn;".. fgettext("Respawn") .."]"
|
||||
core.show_formspec("bultin:death", formspec)
|
||||
end)
|
||||
|
||||
|
@ -3,7 +3,7 @@
|
||||
--
|
||||
--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 2.1 of the License, or
|
||||
--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,
|
||||
|
@ -16,7 +16,10 @@ local LIST_FORMSPEC_DESCRIPTION = [[
|
||||
tablecolumns[color;tree;text;text]
|
||||
table[0,0.5;12.8,4.8;list;%s;%i]
|
||||
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]
|
||||
]]
|
||||
|
||||
@ -149,4 +152,3 @@ help_command.func = function(name, param)
|
||||
|
||||
return old_help_func(name, param)
|
||||
end
|
||||
|
||||
|
@ -280,7 +280,7 @@ end
|
||||
--------------------------------------------------------------------------------
|
||||
|
||||
if INIT == "game" then
|
||||
local dirs1 = {9, 18, 7, 12}
|
||||
local dirs1 = {10, 19, 4, 13}
|
||||
local dirs2 = {20, 23, 22, 21}
|
||||
|
||||
function core.rotate_and_place(itemstack, placer, pointed_thing,
|
||||
@ -292,8 +292,7 @@ if INIT == "game" then
|
||||
return
|
||||
end
|
||||
local undef = core.registered_nodes[unode.name]
|
||||
local sneaking = placer and placer:get_player_control().sneak
|
||||
if undef and undef.on_rightclick and not sneaking then
|
||||
if undef and undef.on_rightclick then
|
||||
return undef.on_rightclick(pointed_thing.under, unode, placer,
|
||||
itemstack, pointed_thing)
|
||||
end
|
||||
|
@ -115,6 +115,7 @@ function core.serialize(x)
|
||||
function dump_val(x)
|
||||
local tp = type(x)
|
||||
if x == nil then return "nil"
|
||||
elseif tp == "userdata" then return "nil"
|
||||
elseif tp == "string" then return string.format("%q", x)
|
||||
elseif tp == "boolean" then return x and "true" or "false"
|
||||
elseif tp == "function" then
|
||||
|
@ -1,6 +1,8 @@
|
||||
|
||||
vector = {}
|
||||
|
||||
local floor, hypot = math.floor, math.hypot
|
||||
local min, max, pi = math.min, math.max, math.pi
|
||||
|
||||
function vector.new(a, b, c)
|
||||
if type(a) == "table" then
|
||||
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
|
||||
|
||||
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
|
||||
|
||||
function vector.normalize(v)
|
||||
@ -33,17 +35,17 @@ end
|
||||
|
||||
function vector.floor(v)
|
||||
return {
|
||||
x = math.floor(v.x),
|
||||
y = math.floor(v.y),
|
||||
z = math.floor(v.z)
|
||||
x = floor(v.x),
|
||||
y = floor(v.y),
|
||||
z = floor(v.z)
|
||||
}
|
||||
end
|
||||
|
||||
function vector.round(v)
|
||||
return {
|
||||
x = math.floor(v.x + 0.5),
|
||||
y = math.floor(v.y + 0.5),
|
||||
z = math.floor(v.z + 0.5)
|
||||
x = floor(v.x + 0.5),
|
||||
y = floor(v.y + 0.5),
|
||||
z = floor(v.z + 0.5)
|
||||
}
|
||||
end
|
||||
|
||||
@ -59,7 +61,7 @@ function vector.distance(a, b)
|
||||
local x = a.x - b.x
|
||||
local y = a.y - b.y
|
||||
local z = a.z - b.z
|
||||
return math.hypot(x, math.hypot(y, z))
|
||||
return hypot(x, hypot(y, z))
|
||||
end
|
||||
|
||||
function vector.direction(pos1, pos2)
|
||||
@ -138,12 +140,12 @@ function vector.divide(a, b)
|
||||
end
|
||||
|
||||
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)},
|
||||
{x = math.max(a.x, b.x), y = math.max(a.y, b.y), z = math.max(a.z, b.z)}
|
||||
return {x = min(a.x, b.x), y = min(a.y, b.y), z = min(a.z, b.z)},
|
||||
{x = max(a.x, b.x), y = max(a.y, b.y), z = max(a.z, b.z)}
|
||||
end
|
||||
|
||||
local function sin(x)
|
||||
if x % math.pi == 0 then
|
||||
if x % pi == 0 then
|
||||
return 0
|
||||
else
|
||||
return math.sin(x)
|
||||
@ -151,7 +153,7 @@ local function sin(x)
|
||||
end
|
||||
|
||||
local function cos(x)
|
||||
if x % math.pi == math.pi / 2 then
|
||||
if x % pi == pi / 2 then
|
||||
return 0
|
||||
else
|
||||
return math.cos(x)
|
||||
|
@ -138,10 +138,10 @@ core.register_on_prejoinplayer(function(name, ip)
|
||||
local name_lower = name:lower()
|
||||
for k in core.builtin_auth_handler.iterate() do
|
||||
if k:lower() == name_lower then
|
||||
return string.format("\nCannot create new player called '%s'. "..
|
||||
"Another account called '%s' is already registered. "..
|
||||
return ("\nYou can not register as \"%s\"! "..
|
||||
"Another player called \"%s\" is already registered. " ..
|
||||
"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)
|
||||
|
@ -105,6 +105,15 @@ local function parse_range_str(player_name, str)
|
||||
return p1, p2
|
||||
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
|
||||
--
|
||||
@ -436,7 +445,6 @@ core.register_chatcommand("teleport", {
|
||||
p.y = tonumber(p.y)
|
||||
p.z = tonumber(p.z)
|
||||
if p.x and p.y and p.z then
|
||||
|
||||
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
|
||||
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!"
|
||||
end
|
||||
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
|
||||
|
||||
@ -469,7 +477,7 @@ core.register_chatcommand("teleport", {
|
||||
p = find_free_position_near(p)
|
||||
teleportee:set_pos(p)
|
||||
return true, "Teleporting to " .. target_name
|
||||
.. " at "..core.pos_to_string(p)
|
||||
.. " at " .. core.pos_to_string(p, 1)
|
||||
end
|
||||
|
||||
if not core.check_player_privs(name, {bring=true}) then
|
||||
@ -491,7 +499,7 @@ core.register_chatcommand("teleport", {
|
||||
end
|
||||
teleportee:set_pos(p)
|
||||
return true, "Teleporting " .. teleportee_name
|
||||
.. " to " .. core.pos_to_string(p)
|
||||
.. " to " .. core.pos_to_string(p, 1)
|
||||
end
|
||||
|
||||
teleportee = nil
|
||||
@ -514,13 +522,14 @@ core.register_chatcommand("teleport", {
|
||||
teleportee:set_pos(p)
|
||||
return true, "Teleporting " .. teleportee_name
|
||||
.. " to " .. target_name
|
||||
.. " at " .. core.pos_to_string(p)
|
||||
.. " at " .. core.pos_to_string(p, 1)
|
||||
end
|
||||
|
||||
return false, 'Invalid parameters ("' .. param
|
||||
.. '") or player not found (see /help teleport)'
|
||||
end,
|
||||
})
|
||||
register_chatcommand_alias("tp", "teleport")
|
||||
|
||||
core.register_chatcommand("set", {
|
||||
params = "([-n] <name> <value>) | <name>",
|
||||
@ -649,7 +658,7 @@ core.register_chatcommand("fixlight", {
|
||||
core.register_chatcommand("mods", {
|
||||
params = "",
|
||||
description = "List mods installed on the server",
|
||||
privs = {},
|
||||
privs = {server = true},
|
||||
func = function(name, param)
|
||||
return true, table.concat(core.get_modnames(), ", ")
|
||||
end,
|
||||
@ -658,6 +667,18 @@ core.register_chatcommand("mods", {
|
||||
local function handle_give_command(cmd, giver, receiver, stackstring)
|
||||
core.log("action", giver .. " invoked " .. cmd
|
||||
.. ', 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)
|
||||
if itemstack:is_empty() then
|
||||
return false, "Cannot give an empty item"
|
||||
@ -926,6 +947,7 @@ core.register_chatcommand("time", {
|
||||
return true, "Time of day changed."
|
||||
end,
|
||||
})
|
||||
register_chatcommand_alias("settime", "time")
|
||||
|
||||
core.register_chatcommand("days", {
|
||||
description = "Show day count since world creation",
|
||||
@ -1030,8 +1052,7 @@ core.register_chatcommand("clearobjects", {
|
||||
core.log("action", name .. " clears all objects ("
|
||||
.. options.mode .. " mode).")
|
||||
core.chat_send_all("Clearing all objects. This may take a long time."
|
||||
.. " You may experience a timeout. (by "
|
||||
.. name .. ")")
|
||||
.. " You may experience a timeout.")
|
||||
core.clear_objects(options)
|
||||
core.log("action", "Object clearing done.")
|
||||
core.chat_send_all("*** Cleared all objects.")
|
||||
@ -1041,7 +1062,7 @@ core.register_chatcommand("clearobjects", {
|
||||
|
||||
core.register_chatcommand("msg", {
|
||||
params = "<name> <message>",
|
||||
description = "Send a direct message to a player",
|
||||
description = "Send a private message to a player",
|
||||
privs = {shout=true},
|
||||
func = function(name, param)
|
||||
local sendto, message = param:match("^(%S+)%s(.+)$")
|
||||
@ -1052,13 +1073,15 @@ core.register_chatcommand("msg", {
|
||||
return false, "The player " .. sendto
|
||||
.. " is not online."
|
||||
end
|
||||
core.log("action", "DM from " .. name .. " to " .. sendto
|
||||
core.log("action", "PM from " .. name .. " to " .. sendto
|
||||
.. ": " .. message)
|
||||
core.chat_send_player(sendto, "DM from " .. name .. ": "
|
||||
.. message)
|
||||
core.chat_send_player(sendto, minetest.colorize("green",
|
||||
"PM from " .. name .. ": " .. message))
|
||||
return true, "Message sent."
|
||||
end,
|
||||
})
|
||||
register_chatcommand_alias("m", "msg")
|
||||
register_chatcommand_alias("pm", "msg")
|
||||
|
||||
core.register_chatcommand("last-login", {
|
||||
params = "[<name>]",
|
||||
@ -1135,3 +1158,34 @@ core.register_chatcommand("kill", {
|
||||
return handle_kill_command(name, param == "" and name or param)
|
||||
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
|
||||
|
||||
local random, pi = math.random, math.pi
|
||||
local vnew, vround, vadd, vapply = vector.new, vector.round, vector.add, vector.apply
|
||||
|
||||
local builtin_shared = ...
|
||||
local SCALE = 0.667
|
||||
|
||||
local facedir_to_euler = {
|
||||
{y = 0, x = 0, z = 0},
|
||||
{y = -math.pi/2, x = 0, z = 0},
|
||||
{y = math.pi, x = 0, z = 0},
|
||||
{y = math.pi/2, x = 0, z = 0},
|
||||
{y = math.pi/2, x = -math.pi/2, z = math.pi/2},
|
||||
{y = math.pi/2, x = math.pi, z = math.pi/2},
|
||||
{y = math.pi/2, x = math.pi/2, z = math.pi/2},
|
||||
{y = math.pi/2, x = 0, z = math.pi/2},
|
||||
{y = -math.pi/2, x = math.pi/2, z = math.pi/2},
|
||||
{y = -math.pi/2, x = 0, z = math.pi/2},
|
||||
{y = -math.pi/2, x = -math.pi/2, z = math.pi/2},
|
||||
{y = -math.pi/2, x = math.pi, z = math.pi/2},
|
||||
{y = 0, x = 0, z = math.pi/2},
|
||||
{y = 0, x = -math.pi/2, z = math.pi/2},
|
||||
{y = 0, x = math.pi, z = math.pi/2},
|
||||
{y = 0, x = math.pi/2, z = math.pi/2},
|
||||
{y = math.pi, x = math.pi, z = math.pi/2},
|
||||
{y = math.pi, x = math.pi/2, z = math.pi/2},
|
||||
{y = math.pi, x = 0, z = math.pi/2},
|
||||
{y = math.pi, x = -math.pi/2, z = math.pi/2},
|
||||
{y = math.pi, x = math.pi, z = 0},
|
||||
{y = -math.pi/2, x = math.pi, z = 0},
|
||||
{y = 0, x = math.pi, z = 0},
|
||||
{y = math.pi/2, x = math.pi, z = 0}
|
||||
{y = -pi/2, x = 0, z = 0},
|
||||
{y = pi, x = 0, z = 0},
|
||||
{y = pi/2, x = 0, z = 0},
|
||||
{y = pi/2, x = -pi/2, z = pi/2},
|
||||
{y = pi/2, x = pi, z = pi/2},
|
||||
{y = pi/2, x = pi/2, z = pi/2},
|
||||
{y = pi/2, x = 0, z = pi/2},
|
||||
{y = -pi/2, x = pi/2, z = pi/2},
|
||||
{y = -pi/2, x = 0, z = pi/2},
|
||||
{y = -pi/2, x = -pi/2, z = pi/2},
|
||||
{y = -pi/2, x = pi, z = pi/2},
|
||||
{y = 0, x = 0, z = pi/2},
|
||||
{y = 0, x = -pi/2, z = pi/2},
|
||||
{y = 0, x = pi, z = pi/2},
|
||||
{y = 0, x = pi/2, z = pi/2},
|
||||
{y = pi, x = pi, z = pi/2},
|
||||
{y = pi, x = pi/2, z = pi/2},
|
||||
{y = pi, x = 0, z = pi/2},
|
||||
{y = pi, x = -pi/2, z = pi/2},
|
||||
{y = pi, x = pi, z = 0},
|
||||
{y = -pi/2, x = pi, z = 0},
|
||||
{y = 0, x = pi, z = 0},
|
||||
{y = pi/2, x = pi, z = 0}
|
||||
}
|
||||
|
||||
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
|
||||
--
|
||||
|
||||
local drop_attached_node
|
||||
|
||||
core.register_entity(":__builtin:falling_node", {
|
||||
initial_properties = {
|
||||
visual = "item",
|
||||
@ -50,8 +55,10 @@ core.register_entity(":__builtin:falling_node", {
|
||||
node = {},
|
||||
meta = {},
|
||||
floats = false,
|
||||
timer = 0,
|
||||
|
||||
set_node = function(self, node, meta)
|
||||
node.param2 = node.param2 or 0
|
||||
self.node = node
|
||||
meta = meta or {}
|
||||
if type(meta.to_table) == "function" then
|
||||
@ -143,7 +150,7 @@ core.register_entity(":__builtin:falling_node", {
|
||||
|
||||
-- Rotate entity
|
||||
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 == ""
|
||||
or def.wield_image == nil))
|
||||
or def.drawtype == "signlike"
|
||||
@ -161,28 +168,28 @@ core.register_entity(":__builtin:falling_node", {
|
||||
local rot = node.param2 % 8
|
||||
local pitch, yaw, roll = 0, 0, 0
|
||||
if rot == 1 then
|
||||
pitch, yaw = math.pi, math.pi
|
||||
pitch, yaw = pi, pi
|
||||
elseif rot == 2 then
|
||||
pitch, yaw = math.pi/2, math.pi/2
|
||||
pitch, yaw = pi/2, pi/2
|
||||
elseif rot == 3 then
|
||||
pitch, yaw = math.pi/2, -math.pi/2
|
||||
pitch, yaw = pi/2, -pi/2
|
||||
elseif rot == 4 then
|
||||
pitch, yaw = math.pi/2, math.pi
|
||||
pitch, yaw = pi/2, pi
|
||||
elseif rot == 5 then
|
||||
pitch, yaw = math.pi/2, 0
|
||||
pitch, yaw = pi/2, 0
|
||||
end
|
||||
if def.drawtype == "signlike" then
|
||||
pitch = pitch - math.pi/2
|
||||
pitch = pitch - pi/2
|
||||
if rot == 0 then
|
||||
yaw = yaw + math.pi/2
|
||||
yaw = yaw + pi/2
|
||||
elseif rot == 1 then
|
||||
yaw = yaw - math.pi/2
|
||||
yaw = yaw - pi/2
|
||||
end
|
||||
elseif def.drawtype == "mesh" or def.drawtype == "normal" then
|
||||
if rot >= 0 and rot <= 1 then
|
||||
roll = roll + math.pi
|
||||
roll = roll + pi
|
||||
else
|
||||
yaw = yaw + math.pi
|
||||
yaw = yaw + pi
|
||||
end
|
||||
end
|
||||
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
|
||||
if bcd and bcd.paramtype2 == "leveled" and
|
||||
bcn.name == self.node.name then
|
||||
local addlevel = self.node.level
|
||||
if (addlevel or 0) <= 0 then
|
||||
addlevel = bcd.leveled
|
||||
local addlevel = self.node.level or 0
|
||||
if addlevel <= 0 then
|
||||
addlevel = bcd.leveled or 0
|
||||
end
|
||||
if core.add_node_level(bcp, addlevel) < addlevel then
|
||||
return true
|
||||
@ -230,7 +237,7 @@ core.register_entity(":__builtin:falling_node", {
|
||||
end
|
||||
|
||||
-- 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
|
||||
(not self.floats or bcd.liquidtype == "none") then
|
||||
core.remove_node(bcp)
|
||||
@ -262,6 +269,12 @@ core.register_entity(":__builtin:falling_node", {
|
||||
if self.meta then
|
||||
core.get_meta(np):from_table(self.meta)
|
||||
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
|
||||
core.sound_play(def.sounds.place, {pos = np}, true)
|
||||
end
|
||||
@ -276,7 +289,7 @@ core.register_entity(":__builtin:falling_node", {
|
||||
if self.floats then
|
||||
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 bcd = core.registered_nodes[bcn.name]
|
||||
@ -322,7 +335,7 @@ core.register_entity(":__builtin:falling_node", {
|
||||
y = player_collision.old_velocity.y,
|
||||
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}))
|
||||
end
|
||||
return
|
||||
@ -335,7 +348,7 @@ core.register_entity(":__builtin:falling_node", {
|
||||
local failure = false
|
||||
|
||||
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
|
||||
-- We're colliding with some part of a node that's sticking out
|
||||
-- Since we don't want to visually teleport, drop as item
|
||||
@ -353,6 +366,14 @@ core.register_entity(":__builtin:falling_node", {
|
||||
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
|
||||
if not failure then
|
||||
failure = not self:try_place(bcp, bcn)
|
||||
@ -396,7 +417,7 @@ function core.spawn_falling_node(pos)
|
||||
return convert_to_falling_node(pos, node)
|
||||
end
|
||||
|
||||
local function drop_attached_node(p)
|
||||
function drop_attached_node(p)
|
||||
local n = core.get_node(p)
|
||||
local drops = core.get_node_drops(n, "")
|
||||
local def = core.registered_items[n.name]
|
||||
@ -418,9 +439,9 @@ local function drop_attached_node(p)
|
||||
core.remove_node(p)
|
||||
for _, item in pairs(drops) do
|
||||
local pos = {
|
||||
x = p.x + math.random()/2 - 0.25,
|
||||
y = p.y + math.random()/2 - 0.25,
|
||||
z = p.z + math.random()/2 - 0.25,
|
||||
x = p.x + random()/2 - 0.25,
|
||||
y = p.y + random()/2 - 0.25,
|
||||
z = p.z + random()/2 - 0.25,
|
||||
}
|
||||
core.add_item(pos, item)
|
||||
end
|
||||
@ -439,7 +460,7 @@ function builtin_shared.check_attached_node(p, n)
|
||||
else
|
||||
d.y = -1
|
||||
end
|
||||
local p2 = vector.add(p, d)
|
||||
local p2 = vadd(p, d)
|
||||
local nn = core.get_node(p2).name
|
||||
local def2 = core.registered_nodes[nn]
|
||||
if def2 and not def2.walkable then
|
||||
@ -486,6 +507,33 @@ function core.check_single_for_falling(p)
|
||||
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
|
||||
end
|
||||
|
||||
@ -509,7 +557,7 @@ local check_for_falling_neighbors = {
|
||||
|
||||
function core.check_for_falling(p)
|
||||
-- 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.
|
||||
-- Stored in the stack, we will maintain tables with pos, and
|
||||
@ -526,7 +574,7 @@ function core.check_for_falling(p)
|
||||
n = n + 1
|
||||
s[n] = {p = p, v = v}
|
||||
-- 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,
|
||||
-- it will let us know in the return value (true = updated).
|
||||
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 commonpath = scriptpath .. "common" .. 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 .. "voxelarea.lua")
|
||||
dofile(gamepath .. "forceloading.lua")
|
||||
dofile(gamepath .. "hud.lua")
|
||||
dofile(gamepath .. "statbars.lua")
|
||||
dofile(gamepath .. "knockback.lua")
|
||||
dofile(gamepath .. "hunger.lua")
|
||||
dofile(gamepath .. "sscsm" .. DIR_DELIM .. "init.lua")
|
||||
|
||||
profiler = nil
|
||||
|
@ -2,11 +2,19 @@
|
||||
|
||||
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)
|
||||
return {
|
||||
type = pointed_thing.type,
|
||||
above = vector.new(pointed_thing.above),
|
||||
under = vector.new(pointed_thing.under),
|
||||
above = vnew(pointed_thing.above),
|
||||
under = vnew(pointed_thing.under),
|
||||
ref = pointed_thing.ref,
|
||||
}
|
||||
end
|
||||
@ -39,11 +47,11 @@ end
|
||||
|
||||
function core.dir_to_facedir(dir, is6d)
|
||||
--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
|
||||
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
|
||||
return 19
|
||||
else
|
||||
@ -59,7 +67,7 @@ function core.dir_to_facedir(dir, is6d)
|
||||
|
||||
--from below
|
||||
else
|
||||
if math.abs(dir.x) > math.abs(dir.z) then
|
||||
if abs(dir.x) > abs(dir.z) then
|
||||
if dir.x < 0 then
|
||||
return 15
|
||||
else
|
||||
@ -75,7 +83,7 @@ function core.dir_to_facedir(dir, is6d)
|
||||
end
|
||||
|
||||
--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
|
||||
return 3
|
||||
else
|
||||
@ -113,13 +121,13 @@ function core.facedir_to_dir(facedir)
|
||||
end
|
||||
|
||||
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
|
||||
return 1
|
||||
else
|
||||
return 0
|
||||
end
|
||||
elseif math.abs(dir.x) > math.abs(dir.z) then
|
||||
elseif abs(dir.x) > abs(dir.z) then
|
||||
if dir.x < 0 then
|
||||
return 3
|
||||
else
|
||||
@ -148,11 +156,11 @@ function core.wallmounted_to_dir(wallmounted)
|
||||
end
|
||||
|
||||
function core.dir_to_yaw(dir)
|
||||
return -math.atan2(dir.x, dir.z)
|
||||
return -atan2(dir.x, dir.z)
|
||||
end
|
||||
|
||||
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
|
||||
|
||||
function core.is_colored_paramtype(ptype)
|
||||
@ -165,9 +173,9 @@ function core.strip_param2_color(param2, paramtype2)
|
||||
return nil
|
||||
end
|
||||
if paramtype2 == "colorfacedir" then
|
||||
param2 = math.floor(param2 / 32) * 32
|
||||
param2 = floor(param2 / 32) * 32
|
||||
elseif paramtype2 == "colorwallmounted" then
|
||||
param2 = math.floor(param2 / 8) * 8
|
||||
param2 = floor(param2 / 8) * 8
|
||||
end
|
||||
-- paramtype2 == "color" requires no modification.
|
||||
return param2
|
||||
@ -210,7 +218,7 @@ function core.get_node_drops(node, toolname)
|
||||
local good_rarity = true
|
||||
local good_tool = true
|
||||
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
|
||||
if item.tools ~= nil then
|
||||
good_tool = false
|
||||
@ -347,7 +355,7 @@ function core.item_place_node(itemstack, placer, pointed_thing, param2,
|
||||
color_divisor = 32
|
||||
end
|
||||
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
|
||||
newnode.param2 = color * color_divisor + other
|
||||
end
|
||||
@ -406,16 +414,6 @@ function core.item_place_node(itemstack, placer, pointed_thing, param2,
|
||||
return itemstack, place_to
|
||||
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)
|
||||
-- Call on_rightclick if the pointed node defines it
|
||||
if pointed_thing.type == "node" and placer and
|
||||
@ -439,10 +437,103 @@ function core.item_secondary_use(itemstack, placer)
|
||||
return itemstack
|
||||
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)
|
||||
local dropper_is_player = dropper and dropper:is_player()
|
||||
local p = table.copy(pos)
|
||||
local cnt = itemstack:get_count()
|
||||
if not core.is_valid_pos(p) then
|
||||
return
|
||||
end
|
||||
if dropper_is_player then
|
||||
p.y = p.y + 1.2
|
||||
end
|
||||
@ -450,12 +541,19 @@ function core.item_drop(itemstack, dropper, pos)
|
||||
local obj = core.add_item(p, item)
|
||||
if obj then
|
||||
if dropper_is_player then
|
||||
local vel = dropper:get_player_velocity()
|
||||
local dir = dropper:get_look_dir()
|
||||
dir.x = dir.x * 2.9
|
||||
dir.y = dir.y * 2.9 + 2
|
||||
dir.z = dir.z * 2.9
|
||||
dir.x = vel.x + dir.x * 4
|
||||
dir.y = vel.y + dir.y * 4 + 2
|
||||
dir.z = vel.z + dir.z * 4
|
||||
obj:set_velocity(dir)
|
||||
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
|
||||
return itemstack
|
||||
end
|
||||
@ -463,7 +561,8 @@ function core.item_drop(itemstack, dropper, pos)
|
||||
-- environment failed
|
||||
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
|
||||
local result = callback(hp_change, replace_with_item, itemstack, user, pointed_thing)
|
||||
if result then
|
||||
@ -472,15 +571,47 @@ function core.do_item_eat(hp_change, replace_with_item, itemstack, user, pointed
|
||||
end
|
||||
local def = itemstack:get_definition()
|
||||
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)
|
||||
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
|
||||
core.sound_play(def.sound.eat, {
|
||||
pos = user:get_pos(),
|
||||
max_hear_distance = 16
|
||||
}, true)
|
||||
pos = pos,
|
||||
max_hear_distance = 16})
|
||||
else
|
||||
core.sound_play("player_eat", {
|
||||
pos = pos,
|
||||
max_hear_distance = 10,
|
||||
gain = 0.3})
|
||||
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 itemstack:is_empty() then
|
||||
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
|
||||
inv:add_item("main", replace_with_item)
|
||||
else
|
||||
local pos = user:get_pos()
|
||||
pos.y = math.floor(pos.y + 0.5)
|
||||
pos.y = pos.y + 0.5
|
||||
core.add_item(pos, replace_with_item)
|
||||
end
|
||||
end
|
||||
@ -500,10 +630,10 @@ function core.do_item_eat(hp_change, replace_with_item, itemstack, user, pointed
|
||||
return itemstack
|
||||
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
|
||||
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
|
||||
@ -512,7 +642,7 @@ function core.node_punch(pos, node, puncher, pointed_thing)
|
||||
-- Run script hook
|
||||
for _, callback in ipairs(core.registered_on_punchnodes) do
|
||||
-- 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 pointed_thing_copy = pointed_thing and copy_pointed_thing(pointed_thing) or nil
|
||||
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
|
||||
local inv = digger and digger:get_inventory()
|
||||
local give_item
|
||||
if inv then
|
||||
if (not node_drop or creative_mode) and inv then
|
||||
give_item = function(item)
|
||||
return inv:add_item("main", item)
|
||||
end
|
||||
@ -537,12 +667,7 @@ function core.handle_node_drops(pos, drops, digger)
|
||||
for _, dropped_item in pairs(drops) do
|
||||
local left = give_item(dropped_item)
|
||||
if not left:is_empty() then
|
||||
local p = {
|
||||
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)
|
||||
core.item_drop(left, nil, pos)
|
||||
end
|
||||
end
|
||||
end
|
||||
@ -675,7 +800,7 @@ end
|
||||
-- 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 = {
|
||||
-- Item properties
|
||||
|
@ -1,5 +1,7 @@
|
||||
-- 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)
|
||||
-- Take item in any format
|
||||
local stack = ItemStack(item)
|
||||
@ -14,9 +16,53 @@ end
|
||||
-- If item_entity_ttl is not set, enity will have default life time
|
||||
-- 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 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", {
|
||||
initial_properties = {
|
||||
@ -52,10 +98,10 @@ core.register_entity(":__builtin:item", {
|
||||
local itemname = stack:is_known() and stack:get_name() or "unknown"
|
||||
|
||||
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 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({
|
||||
is_visible = true,
|
||||
@ -63,9 +109,10 @@ core.register_entity(":__builtin:item", {
|
||||
textures = {itemname},
|
||||
visual_size = {x = size, y = 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,
|
||||
glow = glow,
|
||||
infotext = core.registered_items[itemname].description
|
||||
})
|
||||
|
||||
end,
|
||||
@ -79,7 +126,7 @@ core.register_entity(":__builtin:item", {
|
||||
end,
|
||||
|
||||
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)
|
||||
if data and type(data) == "table" then
|
||||
self.itemstring = data.itemstring
|
||||
@ -89,7 +136,7 @@ core.register_entity(":__builtin:item", {
|
||||
else
|
||||
self.itemstring = staticdata
|
||||
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_acceleration({x = 0, y = -gravity, z = 0})
|
||||
self:set_item()
|
||||
@ -165,6 +212,7 @@ core.register_entity(":__builtin:item", {
|
||||
y = pos.y + self.object:get_properties().collisionbox[2] - 0.05,
|
||||
z = pos.z
|
||||
})
|
||||
local node_inside = core.get_node_or_nil(pos)
|
||||
-- Delete in 'ignore' nodes
|
||||
if node and node.name == "ignore" then
|
||||
self.itemstring = ""
|
||||
@ -264,16 +312,64 @@ core.register_entity(":__builtin:item", {
|
||||
end
|
||||
end
|
||||
|
||||
-- Slide on slippery nodes
|
||||
local vel = self.object:get_velocity()
|
||||
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
|
||||
|
||||
if def then
|
||||
local slippery = core.get_item_group(node.name, "slippery")
|
||||
local vel = self.object:get_velocity()
|
||||
if slippery ~= 0 and (math.abs(vel.x) > 0.1 or math.abs(vel.z) > 0.1) then
|
||||
if slippery ~= 0 and (abs(vel.x) > 0.1 or abs(vel.z) > 0.1) then
|
||||
-- Horizontal deceleration
|
||||
local factor = math.min(4 / (slippery + 4) * dtime, 1)
|
||||
local factor = min(4 / (slippery + 4) * dtime, 1)
|
||||
self.object:set_velocity({
|
||||
x = vel.x * (1 - factor),
|
||||
y = 0,
|
||||
@ -302,7 +398,7 @@ core.register_entity(":__builtin:item", {
|
||||
if own_stack:get_free_space() == 0 then
|
||||
return
|
||||
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
|
||||
local entity = obj:get_luaentity()
|
||||
if entity and entity.name == "__builtin:item" then
|
||||
@ -329,3 +425,57 @@ core.register_entity(":__builtin:item", {
|
||||
self.object:remove()
|
||||
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
|
||||
end
|
||||
|
||||
local function vector_absmax(v)
|
||||
local max, abs = math.max, math.abs
|
||||
local function vector_absmax(v)
|
||||
return max(max(abs(v.x), abs(v.y)), abs(v.z))
|
||||
end
|
||||
|
||||
core.register_on_punchplayer(function(player, hitter, time_from_last_punch, tool_capabilities, unused_dir, damage)
|
||||
if player:get_hp() == 0 then
|
||||
return -- RIP
|
||||
if player:get_hp() == 0
|
||||
or player:get_animation().range ~= "stand" then
|
||||
return -- RIP or attached
|
||||
end
|
||||
|
||||
-- Server::handleCommand_Interact() adds eye offset to one but not the other
|
||||
|
@ -42,13 +42,13 @@ end
|
||||
|
||||
function core.send_join_message(player_name)
|
||||
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
|
||||
|
||||
|
||||
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
|
||||
announcement = announcement .. " (timed out)"
|
||||
end
|
||||
@ -109,6 +109,22 @@ function core.get_player_radius_area(player_name, radius)
|
||||
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)
|
||||
return (pos.z + 32768) * 65536 * 65536
|
||||
+ (pos.y + 32768) * 65536
|
||||
@ -157,6 +173,9 @@ function core.is_protected(pos, name)
|
||||
return false
|
||||
end
|
||||
|
||||
function core.is_protected_action()
|
||||
return false
|
||||
end
|
||||
|
||||
function core.record_protection_violation(pos, name)
|
||||
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
|
||||
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("shout", "Can speak in chat")
|
||||
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", {
|
||||
description = "Can teleport self",
|
||||
give_to_singleplayer = false,
|
||||
give_to_singleplayer = creative,
|
||||
})
|
||||
core.register_privilege("bring", {
|
||||
description = "Can teleport other players",
|
||||
@ -43,7 +48,7 @@ core.register_privilege("bring", {
|
||||
})
|
||||
core.register_privilege("settime", {
|
||||
description = "Can set the time of day using /time",
|
||||
give_to_singleplayer = false,
|
||||
give_to_singleplayer = creative,
|
||||
})
|
||||
core.register_privilege("server", {
|
||||
description = "Can do server maintenance stuff",
|
||||
@ -75,11 +80,11 @@ core.register_privilege("password", {
|
||||
})
|
||||
core.register_privilege("fly", {
|
||||
description = "Can use fly mode",
|
||||
give_to_singleplayer = false,
|
||||
give_to_singleplayer = creative,
|
||||
})
|
||||
core.register_privilege("fast", {
|
||||
description = "Can use fast mode",
|
||||
give_to_singleplayer = false,
|
||||
give_to_singleplayer = creative,
|
||||
})
|
||||
core.register_privilege("noclip", {
|
||||
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 "??"
|
||||
end
|
||||
|
||||
-- Intllib
|
||||
Sl = intllib.make_gettext_pair("locales")
|
||||
|
||||
function core.register_item(name, itemdef)
|
||||
-- Check name
|
||||
if name == nil then
|
||||
@ -134,6 +137,9 @@ function core.register_item(name, itemdef)
|
||||
core.log("warning", "Node 'light_source' value exceeds maximum," ..
|
||||
" limiting to maximum: " ..name)
|
||||
end
|
||||
if itemdef.light_source == nil then
|
||||
itemdef.light_source = 0
|
||||
end
|
||||
setmetatable(itemdef, {__index = core.nodedef_default})
|
||||
core.registered_nodes[itemdef.name] = itemdef
|
||||
elseif itemdef.type == "craft" then
|
||||
@ -153,6 +159,12 @@ function core.register_item(name, itemdef)
|
||||
itemdef.paramtype2 = "flowingliquid"
|
||||
end
|
||||
|
||||
-- Intllib
|
||||
if itemdef.description and itemdef.description ~= "" then
|
||||
itemdef.description = Sl(itemdef.description:gsub("@", "\001"))
|
||||
:gsub("\001", "@")
|
||||
end
|
||||
|
||||
-- BEGIN Legacy stuff
|
||||
if itemdef.cookresult_itemstring ~= nil and itemdef.cookresult_itemstring ~= "" then
|
||||
core.register_craft({
|
||||
@ -344,8 +356,8 @@ core.register_item(":unknown", {
|
||||
|
||||
core.register_node(":air", {
|
||||
description = "Air",
|
||||
inventory_image = "air.png",
|
||||
wield_image = "air.png",
|
||||
inventory_image = "blank.png",
|
||||
wield_image = "blank.png",
|
||||
drawtype = "airlike",
|
||||
paramtype = "light",
|
||||
sunlight_propagates = true,
|
||||
@ -356,6 +368,7 @@ core.register_node(":air", {
|
||||
floodable = true,
|
||||
air_equivalent = true,
|
||||
drop = "",
|
||||
drowning = 0,
|
||||
groups = {not_in_creative_inventory=1},
|
||||
})
|
||||
|
||||
@ -372,6 +385,7 @@ core.register_node(":ignore", {
|
||||
buildable_to = true, -- A way to remove accidentally placed ignores
|
||||
air_equivalent = true,
|
||||
drop = "",
|
||||
drowning = 0,
|
||||
groups = {not_in_creative_inventory=1},
|
||||
on_place = function(itemstack, placer, pointed_thing)
|
||||
core.chat_send_player(
|
||||
@ -385,7 +399,11 @@ core.register_node(":ignore", {
|
||||
-- The hand (bare definition)
|
||||
core.register_item(":", {
|
||||
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},
|
||||
})
|
||||
|
||||
@ -408,6 +426,15 @@ function core.override_item(name, redefinition)
|
||||
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 = {}
|
||||
|
||||
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_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()
|
||||
--
|
||||
|
@ -1,41 +1,30 @@
|
||||
-- cache setting
|
||||
local enable_damage = core.settings:get_bool("enable_damage")
|
||||
local disable_health = false
|
||||
|
||||
local health_bar_definition = {
|
||||
hud_elem_type = "statbar",
|
||||
position = {x = 0.5, y = 1},
|
||||
text = "heart.png",
|
||||
text2 = "heart_gone.png",
|
||||
number = core.PLAYER_MAX_HP_DEFAULT,
|
||||
item = core.PLAYER_MAX_HP_DEFAULT,
|
||||
direction = 0,
|
||||
alignment = {x = -1, y = -1},
|
||||
offset = {x = -247, y = -94},
|
||||
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 = {
|
||||
hud_elem_type = "statbar",
|
||||
position = {x = 0.5, y = 1},
|
||||
text = "bubble.png",
|
||||
text2 = "bubble_gone.png",
|
||||
number = core.PLAYER_MAX_BREATH_DEFAULT,
|
||||
item = core.PLAYER_MAX_BREATH_DEFAULT * 2,
|
||||
direction = 0,
|
||||
alignment = {x = -1, y = -1},
|
||||
offset = {x = 8, y = -120},
|
||||
size = {x = 24, y = 24},
|
||||
offset = {x = 25, y= -(48 + 24 + 16)},
|
||||
text = "bubble.png",
|
||||
number = 20
|
||||
}
|
||||
|
||||
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 name = player:get_player_name()
|
||||
|
||||
@ -50,47 +39,24 @@ local function update_builtin_statbars(player)
|
||||
-- our current flags are transmitted by sending them actively
|
||||
player:hud_set_flags(flags)
|
||||
end
|
||||
local hud = hud_ids[name]
|
||||
local hud_id = hud_ids[name]
|
||||
|
||||
local immortal = player:get_armor_groups().immortal == 1
|
||||
|
||||
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
|
||||
if flags.healthbar and not disable_health then
|
||||
hud.change_item(player, "health", {number = player:get_hp()})
|
||||
end
|
||||
|
||||
local show_breathbar = flags.breathbar and enable_damage and not immortal
|
||||
|
||||
local breath = player:get_breath()
|
||||
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
|
||||
if flags.breathbar and player:get_breath() < 10 then
|
||||
local number = player:get_breath() * 2
|
||||
if hud_id.id_breathbar == nil then
|
||||
local hud_def = table.copy(breath_bar_definition)
|
||||
hud_def.number = number
|
||||
hud.id_breathbar = player:hud_add(hud_def)
|
||||
elseif hud.id_breathbar then
|
||||
player:hud_change(hud.id_breathbar, "number", number)
|
||||
hud_id.id_breathbar = player:hud_add(hud_def)
|
||||
else
|
||||
player:hud_change(hud_id.id_breathbar, "number", number)
|
||||
end
|
||||
end
|
||||
|
||||
if hud.id_breathbar and (not show_breathbar or breath == breath_max) then
|
||||
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
|
||||
elseif hud_id.id_breathbar then
|
||||
player:hud_remove(hud_id.id_breathbar)
|
||||
hud_id.id_breathbar = nil
|
||||
end
|
||||
end
|
||||
|
||||
@ -109,7 +75,7 @@ local function player_event_handler(player,eventname)
|
||||
|
||||
local name = player:get_player_name()
|
||||
|
||||
if name == "" or not hud_ids[name] then
|
||||
if name == "" then
|
||||
return
|
||||
end
|
||||
|
||||
@ -138,26 +104,16 @@ local function player_event_handler(player,eventname)
|
||||
end
|
||||
|
||||
function core.hud_replace_builtin(hud_name, definition)
|
||||
if hud_name == "health" then
|
||||
disable_health = true
|
||||
return true
|
||||
end
|
||||
|
||||
if type(definition) ~= "table" or
|
||||
definition.hud_elem_type ~= "statbar" then
|
||||
return false
|
||||
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
|
||||
breath_bar_definition = definition
|
||||
|
||||
@ -177,8 +133,13 @@ end
|
||||
|
||||
-- Append "update_builtin_statbars" as late as possible
|
||||
-- This ensures that the HUD is hidden when the flags are updated in this callback
|
||||
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)
|
||||
end
|
||||
end)
|
||||
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 commonpath = scriptdir .. "common" .. DIR_DELIM
|
||||
local asyncpath = scriptdir .. "async" .. DIR_DELIM
|
||||
local intlpath = scriptdir .. "intllib" .. DIR_DELIM
|
||||
|
||||
dofile(commonpath .. "strict.lua")
|
||||
dofile(commonpath .. "serialize.lua")
|
||||
dofile(commonpath .. "misc_helpers.lua")
|
||||
|
||||
if INIT == "game" then
|
||||
dofile(intlpath .. "init.lua")
|
||||
dofile(gamepath .. "init.lua")
|
||||
assert(not core.get_http_api)
|
||||
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
|
||||
--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.
|
||||
--
|
||||
--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
|
||||
--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.
|
||||
--
|
||||
--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
|
||||
--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.
|
||||
--
|
||||
--This program is distributed in the hope that it will be useful,
|
||||
@ -73,7 +73,7 @@ local Formatter = {
|
||||
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 HR = {}
|
||||
|
@ -3,7 +3,7 @@
|
||||
--
|
||||
--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 2.1 of the License, or
|
||||
--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,
|
||||
|
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 |