Advertisement
Not a member of Pastebin yet?
Sign Up,
it unlocks many cool features!
- using System.Collections;
- using System.Collections.Generic;
- using UnityEngine;
- using System.Text.RegularExpressions;
- public class LevelManager : MonoBehaviour {
- // singleton
- public static LevelManager instance;
- // file prefix
- private readonly string prefix = "Assets/Levels/";
- // player prefab
- public Transform pacman;
- // player coods
- int pacmanX;
- int pacmanZ;
- // enemy prefabs
- public Transform blinky;
- public Transform pinky;
- public Transform inky;
- public Transform clyde;
- // enemy coords
- int blinkyX;
- int blinkyZ;
- int pinkyX;
- int pinkyZ;
- int inkyX;
- int inkyZ;
- int clydeX;
- int clydeZ;
- // tiles
- public Transform corridor;
- public Transform corner;
- public Transform cross;
- public Transform tCross;
- public Transform deadEnd;
- // corresponding strings
- readonly char sCorridorHorizontal = '-';
- readonly char sCorridorVertical = '|';
- readonly char sCross = '+'; // could be cross or tCross or corner
- // the level as a string
- private List<string> level = new List<string> ();
- private bool isReady;
- // a struct that helps setting the wayPoints
- struct WayPointData {
- public Transform tile;
- public bool goUp;
- public bool goDown;
- public bool goLeft;
- public bool goRight;
- };
- WayPointData[,] wayPointDataArray; // 2D array
- int rows = 0; // final # of rows
- int cols = -1; // final # of columns
- int r = 0; // row idx (needed when filling in the array)
- int c = 0; // col idx (needed when filling in the array)
- // instantiated objects
- List<Transform> createdTiles = new List<Transform>();
- List<Transform> createdCharacters = new List<Transform>();
- void Awake() {
- instance = this;
- }
- void Update() {
- if(isReady) {
- wayPointDataArray = new WayPointData[rows, cols];
- CreateLevel ();
- SetWayPoints ();
- SetAndInstantiateCharacters ();
- isReady = false;
- }
- }
- /// <summary>
- /// Reads the file. In doing so, it also destroys the current level and creates the data necessary for the new level.
- /// While reading the file, syntax and semantic checks are done as well.
- /// </summary>
- /// <param name="fileName">File name.</param>
- public void ReadFile(string fileName) {
- Destroy ();
- string text = System.IO.File.ReadAllText (prefix + fileName);
- SyntaxChecker.instance.errorMsg.text = "";
- SemanticsChecker.instance.errorMsg.text = "";
- if(!SyntaxChecker.instance.IsSyntacticallyCorrect (text)) {
- SyntaxChecker.instance.errorMsg.text = "Syntax Error!";
- return;
- }
- level.Clear ();
- List<string> lines = new List<string>(Regex.Split (text, "\n"));
- rows = 0;
- cols = -1;
- r = 0;
- c = 0;
- for(int i=0; i < lines.Count; ++i) {
- string currentLine = lines[i].Trim();
- string[] currentLineSplit = Regex.Split (currentLine, " ");
- // important note #1: we have to invert the provided z-value as the level grows downward but the z-axis points upward
- // important note #2: the x-value is to be used in the 2nd dimension of the level-array (-> columns!)
- // important note #3: as the z-value will be negative, we have to invert it again when accessing the level-array later on
- switch(currentLineSplit[0]) {
- case "Pacman":
- pacmanX = int.Parse (currentLineSplit [1]);
- pacmanZ = -int.Parse (currentLineSplit [2]);
- break;
- case "Blinky":
- blinkyX = int.Parse (currentLineSplit [1]);
- blinkyZ = -int.Parse (currentLineSplit [2]);
- break;
- case "Pinky":
- pinkyX = int.Parse (currentLineSplit [1]);
- pinkyZ = -int.Parse (currentLineSplit [2]);
- break;
- case "Inky":
- inkyX = int.Parse (currentLineSplit [1]);
- inkyZ = -int.Parse (currentLineSplit [2]);
- break;
- case "Clyde":
- clydeX = int.Parse (currentLineSplit [1]);
- clydeZ = -int.Parse (currentLineSplit [2]);
- break;
- default:
- // else it must be some level object
- level.Add (currentLine);
- rows++;
- if(cols == -1) {
- // assumes that each row is of the same length
- cols = currentLine.Length;
- }
- break;
- } // switch()
- } // for()
- if(!SemanticsChecker.instance.IsSemanticallyCorrect (lines, rows, cols)) {
- SemanticsChecker.instance.errorMsg.text = "Semantics Error!";
- return;
- }
- isReady = true;
- } // ReadFile()
- /// <summary>
- /// Destroys the level and the characters.
- /// </summary>
- void Destroy() {
- foreach(Transform character in createdCharacters) {
- if(character != null) {
- Destroy (character.gameObject);
- }
- }
- foreach(Transform tile in createdTiles) {
- if(tile != null) {
- Destroy (tile.gameObject);
- }
- }
- createdCharacters.Clear ();
- createdTiles.Clear ();
- }
- /// <summary>
- /// Creates the level by instantiating the tiles in the correct position and rotation.
- /// </summary>
- void CreateLevel() {
- float startX = 0f;
- float x = startX;
- float y = 0f;
- float z = 0f;
- Transform tile;
- for(int i=0; i < level.Count; ++i) {
- for(int j=0; j < level[i].Length; ++j) {
- Vector3 vec = new Vector3 (x, y, z);
- if(level[i][j] == sCorridorHorizontal) {
- bool isDeadEndLeft = (j == 0) || level[i][j-1] == sCorridorVertical;
- bool isDeadEndRight = (j == level[i].Length-1) || level[i][j+1] == sCorridorVertical;
- if(isDeadEndLeft) {
- tile = InstantiateWithRotationAndCreateWayPointData(deadEnd, vec, -90f, false, false, false, true);
- }
- else if(isDeadEndRight) {
- tile = InstantiateWithRotationAndCreateWayPointData(deadEnd, vec, 90f, false, false, true, false);
- }
- else {
- tile = InstantiateWithRotationAndCreateWayPointData(corridor, vec, 90f, false, false, true, true);
- }
- createdTiles.Add (tile);
- }
- else if(level[i][j] == sCorridorVertical) {
- bool isDeadEndUp = (i == 0) || level[i-1][j] == sCorridorHorizontal;
- bool isDeadEndDown = (i == level.Count-1) || level[i+1][j] == sCorridorHorizontal;
- if(isDeadEndUp) {
- tile = InstantiateWithRotationAndCreateWayPointData(deadEnd, vec, 0f, false, true, false, false);
- }
- else if(isDeadEndDown) {
- tile = InstantiateWithRotationAndCreateWayPointData(deadEnd, vec, 180f, true, false, false, false);
- }
- else {
- tile = InstantiateWithRotationAndCreateWayPointData(corridor, vec, 0f, true, true, false, false);
- }
- createdTiles.Add (tile);
- }
- char empty = ' ';
- if(level[i][j] == sCross) {
- char up = (i > 0) ? level [i - 1] [j] : empty;
- char down = ((i + 1) < level.Count) ? level [i + 1] [j] : empty;
- char left = (j > 0) ? level [i] [j - 1] : empty;
- char right = ((j + 1) < level [i].Length) ? level [i] [j + 1] : empty;
- bool isLeftTopCorner = (up == empty && left == empty && right != empty && down != empty);
- bool isLeftBottomCorner = (up != empty && left == empty && right != empty && down == empty);
- bool isRightTopCorner = (up == empty && left != empty && right == empty && down != empty);
- bool isRightBottomCorner = (up != empty && left != empty && right == empty && down == empty);
- bool isLeftTCross = (up != empty && left == empty && right != empty && down != empty);
- bool isRightTCross = (up != empty && left != empty && right == empty && down != empty);
- bool isTopTCross = (up == empty && left != empty && right != empty && down != empty);
- bool isBottomTCross = (up != empty && left != empty && right != empty && down == empty);
- bool isCenterCross = (up != empty && left != empty && right != empty && down != empty);
- bool isDeadEndUp = (i > 0) && (level [i - 1] [j] == sCorridorHorizontal);
- bool isDeadEndDown = ((i + 1) < level.Count) && (level [i + 1] [j] == sCorridorHorizontal);
- bool isDeadEndLeft = (j > 0) && (level [i] [j - 1] == sCorridorVertical);
- bool isDeadEndRight = ((j + 1) < level[i].Length) && (level [i] [j + 1] == sCorridorVertical);
- // long if-sequence:
- // first the corners
- if(isLeftTopCorner) {
- if(isDeadEndDown) {
- // only way: right
- tile = InstantiateWithRotationAndCreateWayPointData (deadEnd, vec, -90f, false, false, false, true);
- }
- else if(isDeadEndRight) {
- // only way: down
- tile = InstantiateWithRotationAndCreateWayPointData (deadEnd, vec, 0f, false, true, false, false);
- }
- else {
- tile = InstantiateWithRotationAndCreateWayPointData(corner, vec, -90f, false, true, false, true);
- }
- createdTiles.Add (tile);
- }
- else if(isLeftBottomCorner) {
- if(isDeadEndUp) {
- tile = InstantiateWithRotationAndCreateWayPointData (deadEnd, vec, -90f, false, false, false, true);
- }
- else if(isDeadEndRight) {
- tile = InstantiateWithRotationAndCreateWayPointData (deadEnd, vec, 180f, true, false, false, false);
- }
- else {
- tile = InstantiateWithRotationAndCreateWayPointData(corner, vec, 180f, true, false, false, true);
- }
- createdTiles.Add (tile);
- }
- else if(isRightTopCorner) {
- if(isDeadEndDown) {
- tile = InstantiateWithRotationAndCreateWayPointData (deadEnd, vec, 90f, false, false, true, false);
- }
- else if(isDeadEndLeft) {
- tile = InstantiateWithRotationAndCreateWayPointData (deadEnd, vec, 90f, false, true, false, false);
- }
- else {
- tile = InstantiateWithRotationAndCreateWayPointData(corner, vec, 0f, false, true, true, false);
- }
- createdTiles.Add (tile);
- }
- else if(isRightBottomCorner) {
- if(isDeadEndUp) {
- tile = InstantiateWithRotationAndCreateWayPointData (deadEnd, vec, 90f, false, false, true, false);
- }
- else if(isDeadEndLeft) {
- tile = InstantiateWithRotationAndCreateWayPointData (deadEnd, vec, 90f, true, false, false, false);
- }
- else {
- tile = InstantiateWithRotationAndCreateWayPointData(corner, vec, 90f, true, false, true, false);
- }
- createdTiles.Add (tile);
- }
- // still same if case
- // now TCrosses
- else if(isLeftTCross) {
- if(isDeadEndUp && isDeadEndDown) {
- tile = InstantiateWithRotationAndCreateWayPointData(deadEnd, vec, -90f, false, false, false, true);
- }
- else if(isDeadEndUp && isDeadEndRight) {
- tile = InstantiateWithRotationAndCreateWayPointData(deadEnd, vec, 0f, false, true, false, false);
- }
- else if(isDeadEndDown && isDeadEndRight) {
- tile = InstantiateWithRotationAndCreateWayPointData(deadEnd, vec, 180f, true, false, false, false);
- }
- else if(isDeadEndUp) {
- tile = InstantiateWithRotationAndCreateWayPointData(corner, vec, -90f, false, true, false, true);
- }
- else if(isDeadEndDown) {
- tile = InstantiateWithRotationAndCreateWayPointData(corner, vec, 180f, true, false, false, true);
- }
- else if(isDeadEndRight) {
- tile = InstantiateWithRotationAndCreateWayPointData(corridor, vec, 0f, true, true, false, false);
- }
- else {
- tile = InstantiateWithRotationAndCreateWayPointData(tCross, vec, 0f, true, true, false, true);
- }
- createdTiles.Add (tile);
- }
- else if(isRightTCross) {
- if(isDeadEndUp && isDeadEndDown) {
- tile = InstantiateWithRotationAndCreateWayPointData(deadEnd, vec, 90f, false, false, true, false);
- }
- else if(isDeadEndUp && isDeadEndLeft) {
- tile = InstantiateWithRotationAndCreateWayPointData(deadEnd, vec, 0f, false, true, false, false);
- }
- else if(isDeadEndDown && isDeadEndLeft) {
- tile = InstantiateWithRotationAndCreateWayPointData(deadEnd, vec, 180f, true, false, false, false);
- }
- else if(isDeadEndUp) {
- tile = InstantiateWithRotationAndCreateWayPointData(corner, vec, 0f, false, true, true, false);
- }
- else if(isDeadEndDown) {
- tile = InstantiateWithRotationAndCreateWayPointData(corner, vec, 90f, true, false, true, false);
- }
- else if(isDeadEndLeft) {
- tile = InstantiateWithRotationAndCreateWayPointData(corridor, vec, 0f, true, true, false, false);
- }
- else {
- tile = InstantiateWithRotationAndCreateWayPointData(tCross, vec, 180f, true, true, true, false);
- }
- createdTiles.Add (tile);
- }
- else if(isTopTCross) {
- if(isDeadEndDown && isDeadEndLeft) {
- tile = InstantiateWithRotationAndCreateWayPointData(deadEnd, vec, -90f, false, false, false, true);
- }
- else if(isDeadEndDown && isDeadEndRight) {
- tile = InstantiateWithRotationAndCreateWayPointData(deadEnd, vec, 90f, false, false, true, false);
- }
- else if(isDeadEndLeft && isDeadEndRight) {
- tile = InstantiateWithRotationAndCreateWayPointData(deadEnd, vec, 0f, false, true, false, false);
- }
- else if(isDeadEndDown) {
- tile = InstantiateWithRotationAndCreateWayPointData(corridor, vec, 90f, false, false, true, true);
- }
- else if(isDeadEndLeft) {
- tile = InstantiateWithRotationAndCreateWayPointData(corner, vec, -90f, false, true, false, true);
- }
- else if(isDeadEndRight) {
- tile = InstantiateWithRotationAndCreateWayPointData(corner, vec, 0f, false, true, true, false);
- }
- else {
- tile = InstantiateWithRotationAndCreateWayPointData(tCross, vec, 90f, false, true, true, true);
- }
- createdTiles.Add (tile);
- }
- else if(isBottomTCross) {
- if(isDeadEndUp && isDeadEndLeft) {
- tile = InstantiateWithRotationAndCreateWayPointData(deadEnd, vec, -90f, false, false, false, true);
- }
- else if(isDeadEndUp && isDeadEndRight) {
- tile = InstantiateWithRotationAndCreateWayPointData(deadEnd, vec, 90f, false, false, true, false);
- }
- else if(isDeadEndLeft && isDeadEndRight) {
- tile = InstantiateWithRotationAndCreateWayPointData(deadEnd, vec, 180f, true, false, false, false);
- }
- else if(isDeadEndUp) {
- tile = InstantiateWithRotationAndCreateWayPointData(corridor, vec, 90f, false, false, true, true);
- }
- else if(isDeadEndLeft) {
- tile = InstantiateWithRotationAndCreateWayPointData(corner, vec, 180f, true, false, false, true);
- }
- else if(isDeadEndRight) {
- tile = InstantiateWithRotationAndCreateWayPointData(corner, vec, 90f, true, false, true, false);
- }
- else {
- tile = InstantiateWithRotationAndCreateWayPointData(tCross, vec, -90f, true, false, true, true);
- }
- createdTiles.Add (tile);
- }
- // still same if case
- // now full cross
- else if(isCenterCross) {
- if(isDeadEndUp && isDeadEndDown && isDeadEndLeft) {
- tile = CreateDeadEndLeft (vec);
- }
- else if(isDeadEndUp && isDeadEndDown && isDeadEndRight) {
- tile = CreateDeadEndRight (vec);
- }
- else if(isDeadEndUp && isDeadEndLeft && isDeadEndRight) {
- tile = CreateDeadEndUp (vec);
- }
- else if(isDeadEndDown && isDeadEndLeft && isDeadEndRight) {
- tile = CreateDeadEndDown (vec);
- }
- else if(isDeadEndUp && isDeadEndDown) {
- tile = CreateCorridorHorizontal (vec);
- }
- else if(isDeadEndUp && isDeadEndLeft) {
- tile = CreateCornerTopRight (vec);
- }
- else if(isDeadEndUp && isDeadEndRight) {
- tile = CreateCornerTopLeft (vec);
- }
- else if(isDeadEndDown && isDeadEndLeft) {
- tile = CreateCornerBottomLeft (vec);
- }
- else if(isDeadEndDown && isDeadEndRight) {
- tile = CreateCornerBottomRight (vec);
- }
- else if(isDeadEndLeft && isDeadEndRight) {
- tile = CreateCorridorVertical (vec);
- }
- else if(isDeadEndUp) {
- tile = CreateTCrossDownLeftRight (vec);
- }
- else if(isDeadEndDown) {
- tile = CreateTCrossUpLeftRight (vec);
- }
- else if(isDeadEndLeft) {
- tile = CreateTCrossUpDownRight (vec);
- }
- else if(isDeadEndRight) {
- tile = CreateTCrossUpDownLeft (vec);
- }
- else {
- tile = CreateCross (vec);
- }
- createdTiles.Add (tile);
- }
- // end of long if-sequence
- // end of long if-sequence
- // end of long if-sequence
- }
- x += 1f; // advance to the right
- } // for(j)
- z -= 1f; // advance down
- x = startX; // start from the left
- } // for(i)
- } // CreateLevel()
- // ---
- // | |
- // | |
- Transform CreateDeadEndUp(Vector3 vec) {
- return InstantiateWithRotationAndCreateWayPointData (deadEnd, vec, 0f, false, true, false, false);
- }
- // | |
- // | |
- // ---
- Transform CreateDeadEndDown(Vector3 vec) {
- return InstantiateWithRotationAndCreateWayPointData (deadEnd, vec, 180f, true, false, false, false);
- }
- // |-----
- // |
- // |-----
- Transform CreateDeadEndLeft(Vector3 vec) {
- return InstantiateWithRotationAndCreateWayPointData (deadEnd, vec, -90f, false, false, false, true);
- }
- // -----|
- // |
- // -----|
- Transform CreateDeadEndRight(Vector3 vec) {
- InstantiateWithRotationAndCreateWayPointData (deadEnd, vec, 90f, false, false, true, false);
- }
- //
- // --------
- // --------
- //
- Transform CreateCorridorHorizontal(Vector3 vec) {
- return InstantiateWithRotationAndCreateWayPointData(corridor, vec, 90f, false, false, true, true);
- }
- // ||
- // ||
- // ||
- Transform CreateCorridorVertical(Vector3 vec) {
- return InstantiateWithRotationAndCreateWayPointData(corridor, vec, 0f, true, true, false, false);
- }
- // --------
- // | -----
- // | |
- // | |
- Transform CreateCornerTopLeft(Vector3 vec) {
- return InstantiateWithRotationAndCreateWayPointData(corner, vec, -90f, false, true, false, true);
- }
- // --------
- // ----- |
- // | |
- // | |
- Transform CreateCornerTopRight(Vector3 vec) {
- return InstantiateWithRotationAndCreateWayPointData(corner, vec, 0f, false, true, true, false);
- }
- // | |
- // | |
- // | -----
- // --------
- Transform CreateCornerBottomLeft(Vector3 vec) {
- return InstantiateWithRotationAndCreateWayPointData(corner, vec, 180f, true, false, false, true);
- }
- // | |
- // | |
- // ----- |
- // --------
- Transform CreateCornerBottomRight(Vector3 vec) {
- return InstantiateWithRotationAndCreateWayPointData(corner, vec, 90f, true, false, true, false);
- }
- // ||
- // -----||
- // ||
- Transform CreateTCrossUpDownLeft(Vector3 vec) {
- return InstantiateWithRotationAndCreateWayPointData (tCross, vec, 180f, true, true, true, false);
- }
- // ||
- // ||----
- // ||
- Transform CreateTCrossUpDownRight(Vector3 vec) {
- return InstantiateWithRotationAndCreateWayPointData (tCross, vec, 0f, true, true, false, true);
- }
- // ||
- // ||
- // --------
- Transform CreateTCrossUpLeftRight(Vector3 vec) {
- return InstantiateWithRotationAndCreateWayPointData (tCross, vec, -90f, true, false, true, true);
- }
- // --------
- // ||
- // ||
- Transform CreateTCrossDownLeftRight(Vector3 vec) {
- return InstantiateWithRotationAndCreateWayPointData (tCross, vec, 90f, false, true, true, true);
- }
- // ||
- // --------
- // --------
- // ||
- Transform CreateCross(Vector3 vec) {
- return InstantiateWithRotationAndCreateWayPointData(cross, vec, 0f, true, true, true, true);
- }
- /// <summary>
- /// Instantiates the given Transform object (a tile) at position 'vec', rotated by 'deg' degrees.
- /// The boolean parameters are used to store the necessary information to create the WayPoint data for each tile later on.
- /// </summary>
- /// <returns>The instantiated object at position 'vec', rotated by 'deg' degrees.</returns>
- /// <param name="obj">The Transform to be instantiated.</param>
- /// <param name="vec">The Position at which the object is instantiated.</param>
- /// <param name="deg">The degree by which the object is rotated.</param>
- /// <param name="goUp">If set to <c>true</c> it's possible to go up.</param>
- /// <param name="goDown">If set to <c>true</c> it's possible to go down.</param>
- /// <param name="goLeft">If set to <c>true</c> it's possible to go left.</param>
- /// <param name="goRight">If set to <c>true</c> it's possible to go right.</param>
- Transform InstantiateWithRotationAndCreateWayPointData(Transform obj, Vector3 vec, float deg, bool goUp, bool goDown, bool goLeft, bool goRight) {
- Vector3 rot = obj.transform.eulerAngles;
- rot = new Vector3 (rot.x, rot.y + deg, rot.z);
- Transform result = Instantiate (obj, vec, Quaternion.Euler (rot));
- WayPointData temp;
- temp.tile = result;
- temp.goUp = goUp;
- temp.goDown = goDown;
- temp.goLeft = goLeft;
- temp.goRight = goRight;
- if(r < rows && c < cols) {
- wayPointDataArray [r, c++] = temp;
- if(c == cols) {
- r++;
- c = 0;
- }
- }
- return result;
- } // InstantiateWithRotationAndCreateWayPointData()
- /// <summary>
- /// Sets the way points of each tile in the level.
- /// </summary>
- void SetWayPoints() {
- // map
- for(int i=0; i < rows; ++i) {
- for(int j=0; j < cols; ++j) {
- WayPointData temp = wayPointDataArray [i, j];
- Transform obj = temp.tile;
- if(obj != null) {
- WayPoint wp = obj.GetComponent<WayPoint> ();
- if(temp.goUp) {
- wp.upWaypoint = wayPointDataArray [i - 1, j].tile.GetComponent<WayPoint> ();
- }
- if(temp.goDown) {
- wp.downWaypoint = wayPointDataArray [i + 1, j].tile.GetComponent<WayPoint> ();
- }
- if(temp.goLeft) {
- wp.leftWaypoint = wayPointDataArray [i, j - 1].tile.GetComponent<WayPoint> ();
- }
- if(temp.goRight) {
- wp.rightWaypoint = wayPointDataArray [i, j + 1].tile.GetComponent<WayPoint> ();
- }
- }
- } // for(j)
- } // for(i)
- } // SetWayPoints()
- /// <summary>
- /// Sets the necessary data (currentWayPoint and position) of each character and instantiates them.
- /// </summary>
- void SetAndInstantiateCharacters() {
- InstantiateCharacter (ref pacman, pacmanX, pacmanZ);
- InstantiateCharacter (ref blinky, blinkyX, blinkyZ);
- InstantiateCharacter (ref pinky, pinkyX, pinkyZ);
- InstantiateCharacter (ref inky, inkyX, inkyZ);
- InstantiateCharacter (ref clyde, clydeX, clydeZ);
- } // SetAndInstantiateCharacters()
- /// <summary>
- /// Instantiates the given character at position (x, z).
- /// </summary>
- /// <param name="character">The character to instantiate.</param>
- /// <param name="x">The x coordinate.</param>
- /// <param name="z">The z coordinate.</param>
- void InstantiateCharacter(ref Transform character, int x, int z) {
- // note the negation of the z-value!!
- Transform tile = wayPointDataArray [-z, x].tile;
- if(tile != null) {
- WayPoint wp = tile.GetComponent<WayPoint> ();
- if(character.CompareTag ("Pacman")) {
- character.GetComponent<PlayerControlScript> ().currentWaypoint = wp;
- }
- else{
- character.GetComponent<EnemyBehaviourScript> ().currentWaypoint = wp;
- }
- Transform instantiatedCharacter;
- instantiatedCharacter = Instantiate (character, new Vector3 (x, character.transform.position.y, z), Quaternion.identity);
- createdCharacters.Add (instantiatedCharacter);
- }
- } // InstantiateCharacter()
- }
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement