kendallroth / cs2-city-stats

Mod to view city statistics
https://mods.paradoxplaza.com/mods/85284/Windows
1 stars 2 forks source link

Display homelessness count #17

Open kendallroth opened 3 months ago

kendallroth commented 3 months ago

With the current issues with homelessness, it could be nice to show a homeless count/percentage. However, this data is not currently exposed to the UI, and may not even be calculated behind the scenes? Once implemented, the displayed value will also need to be determined, as a percentage of entire population may not be super beneficial at massive populations (then again, it is a very similar idea to unemployment, and could benefit from #16).

This will require some additional ECS work, particularly with Queries, but may also require a Job (depending on approach).

Note that unemployment does not necessarily indicate homeless (due to temporary unemployment allowance), but might help pare down list of citizens?

There are several mods that are already calculating this, including ByeByeHomeless and CimCensus. However, both of these mods seem a little bit overkill for simply calculating the number of homeless in the city, but they may be the only viable options...

kendallroth commented 3 months ago

"Scene Explorer" mod is useful in-game for selecting an entity and viewing its components. From investigations there are a couple of potential options:

[!Warning] Some information about HomelessHousehold has been adjusted!

kendallroth commented 3 months ago

Game.UI.InGame.CitizenUIUtils.cs has an example (GetCitizenConditions()) of indicating whether a citizen is homeless (for selected citizen info screen).

// From 'StatusSection', which inherits 'InfoSectionBase', which inherits 'UISystemBase'
Entity selectedEntity;  // From 'InfoSectionBase' property

EntityManager entityManager = ((ComponentSystemBase)this).EntityManager;
Citizen componentData = ((EntityManager)(ref entityManager)).GetComponentData<Citizen>(selectedEntity);
entityManager = ((ComponentSystemBase)this).EntityManager;
HouseholdMember componentData2 = ((EntityManager)(ref entityManager)).GetComponentData<HouseholdMember>(selectedEntity);
conditions = CitizenUIUtils.GetCitizenConditions(((ComponentSystemBase)this).EntityManager, selectedEntity, componentData, componentData2, conditions);
public static NativeList<CitizenCondition> GetCitizenConditions(
    EntityManager entityManager,
    Entity entity,
    Citizen citizen,
    HouseholdMember householdMember,
    NativeList<CitizenCondition> conditions
)
{
    PropertyRenter propertyRenter = default(PropertyRenter);
    if (
        !((EntityManager)(ref entityManager)).HasComponent<CommuterHousehold>(householdMember.m_Household) &&
        !((EntityManager)(ref entityManager)).HasComponent<TouristHousehold>(householdMember.m_Household) &&
        (
            !EntitiesExtensions.TryGetComponent<PropertyRenter>(entityManager, householdMember.m_Household, ref propertyRenter) ||
            ((EntityManager)(ref entityManager)).HasComponent<Game.Buildings.Park>(propertyRenter.m_Property))
        )
    {
        CitizenCondition citizenCondition = new CitizenCondition(CitizenConditionKey.Homeless);
        conditions.Add(ref citizenCondition);
    }
}
kendallroth commented 3 months ago

Game.Creatures.CreatureUtils.cs has an example (GetConditions) of checking if a human is homeless. However, this does not necessarily correspond with whether the game considers them homeless, as even with this flag some citizens do not display the "Homeless" condition when selected (due to having PropertyRenter component?).

public static ActivityCondition GetConditions(Human human)
{
    if ((human.m_Flags & HumanFlags.Homeless) != 0)
    { ... }
}

Game.Debug.DebugSystem.cs has an example (RemoveAllHomeless) of querying for homeless households (and adding a Deleted component).

private void RemoveAllHomeless()
{
    EntityManager entityManager = ((ComponentSystemBase)this).EntityManager;
    EntityQuery val = ((EntityManager)(ref entityManager)).CreateEntityQuery((ComponentType[])(object)new ComponentType[1] { ComponentType.ReadOnly<HomelessHousehold>() });
    entityManager = ((ComponentSystemBase)this).EntityManager;
    ((EntityManager)(ref entityManager)).AddComponent<Deleted>(val);
}

Game.Simulation.CountHouseholdDataSystem.cs has an example (Accumulate) of counting homeless households via iteration in job.

Game.UI.InGame.DeveloperInfoUISystem.cs has an example appearing to reference a homeless count, but unfortunately it was only counting homeless within selected park (in debug mode) 🤷.

kendallroth commented 3 months ago

Ran some queries in Scene Explorer to compare with in-game (population 12,127)

Component Value
Citizen 12,709
HouseholdMember 12,709
Household 5,837
HouseholdCitizen 5,837
PropertyRenter 5,613
Human 3,112
HomelessHousehold 52
TouristHousehold 4
CommuterHousehold 0
Renter 897
Resident 3,112

less useful stats

Component Value
Temp 0
Deleted 0
Criminal 10 ✔️
Creature 4,107
HouseholdPet 3,223
Pet 981
kendallroth commented 3 months ago

Old code comments during experimentation

            // Homeless Person
            //   Components:
            //      - Game.Creatures.Human [m_Flags=Homeless]
            //      - Game.Creatures.Resident [m_Flags=Arrive,Hangaround,IgnoreBenches]
            //        - m_Citizen: Entity(78485:1 - Citizen Male)
            //          - Components:
            //            - Game.Citizens.HouseholdMember (single household)
            //              - m_Household: Entity(95261:1 - Single Household)
            //                - Components:
            //                  - Game.Prefabs.PrefabRef - DynamicHousehold
            //                  - Game.Buildings.PropertyRenter
            //                    - m_Rent: 0
            //                  - Game.Citizens.HomelessHousehold
            //                    - m_TempHome (park entity)
            //                  - Game.Citizens.Household [m_Flags=MovedIn]
            //            - Game.Citizens.Citizen
            //              - m_UnemploymentCounter: 0 (not helpful?)
            //            - Game.Citizens.Arrived (tag)
            //       - Game.Prefabs.PrefabRef - Elderly_Male
            //         - Game.Prefabs.ResidentData
            //           - m_Age: Elderly
kendallroth commented 3 months ago

It turns out that HomelessHousehold component is only for homeless households living in parks... Any homeless not living in parks (which could be even more, likely) will not be included in this query! Parks appear to have a maximum amount of homeless slots, which would explain why the homeless households count was remaining steady (even with Bye Bye Homeless active).

The most reliable option appears to be iterating over Human flag and checking for m_Flags indicating homelessness... However, this is assuming that all Human component entities are always available in simulation 🤷‍♂️ (which might not be the case for performance (see above in have queries), although households do have a list of members...).


Should check whether HomelessHouseholds are associated with renting process, even if living temporarily in park?

It does appear that HomelessHousehold entities are associated with renting process (ie. have Game.Buildings.PropertyRenter component) when living in a park (albeit with m_Rent=0). This PropertyRenter component is associated with the same entity as HomelessHousehold (ie. HouseholdMember.m_Household.[HomelessHousehold | PropertyRenter | PropertySeeker]).