コード例 #1
0
 /// <summary>
 /// Initializes a new instance of the <see cref="CubicPathfinding"/> class.
 /// </summary>
 /// <param name="terrain">Terrain.</param>
 private CubicPathfinding(CubicTerrain terrain)
 {
     this.terrain      = terrain;
     this.workerThread = new Thread(this.WorkerThread);
     this.workerThread.Start();
     this.pathQueue = new Queue <CubicPath> ();
 }
コード例 #2
0
ファイル: CubicPathfinding.cs プロジェクト: kennux/cubicworld
 /// <summary>
 /// Initializes a new instance of the <see cref="CubicPathfinding"/> class.
 /// </summary>
 /// <param name="terrain">Terrain.</param>
 private CubicPathfinding(CubicTerrain terrain)
 {
     this.terrain = terrain;
     this.workerThread = new Thread (this.WorkerThread);
     this.workerThread.Start ();
     this.pathQueue = new Queue<CubicPath> ();
 }
コード例 #3
0
 public static CubicPathfinding GetInstance()
 {
     if (instance == null)
     {
         instance = new CubicPathfinding(CubicTerrain.GetInstance());
     }
     return(instance);
 }
コード例 #4
0
ファイル: BlockRemoval.cs プロジェクト: wty0512/cubicworld
    public void Update()
    {
        // Right mouse button.
        if (Input.GetMouseButtonDown(1))
        {
            // The unity physics way

            if (CubicTerrain.GetInstance().useMeshColliders)
            {
                // Get ready to perform the raycast.
                RaycastHit hitInfo   = new RaycastHit();
                Ray        cameraRay = this.playerCamera.GetComponent <Camera>().ScreenPointToRay(new Vector3(Screen.width / 2, Screen.height / 2));
                Debug.DrawRay(cameraRay.origin, cameraRay.direction, Color.red, 100.0f);

                // Perform the raycast
                if (Physics.Raycast(cameraRay, out hitInfo, 50, this.detectionMask.value))
                {
                    if (hitInfo.collider == null)
                    {
                        return;
                    }

                    // get collider parent
                    Transform chunkTransform = hitInfo.collider.transform.parent;

                    if (chunkTransform != null)
                    {
                        // Chunk hit?
                        CubicTerrainChunk chunk = chunkTransform.GetComponent <CubicTerrainChunk>();
                        if (chunk != null && !chunk.isDirty)
                        {
                            // Yes, chunk hit!
                            // Delete the clicked block
                            Vector3 block = chunk.GetBlockPosition(hitInfo, -0.5f);

                            chunk.chunkData.SetVoxel((int)block.x, (int)block.y, (int)block.z, -1);
                        }
                    }
                }
            }
            else
            {
                // Cubic World Physics way
                CubicRaycastHitInfo hitInfo = new CubicRaycastHitInfo();
                if (CubicPhysics.TerrainRaycastUnprecise(this.playerCamera.GetComponent <Camera>().ScreenPointToRay(new Vector3(Screen.width / 2, Screen.height / 2)), 5.0f, out hitInfo))
                {
                    // Debug.Log ("Hit: " + hitInfo.hitPoint + ", Block: " + hitInfo.blockHit + ", Face: " + hitInfo.faceHit);
                    // Hit block
                    CubicTerrain.GetInstance().SetBlock((int)hitInfo.blockHit.x, (int)hitInfo.blockHit.y, (int)hitInfo.blockHit.z, -1);
                }
            }
        }
    }
コード例 #5
0
    /// <summary>
    /// Gets the chunk data.
    /// </summary>
    /// <returns>The chunk data.</returns>
    /// <param name="x">The x coordinate.</param>
    /// <param name="z">The z coordinate.</param>
    /// <param name="width">Width.</param>
    /// <param name="height">Height.</param>
    /// <param name="depth">Depth.</param>
    /// <param name="chunkOwner">The owner of the chunk you intend to load</param>
    public CubicTerrainData GetChunkData(CubicTerrain chunkOwner, int x, int y, int z, int width, int height, int depth)
    {
        BufferedStream   chunkDataStream = new BufferedStream(File.Open(this.chunkDataFile, FileMode.Open));
        CubicTerrainData terrainData     = new CubicTerrainData(chunkOwner, width, height, depth);

        // Get chunk starting position
        chunkDataStream.Position = this.chunkLookupTable [new ListIndex <int>(x, y, z)];

        terrainData.DeserializeChunk(chunkDataStream);
        chunkDataStream.Close();

        return(terrainData);
    }
コード例 #6
0
    /// <summary>
    /// Gets the chunk data.
    /// </summary>
    /// <returns>The chunk data.</returns>
    /// <param name="x">The x coordinate.</param>
    /// <param name="z">The z coordinate.</param>
    /// <param name="width">Width.</param>
    /// <param name="height">Height.</param>
    /// <param name="depth">Depth.</param>
    /// <param name="chunkOwner">The owner of the chunk you intend to load</param>
    public CubicTerrainData GetChunkData(CubicTerrain chunkOwner, int x, int y, int z, int width, int height, int depth)
    {
        BufferedStream chunkDataStream = new BufferedStream (File.Open (this.chunkDataFile, FileMode.Open));
        CubicTerrainData terrainData = new CubicTerrainData (chunkOwner, width, height, depth);

        // Get chunk starting position
        chunkDataStream.Position = this.chunkLookupTable [new ListIndex<int>(x,y,z)];

        terrainData.DeserializeChunk (chunkDataStream);
        chunkDataStream.Close ();

        return terrainData;
    }
コード例 #7
0
    /// <summary>
    /// Initializes the chunk file, chunk generation thread and initializes all needed variables.
    /// </summary>
    public void Start()
    {
        // Make sure we are at 0|0|0
        this.transform.position = new Vector3(0, 0, 0);

        // Singleton
        if (instance != null)
        {
            Debug.LogError("Multiple CubicTerrain Script GameObject detected! Error! Disabling this instance.");
            this.enabled = false;
            return;
        }
        instance = this;

        // Terrain stream?
        if (this.serializeTerrain)
        {
            this.terrainFile = new CubicTerrainFile(this.chunkFilesPath + "table.clt", this.chunkFilesPath + "data.cfd");
        }

        // Initialize lists
        this.chunkObjects   = new List3D <GameObject> ();
        this.chunkData      = new List3D <CubicTerrainData> ();
        this.generationJobs = new List3D <ChunkGenerationJob> ();

        this.terrainGenerator      = this.GetComponent <ATerrainGenerator> ();
        this.chunkGenerationThread = new Thread(this.ChunkGenerationThread);
        this.chunkGenerationThread.Start();

        // Init
        this.terrainMaterial.SetTexture("_MainTex", Blocks.textureAtlas);
        this.transparentTerrainMaterial.SetTexture("_MainTex", Blocks.textureAtlas);

        if (!this.loadPlayerChunkFirst)
        {
            return;
        }

        Vector3 chunkPosition = this.GetChunkPosition(this.playerTransform.position);

        this.GenerateChunk((int)chunkPosition.x, (int)chunkPosition.z);
        this.transformPosition = this.transform.position;
    }
コード例 #8
0
    /// <summary>
    /// Initializes a new instance of the <see cref="CubicTerrainData"/> class.
    /// </summary>
    /// <param name="width">Width.</param>
    /// <param name="height">Height.</param>
    /// <param name="depth">Depth.</param>
    public CubicTerrainData(CubicTerrain owner, int width, int height, int depth)
    {
        // Set owner reference
        this.owner = owner;

        // Save dimensions
        this._width = width;
        this._height = height;
        this._depth = depth;

        // Initialize voxel data array
        this._voxelData = new VoxelData[width][][];
        for (int i = 0; i < width; i++)
        {
            this._voxelData[i] = new VoxelData[height][];
            for (int j = 0; j < height; j++)
            {
                this._voxelData[i][j] = new VoxelData[depth];
            }
        }
    }
コード例 #9
0
    /// <summary>
    /// Initializes a new instance of the <see cref="CubicTerrainData"/> class.
    /// </summary>
    /// <param name="width">Width.</param>
    /// <param name="height">Height.</param>
    /// <param name="depth">Depth.</param>
    public CubicTerrainData(CubicTerrain owner, int width, int height, int depth)
    {
        // Set owner reference
        this.owner = owner;

        // Save dimensions
        this._width  = width;
        this._height = height;
        this._depth  = depth;

        // Initialize voxel data array
        this._voxelData = new VoxelData[width][][];
        for (int i = 0; i < width; i++)
        {
            this._voxelData[i] = new VoxelData[height][];
            for (int j = 0; j < height; j++)
            {
                this._voxelData[i][j] = new VoxelData[depth];
            }
        }
    }
コード例 #10
0
ファイル: CubicPhysics.cs プロジェクト: wty0512/cubicworld
    /// <summary>
    /// Performs a raycast with the current terrain data.
    /// Raycasts are performed very simple, this function will move along your ray by adding direction*stepWidth every step.
    /// In every step it will check if it hit a block and if true it will stop and return the data.
    ///
    /// TODO: Implement a better way (more precise!) for raycasting
    /// </summary>
    /// <returns><c>true</c>, if the raycast hit a block, <c>false</c> otherwise.</returns>
    /// <param name="worldspaceStartpoint">Worldspace startpoint.</param>
    /// <param name="direction">Direction.</param>
    /// <param name="stepWidth">The step width you want to use for raycasting. the lower this is the more precise the result and slower the calculation will be.</param>
    /// <param name="length">The length of the ray.</param>
    /// <param name="hitInfo">The raycast hit info.</param>
    /// <param name="calculateHitFace">hitFace will only get set if this is true.</param>
    public static bool TerrainRaycast(Vector3 worldspaceStartpoint, Vector3 direction, float stepWidth, float length, out CubicRaycastHitInfo hitInfo, bool calculateHitFace)
    {
        // Initialize
        bool    blockHit         = false;
        float   distanceTraveled = 0;
        Vector3 currentPos       = worldspaceStartpoint;

        hitInfo = new CubicRaycastHitInfo();
        Vector3 blockPos = Vector3.zero;

        CubicTerrain terrain = CubicTerrain.GetInstance();

        // Search for blocks on the ray
        while (!blockHit && distanceTraveled < length)
        {
            // Calculate current step data
            currentPos       += direction * stepWidth;
            distanceTraveled += stepWidth;
            blockPos          = terrain.GetBlockPosition(currentPos);

            // Check if there is a block at this position
            if (terrain.HasBlock(blockPos))
            {
                blockHit         = true;
                hitInfo.blockHit = blockPos;
                hitInfo.hitPoint = currentPos;

                if (calculateHitFace)
                {
                    // Get hit face

                    /*LEFT = 0,
                     * RIGHT = 1,
                     * TOP = 2,
                     * BOTTOM = 3,
                     * FRONT = 4,
                     * BACK = 5*/

                    // Back and forward flipped
                    Vector3[] faceNormals = new Vector3[]
                    {
                        Vector3.left,
                        Vector3.right,
                        Vector3.up,
                        Vector3.down,
                        Vector3.forward,
                        Vector3.back
                    };

                    // Get block center position
                    Vector3 blockCenterPosition = blockPos + new Vector3(0.5f, 0.5f, 0.5f);

                    // Get shortest distance face.
                    float shortestDistance = stepWidth + 10;
                    int   shortestFace     = -1;

                    for (int i = 0; i < faceNormals.Length; i++)
                    {
                        // Get distance from hit point to the current normal + blockcenter
                        Vector3 blockNormalPosition = blockCenterPosition + faceNormals[i];
                        float   distance            = Vector3.Distance(currentPos, blockNormalPosition);

                        if (shortestDistance > distance)
                        {
                            shortestDistance = distance;
                            shortestFace     = i;
                        }
                    }

                    // Get face
                    hitInfo.faceHit = (BlockFace)shortestFace;
                }
            }
        }

        return(blockHit);
    }
コード例 #11
0
ファイル: BlockAdder.cs プロジェクト: wty0512/cubicworld
    public void Update()
    {
        // Right mouse button.
        if (Input.GetMouseButtonDown(0))
        {
            // Unity physics
            if (CubicTerrain.GetInstance().useMeshColliders)
            {
                // Get ready to perform the raycast.
                RaycastHit hitInfo   = new RaycastHit();
                Ray        cameraRay = this.playerCamera.GetComponent <Camera>().ScreenPointToRay(new Vector3(Screen.width / 2, Screen.height / 2));
                Debug.DrawRay(cameraRay.origin, cameraRay.direction, Color.red, 100.0f);

                // Perform the raycast
                if (Physics.Raycast(cameraRay, out hitInfo, 5, this.detectionMask.value))
                {
                    if (hitInfo.collider == null)
                    {
                        return;
                    }

                    // get collider parent
                    Transform chunkTransform = hitInfo.collider.transform.parent;

                    if (chunkTransform != null)
                    {
                        // Chunk hit?
                        CubicTerrainChunk chunk = chunkTransform.GetComponent <CubicTerrainChunk>();
                        if (chunk != null && !chunk.isDirty)
                        {
                            // Yes, chunk hit!
                            BlockHitInfo blockHitInfo = chunk.GetBlockHitInfo(hitInfo);
                            int          x            = (int)blockHitInfo.hitBlock.x;
                            int          y            = (int)blockHitInfo.hitBlock.y;
                            int          z            = (int)blockHitInfo.hitBlock.z;

                            // Which face was hit? calculate target position for the new block
                            switch (blockHitInfo.hitFace)
                            {
                            case BlockFace.LEFT:
                                x -= 1;
                                break;

                            case BlockFace.RIGHT:
                                x += 1;
                                break;

                            case BlockFace.TOP:
                                y += 1;
                                break;

                            case BlockFace.BOTTOM:
                                y -= 1;
                                break;

                            case BlockFace.FRONT:
                                z += 1;
                                break;

                            case BlockFace.BACK:
                                z -= 1;
                                break;
                            }

                            Vector3 chunkPos = chunk.chunkPosition;

                            // Get chunk we want to place the block on
                            if (x < 0)
                            {
                                chunkPos.x -= 1;
                                x           = chunk.master.chunkWidth - 1;
                            }
                            if (x >= chunk.master.chunkWidth)
                            {
                                chunkPos.x += 1;
                                x           = 0;
                            }
                            if (y < 0)
                            {
                                chunkPos.y -= 1;
                                y           = chunk.master.chunkDepth - 1;
                            }
                            if (z >= chunk.master.chunkHeight)
                            {
                                chunkPos.y += 1;
                                y           = 0;
                            }
                            if (z < 0)
                            {
                                chunkPos.z -= 1;
                                z           = chunk.master.chunkDepth - 1;
                            }
                            if (z >= chunk.master.chunkDepth)
                            {
                                chunkPos.z += 1;
                                z           = 0;
                            }

                            // Finally place the object
                            GameObject chunkObject = chunk.master.GetChunkObject((int)chunkPos.x, (int)chunkPos.y, (int)chunkPos.z);
                            chunkObject.GetComponent <CubicTerrainChunk>().chunkData.SetVoxel(x, y, z, (short)this.blockId);
                        }
                    }
                }
            }
            else
            {
                // Cubic physics
                CubicRaycastHitInfo hitInfo = new CubicRaycastHitInfo();
                if (CubicPhysics.TerrainRaycastPrecise(this.playerCamera.GetComponent <Camera>().ScreenPointToRay(new Vector3(Screen.width / 2, Screen.height / 2)), 5.0f, out hitInfo))
                {
                    Debug.Log("Hit: " + hitInfo.hitPoint + ", Block: " + hitInfo.blockHit + ", Face: " + hitInfo.faceHit);

                    // Get block to place position
                    int x = (int)hitInfo.blockHit.x;
                    int y = (int)hitInfo.blockHit.y;
                    int z = (int)hitInfo.blockHit.z;

                    // Which face was hit? calculate target position for the new block
                    switch (hitInfo.faceHit)
                    {
                    case BlockFace.LEFT:
                        x -= 1;
                        break;

                    case BlockFace.RIGHT:
                        x += 1;
                        break;

                    case BlockFace.TOP:
                        y += 1;
                        break;

                    case BlockFace.BOTTOM:
                        y -= 1;
                        break;

                    case BlockFace.FRONT:
                        z += 1;
                        break;

                    case BlockFace.BACK:
                        z -= 1;
                        break;
                    }

                    CubicTerrain.GetInstance().SetBlock(x, y, z, (short)this.blockId);
                }
            }
        }
    }
コード例 #12
0
ファイル: CubicTerrain.cs プロジェクト: geekytime/cubicworld
    /// <summary>
    /// Initializes the chunk file, chunk generation thread and initializes all needed variables.
    /// </summary>
    public void Start()
    {
        // Make sure we are at 0|0|0
        this.transform.position = new Vector3 (0, 0, 0);

        // Singleton
        if (instance != null)
        {
            Debug.LogError ("Multiple CubicTerrain Script GameObject detected! Error! Disabling this instance.");
            this.enabled = false;
            return;
        }
        instance = this;

        // Terrain stream?
        if (this.serializeTerrain)
            this.terrainFile = new CubicTerrainFile(this.chunkFilesPath+"table.clt", this.chunkFilesPath+"data.cfd");

        // Initialize lists
        this.chunkObjects = new List3D<GameObject> ();
        this.chunkData = new List3D<CubicTerrainData> ();
        this.generationJobs = new List3D<ChunkGenerationJob> ();

        this.terrainGenerator = this.GetComponent<ATerrainGenerator> ();
        this.chunkGenerationThread = new Thread (this.ChunkGenerationThread);
        this.chunkGenerationThread.Start ();

        // Init
        this.terrainMaterial.SetTexture ("_MainTex", Blocks.textureAtlas);
        this.transparentTerrainMaterial.SetTexture ("_MainTex", Blocks.textureAtlas);

        if (!this.loadPlayerChunkFirst)
            return;

        Vector3 chunkPosition = this.GetChunkPosition(this.playerTransform.position);
        this.GenerateChunk((int)chunkPosition.x,(int)chunkPosition.z);
        this.transformPosition = this.transform.position;
    }
コード例 #13
0
    public void FixedUpdate()
    {
        CubicTerrain terrainInstance = CubicTerrain.GetInstance();

        // Initialize
        Vector3 currentPosition = this.transform.position;
        Vector3 newPosition     = this.transform.position;

        // Add gravity to current velocity
        this.velocity += this.gravity * Time.deltaTime;

        // Move based on velocity
        newPosition += this.velocity * Time.deltaTime;
        Vector3 realNewPosition = newPosition;

        Vector3 blockSpaceNewPosition = terrainInstance.GetBlockPosition(newPosition);
        bool    newPositionValid      = true;

        blockSpaceNewPosition.y -= (float)this.characterHeight / 2.0f;
        newPosition.y            = blockSpaceNewPosition.y;

        // New position valid?
        for (int i = 0; i < this.characterHeight && newPositionValid; i++)
        {
            if (terrainInstance.HasBlock(blockSpaceNewPosition))
            {
                newPositionValid = false;
            }

            // Check left, right, front and back
            Vector3[] checkPositions = new Vector3[]
            {
                Vector3.left * 0.5f,
                      Vector3.right * 0.5f,
                      Vector3.forward * 0.5f,
                      Vector3.back * 0.5f
            };

            // Check all directions
            foreach (Vector3 checkPosition in checkPositions)
            {
                Vector3 checkPos = checkPosition + newPosition;

                if (terrainInstance.HasBlock(terrainInstance.GetBlockPosition(checkPos)))
                {
                    newPositionValid = false;
                }
            }

            blockSpaceNewPosition.y++;
            newPosition.y = blockSpaceNewPosition.y;
        }

        if (newPositionValid)
        {
            this.transform.position = realNewPosition;
            this.velocity          -= this.velocity / 2;
        }
        else
        {
            this.velocity = Vector3.zero;
        }
    }
コード例 #14
0
    public void Update()
    {
        bool firstPoint  = Input.GetKeyDown(KeyCode.P);
        bool lastPoint   = Input.GetKeyDown(KeyCode.L);
        bool definePoint = firstPoint || lastPoint;

        if (this.path != null)
        {
            if (this.path.isReady)
            {
                Debug.Log("Path found in " + path.runtime + " milliseconds!");
                foreach (Vector3 blockPos in this.path.pathData)
                {
                    CubicTerrain.GetInstance().SetBlock((int)blockPos.x, (int)blockPos.y - 1, (int)blockPos.z, -1);
                }
                this.path = null;
            }
            else if (!this.path.foundPath)
            {
                Debug.Log("Path not found :-( after search for " + path.runtime + " milliseconds!");
            }
        }

        // Right mouse button.
        if (definePoint)
        {
            if (CubicTerrain.GetInstance().useMeshColliders)
            {
                // Get ready to perform the raycast.
                RaycastHit hitInfo   = new RaycastHit();
                Ray        cameraRay = this.playerCamera.GetComponent <Camera>().ScreenPointToRay(new Vector3(Screen.width / 2, Screen.height / 2));
                Debug.DrawRay(cameraRay.origin, cameraRay.direction, Color.red, 100.0f);

                // Perform the raycast
                if (Physics.Raycast(cameraRay, out hitInfo, 5, this.detectionMask.value))
                {
                    if (hitInfo.collider == null)
                    {
                        return;
                    }

                    // get collider parent
                    Transform chunkTransform = hitInfo.collider.transform.parent;

                    if (chunkTransform != null)
                    {
                        // Chunk hit?
                        CubicTerrainChunk chunk = chunkTransform.GetComponent <CubicTerrainChunk>();
                        if (chunk != null && !chunk.isDirty)
                        {
                            // Yes, chunk hit!
                            BlockHitInfo blockHitInfo = chunk.GetBlockHitInfo(hitInfo);
                            int          x            = (int)blockHitInfo.hitBlock.x;
                            int          y            = (int)blockHitInfo.hitBlock.y;
                            int          z            = (int)blockHitInfo.hitBlock.z;

                            // Which face was hit? calculate target position for the new block
                            switch (blockHitInfo.hitFace)
                            {
                            case BlockFace.LEFT:
                                x -= 1;
                                break;

                            case BlockFace.RIGHT:
                                x += 1;
                                break;

                            case BlockFace.TOP:
                                y += 1;
                                break;

                            case BlockFace.BOTTOM:
                                y -= 1;
                                break;

                            case BlockFace.FRONT:
                                z += 1;
                                break;

                            case BlockFace.BACK:
                                z -= 1;
                                break;
                            }

                            Vector3 chunkPos = chunk.chunkPosition;

                            // Get chunk we want to place the block on
                            if (x < 0)
                            {
                                chunkPos.x -= 1;
                                x           = chunk.master.chunkWidth - 1;
                            }
                            if (x >= chunk.master.chunkWidth)
                            {
                                chunkPos.x += 1;
                                x           = 0;
                            }
                            if (z < 0)
                            {
                                chunkPos.z -= 1;
                                z           = chunk.master.chunkDepth - 1;
                            }
                            if (z >= chunk.master.chunkDepth)
                            {
                                chunkPos.z += 1;
                                z           = 0;
                            }

                            // Finally place the object
                            GameObject chunkObject = chunk.master.GetChunkObject((int)chunkPos.x, (int)chunkPos.y, (int)chunkPos.z);
                            chunk = chunkObject.GetComponent <CubicTerrainChunk>();

                            // Get absolute position
                            Vector3 absoluteVoxelspace = chunk.GetAbsolutePosition(new Vector3(x, y, z));

                            Debug.Log("Point: " + absoluteVoxelspace);
                            if (firstPoint)
                            {
                                startPoint = absoluteVoxelspace;
                            }
                            else if (lastPoint)
                            {
                                goalPoint = absoluteVoxelspace;
                            }
                        }
                    }
                }
            }
            else
            {
                // Cubic World Physics way
                CubicRaycastHitInfo hitInfo = new CubicRaycastHitInfo();
                if (CubicPhysics.TerrainRaycastUnprecise(this.playerCamera.GetComponent <Camera>().ScreenPointToRay(new Vector3(Screen.width / 2, Screen.height / 2)), 5.0f, out hitInfo))
                {
                    // Debug.Log ("Hit: " + hitInfo.hitPoint + ", Block: " + hitInfo.blockHit + ", Face: " + hitInfo.faceHit);
                    // Hit block
                    Vector3 topBlock = hitInfo.blockHit + Vector3.up;
                    Debug.Log("Top Block: " + topBlock);
                    if (firstPoint)
                    {
                        startPoint = topBlock;
                    }
                    else if (lastPoint)
                    {
                        goalPoint = topBlock;
                    }
                }
            }
        }

        if (startPoint != Vector3.zero && goalPoint != Vector3.zero)
        {
            Debug.Log("Starting A* path finding. Distance: " + Vector3.Distance(startPoint, goalPoint));

            // Start pathfinding
            CubicPathfinding pathfinder = CubicPathfinding.GetInstance();
            this.path = pathfinder.GetPath(startPoint, goalPoint, true);

            startPoint = Vector3.zero;
            goalPoint  = Vector3.zero;
        }
    }