2019-03-07 23:22:28 +00:00
local S = minetest.get_translator ( " mcl_portals " )
2017-08-16 22:16:29 +00:00
-- Parameters
local TCAVE = 0.6
local nobj_cave = nil
2017-08-17 13:20:10 +00:00
-- Portal frame sizes
local FRAME_SIZE_X_MIN = 4
local FRAME_SIZE_Y_MIN = 5
2020-01-06 14:10:44 +00:00
local FRAME_SIZE_X_MAX = 4 -- TODO: 23
local FRAME_SIZE_Y_MAX = 5 -- TODO: 23
2017-08-17 13:20:10 +00:00
2018-06-15 17:15:39 +00:00
local TELEPORT_DELAY = 3 -- seconds before teleporting in Nether portal
local TELEPORT_COOLOFF = 4 -- after object was teleported, for this many seconds it won't teleported again
2018-06-15 17:07:20 +00:00
2017-08-21 16:30:37 +00:00
local mg_name = minetest.get_mapgen_setting ( " mg_name " )
2019-02-09 01:42:11 +00:00
local superflat = mg_name == " flat " and minetest.get_mapgen_setting ( " mcl_superflat_classic " ) == " true "
2017-08-21 16:30:37 +00:00
2017-08-16 22:16:29 +00:00
-- 3D noise
local np_cave = {
offset = 0 ,
scale = 1 ,
spread = { x = 384 , y = 128 , z = 384 } ,
seed = 59033 ,
octaves = 5 ,
persist = 0.7
}
2017-08-17 16:14:49 +00:00
-- Table of objects (including players) which recently teleported by a
-- Nether portal. Those objects have a brief cooloff period before they
-- can teleport again. This prevents annoying back-and-forth teleportation.
local portal_cooloff = { }
2017-08-17 01:27:31 +00:00
-- Destroy portal if pos (portal frame or portal node) got destroyed
local destroy_portal = function ( pos )
-- Deactivate Nether portal
local meta = minetest.get_meta ( pos )
2017-08-17 01:43:26 +00:00
local p1 = minetest.string_to_pos ( meta : get_string ( " portal_frame1 " ) )
local p2 = minetest.string_to_pos ( meta : get_string ( " portal_frame2 " ) )
2017-08-17 01:27:31 +00:00
if not p1 or not p2 then
return
end
2017-08-17 13:08:07 +00:00
local counter = 1
2017-08-17 01:27:31 +00:00
local mp1
for x = p1.x , p2.x do
for y = p1.y , p2.y do
for z = p1.z , p2.z do
local p = vector.new ( x , y , z )
local m = minetest.get_meta ( p )
2017-08-17 13:08:07 +00:00
if counter == 2 then
--[[ Only proceed if the second node still has metadata.
( first node is a corner and not needed for the portal )
2017-08-17 01:27:31 +00:00
If it doesn ' t have metadata, another node propably triggred the delection
routine earlier , so we bail out earlier to avoid an infinite cascade
of on_destroy events . ] ]
2017-08-17 01:43:26 +00:00
mp1 = minetest.string_to_pos ( m : get_string ( " portal_frame1 " ) )
2017-08-17 01:27:31 +00:00
if not mp1 then
return
end
end
local nn = minetest.get_node ( p ) . name
if nn == " mcl_core:obsidian " or nn == " mcl_portals:portal " then
-- Remove portal nodes, but not myself
if nn == " mcl_portals:portal " and not vector.equals ( p , pos ) then
minetest.remove_node ( p )
end
-- Clear metadata of portal nodes and the frame
2017-08-17 01:43:26 +00:00
m : set_string ( " portal_frame1 " , " " )
m : set_string ( " portal_frame2 " , " " )
m : set_string ( " portal_target " , " " )
2017-08-17 01:27:31 +00:00
end
2017-08-17 13:08:07 +00:00
counter = counter + 1
2017-08-17 01:27:31 +00:00
end
end
end
end
2017-08-16 22:16:29 +00:00
minetest.register_node ( " mcl_portals:portal " , {
2019-03-07 23:22:28 +00:00
description = S ( " Nether Portal " ) ,
_doc_items_longdesc = S ( " A Nether portal teleports creatures and objects to the hot and dangerous Nether dimension (and back!). Enter at your own risk! " ) ,
_doc_items_usagehelp = S ( " Stand in the portal for a moment to activate the teleportation. Entering a Nether portal for the first time will also create a new portal in the other dimension. If a Nether portal has been built in the Nether, it will lead to the Overworld. A Nether portal is destroyed if the any of the obsidian which surrounds it is destroyed, or if it was caught in an explosion. " ) ,
2017-08-17 16:41:58 +00:00
2017-08-16 22:16:29 +00:00
tiles = {
" blank.png " ,
" blank.png " ,
" blank.png " ,
" blank.png " ,
{
name = " mcl_portals_portal.png " ,
animation = {
type = " vertical_frames " ,
aspect_w = 16 ,
aspect_h = 16 ,
length = 0.5 ,
} ,
} ,
{
name = " mcl_portals_portal.png " ,
animation = {
type = " vertical_frames " ,
aspect_w = 16 ,
aspect_h = 16 ,
length = 0.5 ,
} ,
} ,
} ,
drawtype = " nodebox " ,
paramtype = " light " ,
paramtype2 = " facedir " ,
sunlight_propagates = true ,
use_texture_alpha = true ,
walkable = false ,
diggable = false ,
pointable = false ,
buildable_to = false ,
is_ground_content = false ,
drop = " " ,
light_source = 11 ,
2017-08-21 02:34:50 +00:00
post_effect_color = { a = 180 , r = 51 , g = 7 , b = 89 } ,
2017-08-16 22:16:29 +00:00
alpha = 192 ,
node_box = {
type = " fixed " ,
fixed = {
{ - 0.5 , - 0.5 , - 0.1 , 0.5 , 0.5 , 0.1 } ,
} ,
} ,
2019-12-13 09:20:08 +00:00
groups = { portal = 1 , not_in_creative_inventory = 1 } ,
2017-08-17 01:27:31 +00:00
on_destruct = destroy_portal ,
2017-08-16 22:16:29 +00:00
2017-08-17 01:27:31 +00:00
_mcl_hardness = - 1 ,
_mcl_blast_resistance = 0 ,
} )
2017-08-16 22:16:29 +00:00
-- Functions
--Build arrival portal
2017-09-19 13:08:46 +00:00
local function build_portal ( pos , target )
2017-08-16 22:16:29 +00:00
local p = { x = pos.x - 1 , y = pos.y - 1 , z = pos.z }
local p1 = { x = pos.x - 1 , y = pos.y - 1 , z = pos.z }
local p2 = { x = p1.x + 3 , y = p1.y + 4 , z = p1.z }
2017-08-17 13:20:10 +00:00
for i = 1 , FRAME_SIZE_Y_MIN - 1 do
2017-08-16 22:16:29 +00:00
minetest.set_node ( p , { name = " mcl_core:obsidian " } )
p.y = p.y + 1
end
2017-08-17 13:20:10 +00:00
for i = 1 , FRAME_SIZE_X_MIN - 1 do
2017-08-16 22:16:29 +00:00
minetest.set_node ( p , { name = " mcl_core:obsidian " } )
p.x = p.x + 1
end
2017-08-17 13:20:10 +00:00
for i = 1 , FRAME_SIZE_Y_MIN - 1 do
2017-08-16 22:16:29 +00:00
minetest.set_node ( p , { name = " mcl_core:obsidian " } )
p.y = p.y - 1
end
2017-08-17 13:20:10 +00:00
for i = 1 , FRAME_SIZE_X_MIN - 1 do
2017-08-16 22:16:29 +00:00
minetest.set_node ( p , { name = " mcl_core:obsidian " } )
p.x = p.x - 1
end
for x = p1.x , p2.x do
for y = p1.y , p2.y do
p = { x = x , y = y , z = p1.z }
2017-08-17 13:08:07 +00:00
if not ( ( x == p1.x or x == p2.x ) and ( y == p1.y or y == p2.y ) ) then
if not ( x == p1.x or x == p2.x or y == p1.y or y == p2.y ) then
minetest.set_node ( p , { name = " mcl_portals:portal " , param2 = 0 } )
end
local meta = minetest.get_meta ( p )
meta : set_string ( " portal_frame1 " , minetest.pos_to_string ( p1 ) )
meta : set_string ( " portal_frame2 " , minetest.pos_to_string ( p2 ) )
meta : set_string ( " portal_target " , minetest.pos_to_string ( target ) )
2017-08-16 22:16:29 +00:00
end
2017-09-19 13:08:46 +00:00
if y ~= p1.y then
2017-08-16 22:16:29 +00:00
for z = - 2 , 2 do
if z ~= 0 then
p.z = p.z + z
if minetest.registered_nodes [
minetest.get_node ( p ) . name ] . is_ground_content then
minetest.remove_node ( p )
end
p.z = p.z - z
end
end
end
end
end
2018-06-15 17:07:20 +00:00
minetest.log ( " action " , " [mcl_portal] Destination Nether portal generated at " .. minetest.pos_to_string ( p2 ) .. " ! " )
2017-08-16 22:16:29 +00:00
end
local function find_nether_target_y ( target_x , target_z )
2017-08-21 16:30:37 +00:00
if mg_name == " flat " then
2019-02-09 01:42:11 +00:00
return mcl_vars.mg_flat_nether_floor + 1
2017-08-21 16:30:37 +00:00
end
2017-09-15 20:00:03 +00:00
local start_y = math.random ( mcl_vars.mg_lava_nether_max + 1 , mcl_vars.mg_bedrock_nether_top_min - 5 ) -- Search start
2017-08-16 23:09:32 +00:00
if not nobj_cave then
nobj_cave = minetest.get_perlin ( np_cave )
end
2017-08-16 22:16:29 +00:00
local air = 4
2017-09-15 20:00:03 +00:00
for y = start_y , math.max ( mcl_vars.mg_lava_nether_max + 1 ) , - 1 do
2019-03-06 03:38:57 +00:00
local nval_cave = nobj_cave : get_3d ( { x = target_x , y = y , z = target_z } )
2017-08-16 22:16:29 +00:00
if nval_cave > TCAVE then -- Cavern
air = air + 1
else -- Not cavern, check if 4 nodes of space above
if air >= 4 then
return y + 2
else -- Not enough space, reset air to zero
air = 0
end
end
end
return start_y -- Fallback
end
local function move_check ( p1 , max , dir )
local p = { x = p1.x , y = p1.y , z = p1.z }
2017-08-17 13:08:07 +00:00
local d = math.sign ( max - p1 [ dir ] )
local min = p [ dir ]
for k = min , max , d do
p [ dir ] = k
local node = minetest.get_node ( p )
-- Check for obsidian (except at corners)
if k ~= min and k ~= max and node.name ~= " mcl_core:obsidian " then
2017-08-16 22:16:29 +00:00
return false
end
2017-08-17 02:36:08 +00:00
-- Abort if any of the portal frame blocks already has metadata.
-- This mod does not yet portals which neighbor each other directly.
-- TODO: Reorganize the way how portal frame coordinates are stored.
2017-08-17 13:08:07 +00:00
if node.name == " mcl_core:obsidian " then
local meta = minetest.get_meta ( p )
local pframe1 = meta : get_string ( " portal_frame1 " )
if minetest.string_to_pos ( pframe1 ) ~= nil then
return false
end
2017-08-17 02:36:08 +00:00
end
2017-08-16 22:16:29 +00:00
end
return true
end
local function check_portal ( p1 , p2 )
if p1.x ~= p2.x then
if not move_check ( p1 , p2.x , " x " ) then
return false
end
if not move_check ( p2 , p1.x , " x " ) then
return false
end
elseif p1.z ~= p2.z then
if not move_check ( p1 , p2.z , " z " ) then
return false
end
if not move_check ( p2 , p1.z , " z " ) then
return false
end
else
return false
end
if not move_check ( p1 , p2.y , " y " ) then
return false
end
if not move_check ( p2 , p1.y , " y " ) then
return false
end
return true
end
local function is_portal ( pos )
2017-08-17 13:20:10 +00:00
local xsize , ysize = FRAME_SIZE_X_MIN - 1 , FRAME_SIZE_Y_MIN - 1
2020-01-06 14:10:44 +00:00
for sx = FRAME_SIZE_X_MIN , FRAME_SIZE_X_MAX do
local xsize = sx - 1
for sy = FRAME_SIZE_Y_MIN , FRAME_SIZE_Y_MAX do
local ysize = sy - 1
for d = - xsize , xsize do
for y = - ysize , ysize do
local px = { x = pos.x + d , y = pos.y + y , z = pos.z }
local pz = { x = pos.x , y = pos.y + y , z = pos.z + d }
if check_portal ( px , { x = px.x + xsize , y = px.y + ysize , z = px.z } ) then
return px , { x = px.x + xsize , y = px.y + ysize , z = px.z }
end
if check_portal ( pz , { x = pz.x , y = pz.y + ysize , z = pz.z + xsize } ) then
return pz , { x = pz.x , y = pz.y + ysize , z = pz.z + xsize }
end
end
2017-08-16 22:16:29 +00:00
end
end
end
end
2017-09-19 13:45:23 +00:00
-- Attempts to light a Nether portal at pos and
-- select target position.
-- Pos can be any of the obsidian frame blocks or the inner part.
-- The frame MUST be filled only with air or any fire, which will be replaced with Nether portal blocks.
-- If no Nether portal can be lit, nothing happens.
-- Returns true on success and false on failure.
function mcl_portals . light_nether_portal ( pos )
2017-11-21 01:05:52 +00:00
-- Only allow to make portals in Overworld and Nether
2017-11-24 02:10:02 +00:00
local dim = mcl_worlds.pos_to_dimension ( pos )
2017-11-21 01:05:52 +00:00
if dim ~= " overworld " and dim ~= " nether " then
return false
end
2017-09-19 13:08:46 +00:00
-- Create Nether portal nodes
2017-08-16 22:16:29 +00:00
local p1 , p2 = is_portal ( pos )
if not p1 or not p2 then
return false
end
for d = 1 , 2 do
for y = p1.y + 1 , p2.y - 1 do
local p
if p1.z == p2.z then
p = { x = p1.x + d , y = y , z = p1.z }
else
p = { x = p1.x , y = y , z = p1.z + d }
end
2017-09-19 13:45:23 +00:00
local nn = minetest.get_node ( p ) . name
if nn ~= " air " and minetest.get_item_group ( nn , " fire " ) ~= 1 then
2017-08-16 22:16:29 +00:00
return false
end
end
end
local param2
if p1.z == p2.z then
param2 = 0
else
param2 = 1
end
2017-09-19 13:08:46 +00:00
-- Find target
2017-08-16 22:16:29 +00:00
local target = { x = p1.x , y = p1.y , z = p1.z }
target.x = target.x + 1
2017-08-16 23:58:17 +00:00
if target.y < mcl_vars.mg_nether_max and target.y > mcl_vars.mg_nether_min then
2019-02-09 01:42:11 +00:00
if superflat then
2017-08-21 16:30:37 +00:00
target.y = mcl_vars.mg_bedrock_overworld_max + 5
2019-02-09 01:42:11 +00:00
elseif mg_name == " flat " then
local ground = minetest.get_mapgen_setting ( " mgflat_ground_level " )
ground = tonumber ( ground )
if not ground then
ground = 8
end
target.y = ground + 2
2017-08-21 16:30:37 +00:00
else
target.y = math.random ( mcl_vars.mg_overworld_min + 40 , mcl_vars.mg_overworld_min + 96 )
end
2017-08-16 22:16:29 +00:00
else
target.y = find_nether_target_y ( target.x , target.z )
end
2020-01-06 14:10:44 +00:00
local dmin , ymin , ymax = 0 , p1.y , p2.y
local dmax = math.max ( math.abs ( p1.x - p2.x ) , math.abs ( p1.z - p2.z ) )
2017-08-17 13:08:07 +00:00
for d = dmin , dmax do
for y = ymin , ymax do
if not ( ( d == dmin or d == dmax ) and ( y == ymin or y == ymax ) ) then
local p
2017-08-16 22:16:29 +00:00
if param2 == 0 then
p = { x = p1.x + d , y = y , z = p1.z }
else
p = { x = p1.x , y = y , z = p1.z + d }
end
2017-09-19 13:08:46 +00:00
if d ~= dmin and d ~= dmax and y ~= ymin and y ~= ymax then
minetest.set_node ( p , { name = " mcl_portals:portal " , param2 = param2 } )
end
2017-08-16 22:16:29 +00:00
local meta = minetest.get_meta ( p )
2017-08-17 01:43:26 +00:00
-- Portal frame corners
meta : set_string ( " portal_frame1 " , minetest.pos_to_string ( p1 ) )
meta : set_string ( " portal_frame2 " , minetest.pos_to_string ( p2 ) )
-- Portal target coordinates
meta : set_string ( " portal_target " , minetest.pos_to_string ( target ) )
2017-08-16 22:16:29 +00:00
end
end
2017-08-17 13:08:07 +00:00
end
2017-08-16 22:16:29 +00:00
return true
end
minetest.register_abm ( {
label = " Nether portal teleportation and particles " ,
nodenames = { " mcl_portals:portal " } ,
interval = 1 ,
chance = 2 ,
action = function ( pos , node )
2019-02-01 00:21:08 +00:00
minetest.add_particlespawner ( {
amount = 32 ,
time = 4 ,
minpos = { x = pos.x - 0.25 , y = pos.y - 0.25 , z = pos.z - 0.25 } ,
maxpos = { x = pos.x + 0.25 , y = pos.y + 0.25 , z = pos.z + 0.25 } ,
minvel = { x = - 0.8 , y = - 0.8 , z = - 0.8 } ,
maxvel = { x = 0.8 , y = 0.8 , z = 0.8 } ,
minacc = { x = 0 , y = 0 , z = 0 } ,
maxacc = { x = 0 , y = 0 , z = 0 } ,
minexptime = 0.5 ,
maxexptime = 1 ,
minsize = 1 ,
maxsize = 2 ,
collisiondetection = false ,
texture = " mcl_particles_teleport.png " ,
} )
2017-08-16 22:16:29 +00:00
for _ , obj in ipairs ( minetest.get_objects_inside_radius ( pos , 1 ) ) do --maikerumine added for objects to travel
local lua_entity = obj : get_luaentity ( ) --maikerumine added for objects to travel
if obj : is_player ( ) or lua_entity then
2017-08-17 16:14:49 +00:00
-- Prevent quick back-and-forth teleportation
if portal_cooloff [ obj ] then
return
end
2017-08-16 22:16:29 +00:00
local meta = minetest.get_meta ( pos )
2017-08-17 01:43:26 +00:00
local target = minetest.string_to_pos ( meta : get_string ( " portal_target " ) )
2017-08-16 22:16:29 +00:00
if target then
-- force emerge of target area
minetest.get_voxel_manip ( ) : read_from_map ( target , target )
if not minetest.get_node_or_nil ( target ) then
2018-06-15 17:07:20 +00:00
minetest.emerge_area ( vector.subtract ( target , 4 ) , vector.add ( target , 4 ) )
2017-08-16 22:16:29 +00:00
end
2018-06-15 17:07:20 +00:00
-- teleport function
local teleport = function ( obj , pos , target )
2018-06-15 16:26:59 +00:00
if ( not obj : get_luaentity ( ) ) and ( not obj : is_player ( ) ) then
2018-06-03 14:44:37 +00:00
return
end
2017-08-17 16:14:49 +00:00
-- Prevent quick back-and-forth teleportation
if portal_cooloff [ obj ] then
return
end
2019-02-01 05:33:07 +00:00
local objpos = obj : get_pos ( )
2017-08-17 02:21:59 +00:00
if objpos == nil then
return
end
-- If player stands, player is at ca. something+0.5
-- which might cause precision problems, so we used ceil.
objpos.y = math.ceil ( objpos.y )
2017-08-16 22:16:29 +00:00
if minetest.get_node ( objpos ) . name ~= " mcl_portals:portal " then
return
end
2017-08-16 23:01:09 +00:00
-- Teleport
2017-11-24 02:10:02 +00:00
obj : set_pos ( target )
2017-11-21 05:03:07 +00:00
if obj : is_player ( ) then
2018-01-26 18:37:00 +00:00
mcl_worlds.dimension_change ( obj , mcl_worlds.pos_to_dimension ( target ) )
2017-11-21 05:03:07 +00:00
minetest.sound_play ( " mcl_portals_teleport " , { pos = target , gain = 0.5 , max_hear_distance = 16 } )
end
2017-08-16 23:01:09 +00:00
2018-06-15 17:15:39 +00:00
-- Enable teleportation cooloff for some seconds, to prevent back-and-forth teleportation
2017-08-17 16:14:49 +00:00
portal_cooloff [ obj ] = true
2018-06-15 17:15:39 +00:00
minetest.after ( TELEPORT_COOLOFF , function ( o )
2017-08-17 16:14:49 +00:00
portal_cooloff [ o ] = false
end , obj )
2018-06-15 17:07:20 +00:00
if obj : is_player ( ) then
local name = obj : get_player_name ( )
minetest.log ( " action " , " [mcl_portal] " .. name .. " teleported to Nether portal at " .. minetest.pos_to_string ( target ) .. " . " )
end
end
local n = minetest.get_node_or_nil ( target )
if n and n.name ~= " mcl_portals:portal " then
-- Emerge target area, wait for emerging to be finished, build destination portal
-- (if there isn't already one, teleport object after a short delay.
local emerge_callback = function ( blockpos , action , calls_remaining , param )
minetest.log ( " verbose " , " [mcl_portal] emerge_callack called! action= " .. action )
2018-06-15 17:15:39 +00:00
if calls_remaining <= 0 and action ~= minetest.EMERGE_CANCELLED and action ~= minetest.EMERGE_ERRORED then
2018-06-15 17:07:20 +00:00
minetest.log ( " verbose " , " [mcl_portal] Area for destination Nether portal emerged! " )
build_portal ( param.target , param.pos , false )
2018-06-15 17:15:39 +00:00
minetest.after ( TELEPORT_DELAY , teleport , obj , pos , target )
2018-06-15 17:07:20 +00:00
end
end
minetest.log ( " verbose " , " [mcl_portal] Emerging area for destination Nether portal ... " )
minetest.emerge_area ( vector.subtract ( target , 7 ) , vector.add ( target , 7 ) , emerge_callback , { pos = pos , target = target } )
else
2018-06-15 17:15:39 +00:00
minetest.after ( TELEPORT_DELAY , teleport , obj , pos , target )
2018-06-15 17:07:20 +00:00
end
2017-08-16 23:01:09 +00:00
2017-08-16 22:16:29 +00:00
end
end
end
end ,
} )
--[[ ITEM OVERRIDES ]]
2017-08-17 17:05:13 +00:00
local longdesc = minetest.registered_nodes [ " mcl_core:obsidian " ] . _doc_items_longdesc
2019-03-07 23:22:28 +00:00
longdesc = longdesc .. " \n " .. S ( " Obsidian is also used as the frame of Nether portals. " )
local usagehelp = S ( " To open a Nether portal, place an upright frame of obsidian with a width of 4 blocks and a height of 5 blocks, leaving only air in the center. After placing this frame, light a fire in the obsidian frame. Nether portals only work in the Overworld and the Nether. " )
2017-08-17 17:05:13 +00:00
2017-08-16 22:16:29 +00:00
minetest.override_item ( " mcl_core:obsidian " , {
2017-08-17 17:05:13 +00:00
_doc_items_longdesc = longdesc ,
_doc_items_usagehelp = usagehelp ,
2017-08-17 01:27:31 +00:00
on_destruct = destroy_portal ,
2017-08-17 02:12:34 +00:00
_on_ignite = function ( user , pointed_thing )
local pos = pointed_thing.under
2017-09-19 13:45:23 +00:00
local portal_placed = mcl_portals.light_nether_portal ( pos )
2018-06-15 17:07:20 +00:00
if portal_placed then
minetest.log ( " action " , " [mcl_portal] Nether portal activated at " .. minetest.pos_to_string ( pos ) .. " . " )
end
2017-08-17 02:12:34 +00:00
if portal_placed and minetest.get_modpath ( " doc " ) then
doc.mark_entry_as_revealed ( user : get_player_name ( ) , " nodes " , " mcl_portals:portal " )
2017-09-15 16:03:37 +00:00
-- Achievement for finishing a Nether portal TO the Nether
2017-11-24 02:10:02 +00:00
local dim = mcl_worlds.pos_to_dimension ( pos )
2017-09-15 16:03:37 +00:00
if minetest.get_modpath ( " awards " ) and dim ~= " nether " and user : is_player ( ) then
awards.unlock ( user : get_player_name ( ) , " mcl:buildNetherPortal " )
end
2017-09-19 13:45:23 +00:00
return true
2017-08-16 22:16:29 +00:00
else
2017-09-19 13:45:23 +00:00
return false
2017-08-16 22:16:29 +00:00
end
end ,
} )