Esempio n. 1
0
    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;
    }
Esempio n. 2
0
    /// <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;
    }