/// <summary> /// 返回对于指定建筑,在标准状态下,需要多少对应资质以达到 50% / 100% 工作效率 /// </summary> /// <param name="partId"></param> /// <param name="placeId"></param> /// <param name="buildingIndex"></param> /// <param name="withAdjacentBedrooms"></param> /// <param name="getStandardAttrValue">是否返回标准化的能力值</param> /// <returns></returns> public static int[] GetRequiredAttributeValues(int partId, int placeId, int buildingIndex, bool withAdjacentBedrooms = true, bool getStandardAttrValue = false) { int[] building = DateFile.instance.homeBuildingsDate[partId][placeId][buildingIndex]; int baseBuildingId = building[0]; int requiredAttrId = int.Parse(DateFile.instance.basehomePlaceDate[baseBuildingId][33]); if (requiredAttrId == 0) { return new int[] { 0, 0 } } ; int mood = HumanResource.STANDARD_MOOD; int scaledFavor = Original.GetScaledFavor(HumanResource.STANDARD_FAVOR_LEVEL); scaledFavor = Original.AdjustScaledFavorWithMood(scaledFavor, mood); int workDifficulty = Original.GetWorkDifficulty(partId, placeId, buildingIndex); int adjacentAttrBonus = withAdjacentBedrooms ? Original.GetAdjacentAttrBonus(partId, placeId, buildingIndex, requiredAttrId) : 0; int requiredHalfAttrValue = Mathf.Max(workDifficulty * 100 / Mathf.Max(scaledFavor, 0) - adjacentAttrBonus, 0); int requiredFullAttrValue = Mathf.Max(workDifficulty * 200 / Mathf.Max(scaledFavor, 0) - adjacentAttrBonus, 0); if (!getStandardAttrValue) { requiredHalfAttrValue = Original.FromStandardAttrValue(requiredAttrId, requiredHalfAttrValue); requiredFullAttrValue = Original.FromStandardAttrValue(requiredAttrId, requiredFullAttrValue); } return(new int[] { requiredHalfAttrValue, requiredFullAttrValue }); }
/// <summary> /// 指派工作人员 /// </summary> public void AssignBuildingWorkers() { float compositeWorkIndex = HumanResource.GetCompositeWorkIndex(this.partId, this.placeId); MajordomoWindow.instance.AppendMessage(this.currDate, Message.IMPORTANCE_NORMAL, TaiwuCommon.SetColor(TaiwuCommon.COLOR_LIGHT_BLUE, "开始指派工作人员") + " - " + TaiwuCommon.SetColor(TaiwuCommon.COLOR_DARK_BROWN, "综合工作指数") + ": " + TaiwuCommon.SetColor(TaiwuCommon.COLOR_WHITE, compositeWorkIndex.ToString())); Original.RemoveWorkersFromBuildings(this.partId, this.placeId, this.excludedBuildings, this.excludedWorkers); MajordomoWindow.instance.AppendMessage(this.currDate, Message.IMPORTANCE_LOWEST, TaiwuCommon.SetColor(TaiwuCommon.COLOR_DARK_GRAY, "开始第一轮指派……")); this.AssignBuildingWorkers_PrepareData(); this.AssignBedroomWorkers(); MajordomoWindow.instance.AppendMessage(this.currDate, Message.IMPORTANCE_LOWEST, TaiwuCommon.SetColor(TaiwuCommon.COLOR_DARK_GRAY, "开始第二轮指派……")); // 指派完厢房后,重新计算 this.AssignBuildingWorkers_PrepareData(); this.AssignLeftBuildings(); Original.UpdateAllBuildings(this.partId, this.placeId); compositeWorkIndex = HumanResource.GetCompositeWorkIndex(this.partId, this.placeId); MajordomoWindow.instance.AppendMessage(this.currDate, Message.IMPORTANCE_NORMAL, TaiwuCommon.SetColor(TaiwuCommon.COLOR_LIGHT_BLUE, "结束指派工作人员") + " - " + TaiwuCommon.SetColor(TaiwuCommon.COLOR_DARK_BROWN, "综合工作指数") + ": " + TaiwuCommon.SetColor(TaiwuCommon.COLOR_WHITE, compositeWorkIndex.ToString())); MajordomoWindow.instance.SetCompositeWorkIndex(this.currDate, compositeWorkIndex); }
/// <summary> /// 计算指定地区的综合工作指数 /// /// 综合工作指数 = SUM(缩放平移过的建筑工作效率 * 建筑优先级) /// 厢房效率不计入统计 /// /// 由于必须在最开始执行,所以无法使用 prepared data /// </summary> /// <param name="partId"></param> /// <param name="placeId"></param> /// <returns></returns> private static float GetCompositeWorkIndex(int partId, int placeId) { float compositeWorkIndex = 0.0f; // 统计所有建筑的工作效率(厢房效率不计入统计) var buildings = DateFile.instance.homeBuildingsDate[partId][placeId]; foreach (int buildingIndex in buildings.Keys) { if (!Original.BuildingNeedsWorker(partId, placeId, buildingIndex)) { continue; } if (Bedroom.IsBedroom(partId, placeId, buildingIndex)) { continue; } if (DateFile.instance.actorsWorkingDate.ContainsKey(partId) && DateFile.instance.actorsWorkingDate[partId].ContainsKey(placeId) && DateFile.instance.actorsWorkingDate[partId][placeId].ContainsKey(buildingIndex)) { int workerId = DateFile.instance.actorsWorkingDate[partId][placeId][buildingIndex]; int workEffectiveness = Original.GetWorkEffectiveness(partId, placeId, buildingIndex, workerId); float scaledWorkEffectiveness = (workEffectiveness - 100f) / 100f; int priority = HumanResource.GetBuildingWorkingPriority(partId, placeId, buildingIndex); compositeWorkIndex += scaledWorkEffectiveness * priority; } } return(compositeWorkIndex); }
public static void LogBuildingAndWorker(BuildingWorkInfo info, int selectedWorkerId, int partId, int placeId, TaiwuDate currDate, Dictionary <int, Dictionary <int, int> > workerAttrs, bool suppressNoWorkerWarnning) { var building = DateFile.instance.homeBuildingsDate[partId][placeId][info.buildingIndex]; int baseBuildingId = building[0]; int buildingLevel = building[1]; var baseBuilding = DateFile.instance.basehomePlaceDate[baseBuildingId]; string buildingName = baseBuilding[0]; string attrName = Output.GetRequiredAttrName(info.requiredAttrId); string logText = string.Format("{0}:{1} ({2}) {3} [{4}, {5}] - ", TaiwuCommon.SetColor(TaiwuCommon.COLOR_LIGHT_GRAY, Common.ToFullWidth(info.priority.ToString("F0").PadLeft(4))), TaiwuCommon.SetColor(TaiwuCommon.COLOR_YELLOW, Common.ToFullWidth(buildingName.PadRight(5))), TaiwuCommon.SetColor(TaiwuCommon.COLOR_LIGHT_GRAY, Common.ToFullWidth(buildingLevel.ToString().PadLeft(2))), TaiwuCommon.SetColor(TaiwuCommon.COLOR_RICE_WHITE, Common.ToFullWidth(attrName.PadRight(2))), TaiwuCommon.SetColor(TaiwuCommon.COLOR_LIGHT_GRAY, Common.ToFullWidth(info.halfWorkingAttrValue.ToString().PadLeft(3))), TaiwuCommon.SetColor(TaiwuCommon.COLOR_LIGHT_GRAY, Common.ToFullWidth(info.fullWorkingAttrValue.ToString().PadLeft(3)))); if (selectedWorkerId >= 0) { string workerName = DateFile.instance.GetActorName(selectedWorkerId); int attrValue = info.requiredAttrId != 0 ? workerAttrs[selectedWorkerId][info.requiredAttrId] : -1; int mood = int.Parse(DateFile.instance.GetActorDate(selectedWorkerId, 4, false)); int favor = DateFile.instance.GetActorFavor(false, DateFile.instance.MianActorID(), selectedWorkerId, getLevel: true); // 这里的工作效率并不一定等于最终工作效率,因为可能还有厢房未分配 int workEffectiveness = info.requiredAttrId != 0 ? Original.GetWorkEffectiveness(partId, placeId, info.buildingIndex, selectedWorkerId) : -1; string workEffectivenessStr = workEffectiveness >= 0 ? workEffectiveness / 2 + "%" : "N/A"; MajordomoWindow.instance.AppendMessage(currDate, Message.IMPORTANCE_LOW, logText + string.Format( "{0} 资质: {1} 心情: {2} 好感: {3} 工作效率: {4}", TaiwuCommon.SetColor(TaiwuCommon.COLOR_YELLOW, Common.ToFullWidth(workerName.PadRight(5))), TaiwuCommon.SetColor(TaiwuCommon.COLOR_LIGHT_GRAY, Common.ToFullWidth(attrValue.ToString().PadLeft(3))), TaiwuCommon.SetColor(TaiwuCommon.COLOR_LIGHT_GRAY, Common.ToFullWidth(mood.ToString().PadLeft(4))), TaiwuCommon.SetColor(TaiwuCommon.COLOR_LIGHT_GRAY, Common.ToFullWidth(favor.ToString().PadLeft(2))), TaiwuCommon.SetColor(TaiwuCommon.COLOR_WHITE, Common.ToFullWidth(workEffectivenessStr.PadLeft(4))))); } else { if (suppressNoWorkerWarnning) { MajordomoWindow.instance.AppendMessage(currDate, Message.IMPORTANCE_LOW, logText + TaiwuCommon.SetColor(TaiwuCommon.COLOR_LIGHT_GRAY, "无合适人选")); } else { MajordomoWindow.instance.AppendMessage(currDate, Message.IMPORTANCE_HIGH, logText + TaiwuCommon.SetColor(TaiwuCommon.COLOR_RED, "无合适人选")); } } }
/// <summary> /// 指派完厢房之后,继续指派其他建筑 /// /// 其实本轮指派开始时仍有可能包含厢房,本轮临近结束时也仍有可能尚未指派厢房。 /// 本轮最后厢房指派,不考虑是否达到心情好感阈值,只要还有人就往里放。 /// 上次分配后还有厢房剩下的原因:人太少,辅助性厢房装不满;心情都挺好,一般厢房装不满。 /// 本次分配后还有厢房剩下的原因:人太少。 /// /// 建筑类型优先级因子中的厢房因子依然不能控制此处的厢房指派优先级,所有厢房在最后阶段指派。 /// </summary> private void AssignLeftBuildings() { MajordomoWindow.instance.AppendMessage(this.currDate, Message.IMPORTANCE_LOWEST, TaiwuCommon.SetColor(TaiwuCommon.COLOR_DARK_GRAY, "开始指派主要建筑……")); var sortedBuildings = this.buildings.OrderByDescending(entry => entry.Value.priority).Select(entry => entry.Value); foreach (var info in sortedBuildings) { if (this.excludedBuildings.Contains(info.buildingIndex)) { continue; } if (info.IsBedroom()) { continue; } int selectedWorkerId = this.SelectBuildingWorker(info.buildingIndex, info.requiredAttrId); if (selectedWorkerId >= 0) { Original.SetBuildingWorker(this.partId, this.placeId, info.buildingIndex, selectedWorkerId); } Output.LogBuildingAndWorker(info, selectedWorkerId, this.partId, this.placeId, this.currDate, this.workerAttrs, suppressNoWorkerWarnning: false); } // 最后指派尚未指派的厢房 MajordomoWindow.instance.AppendMessage(this.currDate, Message.IMPORTANCE_LOWEST, TaiwuCommon.SetColor(TaiwuCommon.COLOR_DARK_GRAY, "开始指派尚未指派的厢房……")); sortedBuildings = this.buildings.OrderByDescending(entry => entry.Value.priority).Select(entry => entry.Value); foreach (var info in sortedBuildings) { if (this.excludedBuildings.Contains(info.buildingIndex)) { continue; } if (!info.IsBedroom()) { continue; } int selectedWorkerId = this.SelectLeftBedroomWorker(info.buildingIndex); if (selectedWorkerId >= 0) { Original.SetBuildingWorker(this.partId, this.placeId, info.buildingIndex, selectedWorkerId); } Output.LogBuildingAndWorker(info, selectedWorkerId, this.partId, this.placeId, this.currDate, this.workerAttrs, suppressNoWorkerWarnning: false); } }
/// <summary> /// 计算指定人员对于指定厢房的统合能力值 /// /// 统合能力值 = SUM(MIN(标准化的能力值 / 标准化的标准状态满效率能力值, ATTR_INTEGRATION_THRESHOLD)) /// </summary> /// <param name="attrs">指定人员的能力字典</param> /// <param name="relatedBuildings">指定厢房的辅助建筑列表,不能含有厢房</param> /// <returns></returns> private static float GetIntegratedAttrValue(Dictionary <int, int> attrs, List <BuildingWorkInfo> relatedBuildings) { float integratedAttrValue = 0.0f; foreach (var info in relatedBuildings) { int standardAttrValue = Original.ToStandardAttrValue(info.requiredAttrId, attrs[info.requiredAttrId]); int standardFullWorkingAttrValue = Original.ToStandardAttrValue(info.requiredAttrId, info.fullWorkingAttrValue); float attrRatio = (float)standardAttrValue / standardFullWorkingAttrValue; integratedAttrValue += Mathf.Min(attrRatio, ATTR_INTEGRATION_THRESHOLD); } return(integratedAttrValue); }
/// <summary> /// 计算心情和好感相对标准状态的工作效率差值(差值大的优先休息) /// 注意,此处只关心心情和好感,不关心资质 /// 返回值小于 0 时,说明工作效率已经超过标准状态 /// </summary> /// <param name="actorId"></param> /// <returns></returns> private static int GetLackedMoodAndFavor(int actorId) { int currMood = int.Parse(DateFile.instance.GetActorDate(actorId, 4, addValue: false)); int currFavorLevel = DateFile.instance.GetActorFavor(false, DateFile.instance.MianActorID(), actorId, getLevel: true); int currScaledFavor = Original.GetScaledFavor(currFavorLevel); currScaledFavor = Original.AdjustScaledFavorWithMood(currScaledFavor, currMood); int standardMood = STANDARD_MOOD; int standardScaledFavor = Original.GetScaledFavor(STANDARD_FAVOR_LEVEL); standardScaledFavor = Original.AdjustScaledFavorWithMood(standardScaledFavor, standardMood); return(standardScaledFavor - currScaledFavor); }
/// <summary> /// 获取当前据点需要工作人员的建筑,以及该建筑的工作相关信息 /// </summary> /// <returns></returns> private Dictionary <int, List <BuildingWorkInfo> > GetBuildingsNeedWorker() { // requiredAttrId -> [BuildingWorkInfo, ] var attrBuildings = new Dictionary <int, List <BuildingWorkInfo> >(); var buildings = DateFile.instance.homeBuildingsDate[this.partId][this.placeId]; var validBuildings = buildings.Where(entry => !this.excludedBuildings.Contains(entry.Key)); foreach (var entry in validBuildings) { int buildingIndex = entry.Key; int[] building = entry.Value; int baseBuildingId = building[0]; var baseBuilding = DateFile.instance.basehomePlaceDate[baseBuildingId]; bool needWorker = int.Parse(baseBuilding[3]) == 1; if (!needWorker) { continue; } int requiredAttrId = int.Parse(baseBuilding[33]); BuildingWorkInfo info = new BuildingWorkInfo { buildingIndex = buildingIndex, requiredAttrId = requiredAttrId, priority = HumanResource.GetBuildingWorkingPriority(this.partId, this.placeId, buildingIndex), halfWorkingAttrValue = 0, fullWorkingAttrValue = 0, }; if (requiredAttrId != 0) { int[] requiredAttrValues = Original.GetRequiredAttributeValues(this.partId, this.placeId, buildingIndex); info.halfWorkingAttrValue = requiredAttrValues[0]; info.fullWorkingAttrValue = requiredAttrValues[1]; } if (!attrBuildings.ContainsKey(requiredAttrId)) { attrBuildings[requiredAttrId] = new List <BuildingWorkInfo>(); } attrBuildings[requiredAttrId].Add(info); } return(attrBuildings); }
/// <summary> /// 指派完厢房之后,继续指派其他建筑 /// /// 其实本轮指派开始时仍有可能包含厢房,本轮临近结束时也仍有可能尚未指派厢房。 /// 本轮开始的厢房指派按一般厢房指派规则进行。 /// 本轮最后厢房指派,不考虑是否达到心情好感阈值,只要还有人就往里放。 /// 第一次分配完后还有厢房剩下的原因:人太少,辅助性厢房装不满;心情都挺好,一般厢房装不满。 /// 第二次分配完后还有厢房剩下的原因:人太少。 /// </summary> private void AssignLeftBuildings() { Main.Logger.Log("开始指派主要建筑……"); var sortedBuildings = this.buildings.OrderByDescending(entry => entry.Value.priority).Select(entry => entry.Value); foreach (var info in sortedBuildings) { if (this.excludedBuildings.Contains(info.buildingIndex)) { continue; } int selectedWorkerId = this.SelectBuildingWorker(info.buildingIndex, info.requiredAttrId); if (selectedWorkerId >= 0) { Original.SetBuildingWorker(this.partId, this.placeId, info.buildingIndex, selectedWorkerId); } Output.LogBuildingAndWorker(info, selectedWorkerId, this.partId, this.placeId, this.workerAttrs); } // 最后指派尚未指派的厢房 Main.Logger.Log("开始指派尚未指派的厢房……"); sortedBuildings = this.buildings.OrderByDescending(entry => entry.Value.priority).Select(entry => entry.Value); foreach (var info in sortedBuildings) { if (this.excludedBuildings.Contains(info.buildingIndex)) { continue; } if (!info.IsBedroom()) { continue; } int selectedWorkerId = this.SelectLeftBedroomWorker(info.buildingIndex); if (selectedWorkerId >= 0) { Original.SetBuildingWorker(this.partId, this.placeId, info.buildingIndex, selectedWorkerId); } Output.LogBuildingAndWorker(info, selectedWorkerId, this.partId, this.placeId, this.workerAttrs); } }
/// <summary> /// 为指定建筑选择合适的工作人员 /// 按资质顺序检查实际效率是否达到及格线(或心情好感未达到标准),若达到则选择 /// 若没有任何人达到及格线(或心情好感皆达到标准),或者没有任何工作人员,则返回 -1 /// </summary> /// <param name="buildingIndex"></param> /// <param name="requiredAttrId"></param> /// <returns></returns> private int SelectBuildingWorker(int buildingIndex, int requiredAttrId) { int selectedWorkerId = -1; List <int> candidates = this.attrCandidates[requiredAttrId]; candidates = candidates.Where(workerId => !this.excludedWorkers.Contains(workerId)).ToList(); if (!candidates.Any()) { return(-1); } if (requiredAttrId != 0) { foreach (int workerId in candidates) { int effect = Original.GetWorkEffectiveness(this.partId, this.placeId, buildingIndex, workerId); if (effect >= WORK_EFFECTIVENESS_HALF) { selectedWorkerId = workerId; break; } } } else { foreach (int workerId in candidates) { int lackedMoodAndFavor = this.workerAttrs[workerId][requiredAttrId]; if (lackedMoodAndFavor > 0) { selectedWorkerId = workerId; break; } } } if (selectedWorkerId >= 0) { this.excludedBuildings.Add(buildingIndex); this.excludedWorkers.Add(selectedWorkerId); } return(selectedWorkerId); }
private void AssignBuildingWorkers_PrepareData() { List <int> workerIds = Original.GetWorkerIds(this.partId, this.placeId); workerIds = workerIds.Where(workerId => !this.excludedWorkers.Contains(workerId)).ToList(); Dictionary <int, List <BuildingWorkInfo> > attrBuildings = this.GetBuildingsNeedWorker(); // this.buildings = new Dictionary <int, BuildingWorkInfo>(); foreach (var currBuildingWorkInfos in attrBuildings.Values) { foreach (var info in currBuildingWorkInfos) { this.buildings[info.buildingIndex] = info; } } // this.workerAttrs = new Dictionary <int, Dictionary <int, int> >(); this.attrCandidates = new Dictionary <int, List <int> >(); foreach (int workerId in workerIds) { this.workerAttrs[workerId] = new Dictionary <int, int>(); } foreach (int requiredAttrId in attrBuildings.Keys) { foreach (int workerId in workerIds) { // 不需要资质时(厢房),放入好感和心情相对标准状态的差值 int attrValue = requiredAttrId != 0 ? int.Parse(DateFile.instance.GetActorDate(workerId, requiredAttrId)) : HumanResource.GetLackedMoodAndFavor(workerId); this.workerAttrs[workerId][requiredAttrId] = attrValue; } List <int> sortedWorkerIds = this.workerAttrs .OrderByDescending(elem => elem.Value[requiredAttrId]) .Select(elem => elem.Key) .ToList(); this.attrCandidates[requiredAttrId] = sortedWorkerIds; } }
public void OnPointerUp(PointerEventData eventData) { if (!Main.enabled) { return; } if (!Original.BuildingNeedsWorker(this.partId, this.placeId, this.buildingIndex)) { return; } // 1: 右键, 2: 中键 var button = (PointerEventData.InputButton)(Main.settings.exclusionMouseButton + 1); if (eventData.button == button && (Input.GetKey(KeyCode.LeftAlt) || Input.GetKey(KeyCode.RightAlt))) { BuildingExclusion.Toggle(partId, placeId, buildingIndex); } }
/// <summary> /// 返回建筑安排工作人员的优先级。优先级越高,越优先安排高能力的工作人员。 /// 返回值越大,优先级越高;为负数时(因用户配置而排除),不参与人员分配。 /// 目前固定优先级为:厢房 > 病坊、密医 > 收获人才建筑 > 收获资源建筑 > 收获物品建筑 > 收获蛐蛐建筑 /// 同一类别中,工作难度越高,优先级越高 /// </summary> /// <param name="partId"></param> /// <param name="placeId"></param> /// <param name="buildingIndex"></param> /// <returns></returns> private static int GetBuildingWorkingPriority(int partId, int placeId, int buildingIndex) { int[] building = DateFile.instance.homeBuildingsDate[partId][placeId][buildingIndex]; int baseBuildingId = building[0]; int basePriority; if (baseBuildingId == BASE_BUILDING_ID_BEDROOM) { basePriority = 6; } else if (baseBuildingId == BASE_BUILDING_ID_HOSPITAL || baseBuildingId == BASE_BUILDING_ID_DETOXIFICATION) { basePriority = 5; } else { var harvestTypes = HumanResource.buildingsHarvestTypes[baseBuildingId]; if (harvestTypes.Contains(HARVEST_TYPE_CHARACTER)) { basePriority = 4; } else if (harvestTypes.Contains(HARVEST_TYPE_RESOURCE)) { basePriority = 3; } else if (harvestTypes.Contains(HARVEST_TYPE_ITEM)) { basePriority = 2; } else { basePriority = 1; } } int workDifficulty = Original.GetWorkDifficulty(partId, placeId, buildingIndex); int priority = basePriority * WORKING_PRIORITY_STEP_SIZE + workDifficulty; return(priority); }
/// <summary> /// 返回建筑指派工作人员的优先级 /// /// 优先级 = 建筑种类因子 * 标准状态下满效率需求的标准化能力值 /// 对于厢房,“标准状态下满效率需求的标准化能力值” 即为其等级 /// 虽然这会导致方向的优先级和其他建筑没有可比性,但是厢房优先级本来就不会与其他建筑比较,所以没有问题 /// </summary> /// <param name="partId"></param> /// <param name="placeId"></param> /// <param name="buildingIndex"></param> /// <param name="withAdjacentBedrooms">是否考虑邻接厢房的影响</param> /// <returns></returns> private static float GetBuildingWorkingPriority(int partId, int placeId, int buildingIndex, bool withAdjacentBedrooms) { int[] requiredAttrValues = Original.GetRequiredAttributeValues(partId, placeId, buildingIndex, withAdjacentBedrooms, getStandardAttrValue: true); int fullWorkingAttrValue = requiredAttrValues[1]; // 对于厢房,“标准状态下满效率需求的标准化能力值” 即为其等级 int[] building = DateFile.instance.homeBuildingsDate[partId][placeId][buildingIndex]; int baseBuildingId = building[0]; int buildingLevel = building[1]; int requiredAttrId = int.Parse(DateFile.instance.basehomePlaceDate[baseBuildingId][33]); if (requiredAttrId <= 0) { fullWorkingAttrValue = buildingLevel; } float typeFactor = HumanResource.GetBuildingPriorityFactor(baseBuildingId); return(typeFactor * fullWorkingAttrValue); }
/// <summary> /// 计算指定地区的工作统计信息 /// /// 综合工作指数 = SUM(缩放平移过的建筑工作效率 * 建筑优先级) /// 厢房效率不计入统计 /// /// 由于必须在最开始执行,所以无法使用 prepared data /// </summary> /// <param name="partId"></param> /// <param name="placeId"></param> /// <returns></returns> private static WorkingStats GetWorkingStats(int partId, int placeId) { var stats = new WorkingStats(); // 统计所有建筑的工作效率(厢房效率不计入统计) var buildings = DateFile.instance.homeBuildingsDate[partId][placeId]; foreach (int buildingIndex in buildings.Keys) { if (!Original.BuildingNeedsWorker(partId, placeId, buildingIndex)) { continue; } if (Bedroom.IsBedroom(partId, placeId, buildingIndex)) { continue; } if (DateFile.instance.actorsWorkingDate.ContainsKey(partId) && DateFile.instance.actorsWorkingDate[partId].ContainsKey(placeId) && DateFile.instance.actorsWorkingDate[partId][placeId].ContainsKey(buildingIndex)) { int workerId = DateFile.instance.actorsWorkingDate[partId][placeId][buildingIndex]; int workEffectiveness = Original.GetWorkEffectiveness(partId, placeId, buildingIndex, workerId); float scaledWorkEffectiveness = (workEffectiveness - 100f) / 100f; float priority = HumanResource.GetBuildingWorkingPriority(partId, placeId, buildingIndex, withAdjacentBedrooms: false); ++stats.nProductiveBuildings; stats.avgWorkEffectiveness += workEffectiveness / 200f; stats.compositeWorkIndex += scaledWorkEffectiveness * priority; } } if (stats.nProductiveBuildings > 0) { stats.avgWorkEffectiveness /= stats.nProductiveBuildings; } return(stats); }
/// <summary> /// 清理所有建筑内的工作人员(排除列表中的除外) /// 如果某个建筑在排除列表中,那么其中的工作人员也会同时添加到排除列表中 /// </summary> /// <param name="partId"></param> /// <param name="placeId"></param> /// <param name="excludedBuildings"></param> /// <param name="excludedWorkers"></param> public static void RemoveWorkersFromBuildings(int partId, int placeId, HashSet <int> excludedBuildings, HashSet <int> excludedWorkers) { var buildings = DateFile.instance.homeBuildingsDate[partId][placeId]; foreach (int buildingIndex in buildings.Keys) { if (excludedBuildings.Contains(buildingIndex)) { if (DateFile.instance.actorsWorkingDate.ContainsKey(partId) && DateFile.instance.actorsWorkingDate[partId].ContainsKey(placeId) && DateFile.instance.actorsWorkingDate[partId][placeId].ContainsKey(buildingIndex)) { int workerId = DateFile.instance.actorsWorkingDate[partId][placeId][buildingIndex]; excludedWorkers.Add(workerId); } } else { Original.RemoveBuildingWorker(partId, placeId, buildingIndex); } } }
/// <summary> /// 自动指派工作人员 /// </summary> public void AssignBuildingWorkers() { Main.Logger.Log($"自动指派前综合工作指数: {HumanResource.GetComprehensiveWorkIndex(this.partId, this.placeId)}"); Original.RemoveWorkersFromBuildings(this.partId, this.placeId, this.excludedBuildings, this.excludedWorkers); Main.Logger.Log("开始第一轮指派……"); this.AssignBuildingWorkers_PrepareData(); this.AssignBedroomWorkers(); Main.Logger.Log("开始第二轮指派……"); // 指派完厢房后,重新计算 this.AssignBuildingWorkers_PrepareData(); this.AssignLeftBuildings(); Original.UpdateAllBuildings(this.partId, this.placeId); Main.Logger.Log($"自动指派后综合工作指数: {HumanResource.GetComprehensiveWorkIndex(this.partId, this.placeId)}"); }
/// <summary> /// 获取邻接厢房对指定建筑的能力加成 /// </summary> /// <param name="partId"></param> /// <param name="placeId"></param> /// <param name="buildingIndex"></param> /// <param name="requiredAttrId"></param> /// <returns></returns> private static int GetAdjacentAttrBonus(int partId, int placeId, int buildingIndex, int requiredAttrId) { int totalAdjacentAttrValue = 0; foreach (int adjacentBuildingIndex in Bedroom.GetAdjacentBedrooms(partId, placeId, buildingIndex)) { if (!DateFile.instance.actorsWorkingDate.ContainsKey(partId) || !DateFile.instance.actorsWorkingDate[partId].ContainsKey(placeId) || !DateFile.instance.actorsWorkingDate[partId][placeId].ContainsKey(adjacentBuildingIndex)) { continue; } int adjacentActorId = DateFile.instance.actorsWorkingDate[partId][placeId][adjacentBuildingIndex]; int adjacentAttrValue = (requiredAttrId > 0) ? int.Parse(DateFile.instance.GetActorDate(adjacentActorId, requiredAttrId)) : 0; adjacentAttrValue = Original.ToStandardAttrValue(requiredAttrId, adjacentAttrValue); totalAdjacentAttrValue += adjacentAttrValue; } return(totalAdjacentAttrValue); }
/// <summary> /// 计算指定地区的工作人员统计信息 /// </summary> /// <param name="partId"></param> /// <param name="placeId"></param> /// <returns></returns> public static WorkerStats GetWorkerStats(int partId, int placeId) { int mainActorId = DateFile.instance.MianActorID(); List <int> workerIds = Original.GetWorkerIds(partId, placeId); var stats = new WorkerStats(); foreach (int workerId in workerIds) { stats.avgHealthInjury += 1f - TaiwuCommon.GetInjuryRate(workerId); stats.avgHealthCirculating += 1f - TaiwuCommon.GetCirculatingBlockingRate(workerId); stats.avgHealthPoison += 1f - TaiwuCommon.GetPoisoningRate(workerId); stats.avgHealthLifespan += 1f - TaiwuCommon.GetLifespanDamageRate(workerId); int mood = int.Parse(DateFile.instance.GetActorDate(workerId, 4, false)); int favor = DateFile.instance.GetActorFavor(false, mainActorId, workerId); int favorLevel = DateFile.instance.GetActorFavor(false, mainActorId, workerId, getLevel: true); int scaledFavor = Original.GetScaledFavor(favorLevel); scaledFavor = Original.AdjustScaledFavorWithMood(scaledFavor, mood); stats.avgMood += mood; stats.avgFriendliness += favor; stats.avgWorkMotivation += Mathf.Max(scaledFavor, 0) / 100f; } if (workerIds.Count > 0) { stats.nWorkers = workerIds.Count; stats.avgHealthInjury /= workerIds.Count; stats.avgHealthCirculating /= workerIds.Count; stats.avgHealthPoison /= workerIds.Count; stats.avgHealthLifespan /= workerIds.Count; stats.avgCompositeHealth = (stats.avgHealthInjury + stats.avgHealthCirculating + stats.avgHealthPoison + stats.avgHealthLifespan) / 4; stats.avgMood /= workerIds.Count; stats.avgFriendliness /= workerIds.Count; stats.avgWorkMotivation /= workerIds.Count; } return(stats); }
public static void LogBuildingAndWorker(BuildingWorkInfo info, int selectedWorkerId, int partId, int placeId, Dictionary <int, Dictionary <int, int> > workerAttrs) { var building = DateFile.instance.homeBuildingsDate[partId][placeId][info.buildingIndex]; int baseBuildingId = building[0]; int buildingLevel = building[1]; var baseBuilding = DateFile.instance.basehomePlaceDate[baseBuildingId]; string buildingName = baseBuilding[0]; string attrName = Output.GetRequiredAttrName(info.requiredAttrId); string logText = $"{info.priority}\t" + $"{buildingName} ({buildingLevel}): " + $"{attrName} [{info.halfWorkingAttrValue}, {info.fullWorkingAttrValue}] - "; if (selectedWorkerId >= 0) { string workerName = DateFile.instance.GetActorName(selectedWorkerId); int attrValue = info.requiredAttrId != 0 ? workerAttrs[selectedWorkerId][info.requiredAttrId] : -1; int mood = int.Parse(DateFile.instance.GetActorDate(selectedWorkerId, 4, addValue: false)); int favor = DateFile.instance.GetActorFavor(false, DateFile.instance.MianActorID(), selectedWorkerId, getLevel: true); // 这里的工作效率并不一定等于最终工作效率,因为可能还有厢房未分配 int workEffectiveness = info.requiredAttrId != 0 ? Original.GetWorkEffectiveness(partId, placeId, info.buildingIndex, selectedWorkerId) : -1; string workEffectivenessStr = workEffectiveness >= 0 ? workEffectiveness / 2 + "%" : "N/A"; Main.Logger.Log(logText + $"{workerName}, 资质: {attrValue}, 心情: {mood}, 好感: {favor}, 工作效率: {workEffectivenessStr}"); } else { Main.Logger.Log(logText + "<无合适人选>"); } }
/// <summary> /// 获取指定人物在指定建筑内的工作效率 /// 大部分照抄 HomeSystem::GetBuildingLevelPct 方法 /// </summary> /// <param name="partId"></param> /// <param name="placeId"></param> /// <param name="buildingIndex"></param> /// <param name="actorId"></param> /// <param name="withAdjacentBedrooms"></param> /// <returns></returns> public static int GetWorkEffectiveness(int partId, int placeId, int buildingIndex, int actorId, bool withAdjacentBedrooms = true) { int[] building = DateFile.instance.homeBuildingsDate[partId][placeId][buildingIndex]; int baseBuildingId = building[0]; int requiredAttrId = int.Parse(DateFile.instance.basehomePlaceDate[baseBuildingId][33]); int mood = int.Parse(DateFile.instance.GetActorDate(actorId, 4, addValue: false)); int favorLevel = DateFile.instance.GetActorFavor(false, DateFile.instance.MianActorID(), actorId, getLevel: true); int scaledFavor = Original.GetScaledFavor(favorLevel); scaledFavor = Original.AdjustScaledFavorWithMood(scaledFavor, mood); int attrValue = (requiredAttrId > 0) ? int.Parse(DateFile.instance.GetActorDate(actorId, requiredAttrId)) : 0; attrValue = Original.ToStandardAttrValue(requiredAttrId, attrValue); int adjacentAttrBonus = withAdjacentBedrooms ? Original.GetAdjacentAttrBonus(partId, placeId, buildingIndex, requiredAttrId) : 0; int scaledAttrValue = (attrValue + adjacentAttrBonus) * Mathf.Max(scaledFavor, 0) / 100; int workDifficulty = Original.GetWorkDifficulty(partId, placeId, buildingIndex); int workEffectiveness = Mathf.Clamp(scaledAttrValue * 100 / workDifficulty, 50, 200); return(workEffectiveness); }
/// <summary> /// 为所有厢房安排工作人员 /// </summary> private void AssignBedroomWorkers() { // 厢房 ID -> 该厢房辅助的建筑信息列表 // bedroomIndex -> [BuildingWorkInfo, ] var bedroomsForWork = Bedroom.GetBedroomsForWork(this.partId, this.placeId, this.buildings, this.attrCandidates, this.workerAttrs); // 更新辅助类厢房的优先级 // 辅助类厢房优先级 = 基础优先级 + SUM(辅助建筑优先级) // bedroomIndex -> priority var auxiliaryBedroomsPriorities = new Dictionary <int, int>(); foreach (var entry in bedroomsForWork) { int bedroomIndex = entry.Key; var relatedBuildings = entry.Value; int basePriority = 7; int priority = basePriority * WORKING_PRIORITY_STEP_SIZE + relatedBuildings.Select(info => info.priority).Sum(); auxiliaryBedroomsPriorities[bedroomIndex] = priority; } // 对于辅助类厢房,按优先级依次分配合适的人选 MajordomoWindow.instance.AppendMessage(this.currDate, Message.IMPORTANCE_LOWEST, TaiwuCommon.SetColor(TaiwuCommon.COLOR_DARK_GRAY, "开始指派辅助类厢房……")); var sortedAuxiliaryBedrooms = auxiliaryBedroomsPriorities.OrderByDescending(entry => entry.Value).Select(entry => entry.Key); foreach (int bedroomIndex in sortedAuxiliaryBedrooms) { int selectedWorkerId = this.SelectAuxiliaryBedroomWorker(bedroomIndex, bedroomsForWork[bedroomIndex]); if (selectedWorkerId >= 0) { Original.SetBuildingWorker(this.partId, this.placeId, bedroomIndex, selectedWorkerId); } Output.LogAuxiliaryBedroomAndWorker(bedroomIndex, bedroomsForWork[bedroomIndex], auxiliaryBedroomsPriorities[bedroomIndex], selectedWorkerId, this.partId, this.placeId, this.currDate, this.workerAttrs); } // 对于一般厢房,按优先级依次分配合适的人选 MajordomoWindow.instance.AppendMessage(this.currDate, Message.IMPORTANCE_LOWEST, TaiwuCommon.SetColor(TaiwuCommon.COLOR_DARK_GRAY, "开始指派一般厢房……")); var sortedBedrooms = this.buildings.Where(entry => entry.Value.IsBedroom()) .OrderByDescending(entry => entry.Value.priority).Select(entry => entry.Value); foreach (var info in sortedBedrooms) { if (this.excludedBuildings.Contains(info.buildingIndex)) { continue; } int selectedWorkerId = this.SelectBuildingWorker(info.buildingIndex, info.requiredAttrId); if (selectedWorkerId >= 0) { Original.SetBuildingWorker(this.partId, this.placeId, info.buildingIndex, selectedWorkerId); } Output.LogBuildingAndWorker(info, selectedWorkerId, this.partId, this.placeId, this.currDate, this.workerAttrs); } }