private void CreateRelicModel(EquipSlot s, Equipment e) { var gv = gear[s, e]; RelicConfig config = null; SolverConfig.RelicConfigs.TryGetValue(e.ItemLevel.Key, out config); if (config == null || !config.Items.Contains(e.Key)) { return; } var isNonSurjective = config.ConversionFunction != null; var statCap = config.StatCapOverrides.ContainsKey(Job) && config.StatCapOverrides[Job].ContainsKey(s) ? config.StatCapOverrides[Job][s] : config.StatCap; Model.AddConstraint( Expression.Sum(RelevantStats.Select(bp => isNonSurjective ? relicBase[s, e, bp] : cap[s, e, bp])) <= statCap * gv, $"total relic cap for {e} in slot {s}"); foreach (var bp in RelevantStats) { var remCap = e.GetMateriaMeldCap(bp, true); if (remCap == 0) { continue; } var cv = cap[s, e, bp]; var func = config.ConversionFunction; if (config.ConversionOverride != null && config.ConversionOverride.ContainsKey(bp)) { func = config.ConversionOverride[bp]; } if (isNonSurjective) { func.AddToModel(Model, relicBase[s, e, bp], cv, SolverConfig.SolverSupportsSOS); } Model.AddConstraint(cv <= remCap * gv, $"upper stat cap for {bp} of relic {e} in slot {s}"); StatExprs.AddExprToDict(bp, cv); } }
private void CreateObjective() { var objExpr = Expression.EmptyExpression; foreach (var bp in RelevantStats) { if (MainStats.Contains(bp.Name)) { StatExprs.AddExprToDict(bp, allocstat[bp]); } Model.AddConstraint(stat[bp] == StatExprs[bp], "set collected stat " + bp); Model.AddConstraint(modstat[bp] <= FoodExprs[bp], "relative food bonuses for " + bp); if (JobConfig.Weights.ContainsKey(bp)) { if (SolverConfig.MaximizeUnweightedValues && TieredStats.Contains(bp.Name)) { // add a small dummy weight so shown stats are amxed out DummyObjective += modstat[bp] * 1e-5; } objExpr += JobConfig.Weights[bp] * tieredstat[bp]; } if (JobConfig.StatRequirements.ContainsKey(bp)) { if (SolverConfig.MaximizeUnweightedValues && !JobConfig.Weights.ContainsKey(bp)) { DummyObjective += modstat[bp] * 1e-5; } Model.AddConstraint(modstat[bp] >= JobConfig.StatRequirements[bp], "satisfy stat requirement for " + bp); } } Model.AddObjective(new Objective(objExpr + DummyObjective, "stat weight", ObjectiveSense.Maximize), "stat weight"); }
private void CreateMateriaModel(EquipSlot s, Equipment e) { if (!MateriaChoices.Any()) { return; } var gv = gear[s, e]; //TODO: you can probably optimize tons here Model.AddConstraint( Expression.Sum(MateriaChoices.Select(m => materia[s, e, m.Key])) <= e.TotalMateriaSlots() * gv, $"restrict total materia amount to amount permitted for {e} in {s}"); if (e.IsAdvancedMeldingPermitted) { if (MateriaChoices.Any(m => MainStats.Contains(m.Key.BaseParam.Name))) { Model.AddConstraint( Expression.Sum( MateriaChoices.Where(m => MainStats.Contains(m.Key.BaseParam.Name)) .Select(m => materia[s, e, m.Key])) <= e.FreeMateriaSlots * gv, $"restrict advanced melding for mainstat materia to amount of slots in {e} in {s}"); } if (MateriaChoices.Any(m => !m.Value)) { Model.AddConstraint( Expression.Sum( MateriaChoices.Where(m => !m.Value).Select(m => materia[s, e, m.Key])) <= (e.FreeMateriaSlots + SolverConfig.OvermeldThreshold) * gv, $"restrict regular materia amount to amount permitted for {e} in {s}"); } } // SIMPLIFICATION: ignoring whether materia fits; doesn't matter anyway foreach (var matGrp in MateriaChoices.GroupBy(m => m.Key.BaseParam)) { var bp = matGrp.Key; var remCap = e.GetMateriaMeldCap(bp, true); if (remCap == 0) { continue; } // we need to constrain against min(remaining cap, melded materia) to account for stat caps var cv = cap[s, e, bp]; Model.AddConstraint(cv <= remCap * gv, $"cap stats using {e}'s meld cap for {bp} in slot {s}"); var maxRegularMat = matGrp.MaxBy(f => f.Key.Value).Key; var maxAdvancedMat = maxRegularMat; if (e.IsAdvancedMeldingPermitted) { maxAdvancedMat = matGrp.Where(m => m.Value).MaxBy(f => f.Key.Value).Key; } // need hash-set here for uniqueness Model.AddConstraint( cv <= Expression.Sum( new HashSet <MateriaItem> { maxRegularMat, maxAdvancedMat } .Select(m => m.Value * materia[s, e, m])), $"cap stats using used {bp} for {e} in slot {s}"); StatExprs.AddExprToDict(bp, cv); } }
private void CreateGearModel() { foreach (var grp in GearChoices.GroupBy(g => g.EquipSlotCategory)) { // if gear is unique, equip it once only. if (grp.Key.PossibleSlots.Count() > 1) { grp.Where(e => e.IsUnique) .ForEach( e => Model.AddConstraint(Expression.Sum(grp.Key.PossibleSlots.Select(s => gear[s, e])) <= 1, $"ensure gear uniqueness for {e}")); } // SIMPLIFICATION: we ignore blocked slots foreach (var s in grp.Key.PossibleSlots) { // choose at most one gear per slot Model.AddConstraint(Expression.Sum(grp.Select(e => gear[s, e])) <= 1, $"choose at most one item for slot {s}"); foreach (var e in grp) { var gv = gear[s, e]; // ASSUMPTION: all gear choices have fixed parameters var stats = e.AllParameters.Where(p => RelevantStats.Contains(p.BaseParam)) .ToDictionary(p => p.BaseParam, p => p.Values.OfType <ParameterValueFixed>().Select(val => val.Amount).Sum()); if (SolverConfig.EquipmentOverrides?.ContainsKey(e) ?? false) { var statsOverride = SolverConfig.EquipmentOverrides[e]; if (statsOverride == null) { continue; } foreach (var kv in statsOverride) { stats[kv.Key] = kv.Value; } } // sanity check stats foreach (var kv in stats) { if (e.GetMaximumParamValue(kv.Key) < kv.Value) { Console.Error.WriteLine($"{kv.Key} => {kv.Value} for {e} is out of range"); } } stats.ForEach(p => StatExprs.AddExprToDict(p.Key, p.Value * gv)); // ASSUMPTION: all meldable items have at least one materia slot // ASSUMPTION: customisable relics are unmeldable if (e.FreeMateriaSlots > 0) { CreateMateriaModel(s, e); } else { CreateRelicModel(s, e); } } } } }