Skip to content

Commit

Permalink
Merge pull request #549 from C7-Game/twrner/render-citizens
Browse files Browse the repository at this point in the history
Add initial support for rendering citizens and specialists
  • Loading branch information
TomWerner authored Feb 15, 2025
2 parents ed06d91 + 96fa1fa commit 82acab2
Show file tree
Hide file tree
Showing 7 changed files with 118 additions and 14 deletions.
97 changes: 94 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 @@ -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;
}

Expand All @@ -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
};
Expand All @@ -128,5 +133,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
23 changes: 15 additions & 8 deletions C7GameData/ImportCiv3.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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);
}
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 82acab2

Please sign in to comment.