Skip to content

Commit

Permalink
Add initial support for rendering citizens and specialists
Browse files Browse the repository at this point in the history
This doesn't yet handle citizen moods (everyone is rendered as content), doesn't handle citizen gender, and doesn't handle the "region" of the civ. But it does allow us to see specialists, which will be important for tile assignment - we need to be able to turn a citizen into an entertainer to support some types of tile reassignment.

#541
  • Loading branch information
TomWerner committed Feb 12, 2025
1 parent 6452d61 commit 5703463
Show file tree
Hide file tree
Showing 7 changed files with 118 additions and 15 deletions.
96 changes: 93 additions & 3 deletions C7/UIElements/CityScreen/CityScreen.cs
Original file line number Diff line number Diff line change
Expand Up @@ -14,10 +14,12 @@ public partial class CityScreen : CenterContainer {
private ILogger log = LogManager.ForContext<CityScreen>();
public TileAssignmentLayer tileAssignmentLayer;
public MapView mapView;
private TextureRect background;
private List<TextureButton> 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);
Expand Down Expand Up @@ -48,15 +50,16 @@ 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);
}
}
}
}
}
}

private void HandleReassignment(Tile tile) {
private void HandleReassignment(Tile tile, List<CitizenType> citizenTypes) {
City city = tileAssignmentLayer.city;

// We can't assign citizens to other cities.
Expand Down Expand Up @@ -110,6 +113,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
};
Expand All @@ -126,5 +130,91 @@ public void HideScreen() {
private void OnShowCityScreen(ParameterWrapper<City> 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<CityResident> defaultResidents = city.residents.FindAll(x => x.citizenType.IsDefaultCitizen);
List<CityResident> 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;
}
}
}
1 change: 1 addition & 0 deletions C7Engine/EntryPoints/CityInteractions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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) {
Expand Down
1 change: 1 addition & 0 deletions C7Engine/EntryPoints/TurnHandling.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down
4 changes: 3 additions & 1 deletion C7GameData/CityResident.cs
Original file line number Diff line number Diff line change
@@ -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;
Expand Down
25 changes: 16 additions & 9 deletions C7GameData/ImportCiv3.cs
Original file line number Diff line number Diff line change
Expand Up @@ -518,15 +518,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);
}
Expand Down Expand Up @@ -786,7 +793,7 @@ private void ImportCitizenTypes() {
Construction = c.Construction
};
if (!ct.IsDefaultCitizen) {
ct.SpecialistIndex = i - 1;
ct.SpecialistIndex = i;
}
if (c.Prerequisite > -1) {
ct.PrerequisiteTech = save.Techs[c.Prerequisite].id;
Expand Down
4 changes: 3 additions & 1 deletion C7GameData/Save/SaveCity.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

namespace C7GameData.Save {
public class SaveCityResident {
public ID citizenType;
public string nationality;
public ID city;
public TileLocation tileWorked;
Expand Down Expand Up @@ -42,7 +43,7 @@ public SaveCity(City city) {
});
}

public City ToCity(GameMap gameMap, List<Player> players, List<UnitPrototype> unitPrototypes, List<Civilization> civilizations) {
public City ToCity(GameMap gameMap, List<Player> players, List<UnitPrototype> unitPrototypes, List<Civilization> civilizations, List<CitizenType> citizenTypes) {
City city = new City{
id = id,
location = gameMap.tileAt(location.X, location.Y),
Expand All @@ -58,6 +59,7 @@ public City ToCity(GameMap gameMap, List<Player> players, List<UnitPrototype> 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,
Expand Down
2 changes: 1 addition & 1 deletion C7GameData/Save/SaveGame.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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 => {
Expand Down

0 comments on commit 5703463

Please sign in to comment.