public void Remove(PawnAbilityPointsEntry entry) { if (list?.Remove(entry) ?? false) { count--; points -= entry.points; } }
public void Add(PawnAbilityPointsEntry entry, bool addToList = true) { if (addToList) { list ??= new List <PawnAbilityPointsEntry>(); list.Add(entry); } count++; points += entry.points; }
private static void RebalanceGeneratedPawns(PawnGroupMakerParms parms, List <Pawn> pawns) { // PawnGroupKindWorker does not guaranteed that the sum of pawn points (pawn.kindDef.combatPower) <= parms.points. // Chattel pawns (slaves and animals) are not included in parms.points yet are in __result. // Also, PawnGroupKindWorker_Trader decrements parms.points for trader pawns, effectively likewise excluding them. var targetPoints = parms.points; // parms.points + special trader points (see below) var targetCount = 0; // debug only, represents the pawns contributing to targetPoints var origCount = 0; // debug only, represents the pawns contributing to parms.points // Partition into special and normal pawns, calculating combat points for each. // Note: entry lists are allocated only if necessary. var specials = new PawnAbilityPointsEntries(); var normals = new PawnAbilityPointsEntries(); var excluded = new PawnAbilityPointsEntries(); // debug only foreach (var pawn in pawns) { var entry = PawnAbilityPointsEntry.For(pawn); // parms.points is not used for slaves and animals, so exclude them. // Using GetTraderCaravanRole() for this, since Humanoid Alien Races patches this method to account for alienslavekinds. var traderCaravanRole = pawn.GetTraderCaravanRole(); if (traderCaravanRole == TraderCaravanRole.Chattel || traderCaravanRole == TraderCaravanRole.Carrier) { DebugAdd(ref excluded, entry); } // Not using TraderCaravanRole.Trader for determining traders - non-null pawn.traders is the authoritative source. else if (pawn.trader == null) { if (entry.IsSpecial) { specials.Add(entry); } else { normals.Add(entry); } DebugAdd(ref origCount, 1); DebugAdd(ref targetCount, 1); } else { // PawnGroupKindWorker_Trader reduces parms.points by trader's cost, so exclude them as well. if (entry.IsSpecial) { // Not excluding 'special' traders yet, to allow them to be disabled into normal traders in below rebalancing loop. specials.Add(entry); // Since they're not excluded yet, include them in targetPoints (and targetCount); // this will be "undone" if disabled in below rebalancing loop. DebugAdd(ref targetCount, 1); targetPoints += entry.basePoints; } else { DebugAdd(ref excluded, entry, isDebugLog); } } } if (specials.count > 0) { DebugMessage(rgpMsgPrefix + "Target: " + (targetCount != origCount ? $"#pawns = {origCount} orig + {targetCount - origCount} special trader = {targetCount}, " + $"points = {parms.points} orig + {targetPoints - parms.points} special trader = {targetPoints}" : $"#pawns = {targetCount}, points = {targetPoints}")); DebugMessage(rgpMsgPrefix + "Special: " + specials); DebugMessage(rgpMsgPrefix + "Normal: " + normals); DebugMessage(rgpMsgPrefix + "Excluded: " + excluded); // Rebalancing loop: // Until # special pawns = 0 or # pawns <= 1 or special pawn + normal pawn points <= target points: // If # special pawns > # normal pawns or special pawn points > normal pawn points: // Try to disable a random special pawn into a normal pawn. // If this fails, remove the special pawn instead. // Except if the special pawn being disabled is a trader, just exclude them like normal traders even if disabling fails. // Else: // Remove a random normal pawn. var iterCount = 0; // debug only var destroyed = new PawnAbilityPointsEntries(); // debug only while (true) { var condition = specials.count > 0 && specials.count + normals.count > 1 && specials.points + normals.points > targetPoints; DebugMessage(rgpMsgPrefix + (condition ? $"Rebalance iteration {++iterCount}" : "Final")); DebugMessage(rgpMsgPrefix + "#pawns: " + (targetCount != origCount ? $"{origCount} orig + {targetCount - origCount} special trader = {targetCount}" : $"{targetCount} orig") + $", {specials.count} special + {normals.count} normal = {specials.count + normals.count}, " + $"{excluded.count} excluded, {destroyed.count} destroyed"); DebugMessage(rgpMsgPrefix + "points: " + (targetPoints != parms.points ? $"{parms.points} orig + {targetPoints - parms.points} special trader = {targetPoints}" : $"{targetPoints} orig") + $", {specials.points} special + {normals.points} normal = {specials.points + normals.points}, " + $"{excluded.points} excluded, {destroyed.points} destroyed"); if (!condition) { break; } if (specials.count >= normals.count || specials.points >= normals.points) { var entry = specials.list.RandomElement(); var pawn = entry.pawn; specials.Remove(entry); var newEntry = entry.DisableAbilityUser(); if (pawn.trader != null) { if (newEntry.IsSpecial) { Log.Warning(rgpMsgPrefix + "DisableAbilityUser on 'special' trader pawn {entry} into " + $"'normal' trader pawn {newEntry} may not have worked, but keeping this pawn due to trader status"); // Even if disabling didn't work, exclude them like normal trader pawns from more rebalancing, but do NOT // "undo" their inclusion in targetPoints, so that rebalancing still has to account for their base points. } else { DebugMessage(rgpMsgPrefix + "Disabled special trader pawn {entry} into normal trader pawn {newEntry}"); // Once disabled, exclude them like normal trader pawns from more rebalancing, // and "undo" their inclusion in targetPoints (and targetCount). targetPoints -= newEntry.basePoints; DebugAdd(ref targetCount, -1); } DebugAdd(ref excluded, newEntry, addToList: false); } else { if (newEntry.IsSpecial) { Log.Warning(rgpMsgPrefix + "DisableAbilityUser on 'special' pawn {entry} into 'normal' pawn {newEntry} " + "may not have worked, so destroying this pawn"); pawn.DestroyOrPassToWorld(); pawns.Remove(pawn); DebugAdd(ref destroyed, newEntry, addToList: false); } else { DebugMessage(rgpMsgPrefix + "Disabled special non-trader pawn {entry} into normal non-trader pawn {newEntry}"); normals.Add(newEntry); } } } else { // Note: Since specCount < avgCount, there's at least one normal pawn. var entry = normals.list.RandomElement(); var pawn = entry.pawn; DebugMessage(rgpMsgPrefix + "Destroyed normal non-trader pawn {entry}"); normals.Remove(entry); pawn.DestroyOrPassToWorld(); pawns.Remove(pawn); DebugAdd(ref destroyed, entry, addToList: false); } } DebugMessage(rgpMsgPrefix + "Result: #pawns = {pawns.Count}" + pawns.Join(pawn => $"\n\t{PawnAbilityPointsEntry.For(pawn)}", "")); } }
private static void DebugAdd(ref PawnAbilityPointsEntries entries, PawnAbilityPointsEntry entry, bool addToList = true) => entries.Add(entry, addToList);