diff --git a/C7/UIElements/CityScreen/CityScreen.cs b/C7/UIElements/CityScreen/CityScreen.cs index 44f1f33e..8f20ae85 100644 --- a/C7/UIElements/CityScreen/CityScreen.cs +++ b/C7/UIElements/CityScreen/CityScreen.cs @@ -14,10 +14,12 @@ public partial class CityScreen : CenterContainer { private ILogger log = LogManager.ForContext(); public TileAssignmentLayer tileAssignmentLayer; public MapView mapView; + private TextureRect background; + private List popHeads = new(); // Called when the node enters the scene tree for the first time. public override void _Ready() { - TextureRect background = new() { + background = new() { Texture = Util.LoadTextureFromPCX("Art/city screen/background.pcx") }; AddChild(background); @@ -48,7 +50,8 @@ public override void _UnhandledInput(InputEvent @event) { using (UIGameDataAccess gameDataAccess = new()) { Tile tile = mapView.tileOnScreenAt(gameDataAccess.gameData.map, eventMouseButton.Position); if (tile != null) { - HandleReassignment(tile); + HandleReassignment(tile, gameDataAccess.gameData.citizenTypes); + RenderPopHeads(tileAssignmentLayer.city); } } } @@ -56,7 +59,7 @@ public override void _UnhandledInput(InputEvent @event) { } } - private void HandleReassignment(Tile tile) { + private void HandleReassignment(Tile tile, List citizenTypes) { City city = tileAssignmentLayer.city; // We can't assign citizens to other cities. @@ -99,6 +102,7 @@ private void HandleReassignment(Tile tile) { worst.tileWorked.personWorkingTile = null; worst.tileWorked = tile; tile.personWorkingTile = worst; + worst.citizenType = citizenTypes.Find(x => x.IsDefaultCitizen); return; } @@ -112,6 +116,7 @@ private void HandleReassignment(Tile tile) { for (int i = 0; i < numResidents; ++i) { CityResident newResident = new() { + citizenType = citizenTypes.Find(x => x.IsDefaultCitizen), nationality = city.owner.civilization, city = city }; @@ -128,5 +133,91 @@ public void HideScreen() { private void OnShowCityScreen(ParameterWrapper city) { this.Show(); tileAssignmentLayer.city = city.Value; + RenderPopHeads(city.Value); + } + + private void RenderPopHeads(City city) { + // Reset any old heads. + foreach (TextureButton head in popHeads) { + background.RemoveChild(head); + head.QueueFree(); + } + popHeads.Clear(); + + int eraNum = 0; + if (city.owner.eraCivilopediaName == "ERAS_Ancient_Times") { + eraNum = 0; + } else if (city.owner.eraCivilopediaName == "ERAS_Middle_Ages") { + eraNum = 1; + } else if (city.owner.eraCivilopediaName == "ERAS_Industrial_Age") { + eraNum = 2; + } else if (city.owner.eraCivilopediaName == "ERAS_Modern_Era") { + eraNum = 3; + } + + // The pop head textures are 50 x 50, but have a 1px border on all sides + // + // The texture file has 16 rows of the default citizen, in groups of 4 + // per era (content, happy, resisting, unhappy). There are 10 columns, + // the first 5 are male heads of different regions for civs, the other 5 + // are female heads. + // + // After the 16 rows of default citizens there is one row per specialist + // type, and again 10 columns per row. This time they are + // (ancient, middle, industrial, modern, blank) for male and female heads + // + // TODO: handle citizen moods + // TODO: handle per-civ regions + // TODO: handle male/female citizens + + // Start by splitting the default residents from the specialists, since + // they are spaced apart in the UI. + List defaultResidents = city.residents.FindAll(x => x.citizenType.IsDefaultCitizen); + List specialists = city.residents.FindAll(x => !x.citizenType.IsDefaultCitizen); + + // Each head is 48px, so leave a 1 head gap if we have specialists. + int width = city.residents.Count * 48; + if (specialists.Count > 0) { + width += 48; + } + + // Track the x position of each head so that we're centered in the screen + int xPos = background.Texture.GetWidth() / 2 + -width / 2; + + // Add each of the default citizens. These are buttons with the idea that + // we can eventually support clicking on the heads to view details, such + // as the reason for unhappiness. + foreach (CityResident cr in defaultResidents) { + TextureButton tb = new(); + tb.TextureNormal = Util.LoadTextureFromPCX("Art/SmallHeads/popHeads.pcx", + 0 + 1, 200 * eraNum + 1, 48, 48); + tb.SetPosition(new Vector2(xPos, 440)); + background.AddChild(tb); + popHeads.Add(tb); + xPos += 48; + } + + // Add space before specialists. + xPos += 48; + + // Add each of the specialists. + // + // TODO: When clicking a specialist, have it iterate through the types + // of specialists known to the player. + // + // TODO: Render the specialist effect (like a smiley for entertainers) + // in the corner of the head. + foreach (CityResident cr in specialists) { + TextureButton tb = new(); + int textX = 50 * eraNum; + int numRowsOfLaborers = 16; + int textY = 50 * numRowsOfLaborers + 50 * (cr.citizenType.SpecialistIndex - 1); + tb.TextureNormal = Util.LoadTextureFromPCX("Art/SmallHeads/popHeads.pcx", + textX + 1, textY + 1, 48, 48); + tb.SetPosition(new Vector2(xPos, 440)); + background.AddChild(tb); + popHeads.Add(tb); + xPos += 48; + } } } diff --git a/C7Engine/EntryPoints/CityInteractions.cs b/C7Engine/EntryPoints/CityInteractions.cs index f04cf3c4..b90997e4 100644 --- a/C7Engine/EntryPoints/CityInteractions.cs +++ b/C7Engine/EntryPoints/CityInteractions.cs @@ -12,6 +12,7 @@ public static City BuildCity(int X, int Y, ID playerID, string name) { City newCity = new City(tileWithNewCity, owner, name, gameData.ids.CreateID("city")); CityResident firstResident = new CityResident(); firstResident.city = newCity; + firstResident.citizenType = gameData.citizenTypes.Find(x => x.IsDefaultCitizen); CityTileAssignmentAI.AssignNewCitizenToTile(firstResident); newCity.SetItemBeingProduced(CityProductionAI.GetNextItemToBeProduced(newCity, null)); if (owner.cities.Count == 0) { diff --git a/C7Engine/EntryPoints/TurnHandling.cs b/C7Engine/EntryPoints/TurnHandling.cs index c289c91e..95e7ce15 100644 --- a/C7Engine/EntryPoints/TurnHandling.cs +++ b/C7Engine/EntryPoints/TurnHandling.cs @@ -150,6 +150,7 @@ private static void HandleCityResults(GameData gameData) { CityResident newResident = new CityResident(); newResident.nationality = city.owner.civilization; newResident.city = city; + newResident.citizenType = gameData.citizenTypes.Find(x => x.IsDefaultCitizen); CityTileAssignmentAI.AssignNewCitizenToTile(newResident); } else if (newSize < initialSize) { int diff = initialSize - newSize; diff --git a/C7GameData/CityResident.cs b/C7GameData/CityResident.cs index e1124386..6ea30088 100644 --- a/C7GameData/CityResident.cs +++ b/C7GameData/CityResident.cs @@ -1,6 +1,8 @@ namespace C7GameData { public class CityResident { - //Specialist type + public CitizenType citizenType; + + // Only relevant if citizenType.IsDefaultCitizen == true public Tile tileWorked = Tile.NONE; public Civilization nationality; public City city; diff --git a/C7GameData/ImportCiv3.cs b/C7GameData/ImportCiv3.cs index 8347d73b..e93183c9 100644 --- a/C7GameData/ImportCiv3.cs +++ b/C7GameData/ImportCiv3.cs @@ -520,15 +520,22 @@ private void ImportSavCities() { }; foreach (QueryCiv3.Sav.CTZN ctzn in savData.CityCtzn[i]) { - if (ctzn.TileWorked == 0) { - // TODO: handle resistors and specialists - continue; + if (ctzn.Type == 4) { // Specialist + SaveCityResident scr = new(); + scr.city = saveCity.id; + scr.nationality = save.Civilizations[ctzn.Nationality].name; + scr.citizenType = save.CitizenTypes.Find(x => x.SpecialistIndex == ctzn.SpecialistType).Id; + saveCity.residents.Add(scr); + } else if (ctzn.TileWorked == 0) { + // TODO: handle resistors + } else { + SaveCityResident scr = new(); + scr.city = saveCity.id; + scr.tileWorked = GetTileFromSpiral(saveCity.location, ctzn.TileWorked); + scr.nationality = save.Civilizations[ctzn.Nationality].name; + scr.citizenType = save.CitizenTypes.Find(x => x.IsDefaultCitizen).Id; + saveCity.residents.Add(scr); } - SaveCityResident scr = new(); - scr.city = saveCity.id; - scr.tileWorked = GetTileFromSpiral(saveCity.location, ctzn.TileWorked); - scr.nationality = save.Civilizations[ctzn.Nationality].name; - saveCity.residents.Add(scr); } save.Cities.Add(saveCity); } diff --git a/C7GameData/Save/SaveCity.cs b/C7GameData/Save/SaveCity.cs index ab653b6b..5987d711 100644 --- a/C7GameData/Save/SaveCity.cs +++ b/C7GameData/Save/SaveCity.cs @@ -2,6 +2,7 @@ namespace C7GameData.Save { public class SaveCityResident { + public ID citizenType; public string nationality; public ID city; public TileLocation tileWorked; @@ -42,7 +43,7 @@ public SaveCity(City city) { }); } - public City ToCity(GameMap gameMap, List players, List unitPrototypes, List civilizations) { + public City ToCity(GameMap gameMap, List players, List unitPrototypes, List civilizations, List citizenTypes) { City city = new City{ id = id, location = gameMap.tileAt(location.X, location.Y), @@ -58,6 +59,7 @@ public City ToCity(GameMap gameMap, List players, List un city.residents = residents.ConvertAll(resident => { return new CityResident { + citizenType = citizenTypes.Find(x => x.Id == resident.citizenType), nationality = civilizations.Find(civ => civ.name == resident.nationality), tileWorked = gameMap.tileAt(resident.tileWorked.X, resident.tileWorked.Y), city = city, diff --git a/C7GameData/Save/SaveGame.cs b/C7GameData/Save/SaveGame.cs index f12525ee..65cc0197 100644 --- a/C7GameData/Save/SaveGame.cs +++ b/C7GameData/Save/SaveGame.cs @@ -113,7 +113,7 @@ public GameData ToGameData() { }); // cities require game map for location and players for city owner - data.cities = Cities.ConvertAll(city => city.ToCity(data.map, data.players, UnitPrototypes, Civilizations)); + data.cities = Cities.ConvertAll(city => city.ToCity(data.map, data.players, UnitPrototypes, Civilizations, CitizenTypes)); // Once cities are known, players can reference cities. data.players.ForEach(player => {