preview generation
This commit is contained in:
parent
8e46bb1fc3
commit
1722e75ee7
@ -95,13 +95,13 @@ minetest.register_tool("building_lib:autoplace", {
|
|||||||
|
|
||||||
local success, building_name, rotation = building_lib.can_autoplace(mapblock_pos1, playername, autoplacer_name)
|
local success, building_name, rotation = building_lib.can_autoplace(mapblock_pos1, playername, autoplacer_name)
|
||||||
if not success then
|
if not success then
|
||||||
building_lib.clear_preview(playername)
|
building_lib.clear_display(playername)
|
||||||
return
|
return
|
||||||
end
|
end
|
||||||
|
|
||||||
local building_def = building_lib.get_building(building_name)
|
local building_def = building_lib.get_building(building_name)
|
||||||
if not building_def then
|
if not building_def then
|
||||||
building_lib.clear_preview(playername)
|
building_lib.clear_display(playername)
|
||||||
return
|
return
|
||||||
end
|
end
|
||||||
|
|
||||||
@ -114,7 +114,7 @@ minetest.register_tool("building_lib:autoplace", {
|
|||||||
color = "#ffff00"
|
color = "#ffff00"
|
||||||
end
|
end
|
||||||
|
|
||||||
building_lib.show_preview(
|
building_lib.show_display(
|
||||||
playername,
|
playername,
|
||||||
"building_lib_autoplace.png",
|
"building_lib_autoplace.png",
|
||||||
color,
|
color,
|
||||||
@ -126,6 +126,6 @@ minetest.register_tool("building_lib:autoplace", {
|
|||||||
end,
|
end,
|
||||||
on_deselect = function(_, player)
|
on_deselect = function(_, player)
|
||||||
local playername = player:get_player_name()
|
local playername = player:get_player_name()
|
||||||
building_lib.clear_preview(playername)
|
building_lib.clear_display(playername)
|
||||||
end
|
end
|
||||||
})
|
})
|
||||||
|
@ -114,7 +114,7 @@ minetest.register_tool("building_lib:place", {
|
|||||||
|
|
||||||
local building_def, mb_pos1, mb_pos2, rotation = building_lib.get_next_buildable_position(player, buildingname)
|
local building_def, mb_pos1, mb_pos2, rotation = building_lib.get_next_buildable_position(player, buildingname)
|
||||||
if building_def then
|
if building_def then
|
||||||
building_lib.show_preview(
|
building_lib.show_display(
|
||||||
playername,
|
playername,
|
||||||
"building_lib_place.png",
|
"building_lib_place.png",
|
||||||
"#00ff00",
|
"#00ff00",
|
||||||
@ -124,11 +124,11 @@ minetest.register_tool("building_lib:place", {
|
|||||||
rotation
|
rotation
|
||||||
)
|
)
|
||||||
else
|
else
|
||||||
building_lib.clear_preview(playername)
|
building_lib.clear_display(playername)
|
||||||
end
|
end
|
||||||
end,
|
end,
|
||||||
on_deselect = function(_, player)
|
on_deselect = function(_, player)
|
||||||
local playername = player:get_player_name()
|
local playername = player:get_player_name()
|
||||||
building_lib.clear_preview(playername)
|
building_lib.clear_display(playername)
|
||||||
end
|
end
|
||||||
})
|
})
|
||||||
|
89
display.lua
Normal file
89
display.lua
Normal file
@ -0,0 +1,89 @@
|
|||||||
|
|
||||||
|
-- playername => key
|
||||||
|
local active_display = {}
|
||||||
|
|
||||||
|
function building_lib.show_display(playername, texture, color, building_def, mapblock_pos1, mapblock_pos2, rotation)
|
||||||
|
texture = texture .. "^[colorize:" .. color
|
||||||
|
|
||||||
|
mapblock_pos2 = mapblock_pos2 or mapblock_pos1
|
||||||
|
local key =
|
||||||
|
minetest.pos_to_string(mapblock_pos1) .. "/" ..
|
||||||
|
minetest.pos_to_string(mapblock_pos2) .. "/" ..
|
||||||
|
texture .. "/" ..
|
||||||
|
rotation
|
||||||
|
|
||||||
|
if active_display[playername] == key then
|
||||||
|
-- already active on the same region
|
||||||
|
return
|
||||||
|
end
|
||||||
|
-- clear previous entities
|
||||||
|
building_lib.clear_display(playername)
|
||||||
|
active_display[playername] = key
|
||||||
|
|
||||||
|
local min, _ = mapblock_lib.get_mapblock_bounds_from_mapblock(mapblock_pos1)
|
||||||
|
|
||||||
|
local size_mapblocks = vector.subtract(vector.add(mapblock_pos2, 1), mapblock_pos1) -- 1 .. n
|
||||||
|
local size = vector.multiply(size_mapblocks, 16) -- 16 .. n
|
||||||
|
local half_size = vector.divide(size, 2) -- 8 .. n
|
||||||
|
|
||||||
|
local origin = vector.add(min, half_size)
|
||||||
|
origin = vector.subtract(origin, 0.5)
|
||||||
|
|
||||||
|
local ent = building_lib.add_cube_entity(origin, key)
|
||||||
|
ent:set_properties({
|
||||||
|
visual_size = size,
|
||||||
|
textures = {
|
||||||
|
texture,
|
||||||
|
texture,
|
||||||
|
texture,
|
||||||
|
texture,
|
||||||
|
texture,
|
||||||
|
texture
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
if building_def and building_def.markers then
|
||||||
|
-- add markers
|
||||||
|
local texture_modifier = "^[colorize:" .. color
|
||||||
|
local unrotated_size = building_lib.get_building_size(building_def, 360 - rotation)
|
||||||
|
|
||||||
|
for _, marker_opts in ipairs(building_def.markers) do
|
||||||
|
local marker = building_lib.create_marker(marker_opts)
|
||||||
|
local center_rel_pos = vector.add(marker.position, 0.5)
|
||||||
|
local rotated_position = mapblock_lib.rotate_pos(center_rel_pos, unrotated_size, rotation)
|
||||||
|
local node_pos = vector.multiply(vector.add(mapblock_pos1, rotated_position), 16)
|
||||||
|
node_pos = vector.subtract(node_pos, 0.5)
|
||||||
|
local z_rotation = marker.rotation.z
|
||||||
|
|
||||||
|
if rotation == 90 then
|
||||||
|
z_rotation = z_rotation - math.pi/2
|
||||||
|
elseif rotation == 180 then
|
||||||
|
z_rotation = z_rotation + math.pi
|
||||||
|
elseif rotation == 270 then
|
||||||
|
z_rotation = z_rotation + math.pi/2
|
||||||
|
end
|
||||||
|
|
||||||
|
ent = building_lib.add_entity(node_pos, key)
|
||||||
|
ent:set_properties({
|
||||||
|
visual_size = marker.size,
|
||||||
|
textures = {marker.texture .. texture_modifier}
|
||||||
|
})
|
||||||
|
ent:set_rotation({
|
||||||
|
x=marker.rotation.x,
|
||||||
|
y=marker.rotation.y,
|
||||||
|
z=z_rotation
|
||||||
|
})
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
function building_lib.clear_display(playername)
|
||||||
|
if active_display[playername] then
|
||||||
|
building_lib.remove_entities(active_display[playername])
|
||||||
|
active_display[playername] = nil
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
minetest.register_on_leaveplayer(function(player)
|
||||||
|
building_lib.clear_display(player:get_player_name())
|
||||||
|
end)
|
8
init.lua
8
init.lua
@ -20,12 +20,13 @@ building_lib = {
|
|||||||
local MP = minetest.get_modpath("building_lib")
|
local MP = minetest.get_modpath("building_lib")
|
||||||
dofile(MP .. "/memoize.lua")
|
dofile(MP .. "/memoize.lua")
|
||||||
dofile(MP .. "/entity.lua")
|
dofile(MP .. "/entity.lua")
|
||||||
dofile(MP .. "/preview.lua")
|
dofile(MP .. "/display.lua")
|
||||||
dofile(MP .. "/api.lua")
|
dofile(MP .. "/api.lua")
|
||||||
dofile(MP .. "/common.lua")
|
dofile(MP .. "/common.lua")
|
||||||
dofile(MP .. "/markers.lua")
|
dofile(MP .. "/markers.lua")
|
||||||
dofile(MP .. "/placements/mapblock_lib.lua")
|
dofile(MP .. "/placements/mapblock_lib.lua")
|
||||||
dofile(MP .. "/placements/dummy.lua")
|
dofile(MP .. "/placements/dummy.lua")
|
||||||
|
dofile(MP .. "/preview.lua")
|
||||||
dofile(MP .. "/conditions.lua")
|
dofile(MP .. "/conditions.lua")
|
||||||
dofile(MP .. "/build.lua")
|
dofile(MP .. "/build.lua")
|
||||||
dofile(MP .. "/build_tool.lua")
|
dofile(MP .. "/build_tool.lua")
|
||||||
@ -39,11 +40,6 @@ dofile(MP .. "/events.lua")
|
|||||||
dofile(MP .. "/hacks.lua")
|
dofile(MP .. "/hacks.lua")
|
||||||
dofile(MP .. "/mapgen.lua")
|
dofile(MP .. "/mapgen.lua")
|
||||||
|
|
||||||
local ie = minetest.request_insecure_environment()
|
|
||||||
if ie and minetest.get_modpath("isogen") then
|
|
||||||
loadfile(MP .. "/previewgen.lua")(ie)
|
|
||||||
end
|
|
||||||
|
|
||||||
if minetest.get_modpath("mtt") and mtt.enabled then
|
if minetest.get_modpath("mtt") and mtt.enabled then
|
||||||
dofile(MP .. "/events.spec.lua")
|
dofile(MP .. "/events.spec.lua")
|
||||||
dofile(MP .. "/conditions.spec.lua")
|
dofile(MP .. "/conditions.spec.lua")
|
||||||
|
188
preview.lua
188
preview.lua
@ -1,89 +1,123 @@
|
|||||||
|
local cube_len = 8
|
||||||
|
|
||||||
-- playername => key
|
-- name -> { png, width, height, timestamp }
|
||||||
local active_preview = {}
|
local previews = {}
|
||||||
|
|
||||||
function building_lib.show_preview(playername, texture, color, building_def, mapblock_pos1, mapblock_pos2, rotation)
|
function building_lib.generate_building_preview(building_def)
|
||||||
texture = texture .. "^[colorize:" .. color
|
local catalog
|
||||||
|
local offset = {x=0, y=0, z=0}
|
||||||
|
local size
|
||||||
|
|
||||||
mapblock_pos2 = mapblock_pos2 or mapblock_pos1
|
if type(building_def.catalog) == "table" then
|
||||||
local key =
|
catalog = mapblock_lib.get_catalog(building_def.catalog.filename)
|
||||||
minetest.pos_to_string(mapblock_pos1) .. "/" ..
|
offset = building_def.catalog.offset or {x=0, y=0, z=0}
|
||||||
minetest.pos_to_string(mapblock_pos2) .. "/" ..
|
size = building_def.catalog.size or {x=1, y=1, z=1}
|
||||||
texture .. "/" ..
|
else
|
||||||
rotation
|
catalog = mapblock_lib.get_catalog(building_def.catalog)
|
||||||
|
size = catalog:get_size()
|
||||||
|
end
|
||||||
|
|
||||||
if active_preview[playername] == key then
|
local mb_pos2 = vector.add(offset, vector.subtract(size, 1))
|
||||||
-- already active on the same region
|
|
||||||
return
|
|
||||||
end
|
|
||||||
-- clear previous entities
|
|
||||||
building_lib.clear_preview(playername)
|
|
||||||
active_preview[playername] = key
|
|
||||||
|
|
||||||
local min, _ = mapblock_lib.get_mapblock_bounds_from_mapblock(mapblock_pos1)
|
local min = mapblock_lib.get_mapblock_bounds_from_mapblock(offset)
|
||||||
|
local _, max = mapblock_lib.get_mapblock_bounds_from_mapblock(mb_pos2)
|
||||||
|
|
||||||
local size_mapblocks = vector.subtract(vector.add(mapblock_pos2, 1), mapblock_pos1) -- 1 .. n
|
local png = isogen.draw(min, max, {
|
||||||
local size = vector.multiply(size_mapblocks, 16) -- 16 .. n
|
cube_len = cube_len,
|
||||||
local half_size = vector.divide(size, 2) -- 8 .. n
|
get_node = function(pos)
|
||||||
|
return catalog:get_node(pos)
|
||||||
|
end
|
||||||
|
})
|
||||||
|
|
||||||
local origin = vector.add(min, half_size)
|
local node_size = vector.add(vector.subtract(max, min), 1)
|
||||||
origin = vector.subtract(origin, 0.5)
|
local width, height = isogen.calculate_image_size(node_size, cube_len)
|
||||||
|
|
||||||
local ent = building_lib.add_cube_entity(origin, key)
|
return {
|
||||||
ent:set_properties({
|
png = minetest.encode_base64(png),
|
||||||
visual_size = size,
|
width = width,
|
||||||
textures = {
|
height = height,
|
||||||
texture,
|
timestamp = os.time()
|
||||||
texture,
|
}
|
||||||
texture,
|
|
||||||
texture,
|
|
||||||
texture,
|
|
||||||
texture
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
if building_def and building_def.markers then
|
|
||||||
-- add markers
|
|
||||||
local texture_modifier = "^[colorize:" .. color
|
|
||||||
local unrotated_size = building_lib.get_building_size(building_def, 360 - rotation)
|
|
||||||
|
|
||||||
for _, marker_opts in ipairs(building_def.markers) do
|
|
||||||
local marker = building_lib.create_marker(marker_opts)
|
|
||||||
local center_rel_pos = vector.add(marker.position, 0.5)
|
|
||||||
local rotated_position = mapblock_lib.rotate_pos(center_rel_pos, unrotated_size, rotation)
|
|
||||||
local node_pos = vector.multiply(vector.add(mapblock_pos1, rotated_position), 16)
|
|
||||||
node_pos = vector.subtract(node_pos, 0.5)
|
|
||||||
local z_rotation = marker.rotation.z
|
|
||||||
|
|
||||||
if rotation == 90 then
|
|
||||||
z_rotation = z_rotation - math.pi/2
|
|
||||||
elseif rotation == 180 then
|
|
||||||
z_rotation = z_rotation + math.pi
|
|
||||||
elseif rotation == 270 then
|
|
||||||
z_rotation = z_rotation + math.pi/2
|
|
||||||
end
|
|
||||||
|
|
||||||
ent = building_lib.add_entity(node_pos, key)
|
|
||||||
ent:set_properties({
|
|
||||||
visual_size = marker.size,
|
|
||||||
textures = {marker.texture .. texture_modifier}
|
|
||||||
})
|
|
||||||
ent:set_rotation({
|
|
||||||
x=marker.rotation.x,
|
|
||||||
y=marker.rotation.y,
|
|
||||||
z=z_rotation
|
|
||||||
})
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
end
|
||||||
|
|
||||||
function building_lib.clear_preview(playername)
|
-- preview file in the world folder
|
||||||
if active_preview[playername] then
|
local preview_filename = minetest.get_worldpath() .. "/building_preview.json"
|
||||||
building_lib.remove_entities(active_preview[playername])
|
|
||||||
active_preview[playername] = nil
|
minetest.register_chatcommand("building_previewgen", {
|
||||||
end
|
params = "[modname]",
|
||||||
|
privs = {
|
||||||
|
mapblock_lib = true
|
||||||
|
},
|
||||||
|
func = function(name, modname)
|
||||||
|
local world_previews = {}
|
||||||
|
|
||||||
|
local f = io.open(preview_filename, "rb")
|
||||||
|
if f then
|
||||||
|
-- read previous previews
|
||||||
|
local json = f:read("*all")
|
||||||
|
f:close()
|
||||||
|
world_previews = minetest.parse_json(json)
|
||||||
|
end
|
||||||
|
|
||||||
|
local buildings = building_lib.get_buildings()
|
||||||
|
local list = {}
|
||||||
|
for _, building in pairs(buildings) do
|
||||||
|
if building.modname == modname and building.placement == "mapblock_lib" then
|
||||||
|
table.insert(list, building)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
if #list == 0 then
|
||||||
|
return false, "no buildings found with given modname"
|
||||||
|
end
|
||||||
|
|
||||||
|
local count = 0
|
||||||
|
local worker
|
||||||
|
worker = function()
|
||||||
|
local building = table.remove(list)
|
||||||
|
if not building then
|
||||||
|
minetest.chat_send_player(name, "Done generating " .. count .. " previews")
|
||||||
|
minetest.safe_file_write(preview_filename, minetest.write_json(world_previews))
|
||||||
|
return
|
||||||
|
end
|
||||||
|
|
||||||
|
minetest.chat_send_player(name, "Generating preview for '" .. building.name .. "'")
|
||||||
|
count = count + 1
|
||||||
|
local data, err = building_lib.generate_building_preview(building)
|
||||||
|
if not data then
|
||||||
|
minetest.chat_send_player(
|
||||||
|
name,
|
||||||
|
"Preview generation failed for building: '" .. building.name .. "', error: " .. err
|
||||||
|
)
|
||||||
|
else
|
||||||
|
world_previews[building.name] = data
|
||||||
|
minetest.after(0, worker)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
minetest.after(0, worker)
|
||||||
|
return true, "Scheduled " .. #list .. " buildings for preview-generation"
|
||||||
|
end
|
||||||
|
})
|
||||||
|
|
||||||
|
-- returns a cached or ad-hoc generated preview table
|
||||||
|
function building_lib.get_building_preview(building_name)
|
||||||
|
if previews[building_name] then
|
||||||
|
return previews[building_name]
|
||||||
|
end
|
||||||
|
|
||||||
|
local building_def = building_lib.get_building(building_name)
|
||||||
|
if not building_def then
|
||||||
|
return false, "building not found: '" .. building_name .. "'"
|
||||||
|
end
|
||||||
|
|
||||||
|
local preview, err = building_lib.generate_building_preview(building_def)
|
||||||
|
if err then
|
||||||
|
return false, "generate preview error: " .. err
|
||||||
|
end
|
||||||
|
|
||||||
|
previews[building_name] = preview
|
||||||
|
return preview
|
||||||
end
|
end
|
||||||
|
|
||||||
minetest.register_on_leaveplayer(function(player)
|
-- TODO: generate all previews on startup
|
||||||
building_lib.clear_preview(player:get_player_name())
|
|
||||||
end)
|
|
@ -1,82 +0,0 @@
|
|||||||
local ie = ...
|
|
||||||
|
|
||||||
local function generate_preview(building_def)
|
|
||||||
local catalog
|
|
||||||
local offset = {x=0, y=0, z=0}
|
|
||||||
local size
|
|
||||||
|
|
||||||
if type(building_def.catalog) == "table" then
|
|
||||||
catalog = mapblock_lib.get_catalog(building_def.catalog.filename)
|
|
||||||
offset = building_def.catalog.offset or {x=0, y=0, z=0}
|
|
||||||
size = building_def.catalog.size or {x=1, y=1, z=1}
|
|
||||||
else
|
|
||||||
catalog = mapblock_lib.get_catalog(building_def.catalog)
|
|
||||||
size = catalog:get_size()
|
|
||||||
end
|
|
||||||
|
|
||||||
local mb_pos2 = vector.add(offset, vector.subtract(size, 1))
|
|
||||||
|
|
||||||
local min = mapblock_lib.get_mapblock_bounds_from_mapblock(offset)
|
|
||||||
local _, max = mapblock_lib.get_mapblock_bounds_from_mapblock(mb_pos2)
|
|
||||||
|
|
||||||
local png = isogen.draw(min, max, {
|
|
||||||
cube_len = 8,
|
|
||||||
get_node = function(pos)
|
|
||||||
return catalog:get_node(pos)
|
|
||||||
end
|
|
||||||
})
|
|
||||||
|
|
||||||
local filename = building_lib.get_preview_filename(building_def)
|
|
||||||
local f = ie.io.open(filename, "wb")
|
|
||||||
if not f then
|
|
||||||
return false, "could not open file: '" .. filename .. "'"
|
|
||||||
end
|
|
||||||
|
|
||||||
f:write(png)
|
|
||||||
f:close()
|
|
||||||
|
|
||||||
return true
|
|
||||||
end
|
|
||||||
|
|
||||||
minetest.register_chatcommand("building_previewgen", {
|
|
||||||
params = "[modname]",
|
|
||||||
privs = {
|
|
||||||
mapblock_lib = true
|
|
||||||
},
|
|
||||||
func = function(name, modname)
|
|
||||||
local buildings = building_lib.get_buildings()
|
|
||||||
local list = {}
|
|
||||||
for _, building in pairs(buildings) do
|
|
||||||
if building.modname == modname and building.placement == "mapblock_lib" then
|
|
||||||
table.insert(list, building)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
if #list == 0 then
|
|
||||||
return false, "no buildings found with given modname"
|
|
||||||
end
|
|
||||||
|
|
||||||
local worker
|
|
||||||
worker = function()
|
|
||||||
local building = table.remove(list)
|
|
||||||
if not building then
|
|
||||||
minetest.chat_send_player(name, "Done generating previews")
|
|
||||||
return
|
|
||||||
end
|
|
||||||
|
|
||||||
minetest.chat_send_player(name, "Generating preview for '" .. building.name .. "'")
|
|
||||||
local success, err = generate_preview(building)
|
|
||||||
if not success then
|
|
||||||
minetest.chat_send_player(
|
|
||||||
name,
|
|
||||||
"Preview generation failed for building: '" .. building.name .. "', error: " .. err
|
|
||||||
)
|
|
||||||
else
|
|
||||||
minetest.after(0, worker)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
minetest.after(0, worker)
|
|
||||||
return true, "Scheduled " .. #list .. " buildings for preview-generation"
|
|
||||||
end
|
|
||||||
})
|
|
@ -5,18 +5,24 @@ minetest.register_tool("building_lib:remove", {
|
|||||||
stack_max = 1,
|
stack_max = 1,
|
||||||
range = 0,
|
range = 0,
|
||||||
on_use = function(_, player)
|
on_use = function(_, player)
|
||||||
|
local playername = player:get_player_name()
|
||||||
|
|
||||||
local _, mb_pos1 = building_lib.get_next_removable_position(player)
|
local _, mb_pos1 = building_lib.get_next_removable_position(player)
|
||||||
|
if not mb_pos1 then
|
||||||
|
minetest.chat_send_player(playername, "nothing to remove found")
|
||||||
|
return
|
||||||
|
end
|
||||||
|
|
||||||
local success, err = building_lib.remove(mb_pos1)
|
local success, err = building_lib.remove(mb_pos1)
|
||||||
if not success then
|
if not success then
|
||||||
minetest.chat_send_player(player:get_player_name(), err)
|
minetest.chat_send_player(playername, err)
|
||||||
end
|
end
|
||||||
end,
|
end,
|
||||||
on_step = function(_, player)
|
on_step = function(_, player)
|
||||||
local playername = player:get_player_name()
|
local playername = player:get_player_name()
|
||||||
local building_def, mb_pos1, mb_pos2, rotation = building_lib.get_next_removable_position(player)
|
local building_def, mb_pos1, mb_pos2, rotation = building_lib.get_next_removable_position(player)
|
||||||
if building_def then
|
if building_def then
|
||||||
building_lib.show_preview(
|
building_lib.show_display(
|
||||||
playername,
|
playername,
|
||||||
"building_lib_remove.png",
|
"building_lib_remove.png",
|
||||||
"#ff0000",
|
"#ff0000",
|
||||||
@ -26,12 +32,12 @@ minetest.register_tool("building_lib:remove", {
|
|||||||
rotation
|
rotation
|
||||||
)
|
)
|
||||||
else
|
else
|
||||||
building_lib.clear_preview(playername)
|
building_lib.clear_display(playername)
|
||||||
end
|
end
|
||||||
end,
|
end,
|
||||||
on_deselect = function(_, player)
|
on_deselect = function(_, player)
|
||||||
local playername = player:get_player_name()
|
local playername = player:get_player_name()
|
||||||
building_lib.clear_preview(playername)
|
building_lib.clear_display(playername)
|
||||||
end
|
end
|
||||||
})
|
})
|
||||||
|
|
||||||
|
Loading…
x
Reference in New Issue
Block a user