using GatherAndDefend.LevelEditor; using System.Collections.Generic; using System.Text; using UnityEngine.Tilemaps; using UnityEngine; using System.Linq; using Newtonsoft.Json; using System.IO; using System.Collections; using System.Threading.Tasks; using GatherAndDefend.Events; /// /// data class for containing everything level related /// public class LevelManager : Singleton { string SavePath => Application.dataPath + "/save.txt"; public delegate void OnLevelLoaded(Level level); public delegate void LevelAction(ILevelObject levelObject); public delegate bool LevelPredicate(T levelObject) where T : ILevelObject; private readonly List _toAdd; private readonly List _toRemove; private readonly List _levelObjects; private WaveObserver _waveObserver; private bool _isLoading = true; private bool _shouldKillTask = false; private Tilemap _dynamicTilemap; public Tilemap DynamicTilemap { get { if (!_dynamicTilemap) { var grid = Object.FindObjectOfType(); _dynamicTilemap = new GameObject("Dynamic").AddComponent(); _dynamicTilemap.gameObject.AddComponent().sortingOrder = 3; _dynamicTilemap.transform.SetParent(grid.transform); _dynamicTilemap.tileAnchor = Vector3.zero; } return _dynamicTilemap; } } private Level _currentLevel; public Level CurrentLevel => _currentLevel; public Transform LevelTransform { get; set; } public bool Loading { get; private set; } = false; public LevelManager() { _toAdd = new List(); _toRemove = new List(); _levelObjects = new List(); } #region [~CRUD~] public void Add(ILevelObject levelObject) { _toAdd.Add(levelObject); } public void Remove(ILevelObject levelObject) { _toRemove.Add(levelObject); } public void Clear() { _toAdd.Clear(); _toRemove.Clear(); _levelObjects.Clear(); } public T Get(LevelPredicate predicate = null) where T : ILevelObject { if (predicate == null) predicate = (generic) => true; return (T)_levelObjects.Find(levelObject => levelObject is T generic && predicate(generic)); } public List GetAll(LevelPredicate predicate = null) where T : ILevelObject { if (predicate == null) predicate = (generic) => true; List ret = new(); foreach (var levelObject in _levelObjects) if (levelObject is T generic && predicate(generic)) ret.Add(generic); return ret; } public int Count(LevelPredicate predicate = null) where T : ILevelObject { return GetAll(predicate).Count; } public bool Has(LevelPredicate predicate = null) where T : ILevelObject { if (predicate == null) predicate = (generic) => true; return _levelObjects.Exists(levelObject => levelObject is T generic && predicate(generic)); } #endregion #region [Level management] void AddAndRemoveObjects() { //add and remove var toAdd = new List(_toAdd); toAdd.ForEach(addedObject => { _toAdd.Remove(addedObject); _levelObjects.Add(addedObject); addedObject.LevelStart(); }); var toRemove = new List(_toRemove); toRemove.ForEach(removedObject => { _toRemove.Remove(removedObject); _levelObjects.Remove(removedObject); removedObject.LevelDestroy(); }); } public void UpdateLevel() { if (_isLoading) return; AddAndRemoveObjects(); _levelObjects.ForEach(levelObject => { levelObject.LevelUpdate(); }); AddAndRemoveObjects(); } public void ClearLevel() { foreach (var obj in _levelObjects) { obj.RemoveFromLevel(); } Clear(); } public void KillLoading() { _shouldKillTask = true; } /// /// loads a Level scriptable object /// /// the level to load /// do we want to clear what's already there before loading? /// the tiles falling from the top of the screen public async Task LoadLevel(Level level, PlacementAnimationHandler placementAnimation = null) { _isLoading = true; _shouldKillTask = false; float accelerationOfAcceleration = GlobalConfig.Instance.Current.tileSpawnAcceleration; float tileCurrentAcceleration = 1; float tileSpawnAccelerationFunc() => (tileCurrentAcceleration += accelerationOfAcceleration * Time.deltaTime); ClearLevel(); _currentLevel = level; _waveObserver = WaveObserver.Instance; _waveObserver.LevelConfig = _currentLevel.WaveConfig; Grid grid = Object.FindObjectOfType(); //create new grid if there is none if (!grid) { var levelMgrScript = Object.FindObjectOfType(); grid = new GameObject("Grid").AddComponent(); grid.transform.SetParent(levelMgrScript.transform); } //remove all tilemaps if there is a grid else { foreach (Transform child in grid.transform) { Object.Destroy(child.gameObject); } } var tasks = new List(); //generate all tilemaps foreach (TilemapData tilemapData in _currentLevel) { bool allTilesSpawned = false; System.Action OnAllTilesSpawned = () => allTilesSpawned = true; var tilemap = new GameObject(tilemapData.Key).AddComponent(); tilemap.tileAnchor = Vector3.zero; tilemap.gameObject.AddComponent(); tilemap.transform.SetParent(grid.transform); tasks.Add(tilemapData.LoadToTilemap(tilemap, () => _shouldKillTask, placementAnimation, OnAllTilesSpawned, tileSpawnAccelerationFunc)); if (_shouldKillTask) { return; } await Extensions.WaitWhile(() => !allTilesSpawned); } if (_shouldKillTask) { return; } await Task.WhenAll(tasks); EventAggregator.Instance.GetEvent().Invoke(level); _isLoading = false; Debug.Log("level loaded successfully"); } /// /// load a Level scriptable object using its name /// /// the name of the loaded Level /// should we clear what's already there? /// the tiles falling animation public async Task LoadLevel(string levelName, PlacementAnimationHandler placementAnimation = null) { //fetch level from database _currentLevel = Database.Instance.ScriptableObjects[levelName] as Level; await LoadLevel(_currentLevel, placementAnimation).ConfigureAwait(false); } public void SaveFile() { var list = _levelObjects.Select(obj => obj.ToDictionary()).ToList(); foreach (var tilemapData in _currentLevel) { var levelConfig = tilemapData.ToDictionary(); levelConfig[nameof(ILevelObject.ObjectType)] = nameof(ILevelObject.ObjectType.Tilemap); list.Add(levelConfig); } list.Add(OtherValuesToDict()); string saved = JsonConvert.SerializeObject(list); File.WriteAllText(SavePath, saved, Encoding.UTF8); Debug.Log("game saved successfully"); } public void LoadFile() { string saved = File.ReadAllText(SavePath, Encoding.UTF8); var dicts = JsonConvert.DeserializeObject>>(saved); ClearLevel(); var tilemapDicts = dicts.FindAll(x => x[nameof(ILevelObject.ObjectType)].ToString() == nameof(ILevelObject.ObjectType.Tilemap)); foreach (var tilemapDict in tilemapDicts) CreateTilemap(tilemapDict); var prefabDicts = dicts.FindAll(x => x[nameof(ILevelObject.ObjectType)].ToString() == nameof(ILevelObject.ObjectType.Prefab)); foreach (var prefabDict in prefabDicts) CreatePrefab(prefabDict); var tileDicts = dicts.FindAll(x => x[nameof(ILevelObject.ObjectType)].ToString() == nameof(ILevelObject.ObjectType.Tile)); foreach (var tileDict in tileDicts) CreateTile(tileDict); var otherDict = dicts.Find(x => x[nameof(ILevelObject.ObjectType)].ToString() == nameof(ILevelObject.ObjectType.Other)); DictToOtherValues(otherDict); EventAggregator.Instance.GetEvent().Invoke(_currentLevel); Debug.Log("game loaded successfully"); } private void CreatePrefab(Dictionary dict) { var name = dict["Name"].ToString(); var prefab = Database.Instance.Prefabs[name]; var instance = prefab.Create(Vector3.zero, parent: LevelTransform); var comp = instance.GetComponent(); comp.LoadDictionary(dict); } private void CreateTile(Dictionary dict) { var name = dict["Name"].ToString(); var tile = Object.Instantiate(Database.Instance.ScriptableObjects[name]) as LevelTile; tile.Instantiated = true; tile.LoadDictionary(dict); tile.AddToLevel(); } private void CreateTilemap(Dictionary dict) { TilemapData.FromDictionary(dict); } private Dictionary OtherValuesToDict() { return new Dictionary() { [nameof(ILevelObject.ObjectType)] = nameof(ILevelObject.ObjectType.Other), [nameof(_currentLevel)] = _currentLevel.name }; } private void DictToOtherValues(Dictionary dict) { // fetch current level var levelName = dict[nameof(_currentLevel)].ToString(); var level = Database.Instance.ScriptableObjects[levelName] as Level; _currentLevel = level; } #endregion }