diff --git a/Changelog.txt b/Changelog.txt index 3d49991..b75f066 100644 --- a/Changelog.txt +++ b/Changelog.txt @@ -1,4 +1,7 @@ -Version 1.6.3 +- Adjust young adult and adult job-seeking behavior to better reflect dynamic high-school exit ages + + +Version 1.6.3 - Adjust university graduation trigger to better fit custom childhood settings diff --git a/Code/Patches/ResidentAI.cs b/Code/Patches/ResidentAI.cs index 09bf51b..73abbae 100644 --- a/Code/Patches/ResidentAI.cs +++ b/Code/Patches/ResidentAI.cs @@ -244,33 +244,139 @@ public static bool UpdateAge(ref bool __result, ref ResidentAI __instance, uint /// - /// Harmony patch to ResidentAI.UpdateWorkplace to stop children below school age going to school. + /// Harmony pre-emptive Prefix patch to ResidentAI.UpdateWorkplace to stop children below school age going to school, and to align young adult and adult behaviour with custom childhood factors. /// /// Citizen data - /// False (stop execution of original method) if the citizen is too young to go to school, true (execute original method) otherwise + /// Always alse (stop execution of original method) [HarmonyPatch("UpdateWorkplace")] [HarmonyPrefix] [MethodImpl(MethodImplOptions.NoInlining)] - public static bool UpdateWorkplace(ref Citizen data) + public static bool UpdateWorkplace(uint citizenID, ref Citizen data) { - // Is this a young child? - if (data.m_age < ModSettings.SchoolStartAge) + // Don't do anything if the citizen is employed or is homeless. + if (data.m_workBuilding != 0 || data.m_homeBuilding == 0) { - // Young children should never be educated. - // Sometimes the UpdateWellbeing method (called immediately before UpdateWorkplace in SimulationStep) will give these kids education, so we just clear it here. - // Easier than messing with UpdateWellbeing. - data.Education1 = false; + // Don't execute original method (which would just abort anyway). + return false; + } - // Young children should also not go shopping (this is checked in following UpdateLocation call in SimulationStep). - // This prevents children from going shopping normally (vanilla code), but an additional patch is needed for the Real Time mod - see RealTime.cs. - data.m_flags &= ~Citizen.Flags.NeedGoods; + // Local references. + BuildingManager buildingManager = Singleton.instance; + Vector3 position = buildingManager.m_buildings.m_buffer[data.m_homeBuilding].m_position; + DistrictManager districtManager = Singleton.instance; + byte district = districtManager.GetDistrict(position); + DistrictPolicies.Services servicePolicies = districtManager.m_districts.m_buffer[district].m_servicePolicies; + int age = data.Age; - // Don't execute original method (thus avoiding assigning to a school). - return false; + // Default transfer reason - will be replaced with any valid workplace offers. + TransferManager.TransferReason educationReason = TransferManager.TransferReason.None; + + // Treatment depends on citizen age. + switch (Citizen.GetAgeGroup(age)) + { + case Citizen.AgeGroup.Child: + // Is this a young child? + if (data.m_age < ModSettings.SchoolStartAge) + { + // Young children should never be educated. + // Sometimes the UpdateWellbeing method (called immediately before UpdateWorkplace in SimulationStep) will give these kids education, so we just clear it here. + // Easier than messing with UpdateWellbeing. + data.Education1 = false; + + // Young children should also not go shopping (this is checked in following UpdateLocation call in SimulationStep). + // This prevents children from going shopping normally (vanilla code), but an additional patch is needed for the Real Time mod - see RealTime.cs. + data.m_flags &= ~Citizen.Flags.NeedGoods; + + // Don't execute original method (thus avoiding assigning to a school). + return false; + } + + // If of school age, and not already educated, go to elementary school. + if (!data.Education1) + { + educationReason = TransferManager.TransferReason.Student1; + } + break; + + case Citizen.AgeGroup.Teen: + if (data.Education1 && !data.Education2) + { + // Teens go to high school, if they've finished elementary school. + educationReason = TransferManager.TransferReason.Student2; + } + break; + + case Citizen.AgeGroup.Young: + case Citizen.AgeGroup.Adult: + // Try for university, if they've finished elementary and high school - delaying two age units to look for work instead if the 'School's out' policy is set. + if (data.Education1 & data.Education2 & !data.Education3) + { + educationReason = TransferManager.TransferReason.Student3; + } + break; + } + + // If citizen is unemployed (young adults and adults only), and either: + // The citizen isn't eligible for university; + // 'Education boost' is not on, or if it's on, the citizen's age above young adulthood modulo five is 3 or 4 (meaning they've already been looking for work for at least three age units); + // Or the citizen's age above young adulthood modulo five is 3 or 4 (meaning they've already been looking for work for at least three age units) + // ...then they look for work (this can be parallel to still seeking education). + if (data.Unemployed != 0 && + (educationReason != TransferManager.TransferReason.Student3 || + (servicePolicies & DistrictPolicies.Services.EducationBoost) == 0 || + (age - ModSettings.YoungStartAge) % 5 > 2)) + { + TransferManager.TransferOffer jobSeeking = default(TransferManager.TransferOffer); + jobSeeking.Priority = Singleton.instance.m_randomizer.Int32(8u); + jobSeeking.Citizen = citizenID; + jobSeeking.Position = position; + jobSeeking.Amount = 1; + jobSeeking.Active = true; + switch (data.EducationLevel) + { + case Citizen.Education.Uneducated: + Singleton.instance.AddOutgoingOffer(TransferManager.TransferReason.Worker0, jobSeeking); + break; + case Citizen.Education.OneSchool: + Singleton.instance.AddOutgoingOffer(TransferManager.TransferReason.Worker1, jobSeeking); + break; + case Citizen.Education.TwoSchools: + Singleton.instance.AddOutgoingOffer(TransferManager.TransferReason.Worker2, jobSeeking); + break; + case Citizen.Education.ThreeSchools: + Singleton.instance.AddOutgoingOffer(TransferManager.TransferReason.Worker3, jobSeeking); + break; + } + } + + // Handle education reason. + switch (educationReason) + { + case TransferManager.TransferReason.Student3: + // If school's out policy is active, citizens won't look for education for the first two age units after becoming young adults. + if ((servicePolicies & DistrictPolicies.Services.SchoolsOut) != 0 && (age - ModSettings.YoungStartAge) % 5 <= 1) + { + break; + } + goto default; + + default: + // Look for education (this can be parallel with looking for work, above). + TransferManager.TransferOffer educationSeeking = default(TransferManager.TransferOffer); + educationSeeking.Priority = Singleton.instance.m_randomizer.Int32(8u); + educationSeeking.Citizen = citizenID; + educationSeeking.Position = position; + educationSeeking.Amount = 1; + educationSeeking.Active = true; + Singleton.instance.AddOutgoingOffer(educationReason, educationSeeking); + break; + + case TransferManager.TransferReason.None: + break; } // If we got here, we need to continue on to the original method (this is not a young child). - return true; + return false; }