public void Update(float delta_time) { DiagnosticsManager.Instance.Message(GetType(), "--- UPDATE STARTS ---"); DiagnosticsManager.Instance.Start(GetType(), "total", "Total"); DiagnosticsManager.Instance.Start(GetType(), "expeditions", "Expeditions"); if (grace_time_remaining > 0.0f) { grace_time_remaining = Math.Max(grace_time_remaining - TimeManager.Instance.Seconds_To_Days(delta_time), 0.0f); } //TODO: Add stopwatch? //Expeditions foreach (Expedition expedition in Expeditions) { expedition.Update(delta_time); } DiagnosticsManager.Instance.End(GetType(), "expeditions"); DiagnosticsManager.Instance.Start(GetType(), "buildings", "Buildings"); //Buildings foreach (Building building in Buildings) { if (building is Residence) { (building as Residence).Update(delta_time); } else { building.Update(delta_time); } } foreach (Building building in removed_buildings) { Buildings.Remove(building); } removed_buildings.Clear(); foreach (Building building in added_buildings) { Buildings.Add(building); } added_buildings.Clear(); foreach (Expedition expedition in removed_expeditions) { Expeditions.Remove(expedition); } removed_expeditions.Clear(); foreach (Expedition expedition in added_expeditions) { Expeditions.Add(expedition); } added_expeditions.Clear(); DiagnosticsManager.Instance.End(GetType(), "buildings"); DiagnosticsManager.Instance.Start(GetType(), "statistics", "Update statistics"); //Update statistics foreach (Resource resource in Resource.All) { Resource_Totals[resource] = 0.0f; Usable_Resource_Totals[resource] = 0.0f; Resource_Max_Storage[resource] = 0.0f; Resource_Delta[resource] = 0.0f; } Cash_Delta = 0.0f; Food_Current = 0.0f; Food_Max = 0.0f; Food_Produced = 0.0f; Food_Consumed = 0.0f; Food_Delta = 0.0f; Dictionary <Building.Resident, int> current_population = new Dictionary <Building.Resident, int>(); Dictionary <Building.Resident, int> max_population = new Dictionary <Building.Resident, int>(); Dictionary <Building.Resident, float> happiness = new Dictionary <Building.Resident, float>(); Dictionary <Building.Resident, int> workers_required = new Dictionary <Building.Resident, int>(); Dictionary <Building.Resident, int> available_workers = new Dictionary <Building.Resident, int>(); Dictionary <Building.Resident, float> total_education = new Dictionary <Building.Resident, float>(); foreach (Building.Resident resident in Enum.GetValues(typeof(Building.Resident))) { current_population.Add(resident, 0); max_population.Add(resident, 0); happiness.Add(resident, 0.0f); workers_required.Add(resident, 0); available_workers.Add(resident, 0); total_education.Add(resident, 0.0f); } foreach (Building building in Buildings) { foreach (KeyValuePair <Resource, float> pair in building.Storage) { Resource_Totals[pair.Key] += pair.Value; Usable_Resource_Totals[pair.Key] += pair.Value; if (pair.Key.Is_Food) { Food_Current += pair.Value; } } foreach (KeyValuePair <Resource, float> pair in building.Input_Storage) { Resource_Totals[pair.Key] += pair.Value; if (pair.Key.Is_Food) { Food_Current += pair.Value; } } foreach (KeyValuePair <Resource, float> pair in building.Output_Storage) { Resource_Totals[pair.Key] += pair.Value; if (pair.Key.Is_Food) { Food_Current += pair.Value; } } foreach (KeyValuePair <Resource, float> pair in building.Per_Day_Resource_Delta) { Resource_Delta[pair.Key] += pair.Value; } foreach (KeyValuePair <Resource, float> pair in building.Total_Max_Storage) { Resource_Max_Storage[pair.Key] += pair.Value; if (pair.Key.Is_Food) { Food_Max += pair.Value; } } Cash_Delta += building.Per_Day_Cash_Delta; Food_Produced += building.Food_Production_Per_Day; Food_Delta += building.Food_Production_Per_Day; if (building is Residence) { Food_Consumed += (building as Residence).Food_Consumed; Food_Delta -= (building as Residence).Food_Consumed; } if (building.Is_Complete) { if (building is Residence && building.Is_Operational) { Residence residence = building as Residence; foreach (Building.Resident resident in Enum.GetValues(typeof(Building.Resident))) { current_population[resident] += residence.Current_Residents[resident]; available_workers[resident] += residence.Available_Work_Force[resident]; max_population[resident] += residence.Resident_Space[resident]; happiness[resident] += residence.Happiness[resident] * residence.Current_Residents[resident]; total_education[resident] += residence.Education(resident) * residence.Current_Residents[resident]; } } if (building.Requires_Workers && building.Is_Complete && (building.Is_Connected || !building.Requires_Connection) && (!building.Is_Paused || PAUSED_BUILDINGS_KEEP_WORKERS)) { foreach (Building.Resident resident in Enum.GetValues(typeof(Building.Resident))) { workers_required[resident] += building.Worker_Settings[resident]; } } } } DiagnosticsManager.Instance.End(GetType(), "statistics"); DiagnosticsManager.Instance.Start(GetType(), "workers", "Allocate workers"); //Allocate workers Dictionary <Building.Resident, float> worker_ratios = new Dictionary <Building.Resident, float>(); Dictionary <Building.Resident, int> workers_allocated = new Dictionary <Building.Resident, int>(); foreach (Building.Resident resident in Enum.GetValues(typeof(Building.Resident))) { worker_ratios.Add(resident, workers_required[resident] == 0 ? 0.0f : current_population[resident] / (float)workers_required[resident]); workers_allocated.Add(resident, 0); } foreach (Building building in Buildings) { if (!building.Requires_Workers) { continue; } foreach (Building.Resident resident in Enum.GetValues(typeof(Building.Resident))) { building.Current_Workers[resident] = 0; } if (!(building.Is_Complete && (building.Is_Connected || !building.Requires_Connection) && (!building.Is_Paused || PAUSED_BUILDINGS_KEEP_WORKERS))) { continue; } if (building.Is_Complete && (building.Is_Connected || !building.Requires_Connection) && (!building.Is_Paused || PAUSED_BUILDINGS_KEEP_WORKERS)) { foreach (Building.Resident resident in Enum.GetValues(typeof(Building.Resident))) { int workers = Mathf.RoundToInt(building.Worker_Settings[resident] * Mathf.Clamp(worker_ratios[resident], 0.0f, 1.0f)); if (workers > available_workers[resident]) { workers = available_workers[resident]; } building.Current_Workers[resident] = workers; available_workers[resident] -= workers; workers_allocated[resident] += workers; if (available_workers[resident] < 0) { CustomLogger.Instance.Error("More workers allocated, than there is available workers"); } } } } foreach (Building building in Buildings) { if (!building.Requires_Workers || !(building.Is_Complete && (building.Is_Connected || !building.Requires_Connection) && (!building.Is_Paused || PAUSED_BUILDINGS_KEEP_WORKERS))) { continue; } foreach (Building.Resident resident in Enum.GetValues(typeof(Building.Resident))) { if (available_workers[resident] > 0 && worker_ratios[resident] < 1.0f && building.Current_Workers[resident] < building.Worker_Settings[resident]) { building.Current_Workers[resident]++; available_workers[resident]--; workers_allocated[resident]++; } } } foreach (Building.Resident resident in Enum.GetValues(typeof(Building.Resident))) { if (available_workers[resident] > 0 && workers_allocated[resident] < workers_required[resident]) { CustomLogger.Instance.Error("Worker allocation mismatch, not enough allocated"); } if (workers_allocated[resident] > workers_required[resident]) { CustomLogger.Instance.Error("Worker allocation mismatch, too many allocated"); } if (workers_allocated[resident] > current_population[resident]) { CustomLogger.Instance.Error("Worker allocation mismatch, allocated > population"); } } DiagnosticsManager.Instance.End(GetType(), "workers"); DiagnosticsManager.Instance.Start(GetType(), "walkers", "Spawn walkers"); //Spawn walkers int max = Spawn_Walkers ? Mathf.Min(Mathf.RoundToInt((current_population[Building.Resident.Peasant] + current_population[Building.Resident.Citizen] + current_population[Building.Resident.Noble]) / 30.0f) + 1, 10) : 0; if (Walkers.Count < max) { List <Building> possible_buildings = Buildings.Where(x => x.Is_Connected && x.Requires_Connection && x.Is_Operational && !x.Is_Road && x.Entities_Spawned.Count == 0).ToList(); if (possible_buildings.Count > 25) //TODO //TODO: Implement faster pathfinding A* ? { if (Buildings.Count <= 1) { //Use pathfinding for paths Building spawner = RNG.Instance.Item(possible_buildings); possible_buildings.Remove(spawner); if (possible_buildings.Count >= 1) { Building road = null; foreach (Building b in Map.Instance.Get_Buildings_Around(spawner)) { if (b.Is_Road && b.Is_Built && b.Is_Connected) { road = b; break; } } if (road != null) { Building target_building = RNG.Instance.Item(possible_buildings);//TODO: allow x.Entities_Spawned.Count != 0 in this list Building target_road = null; foreach (Building b in Map.Instance.Get_Buildings_Around(target_building)) { if (b.Is_Road && b.Is_Built && b.Is_Connected) { target_road = b; break; } } if (target_road != null) { List <PathfindingNode> path = Pathfinding.Path(Map.Instance.Road_Pathing, road.Tile.PathfindingNode, target_road.Tile.Road_PathfindingNode, false); if (path.Count > 2) { Entity walker = new Entity(EntityPrototypes.Instance.Get("walker"), road.Tile, spawner); walker.Set_Path(path, 0.5f); Walkers.Add(walker); } } } } } else { //Use connected buildings for paths Tile spawn = null; Building spawner = null; List <PathfindingNode> path = Entity.Find_Walker_City_Path(out spawn, out spawner); if (path != null) { Entity walker = new Entity(EntityPrototypes.Instance.Get("walker"), spawn, spawner); walker.Set_Path(path, 0.5f); Walkers.Add(walker); } } } } DiagnosticsManager.Instance.End(GetType(), "walkers"); DiagnosticsManager.Instance.Start(GetType(), "ships", "Spawn ships"); //Spawn ships if (Map.Instance.Ship_Spawns.Count > 0) { ship_spawn_cooldown -= delta_time * TimeManager.Instance.Multiplier; if (ship_spawn_cooldown <= 0.0f) { ship_spawn_cooldown += 120.0f * (200.0f / (100.0f + ((0.5f * current_population[Building.Resident.Peasant]) + (1.5f * current_population[Building.Resident.Citizen]) + current_population[Building.Resident.Noble]))) * (RNG.Instance.Next(50, 150) * 0.01f); foreach (Building building in Buildings) { if (!building.Data.ContainsKey(Building.DOCK_ID_KEY)) { continue; } Building dock = Buildings.FirstOrDefault(x => x.Id == long.Parse(building.Data[Building.DOCK_ID_KEY])); if (dock == null || ships.ContainsKey(dock) || building.Tags.Contains(Building.Tag.Creates_Expeditions) || !building.Is_Operational) { continue; } Tile spawn = RNG.Instance.Item(Map.Instance.Ship_Spawns); Tile target = null; foreach (Tile t in Map.Instance.Get_Adjanced_Tiles(dock.Tile).Select(x => x.Value).ToArray()) { if (t.Building == null && t.Has_Ship_Access) { target = t; break; } } if (target == null) { break; } List <PathfindingNode> path = Pathfinding.Path(Map.Instance.Ship_Pathing, spawn.Ship_PathfindingNode, target.Ship_PathfindingNode, false); if (path.Count > 2) { List <PathfindingNode> return_path = Pathfinding.Path(Map.Instance.Ship_Pathing, target.Ship_PathfindingNode, spawn.Ship_PathfindingNode, false); path.AddRange(return_path.Where(x => !x.Coordinates.Equals(target.Coordinates)).ToList()); Entity ship = new Entity(EntityPrototypes.Instance.Get("ship"), spawn, dock); ship.Set_Path(path, 0.75f); ship.Add_Order(new Entity.PathOrder(target.Ship_PathfindingNode, 20.0f)); ships.Add(dock, ship); } } } } DiagnosticsManager.Instance.End(GetType(), "ships"); DiagnosticsManager.Instance.Start(GetType(), "gui", "Update GUI"); //GUI int peasant_current = current_population[Building.Resident.Peasant]; int peasant_max = max_population[Building.Resident.Peasant]; float peasant_happiness = peasant_current > 0 ? happiness[Building.Resident.Peasant] / peasant_current : 0.0f; Happiness[Building.Resident.Peasant] = peasant_happiness; int peasant_employment = workers_allocated[Building.Resident.Peasant] - workers_required[Building.Resident.Peasant] + available_workers[Building.Resident.Peasant]; float peasant_employment_relative = peasant_current == 0 ? 0.0f : peasant_employment / (float)peasant_current; Unemployment[Building.Resident.Peasant] = peasant_employment_relative > 0.0f ? peasant_employment_relative : 0.0f; Education[Building.Resident.Peasant] = peasant_current == 0 ? 0.0f : total_education[Building.Resident.Peasant] / peasant_current; int citizen_current = current_population[Building.Resident.Citizen]; int citizen_max = max_population[Building.Resident.Citizen]; float citizen_happiness = citizen_current > 0 ? happiness[Building.Resident.Citizen] / citizen_current : 0.0f; Happiness[Building.Resident.Citizen] = citizen_happiness; int citizen_employment = workers_allocated[Building.Resident.Citizen] - workers_required[Building.Resident.Citizen] + available_workers[Building.Resident.Citizen]; float citizen_employment_relative = citizen_current == 0 ? 0.0f : citizen_employment / (float)citizen_current; Unemployment[Building.Resident.Citizen] = citizen_employment_relative > 0.0f ? citizen_employment_relative : 0.0f; Education[Building.Resident.Citizen] = citizen_current == 0 ? 0.0f : total_education[Building.Resident.Citizen] / citizen_current; int noble_current = current_population[Building.Resident.Noble]; int noble_max = max_population[Building.Resident.Noble]; float noble_happiness = noble_current > 0 ? happiness[Building.Resident.Noble] / noble_current : 0.0f; Happiness[Building.Resident.Noble] = noble_happiness; int noble_employment = workers_allocated[Building.Resident.Noble] - workers_required[Building.Resident.Noble] + available_workers[Building.Resident.Noble]; float noble_employment_relative = noble_current == 0 ? 0.0f : noble_employment / (float)noble_current; Unemployment[Building.Resident.Noble] = noble_employment_relative > 0.0f ? noble_employment_relative : 0.0f; Education[Building.Resident.Noble] = noble_current == 0 ? 0.0f : total_education[Building.Resident.Noble] / noble_current; noble_count = noble_current; TopGUIManager.Instance.Update_City_Info(Name, Cash, Cash_Delta, Mathf.RoundToInt(Usable_Resource_Totals[Resource.Wood]), Mathf.RoundToInt(Usable_Resource_Totals[Resource.Lumber]), Mathf.RoundToInt(Usable_Resource_Totals[Resource.Stone]), Mathf.RoundToInt(Usable_Resource_Totals[Resource.Bricks]), Mathf.RoundToInt(Usable_Resource_Totals[Resource.Tools]), Mathf.RoundToInt(Usable_Resource_Totals[Resource.Marble]), Mathf.RoundToInt(Usable_Resource_Totals[Resource.Mechanisms]), Mathf.RoundToInt(Usable_Resource_Totals[Resource.Glass]), peasant_current, peasant_max, peasant_happiness, peasant_employment_relative, peasant_employment, citizen_current, citizen_max, citizen_happiness, citizen_employment_relative, citizen_employment, noble_current, noble_max, noble_happiness, noble_employment_relative, noble_employment); DiagnosticsManager.Instance.End(GetType(), "gui"); DiagnosticsManager.Instance.End(GetType(), "total"); }