Exemplo n.º 1
0
        public void AutoFixMechDef(MechDef mechDef)
        {
            if (!AutoFixerFeature.settings.MechDefEngine)
            {
                return;
            }

            if (!AutoFixerFeature.settings.MechTagsAutoFixEnabled.Any(mechDef.MechTags.Contains))
            {
                return;
            }

            Control.mod.Logger.Log($"Auto fixing mechDef={mechDef.Description.Id} chassisDef={mechDef.Chassis.Description.Id}");

            MechDefBuilder builder;

            {
                var inventory = mechDef.Inventory.ToList();
                foreach (var componentRef in inventory)
                {
                    Control.mod.Logger.LogDebug($" {componentRef.ComponentDefID}{(componentRef.IsFixed?" (fixed)":"")} at {componentRef.MountedLocation}");
                }

                builder = new MechDefBuilder(mechDef.Chassis, inventory);
            }

            ArmorStructureRatioFeature.Shared.AutoFixMechDef(mechDef);

            var res = EngineSearcher.SearchInventory(builder.Inventory);

            var engineHeatSinkDef = mechDef.DataManager.HeatSinkDefs.Get(res.CoolingDef.HeatSinkDefId).GetComponent <EngineHeatSinkDef>();

            float CalcFreeTonnage()
            {
                float currentTotalTonnage = 0, maxValue = 0;

                MechStatisticsRules.CalculateTonnage(mechDef, ref currentTotalTonnage, ref maxValue);
                var freeTonnage = mechDef.Chassis.Tonnage - currentTotalTonnage;

                return(freeTonnage);
            }

            if (!EngineFeature.settings.AllowMixingHeatSinkTypes)
            {
                // remove incompatible heat sinks
                var incompatibleHeatSinks = builder.Inventory
                                            .Where(r => r.Def.Is <EngineHeatSinkDef>(out var hs) && hs.HSCategory != engineHeatSinkDef.HSCategory)
                                            .ToList();

                foreach (var incompatibleHeatSink in incompatibleHeatSinks)
                {
                    builder.Remove(incompatibleHeatSink);
                    builder.Add(engineHeatSinkDef.Def, ChassisLocations.Head, true);
                }
            }

            Engine engine = null;

            if (res.CoreDef != null)
            {
                engine = new Engine(res.CoolingDef, res.HeatBlockDef, res.CoreDef, res.Weights, new List <MechComponentRef>());

                // convert external heat sinks into internal ones
                // TODO only to make space if needed, drop the rest of the heat sinks

                {
                    var max     = engine.HeatSinkInternalAdditionalMaxCount;
                    var current = engine.EngineHeatBlockDef.HeatSinkCount;

                    var heatSinks = builder.Inventory
                                    .Where(r => r.Def.Is <EngineHeatSinkDef>(out var hs) && hs.HSCategory == engineHeatSinkDef.HSCategory)
                                    .ToList();

                    while (current < max && heatSinks.Count > 0)
                    {
                        var component = heatSinks[0];
                        heatSinks.RemoveAt(0);
                        builder.Remove(component);
                        current++;
                    }

                    if (current > 0)
                    {
                        var heatBlock = builder.Inventory.FirstOrDefault(r => r.Def.Is <EngineHeatBlockDef>());
                        if (heatBlock != null)
                        {
                            builder.Remove(heatBlock);
                        }

                        var heatBlockDefId = $"{AutoFixerFeature.settings.MechDefHeatBlockDef}_{current}";
                        var def            = mechDef.DataManager.HeatSinkDefs.Get(heatBlockDefId);
                        builder.Add(def, ChassisLocations.CenterTorso, true);
                    }
                }
            }
            else
            {
                var freeTonnage = CalcFreeTonnage();

                Control.mod.Logger.LogDebug($" find engine for freeTonnage={freeTonnage}");

                var jumpJets       = builder.Inventory.Where(x => x.ComponentDefType == ComponentType.JumpJet).ToList();
                var jumpJetTonnage = jumpJets.Select(x => x.Def.Tonnage).FirstOrDefault(); //0 if no jjs

                var externalHeatSinks = builder.Inventory
                                        .Where(r => r.Def.Is <EngineHeatSinkDef>(out var hs) && hs.HSCategory == engineHeatSinkDef.HSCategory)
                                        .ToList();
                var internalHeatSinksCount = res.HeatBlockDef.HeatSinkCount;

                var engineCandidates = new List <Engine>();

                var engineCoreDefs = mechDef.DataManager.HeatSinkDefs
                                     .Select(hs => hs.Value)
                                     .Select(hs => hs.GetComponent <EngineCoreDef>())
                                     .Where(c => c != null)
                                     .OrderByDescending(x => x.Rating);

                var removedExternalHeatSinksOverUse = false;

                foreach (var coreDef in engineCoreDefs)
                {
                    {
                        // remove superfluous jump jets
                        var maxJetCount = coreDef.GetMovement(mechDef.Chassis.Tonnage).JumpJetCount;
                        while (jumpJets.Count > maxJetCount)
                        {
                            var lastIndex = jumpJets.Count - 1;
                            var jumpJet   = jumpJets[lastIndex];
                            freeTonnage += jumpJet.Def.Tonnage;
                            builder.Remove(jumpJet);
                            jumpJets.Remove(jumpJet);

                            Control.mod.Logger.LogDebug("  Removed JumpJet");
                        }
                    }

                    {
                        var candidate = new Engine(res.CoolingDef, res.HeatBlockDef, coreDef, res.Weights, new List <MechComponentRef>());

                        Control.mod.Logger.LogDebug($"  candidate id={coreDef.Def.Description.Id} TotalTonnage={candidate.TotalTonnage}");

                        engineCandidates.Add(candidate);

                        var internalHeatSinksMax = candidate.HeatSinkInternalAdditionalMaxCount;

                        // convert external ones to internal ones
                        while (internalHeatSinksCount < internalHeatSinksMax && externalHeatSinks.Count > 0)
                        {
                            var component = externalHeatSinks[0];
                            externalHeatSinks.RemoveAt(0);
                            builder.Remove(component);
                            internalHeatSinksCount++;

                            Control.mod.Logger.LogDebug("  ~Converted external to internal");
                        }

                        // this only runs on the engine that takes the most heat sinks (since this is in a for loop with rating descending order)
                        // that way we only remove external heat sinks that couldn't be moved internally
                        while (!removedExternalHeatSinksOverUse && externalHeatSinks.Count > 0)
                        {
                            var component = externalHeatSinks[0];
                            externalHeatSinks.RemoveAt(0);
                            builder.Remove(component);
                            var newComponent = builder.Add(component.Def);
                            if (newComponent == null)
                            {
                                Control.mod.Logger.LogDebug("  Removed external heat sink that doesn't fit");
                                // might still need to remove some
                                continue;
                            }
                            // addition worked
                            externalHeatSinks.Add(newComponent);
                            break;
                        }
                        removedExternalHeatSinksOverUse = true;

                        // convert internal ones to external ones
                        while (internalHeatSinksCount > internalHeatSinksMax)
                        {
                            if (builder.Add(engineHeatSinkDef.Def) == null)
                            {
                                Control.mod.Logger.LogDebug("  ~Dropped external when converting from internal");
                                freeTonnage++;
                            }
                            else
                            {
                                Control.mod.Logger.LogDebug("  ~Converted internal to external");
                            }
                            internalHeatSinksCount--;
                        }

                        // remove candidates that make no sense anymore
                        // TODO not perfect and maybe too large for small mechs
                        engineCandidates = engineCandidates.Where(x => x.TotalTonnage <= freeTonnage + 6 * engineHeatSinkDef.Def.Tonnage + jumpJetTonnage).ToList();
                    }

                    // go through all candidates, larger first
                    engine = engineCandidates.FirstOrDefault(candidate => candidate.TotalTonnage <= freeTonnage);

                    if (engine != null)
                    {
                        break;
                    }
                }

                if (engine != null)
                {
                    Control.mod.Logger.LogDebug($" engine={engine.CoreDef} freeTonnage={freeTonnage}");
                    var dummyCore = builder.Inventory.FirstOrDefault(r => r.ComponentDefID == AutoFixerFeature.settings.MechDefCoreDummy);
                    builder.Remove(dummyCore);
                    builder.Add(engine.CoreDef.Def, ChassisLocations.CenterTorso, true);

                    // convert internal heat sinks back as external ones if the mech can fit it
                    while (internalHeatSinksCount > 0 && builder.Add(engineHeatSinkDef.Def) != null)
                    {
                        internalHeatSinksCount--;
                    }

                    if (internalHeatSinksCount > 0)
                    {
                        var heatBlock = builder.Inventory.FirstOrDefault(r => r.Def.Is <EngineHeatBlockDef>());
                        if (heatBlock != null)
                        {
                            builder.Remove(heatBlock);
                        }

                        var heatBlockDefId = $"{AutoFixerFeature.settings.MechDefHeatBlockDef}_{internalHeatSinksCount}";
                        var def            = mechDef.DataManager.HeatSinkDefs.Get(heatBlockDefId);
                        builder.Add(def, ChassisLocations.CenterTorso, true);
                    }
                }
            }

            if (engine == null)
            {
                return;
            }

            // add free heat sinks
            {
                var max = engine.HeatSinkExternalFreeMaxCount;
                for (var i = 0; i < max; i++)
                {
                    builder.Add(engineHeatSinkDef.Def, ChassisLocations.Head, true);
                }
            }

            // find any overused location
            if (builder.HasOveruseAtAnyLocation())
            {
                Control.mod.Logger.LogError($" Overuse found");
                // heatsinks, upgrades
                var itemsToBeReordered = builder.Inventory
                                         .Where(IsMovable)
                                         .OrderBy(c => MechDefBuilder.LocationCount(c.Def.AllowedLocations))
                                         .ThenByDescending(c => c.Def.InventorySize)
                                         .ToList();

                // remove all items that can be reordered: heatsinks, upgrades
                foreach (var item in itemsToBeReordered)
                {
                    builder.Remove(item);
                }

                // then add most restricting, and then largest items first (probably double head sinks)
                foreach (var item in itemsToBeReordered)
                {
                    if (builder.Add(item.Def) == null)
                    {
                        Control.mod.Logger.LogError($" Component {item.ComponentDefID} from {item.MountedLocation} can't be re-added");
                    }
                    else
                    {
                        Control.mod.Logger.LogDebug($"  Component {item.ComponentDefID} re-added");
                    }
                }
            }

            mechDef.SetInventory(builder.Inventory.OrderBy(element => element, new OrderComparer()).ToArray());

            {
                var freeTonnage = CalcFreeTonnage();
                if (freeTonnage > 0)
                {
                    // TODO add armor for each location with free tonnage left
                }
                else if (freeTonnage < 0)
                {
                    var removableItems = builder.Inventory
                                         .Where(IsRemovable)
                                         .OrderBy(c => c.Def.Tonnage)
                                         .ThenByDescending(c => c.Def.InventorySize)
                                         .ThenByDescending(c =>
                    {
                        switch (c.ComponentDefType)
                        {
                        case ComponentType.HeatSink:
                            return(2);

                        case ComponentType.JumpJet:
                            return(1);

                        default:
                            return(0);
                        }
                    })
                                         .ToList();

                    while (removableItems.Count > 0 && freeTonnage < 0)
                    {
                        var item = removableItems[0];
                        removableItems.RemoveAt(0);
                        freeTonnage += item.Def.Tonnage;
                        builder.Remove(item);
                    }
                }
            }

            mechDef.SetInventory(builder.Inventory.OrderBy(element => element, new OrderComparer()).ToArray());
        }
Exemplo n.º 2
0
        public void AutoFixMechDef(MechDef mechDef)
        {
            if (!AutoFixerFeature.settings.MechDefEngine)
            {
                return;
            }

            //DumpAllAsTable();
            if (mechDef.Inventory.Any(c => c.Def.GetComponent <EngineCoreDef>() != null))
            {
                return;
            }

            Control.mod.Logger.Log($"Auto fixing mechDef={mechDef.Description.Id} chassisDef={mechDef.Chassis.Description.Id}");

            ArmorStructureRatioFeature.Shared.AutoFixMechDef(mechDef);

            var builder             = new MechDefBuilder(mechDef.Chassis, mechDef.Inventory.ToList());
            var standardHeatSinkDef = mechDef.DataManager.GetDefaultEngineHeatSinkDef();
            var engineHeatSinkDef   = builder.Inventory
                                      .Select(r => r.Def.GetComponent <CoolingDef>())
                                      .Where(d => d != null)
                                      .Select(d => mechDef.DataManager.HeatSinkDefs.Get(d.HeatSinkDefId))
                                      .Where(d => d != null)
                                      .Select(d => d.GetComponent <EngineHeatSinkDef>())
                                      .FirstOrDefault() ?? standardHeatSinkDef;

            float freeTonnage;
            {
                float currentTotalTonnage = 0, maxValue = 0;
                MechStatisticsRules.CalculateTonnage(mechDef, ref currentTotalTonnage, ref maxValue);
                var maxFreeTonnage = mechDef.Chassis.Tonnage - currentTotalTonnage;

                var initialTonnage         = mechDef.Chassis.InitialTonnage;
                var originalInitialTonnage = ChassisHandler.GetOriginalInitialTonnage(mechDef.Chassis) ?? initialTonnage;
                var initialTonnageGain     = Mathf.Max(0, originalInitialTonnage - initialTonnage);
                if (AutoFixerFeature.settings.MechDefAutoFixAgainstMaxFreeTonnage.Contains(mechDef.Description.Id))
                {
                    freeTonnage = maxFreeTonnage;
                }
                else
                {
                    var freeTonnageThreshold = AutoFixerFeature.settings.MechDefAutoFixInitialTonnageDiffThreshold;
                    freeTonnage = Mathf.Min(maxFreeTonnage, initialTonnageGain + freeTonnageThreshold);
                }

                Control.mod.Logger.LogDebug($"freeTonnage={freeTonnage}" +
                                            $" currentTotalTonnage={currentTotalTonnage}" +
                                            $" maxFreeTonnage={maxFreeTonnage}" +
                                            $" initialTonnageGain={initialTonnageGain}" +
                                            $" initialGainSmaller={initialTonnageGain < maxFreeTonnage}");
            }

            //Control.mod.Logger.LogDebug("C maxEngineTonnage=" + maxEngineTonnage);
            var standardWeights   = new Weights(); // use default gyro and weights
            var standardHeatBlock = mechDef.DataManager.HeatSinkDefs.Get(AutoFixerFeature.settings.MechDefHeatBlockDef).GetComponent <EngineHeatBlockDef>();
            var standardCooling   = mechDef.DataManager.HeatSinkDefs.Get(AutoFixerFeature.settings.MechDefCoolingDef).GetComponent <CoolingDef>();

            var engineCoreDefs = mechDef.DataManager.HeatSinkDefs
                                 .Select(hs => hs.Value)
                                 .Select(hs => hs.GetComponent <EngineCoreDef>())
                                 .Where(c => c != null)
                                 .OrderByDescending(x => x.Rating);

            Engine maxEngine = null;

            {
                //var heatSinks = builder.Inventory.Where(x => x.ComponentDefType == ComponentType.HeatSink && x.Def.Is<EngineHeatSinkDef>()).ToList();
                var jumpJetList = builder.Inventory.Where(x => x.ComponentDefType == ComponentType.JumpJet).ToList();
                var engines     = new LinkedList <Engine>();

                foreach (var coreDef in engineCoreDefs)
                {
                    {
                        var engine = new Engine(standardCooling, standardHeatBlock, coreDef, standardWeights, new List <MechComponentRef>());
                        engines.AddFirst(engine);
                    }

                    {
                        // remove superfluous jump jets
                        var maxJetCount = coreDef.GetMovement(mechDef.Chassis.Tonnage).JumpJetCount;
                        //Control.mod.Logger.LogDebug($"before Inventory.Count={builder.Inventory.Count} jumpJetList.Count={jumpJetList.Count} maxJetCount={maxJetCount}");
                        while (jumpJetList.Count > maxJetCount)
                        {
                            var lastIndex = jumpJetList.Count - 1;
                            var jumpJet   = jumpJetList[lastIndex];
                            freeTonnage += jumpJet.Def.Tonnage;
                            builder.Remove(jumpJet);
                            jumpJetList.Remove(jumpJet);
                        }
                        //Control.mod.Logger.LogDebug($"after Inventory.Count={builder.Inventory.Count} jumpJetList.Count={jumpJetList.Count} maxJetCount={maxJetCount}");
                    }

                    foreach (var engine in engines)
                    {
//                        Control.mod.Logger.LogDebug($"D engine={engine.CoreDef} engine.TotalTonnage={engine.TotalTonnage} freeTonnage={freeTonnage}");

                        if (engine.TotalTonnage <= freeTonnage)
                        {
                            maxEngine = engine;
                        }
                        else
                        {
                            break;
                        }
                    }
                    if (maxEngine != null)
                    {
                        break;
                    }
                }
            }

            if (maxEngine == null)
            {
                return;
            }

            Control.mod.Logger.LogDebug($" maxEngine={maxEngine.CoreDef} freeTonnage={freeTonnage}");
            {
                var dummyCore = builder.Inventory.FirstOrDefault(r => r.ComponentDefID == AutoFixerFeature.settings.MechDefCoreDummy);
                if (dummyCore != null)
                {
                    builder.Remove(dummyCore);
                }
            }

            // add engine
            builder.Add(maxEngine.CoreDef.Def, ChassisLocations.CenterTorso, true);

            if (!EngineFeature.settings.AllowMixingHeatSinkTypes)
            {
                // remove incompatible heat sinks
                var incompatibleHeatSinks = builder.Inventory
                                            .Where(r => r.Def.Is <EngineHeatSinkDef>(out var hs) && hs.HSCategory != engineHeatSinkDef.HSCategory)
                                            .ToList();
                foreach (var incompatibleHeatSink in incompatibleHeatSinks)
                {
                    builder.Remove(incompatibleHeatSink);
                }

                //Control.mod.Logger.LogDebug($"Inventory.Count={builder.Inventory.Count} incompatibleHeatSinks.Count={incompatibleHeatSinks.Count}");
                // add same amount of compatible heat sinks
                foreach (var unused in incompatibleHeatSinks)
                {
                    builder.Add(engineHeatSinkDef.Def);
                }

                //Control.mod.Logger.LogDebug($"Inventory.Count={builder.Inventory.Count}");
            }

            // add free heatsinks
            {
                //var maxFree = maxEngine.CoreDef.ExternalHeatSinksFreeMaxCount;
                //var current = maxEngine.ExternalHeatSinkCount;
                var maxFree = maxEngine.HeatSinkExternalFreeMaxCount;
                var current = 0; //we assume exiting heatsinks on the mech are additional and not free
                for (var i = current; i < maxFree; i++)
                {
                    if (!builder.Add(engineHeatSinkDef.Def))
                    {
                        break;
                    }
                }
                //Control.mod.Logger.LogDebug($"Inventory.Count={builder.Inventory.Count} maxFree={maxFree}");
            }

            // find any overused location
            if (builder.HasOveruseAtAnyLocation())
            {
                // heatsinks, upgrades
                var itemsToBeReordered = builder.Inventory
                                         .Where(IsReorderable)
                                         .OrderBy(c => MechDefBuilder.LocationCount(c.Def.AllowedLocations))
                                         .ThenByDescending(c => c.Def.InventorySize)
                                         .ThenByDescending(c =>
                {
                    switch (c.ComponentDefType)
                    {
                    case ComponentType.Upgrade:
                        return(2);

                    case ComponentType.AmmunitionBox:
                        return(1);

                    default:
                        return(0);
                    }
                })
                                         .ToList();

                // remove all items that can be reordered: heatsinks, upgrades
                foreach (var item in itemsToBeReordered)
                {
                    builder.Remove(item);
                }

                // then add most restricting, and then largest items first (probably double head sinks)
                foreach (var item in itemsToBeReordered)
                {
                    // couldn't add everything
                    if (!builder.Add(item.Def))
                    {
                        return;
                    }
                }
            }

            mechDef.SetInventory(builder.Inventory.OrderBy(element => element, new OrderComparer()).ToArray());

            //{
            //    float currentTotalTonnage = 0, maxValue = 0;
            //    MechStatisticsRules.CalculateTonnage(mechDef, ref currentTotalTonnage, ref maxValue);
            //    Control.mod.Logger.LogDebug($" end currentTotalTonnage={currentTotalTonnage} mechDef.Chassis.Tonnage={mechDef.Chassis.Tonnage}");
            //}
        }