Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Default beacon overrides #182

Merged
merged 6 commits into from
Jun 29, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
18 changes: 18 additions & 0 deletions Yafc.Model/Data/Database.cs
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
using System;
using System.Collections;
using System.Collections.Generic;
using System.Diagnostics.CodeAnalysis;
using System.Linq;

namespace Yafc.Model {
public static class Database {
Expand Down Expand Up @@ -33,6 +35,22 @@ public static class Database {
public static FactorioIdRange<Entity> entities { get; internal set; } = null!;
public static int constantCombinatorCapacity { get; internal set; } = 18;

/// <summary>
/// Returns the set of beacons filtered to only those that can accept at least one module.
/// </summary>
public static IEnumerable<EntityBeacon> usableBeacons => allBeacons.Where(b => allModules.Any(m => b.CanAcceptModule(m.moduleSpecification)));

/// <summary>
/// Fetches a module that can be used in this beacon, or <see langword="null"/> if no beacon was specified or no module could be found.
/// </summary>
/// <param name="beacon">The beacon to receive a module. If <see langword="null"/>, <paramref name="module"/> will be set to null and this method will return <see langword="false"/>.</param>
/// <param name="module">A module that can be placed in that beacon, if such a module exists.</param>
/// <returns><see langword="true"/> if a module could be found, or <see langword="false"/> if the supplied beacon does not accept any modules or was <see langword="null"/>.</returns>
public static bool GetDefaultModuleFor(EntityBeacon? beacon, [NotNullWhen(true)] out Module? module) {
module = allModules.FirstOrDefault(m => EntityWithModules.CanAcceptModule(m.moduleSpecification, beacon?.allowedEffects ?? AllowedEffects.None));
return module != null;
}

public static FactorioObject? FindClosestVariant(string id) {
string baseId;
int temperature;
Expand Down
54 changes: 51 additions & 3 deletions Yafc.Model/Model/ModuleFillerParameters.cs
Original file line number Diff line number Diff line change
@@ -1,10 +1,44 @@
using System;
using System.Collections.Generic;

namespace Yafc.Model {
public interface IModuleFiller {
void GetModulesInfo(RecipeParameters recipeParams, Recipe recipe, EntityCrafter entity, Goods? fuel, ref ModuleEffects effects, ref RecipeParameters.UsedModule used);
}

/// <summary>
/// An entry in the per-crafter beacon override configuration. It must specify both a beacon and a module, but it may specify zero beacons.
/// </summary>
/// <param name="beacon">The beacon to use for this crafter.</param>
/// <param name="beaconCount">The number of beacons to use. The total number of modules in beacons is this value times the number of modules that can be placed in a beacon.</param>
/// <param name="beaconModule">The module to place in the beacon.</param>
[Serializable]
public record BeaconOverrideConfiguration(EntityBeacon beacon, int beaconCount, Module beaconModule) {
/// <summary>
/// Gets or sets the beacon to use for this crafter.
/// </summary>
public EntityBeacon beacon { get; set; } = beacon;
/// <summary>
/// Gets or sets the number of beacons to use. The total number of modules in beacons is this value times the number of modules that can be placed in a beacon.
/// </summary>
public int beaconCount { get; set; } = beaconCount;
/// <summary>
/// Gets or sets the module to place in the beacon.
/// </summary>
public Module beaconModule { get; set; } = beaconModule;
}

/// <summary>
/// The result of applying the various beacon preferences to a crafter; this may result in a desired configuration where the beacon or module is not specified.
/// </summary>
/// <param name="beacon">The beacon to use for this crafter, or <see langword="null"/> if no beacons or beacon modules should be used.</param>
/// <param name="beaconCount">The number of beacons to use. The total number of modules in beacons is this value times the number of modules that can be placed in a beacon.</param>
/// <param name="beaconModule">The module to place in the beacon, or <see langword="null"/> if no beacons or beacon modules should be used.</param>
[Serializable]
public record BeaconConfiguration(EntityBeacon? beacon, int beaconCount, Module? beaconModule) {
public static implicit operator BeaconConfiguration(BeaconOverrideConfiguration beaconConfiguration) => new(beaconConfiguration.beacon, beaconConfiguration.beaconCount, beaconConfiguration.beaconModule);
}

[Serializable]
public class ModuleFillerParameters : ModelObject<ModelObject>, IModuleFiller {
public ModuleFillerParameters(ModelObject owner) : base(owner) { }
Expand All @@ -15,6 +49,7 @@ public ModuleFillerParameters(ModelObject owner) : base(owner) { }
public EntityBeacon? beacon { get; set; }
public Module? beaconModule { get; set; }
public int beaconsPerBuilding { get; set; } = 8;
public SortedList<EntityCrafter, BeaconOverrideConfiguration> overrideCrafterBeacons { get; } = new(DataUtils.DeterministicComparer);

[Obsolete("Moved to project settings", true)]
public int miningProductivity {
Expand All @@ -25,11 +60,24 @@ public int miningProductivity {
}
}

/// <summary>
/// Given a building that accepts beacon effects, return the type and number of beacons that should affect that building, along with the module to place in those beacons.
/// </summary>
/// <param name="crafter">The building to be affected by beacons.</param>
/// <returns>The type and number of beacons to apply to that type of building, along with the module that will be placed in the beacons.</returns>
public BeaconConfiguration GetBeaconsForCrafter(EntityCrafter? crafter) {
if (crafter is not null && overrideCrafterBeacons.TryGetValue(crafter, out var result)) {
return result;
}
return new(beacon, beaconsPerBuilding, beaconModule);
}

public void AutoFillBeacons(RecipeParameters recipeParams, Recipe recipe, EntityCrafter entity, Goods? fuel, ref ModuleEffects effects, ref RecipeParameters.UsedModule used) {
if (!recipe.flags.HasFlags(RecipeFlags.UsesMiningProductivity) && beacon != null && beaconModule != null) {
effects.AddModules(beaconModule.moduleSpecification, beaconsPerBuilding * beacon.beaconEfficiency * beacon.moduleSlots, entity.allowedEffects);
BeaconConfiguration beaconsToUse = GetBeaconsForCrafter(entity);
if (!recipe.flags.HasFlags(RecipeFlags.UsesMiningProductivity) && beaconsToUse.beacon is EntityBeacon beacon && beaconsToUse.beaconModule != null) {
effects.AddModules(beaconsToUse.beaconModule.moduleSpecification, beaconsToUse.beaconCount * beacon.beaconEfficiency * beacon.moduleSlots, entity.allowedEffects);
used.beacon = beacon;
used.beaconCount = beaconsPerBuilding;
used.beaconCount = beaconsToUse.beaconCount;
}
}

Expand Down
6 changes: 4 additions & 2 deletions Yafc/Widgets/ImmediateWidgets.cs
Original file line number Diff line number Diff line change
Expand Up @@ -262,7 +262,9 @@ public static void BuildObjectSelectDropDownWithNone<T>(this ImGui gui, ICollect
/// <param name="amount">Display this value, formatted appropriately for <paramref name="unit"/>.</param>
/// <param name="unit">Use this unit of measure when formatting <paramref name="amount"/> for display.</param>
/// <param name="newAmount">The new value entered by the user, if this returns <see cref="GoodsWithAmountEvent.TextEditing"/>. Otherwise, the original <paramref name="amount"/>.</param>
public static GoodsWithAmountEvent BuildFactorioObjectWithEditableAmount(this ImGui gui, FactorioObject? obj, float amount, UnitOfMeasure unit, out float newAmount, SchemeColor color = SchemeColor.None, bool useScale = true) {
/// <param name="allowScroll">If <see langword="true"/>, the default, the user can adjust the value by using the scroll wheel while hovering over the editable text.
/// If <see langword="false"/>, the scroll wheel will be ignored when hovering.</param>
public static GoodsWithAmountEvent BuildFactorioObjectWithEditableAmount(this ImGui gui, FactorioObject? obj, float amount, UnitOfMeasure unit, out float newAmount, SchemeColor color = SchemeColor.None, bool useScale = true, bool allowScroll = true) {
using var group = gui.EnterGroup(default, RectAllocator.Stretch, spacing: 0f);
group.SetWidth(3f);
newAmount = amount;
Expand All @@ -274,7 +276,7 @@ public static GoodsWithAmountEvent BuildFactorioObjectWithEditableAmount(this Im
}
}

if (gui.action == ImGuiAction.MouseScroll && gui.ConsumeEvent(gui.lastRect)) {
if (allowScroll && gui.action == ImGuiAction.MouseScroll && gui.ConsumeEvent(gui.lastRect)) {
float digit = MathF.Pow(10, MathF.Floor(MathF.Log10(amount) - 2f));
newAmount = MathF.Round((amount / digit) + gui.actionParameter) * digit;
evt = GoodsWithAmountEvent.TextEditing;
Expand Down
11 changes: 9 additions & 2 deletions Yafc/Windows/SelectObjectPanel.cs
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ public abstract class SelectObjectPanel<T> : PseudoScreen<T> {
private readonly SearchableList<FactorioObject?> list;
private string header = null!; // null-forgiving: set by Select
private Rect searchBox;
private string? noneTooltip;
/// <summary>
/// If <see langword="true"/> and the object being hovered is not a <see cref="Goods"/>, the <see cref="ObjectTooltip"/> should specify the type of object.
/// See also <see cref="ObjectTooltip.extendHeader"/>.
Expand All @@ -34,8 +35,10 @@ public abstract class SelectObjectPanel<T> : PseudoScreen<T> {
/// parameter for each <see cref="FactorioObject"/>. The first parameter may be <see langword="null"/> if <paramref name="allowNone"/> is <see langword="true"/>.</param>
/// <param name="allowNone">If <see langword="true"/>, a "none" option will be displayed. Selection of this item will be conveyed by calling <paramref name="mapResult"/>
/// and <paramref name="selectItem"/> with <see langword="default"/> values for <typeparamref name="T"/> and <typeparamref name="U"/>.</param>
protected void Select<U>(IEnumerable<U> list, string header, Action<U?> selectItem, IComparer<U>? ordering, Action<T?, Action<FactorioObject?>> mapResult, bool allowNone) where U : FactorioObject {
/// <param name="noneTooltip">If not <see langword="null"/>, this tooltip will be displayed when hovering over the "none" item.</param>
protected void Select<U>(IEnumerable<U> list, string header, Action<U?> selectItem, IComparer<U>? ordering, Action<T?, Action<FactorioObject?>> mapResult, bool allowNone, string? noneTooltip = null) where U : FactorioObject {
_ = MainScreen.Instance.ShowPseudoScreen(this);
this.noneTooltip = noneTooltip;
extendHeader = typeof(U) == typeof(FactorioObject);
List<U?> data = new List<U?>(list);
ordering ??= DataUtils.DefaultOrdering;
Expand Down Expand Up @@ -68,7 +71,11 @@ protected void Select<U>(IEnumerable<U> list, string header, Action<U?> selectIt

private void ElementDrawer(ImGui gui, FactorioObject? element, int index) {
if (element == null) {
if (gui.BuildRedButton(Icon.Close)) {
ButtonEvent evt = gui.BuildRedButton(Icon.Close);
if (noneTooltip != null) {
evt.WithTooltip(gui, noneTooltip);
}
if (evt) {
CloseWithResult(default);
}
}
Expand Down
5 changes: 3 additions & 2 deletions Yafc/Windows/SelectSingleObjectPanel.cs
Original file line number Diff line number Diff line change
Expand Up @@ -28,8 +28,9 @@ public static void Select<T>(IEnumerable<T> list, string header, Action<T> selec
/// <param name="header">The string that describes to the user why they're selecting these items.</param>
/// <param name="selectItem">An action to be called for the selected item when the panel is closed. The parameter will be <see langword="null"/> if the "none" or "clear" option is selected.</param>
/// <param name="ordering">An optional ordering specifying how to sort the displayed items. If <see langword="null"/>, defaults to <see cref="DataUtils.DefaultOrdering"/>.</param>
public static void SelectWithNone<T>(IEnumerable<T> list, string header, Action<T?> selectItem, IComparer<T>? ordering = null) where T : FactorioObject
=> new SelectSingleObjectPanel().Select(list, header, selectItem, ordering, (obj, mappedAction) => mappedAction(obj), true);
/// <param name="noneTooltip">If not <see langword="null"/>, this tooltip will be displayed when hovering over the "none" item.</param>
public static void SelectWithNone<T>(IEnumerable<T> list, string header, Action<T?> selectItem, IComparer<T>? ordering = null, string? noneTooltip = null) where T : FactorioObject
=> new SelectSingleObjectPanel().Select(list, header, selectItem, ordering, (obj, mappedAction) => mappedAction(obj), true, noneTooltip);

protected override void NonNullElementDrawer(ImGui gui, FactorioObject element, int index) {
if (gui.BuildFactorioObjectButton(element, 2.5f, MilestoneDisplay.Contained, extendHeader: extendHeader, useScale: true) == Click.Left) {
Expand Down
4 changes: 2 additions & 2 deletions Yafc/Workspace/ProductionTable/ModuleCustomizationScreen.cs
Original file line number Diff line number Diff line change
Expand Up @@ -84,8 +84,8 @@ public override void Build(ImGui gui) {
}

var defaultFiller = recipe?.GetModuleFiller();
if (defaultFiller?.beacon != null && defaultFiller.beaconModule != null) {
effects.AddModules(defaultFiller.beaconModule.moduleSpecification, defaultFiller.beacon.beaconEfficiency * defaultFiller.beacon.moduleSlots * defaultFiller.beaconsPerBuilding);
if (defaultFiller?.GetBeaconsForCrafter(recipe?.entity) is BeaconConfiguration { beacon: not null, beaconModule: not null } beaconsToUse) {
effects.AddModules(beaconsToUse.beaconModule.moduleSpecification, beaconsToUse.beacon.beaconEfficiency * beaconsToUse.beacon.moduleSlots * beaconsToUse.beaconCount);
}
}
else {
Expand Down
Loading