Initial commit
This commit is contained in:
parent
3e890d901a
commit
6404960949
|
@ -1,2 +0,0 @@
|
||||||
# Auto detect text files and perform LF normalization
|
|
||||||
* text=auto
|
|
10
LICENSE.txt
10
LICENSE.txt
|
@ -1,13 +1,9 @@
|
||||||
|
|
||||||
Copyright (C) 2004 Sam Hocevar <sam@hocevar.net> (original mapgen code, modified by MisterE)
|
Mod by seventeenthShulker
|
||||||
|
|
||||||
Copyright (C) 2022 MisterE (api code)
|
luamap by MisterE
|
||||||
|
|
||||||
Copyright (C) 2022 regulus (some mapgen code)
|
MIT LICENSE
|
||||||
|
|
||||||
Copyright (C) 2022 LMD (some mapgen code)
|
|
||||||
|
|
||||||
MIT LICENSE
|
|
||||||
|
|
||||||
Permission is hereby granted, free of charge, to any person obtaining a copy of
|
Permission is hereby granted, free of charge, to any person obtaining a copy of
|
||||||
this software and associated documentation files (the "Software"), to deal in
|
this software and associated documentation files (the "Software"), to deal in
|
||||||
|
|
301
README.md
301
README.md
|
@ -1,300 +1,5 @@
|
||||||
## Intro and usage
|
## WG Base Test
|
||||||
Luamap is a library for modders to focus on mapgen logic rather than figuruing
|
|
||||||
out how mapgen works. using it is simple: override the luamap.logic function.
|
|
||||||
The goal of the luamap logic function is to return a content id at a point.
|
|
||||||
|
|
||||||
Calling the old logic function before running your own logic allows you to add
|
|
||||||
to other mods that use luamap. If you do not save and call the old luamap
|
|
||||||
function, then your mod will completely override any other mapgens made by other
|
|
||||||
mods using luamap.
|
|
||||||
|
|
||||||
```lua
|
|
||||||
local c_stone = minetest.get_content_id("default:stone")
|
|
||||||
local c_water = minetest.get_content_id("default:water_source")
|
|
||||||
local water_level = 0
|
|
||||||
local old_logic = luamap.logic
|
|
||||||
function luamap.logic(noise_vals,x,y,z,seed,original_content)
|
|
||||||
-- get any terrain defined in another mod
|
|
||||||
local content = old_logic(noise_vals,x,y,z,seed,original_content)
|
|
||||||
-- use our own logic to add to that logic
|
|
||||||
-- make any nodes below sea level water and below stone level stone
|
|
||||||
if y < water_level then
|
|
||||||
content = c_water
|
|
||||||
end
|
|
||||||
if y < -3 then
|
|
||||||
content = c_stone
|
|
||||||
end
|
|
||||||
return content
|
|
||||||
end
|
|
||||||
```
|
|
||||||
|
|
||||||
This would make a flat mapgen covered in a layer of water.
|
|
||||||
|
|
||||||
luamap gives you an easy way to make and use 2d and 3d noises in your mapgen:
|
|
||||||
|
|
||||||
```lua
|
|
||||||
-- 2d noise mapgen
|
|
||||||
luamap.register_noise("terrainmap",{
|
|
||||||
type = "2d",
|
|
||||||
np_vals = {
|
|
||||||
offset = 0,
|
|
||||||
scale = 1,
|
|
||||||
spread = {x=384, y=256, z=384},
|
|
||||||
seed = 5900033,
|
|
||||||
octaves = 5,
|
|
||||||
persist = 0.63,
|
|
||||||
lacunarity = 2.0,
|
|
||||||
flags = ""
|
|
||||||
},
|
|
||||||
ymin = -31000,
|
|
||||||
ymax = 31000,
|
|
||||||
})
|
|
||||||
|
|
||||||
local c_stone = minetest.get_content_id("default:stone")
|
|
||||||
local c_water = minetest.get_content_id("default:water_source")
|
|
||||||
local water_level = 0
|
|
||||||
local old_logic = luamap.logic
|
|
||||||
function luamap.logic(noise_vals,x,y,z,seed,original_content)
|
|
||||||
-- get any terrain defined in another mod
|
|
||||||
local content = old_logic(noise_vals,x,y,z,seed,original_content)
|
|
||||||
|
|
||||||
if y < water_level then
|
|
||||||
content = c_water
|
|
||||||
end
|
|
||||||
if y < noise_vals.terrainmap * 50 then
|
|
||||||
content = c_stone
|
|
||||||
end
|
|
||||||
|
|
||||||
return content
|
|
||||||
end
|
|
||||||
```
|
|
||||||
|
|
||||||
you can also register a 3d noise like so:
|
|
||||||
```lua
|
|
||||||
luamap.register_noise("terrainmap",{
|
|
||||||
type = "3d",
|
|
||||||
np_vals = {
|
|
||||||
offset = 0,
|
|
||||||
scale = 1,
|
|
||||||
spread = {x=384, y=384, z=384},
|
|
||||||
seed = 5900033,
|
|
||||||
octaves = 5,
|
|
||||||
persist = 0.63,
|
|
||||||
lacunarity = 2.0,
|
|
||||||
flags = ""
|
|
||||||
},
|
|
||||||
ymin = -31000,
|
|
||||||
ymax = 31000,
|
|
||||||
})
|
|
||||||
```
|
|
||||||
|
|
||||||
## API
|
|
||||||
|
|
||||||
### luamap.logic
|
|
||||||
```lua
|
|
||||||
luamap.logic(noise_vals,x,y,z,seed,original_content)
|
|
||||||
```
|
|
||||||
|
|
||||||
DESCRIPTION: Override this function to define mapgen logic. Best practice is to
|
|
||||||
save the original function and call it to get a preset content id from any
|
|
||||||
previously-defined mapgens.
|
|
||||||
|
|
||||||
PARAMETERS:
|
|
||||||
|
|
||||||
`noise_vals`
|
|
||||||
|
|
||||||
A table indexed by noise names defined with `luamap.register_noise()`. So, if I
|
|
||||||
have registered 2 noises: `terrain_2d` and `caves_3d` then noisevals might be:
|
|
||||||
|
|
||||||
```lua
|
|
||||||
{
|
|
||||||
["terrain_2d"] = .7,
|
|
||||||
["caves_3d"] = -.9,
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
`x,y,z`
|
|
||||||
|
|
||||||
The coordinates of the position to set the content for
|
|
||||||
|
|
||||||
`seed`
|
|
||||||
|
|
||||||
The seed passed to minetest.register_on_generated()
|
|
||||||
|
|
||||||
`original_content`
|
|
||||||
|
|
||||||
The original content of the mapgen
|
|
||||||
|
|
||||||
RETURNS:
|
|
||||||
|
|
||||||
should return a content id such as that gotten from
|
|
||||||
`minetest.get_content_id("nodename")`
|
|
||||||
|
|
||||||
NOTES:
|
|
||||||
remember that this function is called for every single node ever generated.
|
|
||||||
Keep your calculations fast or the mapgen will slow down.
|
|
||||||
2d noise is much faster than 3d noise.
|
|
||||||
|
|
||||||
### luamap.register_noise
|
|
||||||
|
|
||||||
```lua
|
|
||||||
luamap.register_noise(name,data)
|
|
||||||
```
|
|
||||||
|
|
||||||
DESCRIPTION: creates a mapgen noise that will be calculated and returned in
|
|
||||||
`luamap.logic`
|
|
||||||
|
|
||||||
PARAMETERS:
|
|
||||||
|
|
||||||
`name`
|
|
||||||
|
|
||||||
String, the name of the noise
|
|
||||||
|
|
||||||
`data`
|
|
||||||
|
|
||||||
table, the noise data:
|
|
||||||
|
|
||||||
```lua
|
|
||||||
{
|
|
||||||
type = "2d", -- or "3d"
|
|
||||||
np_vals = { -- noise params, see minetest lua api
|
|
||||||
offset = 0,
|
|
||||||
scale = 1,
|
|
||||||
spread = {x=384, y=256, z=384},
|
|
||||||
seed = 5900033,
|
|
||||||
octaves = 5,
|
|
||||||
persist = 0.63,
|
|
||||||
lacunarity = 2.0,
|
|
||||||
flags = ""
|
|
||||||
},
|
|
||||||
ymin = -31000,
|
|
||||||
ymax = 31000,
|
|
||||||
}
|
|
||||||
```
|
|
||||||
NOTES:
|
|
||||||
`ymin` and `ymax` define a range in which to calculate the noise. This is useful
|
|
||||||
for making realms: a noise might not be needed in a certian y-range. Note that
|
|
||||||
if the position passed to `luamap.logic` is outside of the y-range, then
|
|
||||||
`noise_vals` will not contain that noise, which saves on calculation times. Be
|
|
||||||
sure to set the y-range to completely encompass the range that you expect to
|
|
||||||
find your noises.
|
|
||||||
|
|
||||||
|
|
||||||
### luamap.precalc
|
|
||||||
|
|
||||||
```lua
|
|
||||||
luamap.precalc(data, area, vm, minp, maxp, seed)
|
|
||||||
```
|
|
||||||
|
|
||||||
DESCRIPTION: An overridable function called just before calculating the noise
|
|
||||||
and calling the logic function for each node. Best practice is to save the old
|
|
||||||
function and call it before adding one's own logic, just like with
|
|
||||||
`luamap.logic`
|
|
||||||
|
|
||||||
|
|
||||||
### luamap.postcalc
|
|
||||||
|
|
||||||
```lua
|
|
||||||
luamap.postcalc(data, area, vm, minp, maxp, seed)
|
|
||||||
```
|
|
||||||
|
|
||||||
DESCRIPTION: An overridable function called just before setting the map lighting
|
|
||||||
and data after the noise has been calculated and luamap.logic has been called
|
|
||||||
for each node in the mapblock. It offers compatability with biomegen by Gaël de
|
|
||||||
Sailly, which needs these parameters to function. Best practice is to save the
|
|
||||||
old function and call it before adding one's own logic, just like with
|
|
||||||
`luamap.logic`
|
|
||||||
|
|
||||||
example:
|
|
||||||
```lua
|
|
||||||
local old_postcalc = luamap.precalc
|
|
||||||
function luamap.postcalc(data, area, vm, minp, maxp, seed)
|
|
||||||
old_postcalc(data, area, vm, minp, maxp, seed)
|
|
||||||
biomegen.generate_all(data, area, vm, minp, maxp, seed)
|
|
||||||
end
|
|
||||||
```
|
|
||||||
|
|
||||||
### luamap.set_singlenode
|
|
||||||
|
|
||||||
```lua
|
|
||||||
luamap.set_singlenode()
|
|
||||||
```
|
|
||||||
|
|
||||||
DESCRIPTION: Sets the mapgen to singlenode. Do not call this if you intend to
|
|
||||||
make a realm, only call if you intend to completely replace engine mapgens
|
|
||||||
|
|
||||||
No params, returns nothing.
|
|
||||||
|
|
||||||
## HELPER FUNCTIONS
|
|
||||||
|
|
||||||
### luamap.remap
|
|
||||||
|
|
||||||
```lua
|
|
||||||
luamap.remap(val, min_val, max_val, min_map, max_map)
|
|
||||||
```
|
|
||||||
|
|
||||||
DESCRIPTION: remaps a value between a range of values to a new range of values
|
|
||||||
|
|
||||||
PARAMETERS:
|
|
||||||
|
|
||||||
`val`
|
|
||||||
value to remap
|
|
||||||
`min_val`
|
|
||||||
old range minimum
|
|
||||||
`max_val`
|
|
||||||
old range maximum
|
|
||||||
`min_map`
|
|
||||||
new range min
|
|
||||||
`max_map`
|
|
||||||
new range max
|
|
||||||
|
|
||||||
NOTES:
|
|
||||||
|
|
||||||
Minetest's perlin noises range from -2 to 2. If you want a noise that ranges
|
|
||||||
from 0 to 1, then you can use remap:
|
|
||||||
|
|
||||||
```lua
|
|
||||||
mynoise = noisevals.mynoise
|
|
||||||
mynoise = luamap.remap(mynoise, -2, 2, 0, 1)
|
|
||||||
```
|
|
||||||
### luamap.lerp
|
|
||||||
|
|
||||||
```lua
|
|
||||||
luamap.lerp(var_a, var_b, ratio, power)
|
|
||||||
```
|
|
||||||
DESCRIPTION: a linear interpolation function
|
|
||||||
Interpolates between 2 values
|
|
||||||
|
|
||||||
PARAMETERS:
|
|
||||||
`var_a`
|
|
||||||
first value to interpolate between
|
|
||||||
|
|
||||||
`var_b`
|
|
||||||
second value to interpolate between
|
|
||||||
|
|
||||||
`ratio`
|
|
||||||
number between 0 and 1 interpolation ratio: 0 is 100% var_a, 1 is 100%
|
|
||||||
var_b, values between 0 and 1 return a mix of var_a and var_b
|
|
||||||
|
|
||||||
`power`
|
|
||||||
(optional, default 1) controls the exponent of the power. By default, it
|
|
||||||
is 1, which gives linear interpolation. 3 would give cubic interpolation.
|
|
||||||
Controls the shape of the interpolation curve.
|
|
||||||
|
|
||||||
NOTES:
|
|
||||||
|
|
||||||
useful for mixing two noises. If you use a third noise as the ratio, you can
|
|
||||||
have areas that are mostly controlled by one noise, and other areas controlled
|
|
||||||
by another noise.
|
|
||||||
|
|
||||||
### luamap.coserp
|
|
||||||
|
|
||||||
```lua
|
|
||||||
luamap.coserp(var_a,var_b,ratio)
|
|
||||||
```
|
|
||||||
|
|
||||||
DESCRIPTION:
|
|
||||||
Same as `luamap.lerp` but uses cosine interpolation for a smoother curve.
|
|
||||||
|
|
||||||
|
|
||||||
|
No biomes, just terrain shaping.
|
||||||
|
Uses MisterE's [luamap](https://content.minetest.net/packages/MisterE/mandeland/) as a base.
|
||||||
|
|
||||||
|
|
42
example.lua
42
example.lua
|
@ -1,42 +0,0 @@
|
||||||
-- forces the map into singlenode mode, don't do this if this is just a "realm".
|
|
||||||
luamap.set_singlenode()
|
|
||||||
|
|
||||||
-- creates a terrain noise
|
|
||||||
luamap.register_noise("terrain",{
|
|
||||||
type = "2d",
|
|
||||||
np_vals = {
|
|
||||||
offset = 0,
|
|
||||||
scale = 1,
|
|
||||||
spread = {x=384, y=256, z=384},
|
|
||||||
seed = 5900033,
|
|
||||||
octaves = 5,
|
|
||||||
persist = 0.63,
|
|
||||||
lacunarity = 2.0,
|
|
||||||
flags = ""
|
|
||||||
},
|
|
||||||
|
|
||||||
})
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
local c_stone = minetest.get_content_id("default:stone")
|
|
||||||
local c_water = minetest.get_content_id("default:water_source")
|
|
||||||
|
|
||||||
local water_level = 0
|
|
||||||
|
|
||||||
local old_logic = luamap.logic
|
|
||||||
|
|
||||||
function luamap.logic(noise_vals,x,y,z,seed,original_content)
|
|
||||||
|
|
||||||
-- get any terrain defined in another mod
|
|
||||||
local content = old_logic(noise_vals,x,y,z,seed,original_content)
|
|
||||||
|
|
||||||
if y < water_level then
|
|
||||||
content = c_water
|
|
||||||
end
|
|
||||||
if y < noise_vals.terrain * 50 then
|
|
||||||
content = c_stone
|
|
||||||
end
|
|
||||||
|
|
||||||
return content
|
|
||||||
end
|
|
173
init.lua
173
init.lua
|
@ -1,148 +1,53 @@
|
||||||
luamap = {}
|
-- forces the map into singlenode mode, don't do this if this is just a "realm".
|
||||||
luamap.noises_2d = {}
|
luamap.set_singlenode()
|
||||||
luamap.noises_3d = {}
|
|
||||||
|
|
||||||
|
-- main noise
|
||||||
|
local recip_factors = {1115, 743, 446, 318, 202, 171, 131, 117, 97}
|
||||||
|
|
||||||
function luamap.remap(val, min_val, max_val, min_map, max_map)
|
for i, r_fac in ipairs(recip_factors) do
|
||||||
return (val-min_val)/(max_val-min_val) * (max_map-min_map) + min_map
|
minetest.log("warning", r_fac)
|
||||||
|
luamap.register_noise("terrain_o" .. i, {
|
||||||
|
type = "2d",
|
||||||
|
np_vals = {
|
||||||
|
offset = 0,
|
||||||
|
scale = 1,
|
||||||
|
spread = {x=r_fac, y=r_fac, z=r_fac},
|
||||||
|
seed = math.random(2147483647),
|
||||||
|
octaves = 1,
|
||||||
|
persist = 1,
|
||||||
|
lacunarity = 1.0,
|
||||||
|
flags = ""
|
||||||
|
},
|
||||||
|
ymin = -31000,
|
||||||
|
ymax = 31000,
|
||||||
|
})
|
||||||
end
|
end
|
||||||
|
|
||||||
-- linear interpolation, optional power modifier
|
local c_stone = minetest.get_content_id("default:stone")
|
||||||
function luamap.lerp(var_a, var_b, ratio, power)
|
-- local c_water = minetest.get_content_id("default:water_source")
|
||||||
if ratio > 1 then ratio = 1 end
|
|
||||||
if ratio < 0 then ratio = 0 end
|
|
||||||
power = power or 1
|
|
||||||
return (1-ratio)*(var_a^power) + (ratio*(var_b^power))
|
|
||||||
end
|
|
||||||
|
|
||||||
function luamap.coserp(var_a,var_b,ratio)
|
-- local water_level = 0
|
||||||
if ratio > 1 then ratio = 1 end
|
|
||||||
if ratio < 0 then ratio = 0 end
|
|
||||||
local rat2 = (1-math.cos(ratio*3.14159))/2
|
|
||||||
return (var_a*(1-rat2)+var_b*rat2)
|
|
||||||
end
|
|
||||||
|
|
||||||
function luamap.register_noise(name,data)
|
local old_logic = luamap.logic
|
||||||
if data.type == "2d" then
|
|
||||||
luamap.noises_2d[name] = {}
|
|
||||||
luamap.noises_2d[name].np_vals = data.np_vals
|
|
||||||
luamap.noises_2d[name].nobj = nil
|
|
||||||
luamap.noises_2d[name].ymin = data.ymin or -31000
|
|
||||||
luamap.noises_2d[name].ymax = data.ymax or 31000
|
|
||||||
else -- 3d
|
|
||||||
luamap.noises_3d[name] = {}
|
|
||||||
luamap.noises_3d[name].np_vals = data.np_vals
|
|
||||||
luamap.noises_3d[name].nobj = nil
|
|
||||||
luamap.noises_3d[name].ymin = data.ymin or -31000
|
|
||||||
luamap.noises_3d[name].ymax = data.ymax or 31000
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
local c_air = minetest.get_content_id("air")
|
|
||||||
|
|
||||||
-- override this function
|
|
||||||
function luamap.logic(noise_vals,x,y,z,seed,original_content)
|
function luamap.logic(noise_vals,x,y,z,seed,original_content)
|
||||||
return original_content or c_air
|
-- get any terrain defined in another mod
|
||||||
end
|
local content = old_logic(noise_vals,x,y,z,seed,original_content)
|
||||||
|
|
||||||
-- override this function
|
-- if y < water_level then
|
||||||
function luamap.precalc(data, area, vm, minp, maxp, seed)
|
-- content = c_water
|
||||||
return
|
-- end
|
||||||
end
|
|
||||||
|
|
||||||
-- override this function
|
local terrain_height = 0
|
||||||
function luamap.postcalc(data, area, vm, minp, maxp, seed)
|
for i, r_fac in ipairs(recip_factors) do
|
||||||
return
|
local name = "terrain_o" .. i
|
||||||
end
|
terrain_height = terrain_height + r_fac * noise_vals[name]
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
-- Set mapgen parameters
|
|
||||||
function luamap.set_singlenode()
|
|
||||||
minetest.register_on_mapgen_init(function(mgparams)
|
|
||||||
minetest.set_mapgen_params({mgname="singlenode"})
|
|
||||||
end)
|
|
||||||
end
|
|
||||||
|
|
||||||
local noise_vals = {}
|
|
||||||
|
|
||||||
minetest.register_on_generated(function(minp, maxp, seed)
|
|
||||||
|
|
||||||
-- localize vars
|
|
||||||
local logic = luamap.logic
|
|
||||||
local noises_2d = luamap.noises_2d
|
|
||||||
local noises_3d = luamap.noises_3d
|
|
||||||
|
|
||||||
local vm, emin, emax = minetest.get_mapgen_object("voxelmanip")
|
|
||||||
local area = VoxelArea:new{MinEdge=emin, MaxEdge=emax}
|
|
||||||
|
|
||||||
local data = vm:get_data()
|
|
||||||
local sidelen = maxp.x - minp.x + 1
|
|
||||||
local chulens3d = {x=sidelen, y=sidelen, z=sidelen}
|
|
||||||
local chulens2d = {x=sidelen, y=sidelen, z=1}
|
|
||||||
|
|
||||||
local minpos3d = {x=minp.x, y=minp.y-16, z=minp.z}
|
|
||||||
local minpos2d = {x=minp.x, y=minp.z}
|
|
||||||
|
|
||||||
luamap.precalc(data, area, vm, minp, maxp, seed)
|
|
||||||
|
|
||||||
for name,elements in pairs(noises_2d) do
|
|
||||||
if not(maxp.y <= elements.ymin and minp.y >= elements.ymax) then
|
|
||||||
noises_2d[name].nobj = noises_2d[name].nobj or minetest.get_perlin_map(noises_2d[name].np_vals, chulens2d)
|
|
||||||
noises_2d[name].nvals = noises_2d[name].nobj:get_2d_map_flat(minpos2d)
|
|
||||||
noises_2d[name].use = true
|
|
||||||
else
|
|
||||||
noises_2d[name].use = false
|
|
||||||
end
|
|
||||||
end
|
end
|
||||||
|
|
||||||
for name,elements in pairs(noises_3d) do
|
if y < terrain_height * 0.1 then
|
||||||
if not(maxp.y <= elements.ymin and minp.y >= elements.ymax) then
|
if math.random(10) == 7 then minetest.log("warning", "yes") end
|
||||||
noises_3d[name].nobj = noises_3d[name].nobj or minetest.get_perlin_map(noises_3d[name].np_vals, chulens3d)
|
content = c_stone
|
||||||
noises_3d[name].nvals = noises_3d[name].nobj:get_3d_map_flat(minpos3d)
|
|
||||||
noises_3d[name].use = true
|
|
||||||
else
|
|
||||||
noises_3d[name].use = false
|
|
||||||
end
|
|
||||||
end
|
end
|
||||||
|
|
||||||
local xstride, ystride, zstride = 1,sidelen,sidelen*sidelen
|
return content
|
||||||
|
end
|
||||||
|
|
||||||
local i2d = 1
|
|
||||||
local i3dz = 1
|
|
||||||
|
|
||||||
for z = minp.z, maxp.z do
|
|
||||||
local i3dx=i3dz
|
|
||||||
|
|
||||||
for x = minp.x, maxp.x do
|
|
||||||
|
|
||||||
for name,elements in pairs(noises_2d) do
|
|
||||||
if elements.use then
|
|
||||||
noise_vals[name] = elements.nvals[i2d]
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
local i3dy=i3dx
|
|
||||||
for y = minp.y, maxp.y do
|
|
||||||
local vi = area:index(x, y, z)
|
|
||||||
for name,elements in pairs(noises_3d) do
|
|
||||||
if elements.use then
|
|
||||||
noise_vals[name] = elements.nvals[i3dy]
|
|
||||||
end
|
|
||||||
end
|
|
||||||
data[vi] = logic(noise_vals,x,y,z,seed,data[vi])
|
|
||||||
i3dy = i3dy + ystride
|
|
||||||
end
|
|
||||||
|
|
||||||
i3dx = i3dx + xstride
|
|
||||||
i2d = i2d + 1
|
|
||||||
end
|
|
||||||
i3dz=i3dz+zstride
|
|
||||||
end
|
|
||||||
luamap.postcalc(data, area, vm, minp, maxp, seed)
|
|
||||||
vm:set_data(data)
|
|
||||||
vm:calc_lighting()
|
|
||||||
vm:write_to_map(data)
|
|
||||||
vm:update_liquids()
|
|
||||||
end)
|
|
Loading…
Reference in New Issue