public override void UpdateSystem() { if (this.Consumers.Count == 0) { this.isDirty = false; return; } Dictionary <Population, float> liquidTilesPerPopulation = new Dictionary <Population, float>(); Dictionary <Population, HashSet <LiquidBody> > liquidBodiesPerPopulation = new Dictionary <Population, HashSet <LiquidBody> >(); foreach (Population population in Consumers.OfType <Population>()) { if (!liquidTilesPerPopulation.ContainsKey(population) && population.GetNeedValues().ContainsKey("Liquid")) { liquidTilesPerPopulation.Add(population, 0); } } //Go through each body of water in the level and divide it between all populations that can access it foreach (LiquidBody liquidBody in LiquidbodyController.Instance.liquidBodies) { HashSet <Population> accessiblePopulations = new HashSet <Population>(); foreach (Population population in Consumers.OfType <Population>()) { Dictionary <string, Need> popNeeds = population.GetNeedValues(); if (!popNeeds.ContainsKey("Liquid") || //If the food source doesn't need liquid (popNeeds.ContainsKey("WaterPoison") && popNeeds["WaterPoison"].IsThresholdMet(liquidBody.contents[(int)LiquidComposition.Water])) || //or it has a fresh water poison threshold and that threshold is surpassed (popNeeds.ContainsKey("SaltPoison") && popNeeds["SaltPoison"].IsThresholdMet(liquidBody.contents[(int)LiquidComposition.Salt])) || //or it has a salt poison threshold and that threshold is surpassed (popNeeds.ContainsKey("BacteriaPoison") && popNeeds["BacteriaPoison"].IsThresholdMet(liquidBody.contents[(int)LiquidComposition.Bacteria]))) //or it has a bacteria poison threshold and that threshold is surpassed { continue; } if ((!popNeeds.ContainsKey("Water") || popNeeds["Water"].IsThresholdMet(liquidBody.contents[(int)LiquidComposition.Water])) && //If the population either doesn't need fresh water or the fresh water threshold is met (!popNeeds.ContainsKey("Salt") || popNeeds["Salt"].IsThresholdMet(liquidBody.contents[(int)LiquidComposition.Salt])) && //and it either doesn't need salt or the salt threshold is met (!popNeeds.ContainsKey("Bacteria") || popNeeds["Bacteria"].IsThresholdMet(liquidBody.contents[(int)LiquidComposition.Bacteria]))) //and it either doesn't need bacteria or the bacteria threshold is met { bool populationCanAccess = false; foreach (Vector3Int location in GameManager.Instance.m_reservePartitionManager.GetLiquidLocations(population)) //check if any of the liquidbody's tiles are accessible to this population { if (liquidBody.ContainsTile(location)) { populationCanAccess = true; break; } } if (populationCanAccess) //if the population can access this water source, add it to the set { accessiblePopulations.Add(population); } } } //split this water source equally between all populations that have access to it, regardless of that population's size float waterSplit = liquidBody.TileCount / (float)accessiblePopulations.Count; foreach (Population population in accessiblePopulations) { liquidTilesPerPopulation[population] += waterSplit; if (!liquidBodiesPerPopulation.ContainsKey(population)) { liquidBodiesPerPopulation.Add(population, new HashSet <LiquidBody>()); } liquidBodiesPerPopulation[population].Add(liquidBody); } } float[] liquidCompositionToUpdate = default; foreach (Life life in Consumers) { if (life is Population) { Population population = (Population)life; population.UpdateNeed("Liquid", liquidTilesPerPopulation[population]); //Debug.Log(population.name + " updates LiquidTiles with value: " + liquidTilesPerPopulation[population]); // Check is there is found composition if (liquidBodiesPerPopulation.ContainsKey(population)) { int totalTiles = 0; //Average out all the liquid compositions liquidCompositionToUpdate = new float[] { 0, 0, 0 }; foreach (LiquidBody liquidBody in liquidBodiesPerPopulation[population]) { //Weight each composition based on the number of tiles in the liquidbody liquidCompositionToUpdate[0] += liquidBody.contents[0] * liquidBody.TileCount; liquidCompositionToUpdate[1] += liquidBody.contents[1] * liquidBody.TileCount; liquidCompositionToUpdate[2] += liquidBody.contents[2] * liquidBody.TileCount; totalTiles += liquidBody.TileCount; } for (int i = 0; i <= 2; ++i) { liquidCompositionToUpdate[i] /= totalTiles; } } else { this.isDirty = false; continue; } } else if (life is FoodSource) { FoodSource foodSource = (FoodSource)life; Dictionary <string, Need> foodNeeds = foodSource.GetNeedValues(); float liquidCount = 0; List <float[]> liquidCompositions = new List <float[]>(); List <float[]> potentialCompositions = m_gridsystemReference.GetLiquidCompositionWithinRange(m_gridsystemReference.WorldToCell(foodSource.GetPosition()), foodSource.Species.Size, foodSource.Species.RootRadius); foreach (float[] composition in potentialCompositions) { if ((!foodNeeds.ContainsKey("Water") || foodNeeds["Water"].IsThresholdMet(composition[(int)LiquidComposition.Water])) && //If the food source either doesn't need fresh water or the fresh water threshold is met (!foodNeeds.ContainsKey("Salt") || foodNeeds["Salt"].IsThresholdMet(composition[(int)LiquidComposition.Salt])) && //and it either doesn't need salt or the salt threshold is met (!foodNeeds.ContainsKey("Bacteria") || foodNeeds["Bacteria"].IsThresholdMet(composition[(int)LiquidComposition.Bacteria]))) //and it either doesn't need bacteria or the bacteria threshold is met { ++liquidCount; liquidCompositions.Add(composition); } } foodSource.UpdateNeed("LiquidTiles", liquidCount); //Debug.Log(foodSource.name + " updated LiquidTiles with value: " + liquidCount); // Check is there is found composition if (liquidCompositions.Count > 0) { float[] sumComposition = new float[liquidCompositions[0].Count()]; foreach (float[] composition in liquidCompositions) { foreach (var(value, index) in composition.WithIndex()) { sumComposition[index] += value; } } // Use to avergae composition to update liquidCompositionToUpdate = sumComposition.Select(v => v / liquidCompositions.Count).ToArray(); } else { this.isDirty = false; continue; } } else { Debug.Assert(true, "Consumer type error!"); } foreach (var(value, index) in liquidCompositionToUpdate.WithIndex()) { string needName = ((LiquidComposition)index).ToString(); if (life.GetNeedValues().ContainsKey(needName)) { life.UpdateNeed(needName, value); //Debug.Log("Life: " + ((MonoBehaviour)life).gameObject.name + " updates need of type: " + needName + " with value " + value); } } } this.isDirty = false; }
/// <summary> /// Get the terrian conposition of the consumers and update the need values /// </summary> public override void UpdateSystem() { // Unmark dirty when there is not consumer then exit if (this.Consumers.Count == 0) { this.isDirty = false; return; } //All tiles in the grid that can be accessed by some population, organized by tiletype Dictionary <TileType, HashSet <Vector3Int> > accessibleTilesByTileType = new Dictionary <TileType, HashSet <Vector3Int> >(); //All populations currently in the level, organized by speciesType Dictionary <SpeciesType, HashSet <Population> > populationsBySpeciesType = new Dictionary <SpeciesType, HashSet <Population> >(); //Number of tiles needed by a given population Dictionary <Population, int> tilesNeeded = new Dictionary <Population, int>(); //Number of tiles allocated to a given population Dictionary <Population, Dictionary <TileType, int> > tilesAllocated = new Dictionary <Population, Dictionary <TileType, int> >(); int sumAllocatedTiles(Population population) { int sum = 0; foreach (TileType tile in tilesAllocated[population].Keys) { int tileContribution = (tile == TileType.Grass ? 2 : 1); sum += tilesAllocated[population][tile] * tileContribution; } return(sum); } //Organize the populations currently in the level by their SpeciesType foreach (Population population in Consumers.OfType <Population>()) { SpeciesType type = population.Species.Species; if (!populationsBySpeciesType.ContainsKey(type)) { populationsBySpeciesType.Add(type, new HashSet <Population>()); } populationsBySpeciesType[type].Add(population); } //Calculate the tiles needed for each population and add all the tiles that the population can access to the tile dictionary foreach (HashSet <Population> populations in populationsBySpeciesType.Values) { foreach (Population population in populations) { tilesNeeded[population] = population.Count * population.species.TerrainTilesRequired; tilesAllocated[population] = new Dictionary <TileType, int>(); foreach (Vector3Int position in GameManager.Instance.m_reservePartitionManager.GetLocationsWithAccess(population)) { TileType type = GameManager.Instance.m_tileDataController.GetTileData(position).currentTile.type; if (!accessibleTilesByTileType.ContainsKey(type)) { accessibleTilesByTileType.Add(type, new HashSet <Vector3Int>()); } accessibleTilesByTileType[type].Add(position); } } } //Iterate through all of the tiles and assign them to populations according to their dominance ratios foreach (TileType tile in terrainOrder) { //If there are no tiles of this tile type in the level, move on to the next tile type if (!accessibleTilesByTileType.ContainsKey(tile)) { continue; } //Get all of the populations that require this TileType List <Population> accessiblePopulations = new List <Population>(); foreach (SpeciesType species in dominanceRatiosByTileType[tile].Keys) { if (populationsBySpeciesType.ContainsKey(species)) { accessiblePopulations.AddRange(populationsBySpeciesType[species]); } } //If no populations require this tileType, move on to next tile type if (accessiblePopulations.Count == 0) { continue; } //Keep assigning tiles to the accessiblePopulations until either: // 1. All populations in accessiblePopulations have their terrain need satisfied, or // 2. There are no tiles left to assign while (true) { //Convert the remaining accessible tiles into a list so we can remove tiles from the HashSet as we iterate on it Vector3Int[] tileArray = accessibleTilesByTileType[tile].ToArray <Vector3Int>(); if (tileArray.Length == 0) { break; } //Calculate the number of tiles of the current tile type allocated to a population taking into account its dominance ratio (lower ratios return a higher weighted value, resulting in less total tiles received) float allocatedTilesWeighted(Population population) { if (!tilesAllocated[population].ContainsKey(tile)) { return(0); } return(tilesAllocated[population][tile] / dominanceRatiosByTileType[tile][population.species.Species]); } //Find the population with the least tiles allocated (weighted). Also find the next lowest weighted allocation, to use as a stopping place Population populationMostInNeed = null; float minAllocation = float.MaxValue; float secondLeastAllocation = float.MaxValue; foreach (Population population in accessiblePopulations) { //If this population has already satisfied its terrain need, move on to the next one if (sumAllocatedTiles(population) >= tilesNeeded[population]) { continue; } float allocation = allocatedTilesWeighted(population); if (allocation < minAllocation) { populationMostInNeed = population; secondLeastAllocation = minAllocation; minAllocation = allocation; } } //If there are no populations left that need the current tile type, move on to the next tile type if (populationMostInNeed == null) { break; } //Protect against the edge case where a population needs this terrain type but can't reach any of the tiles bool populationCanAccess = false; //Iterate through the array of all tiles of the current tile type. Stop if: // 1. There are no tiles left // 2. This population surpasses the next most-in-need population for tiles allocated // 3. This population has its terrain need satisfied int tileIndex = 0; while (tileIndex < tileArray.Length && allocatedTilesWeighted(populationMostInNeed) < secondLeastAllocation) // && sumAllocatedTiles(populationMostInNeed) < tilesNeeded[populationMostInNeed] { if (GameManager.Instance.m_reservePartitionManager.CanAccess(populationMostInNeed, tileArray[tileIndex])) { accessibleTilesByTileType[tile].Remove(tileArray[tileIndex]); if (!tilesAllocated[populationMostInNeed].ContainsKey(tile)) { tilesAllocated[populationMostInNeed][tile] = 0; } ++tilesAllocated[populationMostInNeed][tile]; populationCanAccess = true; } ++tileIndex; } //If we iterate through all of the current tile type and this population couldn't reach a single tile, remove them from the list of accessible populations if (!populationCanAccess) { accessiblePopulations.Remove(populationMostInNeed); } } } //Go through each tile type and update the need value for that tile for each population that needs it foreach (TileType tile in terrainOrder) { foreach (HashSet <Population> populationSet in populationsBySpeciesType.Values) { foreach (Population population in populationSet) { string needName = tile.ToString(); if (tilesAllocated[population].ContainsKey(tile)) { population.UpdateNeed(needName, tilesAllocated[population][tile] * (tile == TileType.Grass ? 2 : 1)); //Debug.Log(needName + " tiles allocated to " + population.Species.SpeciesName + ": " + tilesAllocated[population][tile]); } } } } foreach (FoodSource foodSource in Consumers.OfType <FoodSource>()) { int[] terrainCountsByType = new int[(int)TileType.TypesOfTiles]; terrainCountsByType = GameManager.Instance.m_tileDataController.CountOfTilesUnderSpecies(GameManager.Instance.m_tileDataController.WorldToCell(foodSource.GetPosition()), foodSource.Species); // Update need values foreach (var(count, index) in terrainCountsByType.WithIndex()) { string needName = ((TileType)index).ToString(); if (needName.Equals("Liquid")) { continue; } if (foodSource.GetNeedValues().ContainsKey(needName)) { //Debug.Log(foodSource.name + " updated " + needName + " with value: " + count); foodSource.UpdateNeed(needName, count); } } } this.isDirty = false; }