VoxelDungeon/mods/voxeldungeon/utils.lua

610 lines
15 KiB
Lua

--[[
Voxel Dungeon
Copyright (C) 2019 Noodlemire
Pixel Dungeon
Copyright (C) 2012-2015 Oleg Dolya
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 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 General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>
--]]
voxeldungeon.utils = {}
local A = 180 / math.pi
voxeldungeon.utils.NEIGHBORS6 =
{
{x = 0, y = 0, z = -1},
{x = 0, y = -1, z = 0},
{x = -1, y = 0, z = 0},
{x = 1, y = 0, z = 0},
{x = 0, y = 1, z = 0},
{x = 0, y = 0, z = 1},
}
voxeldungeon.utils.NEIGHBORS7 =
{
{x = 0, y = 0, z = -1},
{x = 0, y = -1, z = 0},
{x = -1, y = 0, z = 0},
{x = 0, y = 0, z = 0},
{x = 1, y = 0, z = 0},
{x = 0, y = 1, z = 0},
{x = 0, y = 0, z = 1},
}
voxeldungeon.utils.NEIGHBORS8 =
{
{x = -1, y = -1, z = 0},
{x = 0, y = -1, z = 0},
{x = 1, y = -1, z = 0},
{x = -1, y = 0, z = 0},
{x = 1, y = 0, z = 0},
{x = -1, y = 1, z = 0},
{x = 0, y = 1, z = 0},
{x = 1, y = 1, z = 0},
}
voxeldungeon.utils.NEIGHBORS9 =
{
{x = -1, y = -1, z = 0},
{x = 0, y = -1, z = 0},
{x = 1, y = -1, z = 0},
{x = -1, y = 0, z = 0},
{x = 0, y = 0, z = 0},
{x = 1, y = 0, z = 0},
{x = -1, y = 1, z = 0},
{x = 0, y = 1, z = 0},
{x = 1, y = 1, z = 0},
}
voxeldungeon.utils.NEIGHBORS26 =
{
{x = -1, y = -1, z = -1},
{x = 0, y = -1, z = -1},
{x = 1, y = -1, z = -1},
{x = -1, y = 0, z = -1},
{x = 0, y = 0, z = -1},
{x = 1, y = 0, z = -1},
{x = -1, y = 1, z = -1},
{x = 0, y = 1, z = -1},
{x = 1, y = 1, z = -1},
{x = -1, y = -1, z = 0},
{x = 0, y = -1, z = 0},
{x = 1, y = -1, z = 0},
{x = -1, y = 0, z = 0},
{x = 1, y = 0, z = 0},
{x = -1, y = 1, z = 0},
{x = 0, y = 1, z = 0},
{x = 1, y = 1, z = 0},
{x = -1, y = -1, z = 1},
{x = 0, y = -1, z = 1},
{x = 1, y = -1, z = 1},
{x = -1, y = 0, z = 1},
{x = 0, y = 0, z = 1},
{x = 1, y = 0, z = 1},
{x = -1, y = 1, z = 1},
{x = 0, y = 1, z = 1},
{x = 1, y = 1, z = 1},
}
voxeldungeon.utils.NEIGHBORS27 =
{
{x = -1, y = -1, z = -1},
{x = 0, y = -1, z = -1},
{x = 1, y = -1, z = -1},
{x = -1, y = 0, z = -1},
{x = 0, y = 0, z = -1},
{x = 1, y = 0, z = -1},
{x = -1, y = 1, z = -1},
{x = 0, y = 1, z = -1},
{x = 1, y = 1, z = -1},
{x = -1, y = -1, z = 0},
{x = 0, y = -1, z = 0},
{x = 1, y = -1, z = 0},
{x = -1, y = 0, z = 0},
{x = 0, y = 0, z = 0},
{x = 1, y = 0, z = 0},
{x = -1, y = 1, z = 0},
{x = 0, y = 1, z = 0},
{x = 1, y = 1, z = 0},
{x = -1, y = -1, z = 1},
{x = 0, y = -1, z = 1},
{x = 1, y = -1, z = 1},
{x = -1, y = 0, z = 1},
{x = 0, y = 0, z = 1},
{x = 1, y = 0, z = 1},
{x = -1, y = 1, z = 1},
{x = 0, y = 1, z = 1},
{x = 1, y = 1, z = 1},
}
voxeldungeon.utils.surface_valid_ground = {"default:dirt_with_grass", "default:dirt_with_coniferous_litter", "default:dirt_with_rainforest_litter",
"default:dirt_with_dry_grass", "default:dirt_with_snow", "default:dry_dirt", "default:dry_dirt_with_dry_grass",
"default:sand", "default:silver_sand", "default:desert_sand", "default:snowblock"}
voxeldungeon.utils.any_valid_ground = {"default:stone", "voxeldungeon:sewerstone", "voxeldungeon:sewerfloor", "voxeldungeon:sewerwall",
"voxeldungeon:prisonstone", "voxeldungeon:prisonfloor", "voxeldungeon:prisonwall",
"voxeldungeon:cavestone", "voxeldungeon:cavefloor", "voxeldungeon:cavewall",
"voxeldungeon:citystone", "voxeldungeon:cityfloor", "voxeldungeon:citywall",
"voxeldungeon:hallstone", "voxeldungeon:hallfloor", "voxeldungeon:hallwall"}
voxeldungeon.utils.sewers_valid_ground = {"default:stone", "voxeldungeon:sewerstone", "voxeldungeon:sewerfloor", "voxeldungeon:sewerwall"}
voxeldungeon.utils.prisons_valid_ground = {"voxeldungeon:prisonstone", "voxeldungeon:prisonfloor", "voxeldungeon:prisonwall"}
voxeldungeon.utils.caves_valid_ground = {"voxeldungeon:cavestone", "voxeldungeon:cavefloor", "voxeldungeon:cavewall"}
voxeldungeon.utils.cities_valid_ground = {"voxeldungeon:citystone", "voxeldungeon:cityfloor", "voxeldungeon:citywall"}
voxeldungeon.utils.halls_valid_ground = {"voxeldungeon:hallstone", "voxeldungeon:hallfloor", "voxeldungeon:hallwall"}
function voxeldungeon.utils.angleBetweenPoints(x1, x2, y1, y2)
local m = (y2 - x2)/(x2 - x1)
local angle = A * (math.atan(m) + math.pi/2)
if x1 > x2 then angle = angle - 180 end
return angle
end
function voxeldungeon.utils.blastwave(pos, dir, level)
local affected = voxeldungeon.utils.getLivingInArea(pos, 1.5, true)
for _, obj in ipairs(affected) do
local force = (level + 5) * 2
local dmg = 2 + level
if vector.distance(pos, obj:get_pos()) >= 0.5 then
dir = vector.direction(pos, obj:get_pos())
force = force / 2
dmg = dmg / 2
end
if obj:is_player() then
obj:add_player_velocity(vector.multiply(vector.normalize(dir), force))
else
obj:set_velocity(vector.multiply(vector.normalize(dir), force))
mobkit.clear_queue_high(obj:get_luaentity())
end
voxeldungeon.mobs.damage(obj, dmg, "poison")
end
voxeldungeon.particles.blastwave(pos)
end
function voxeldungeon.utils.canDig(pos)
local node = minetest.get_node_or_nil(pos)
if node then
local nodedef = minetest.registered_nodes[node.name]
if minetest.get_item_group(node.name, "immortal") == 0 and nodedef.can_dig(pos) then
return true
end
end
end
function voxeldungeon.utils.countTableEntries(table)
local count = 0
for _ in pairs(table) do
count = count + 1
end
return count
end
function voxeldungeon.utils.deepCloneTable(obj)
local clone
if type(obj) == "table" then
clone = {}
for k, v in next, obj, nil do
clone[voxeldungeon.utils.deepCloneTable(k)] = voxeldungeon.utils.deepCloneTable(v)
end
else
clone = obj
end
return clone
end
function voxeldungeon.utils.directLineOfSight(a, b)
local nodes = voxeldungeon.utils.findNodesInLine(a, b)
for i = 1, #nodes do
if voxeldungeon.utils.solid(nodes[i].pos) then
return false
end
end
return true
end
function voxeldungeon.utils.findNodesInLine(a, b)
local steps = vector.distance(a, b)
local nodes = {}
for i = 0, steps do
local c
if steps > 0 then
c = {
x = a.x + (b.x - a.x) * (i / steps),
y = a.y + (b.y - a.y) * (i / steps),
z = a.z + (b.z - a.z) * (i / steps),
}
else
c = a
end
table.insert(nodes, {pos = c, node = minetest.get_node_or_nil(c)})
end
return nodes
end
function voxeldungeon.utils.flammable(pos)
return minetest.get_item_group(minetest.get_node(pos).name, "flammable") > 0
end
function voxeldungeon.utils.gate(min, val, max)
if min and val and val < min then
return min
elseif val and max and val > max then
return max
else
return val
end
end
function voxeldungeon.utils.getChapter(pos)
if not pos or not pos.y then return end
local chapter = math.ceil(pos.y / -300)
return voxeldungeon.utils.gate(1, chapter, 5)
end
function voxeldungeon.utils.getDepth(pos)
if not pos or not pos.y then return end
local depth = math.ceil(pos.y / -60)
return voxeldungeon.utils.gate(0, depth, 25)
end
function voxeldungeon.utils.getLivingInArea(pos, radius, xray)
local players = voxeldungeon.utils.getPlayersInArea(pos, radius, xray)
local result = entitycontrol.getEntitiesInArea("mobs", pos, radius, xray)
for _, p in ipairs(players) do
table.insert(result, p)
end
return result
end
function voxeldungeon.utils.getPlayersInArea(pos, radius, xray)
local players = {}
for _, plr in pairs(minetest.get_connected_players()) do
local plrpos = plr:get_pos()
if plrpos then
local plrpos2 = {x = plrpos.x, y = plrpos.y + 1, z = plrpos.z}
if (vector.distance(pos, plrpos) <= radius or vector.distance(pos, plrpos2) <= radius) and
(xray or voxeldungeon.buffs.get_buff("voxeldungeon:mindvision", plr)
or voxeldungeon.utils.directLineOfSight(pos, plrpos) or voxeldungeon.utils.directLineOfSight(pos, plrpos2)) then
table.insert(players, plr)
end
end
end
return players
end
function voxeldungeon.utils.itemDescription(desc)
local lines = voxeldungeon.utils.splitstring(desc, '\n')
local result = ""
for i = 1, #lines do
if string.len(lines[i] or "") > 40 then
local longline = lines[i]
local words = voxeldungeon.utils.splitstring(longline)
local charcount = 0
for w = 1, #words do
result = result..words[w]..' '
charcount = charcount + string.len(words[w])
if charcount >= 40 or w == #words then
result = result..'\n'
charcount = 0
end
end
else
result = result..(lines[i] or "")..'\n'
end
end
return result.." "
end
function voxeldungeon.utils.itemShortDescription(item)
local level = item:get_meta():get_int("voxeldungeon:level")
local levelKnown = true
if minetest.get_item_group(item:get_name(), "weapon") > 0 then
levelKnown = voxeldungeon.weapons.isLevelKnown(item)
elseif minetest.get_item_group(item:get_name(), "wand") > 0 then
levelKnown = voxeldungeon.wands.isLevelKnown(item)
elseif minetest.get_item_group(item:get_name(), "armor") > 0 then
levelKnown = voxeldungeon.armor.isLevelKnown(item)
end
if minetest.get_item_group(item:get_name(), "upgradable") > 0 then
local desc = minetest.registered_items[item:get_name()].description
local lvlstr = ""
if level > 0 and levelKnown then
lvlstr = " +"..level
end
return desc..lvlstr
else
return voxeldungeon.utils.splitstring(item:get_description(), '\n')[1]
end
end
function voxeldungeon.utils.iteratePlayerInventories(player, func)
local lists = {"main", "craft"}
local inv = player:get_inventory()
for _, list in ipairs(lists) do
for i = 1, inv:get_size(list) do
local itemstack = inv:get_stack(list, i)
itemstack = func(player, inv, itemstack, i)
inv:set_stack(list, i, itemstack)
end
end
end
function voxeldungeon.utils.on_punch_common(defender, attacker, time_from_last_punch, tool_capabilities, dir, damage)
local para = voxeldungeon.buffs.get_buff("voxeldungeon:paralyzed", defender)
local hp
if defender:is_player() then
hp = defender:get_hp()
else
hp = defender:get_luaentity().hp
end
if para and math.random(0, damage) >= math.random(0, hp) then
para.detach()
if defender:is_player() then
voxeldungeon.glog.i("The pain snapped you out of paralysis!", defender)
end
if attacker:is_player() then
local name
if defender:is_player() then
name = defender:get_player_name()
else
name = defender:get_luaentity().description
end
voxeldungeon.glog.i("The pain snapped "..name.." out of paralysis!", attacker)
end
end
end
function voxeldungeon.utils.randomChances(chanceTable)
local sum = voxeldungeon.utils.sumChances(chanceTable)
if sum == 0 then return end
local selection = math.floor(math.random(1, sum))
local current = 0
for obj, chance in pairs(chanceTable) do
current = current + chance
if current >= selection then
return obj
end
end
end
function voxeldungeon.utils.randomDecimal(upper, lower)
upper = upper or 1
lower = lower or 0
if upper < lower then upper, lower = voxeldungeon.utils.swap(upper, lower) end
return lower + (math.random(0, 10000) / 10000) * (upper - lower)
end
function voxeldungeon.utils.randomObject(tbl, remove)
if #tbl > 0 then
if remove then
return table.remove(tbl, math.random(#tbl))
else
return tbl[math.random(#tbl)]
end
end
return nil
end
function voxeldungeon.utils.randomTeleport(obj)
local range = 75
local p = vector.round(obj:get_pos())
local nonsolids = {}
for a = -range, range, 2 do
for b = -range, range, 2 do
local testpos = {x = p.x + a, y = p.y, z = p.z + b}
local testpos2 = {x = p.x + a, y = p.y + 1, z = p.z + b}
local testpos3 = {x = p.x + a, y = p.y + 2, z = p.z + b}
if not voxeldungeon.utils.solid(testpos) and not voxeldungeon.utils.solid(testpos2) and not voxeldungeon.utils.solid(testpos3) then
table.insert(nonsolids, testpos)
end
end
end
if #nonsolids > 0 then
obj:set_pos(voxeldungeon.utils.randomObject(nonsolids))
minetest.sound_play("voxeldungeon_teleport",
{
pos = obj:get_pos(),
gain = 1.0,
max_hear_distance = 32,
})
if obj:is_player() then
voxeldungeon.glog.i("Chose "..minetest.get_node(obj:get_pos()).name)
voxeldungeon.glog.i("In the blink of an eye, you are teleported another location.", obj)
end
elseif obj:is_player() then
voxeldungeon.glog.i("There's no enough space available for a teleportation.", obj)
end
end
function voxeldungeon.utils.randomItem(invref, listname)
listname = listname or "main"
local items = {}
for i = 1, invref:get_size(listname) do
local stack = invref:get_stack(listname, i)
if not stack:is_empty() then
table.insert(items, stack)
end
end
if #items > 0 then
return items[math.random(#items)]
else
return nil
end
end
function voxeldungeon.utils.removeSpaces(str)
local words = voxeldungeon.utils.splitstring(str, ' ')
local mashed = ""
for _, w in ipairs(words) do
mashed = mashed..w
end
return mashed
end
function voxeldungeon.utils.round(num, factor)
if factor then
return voxeldungeon.utils.round(num * factor) / factor
else
return math.floor(num + .5)
end
end
function voxeldungeon.utils.shallowCloneTable(table)
local clone = {}
for k, v in pairs(table) do
clone[k] = v
end
return clone
end
function voxeldungeon.utils.solid(pos)
local node = minetest.get_node(pos)
if not node or node.name == "ignore" then return true end
local nodedef = minetest.registered_nodes[node.name]
return (not nodedef or nodedef.walkable)
end
function voxeldungeon.utils.splitstring(input, sep)
input = input or "!!!NO TEXT FOUND!!!"
sep = sep or "%s"
local t = {}
for str in string.gmatch(input, "([^"..sep.."]+)") do
table.insert(t, str)
end
return t
end
function voxeldungeon.utils.sumChances(chanceTable)
local sum = 0
for _, chance in pairs(chanceTable) do
sum = sum + chance
end
return sum
end
function voxeldungeon.utils.swap(a, b)
return b, a
end
function voxeldungeon.utils.tableContains(table, obj)
for _, v in pairs(table) do
if v == obj then
return true
end
end
return false
end
function voxeldungeon.utils.take_item(player, itemstack)
if not minetest.settings:get_bool("creative_mode") then
itemstack:take_item()
end
return itemstack
end
function voxeldungeon.utils.return_item(player, itemstack)
if not minetest.settings:get_bool("creative_mode") then
player:get_inventory():add_item("main", itemstack)
end
return itemstack
end
function voxeldungeon.utils.tohex(byte)
byte = voxeldungeon.utils.round(voxeldungeon.utils.gate(0, byte, 255))
local hex = string.format("%x", byte)
if string.len(hex) == 1 then
return "0"..hex
else
return hex
end
end
function voxeldungeon.utils.wet(pos)
return minetest.get_item_group(minetest.get_node(pos).name, "water") > 0
end