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

Make milestone list scrollable and support more milestones #19

Merged
merged 3 commits into from
Feb 7, 2024
Merged
Show file tree
Hide file tree
Changes from 2 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
7 changes: 7 additions & 0 deletions FactorioCalc.sln
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@

Microsoft Visual Studio Solution File, Format Version 12.00
#
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "YAFC", "YAFC\YAFC.csproj", "{73EBA162-A3BE-43CC-9B55-CA16332F439D}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "YAFCui", "YAFCui\YAFCui.csproj", "{70F74D2B-9747-4185-B369-64F77BC8D0D5}"
Expand All @@ -10,6 +11,8 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "YAFCparser", "YAFCparser\YA
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "CommandLineToolExample", "CommandLineToolExample\CommandLineToolExample.csproj", "{57E8CAE8-A3F8-4532-B32F-09347852479E}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "YAFCmodel.Tests", "YAFCmodel.Tests\YAFCmodel.Tests.csproj", "{66B66728-84F0-4242-B49A-B9D746A3CCA5}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
Expand All @@ -36,5 +39,9 @@ Global
{57E8CAE8-A3F8-4532-B32F-09347852479E}.Debug|Any CPU.Build.0 = Debug|Any CPU
{57E8CAE8-A3F8-4532-B32F-09347852479E}.Release|Any CPU.ActiveCfg = Release|Any CPU
{57E8CAE8-A3F8-4532-B32F-09347852479E}.Release|Any CPU.Build.0 = Release|Any CPU
{66B66728-84F0-4242-B49A-B9D746A3CCA5}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{66B66728-84F0-4242-B49A-B9D746A3CCA5}.Debug|Any CPU.Build.0 = Debug|Any CPU
{66B66728-84F0-4242-B49A-B9D746A3CCA5}.Release|Any CPU.ActiveCfg = Release|Any CPU
{66B66728-84F0-4242-B49A-B9D746A3CCA5}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
EndGlobal
4 changes: 1 addition & 3 deletions YAFC/Windows/MilestonesEditor.cs
Original file line number Diff line number Diff line change
Expand Up @@ -59,9 +59,7 @@ public override void Build(ImGui gui)
}
if (gui.BuildButton("Add milestone"))
{
if (Project.current.settings.milestones.Count >= 60)
MessageBox.Show(null, "Milestone limit reached", "60 milestones is the limit. You may delete some of the milestones you've already reached.", "Ok");
else SelectObjectPanel.Select(Database.objects.all, "Add new milestone", AddMilestone);
SelectObjectPanel.Select(Database.objects.all, "Add new milestone", AddMilestone);
}
}
}
Expand Down
58 changes: 30 additions & 28 deletions YAFC/Windows/MilestonesPanel.cs
Original file line number Diff line number Diff line change
@@ -1,46 +1,48 @@
using System.Numerics;
using YAFC.Model;
using YAFC.UI;

namespace YAFC
{
public class MilestonesWidget
public class MilestonesWidget : VirtualScrollList<FactorioObject>
{
public static readonly MilestonesWidget Instance = new MilestonesWidget();
public void Build(ImGui gui)

public MilestonesWidget() : base(30f, new Vector2(3f, 3f), MilestoneDrawer)
{
data = Project.current.settings.milestones;
}

private static void MilestoneDrawer(ImGui gui, FactorioObject element, int index)
{
var settings = Project.current.settings;
using (var grid = gui.EnterInlineGrid(3f))
var unlocked = settings.Flags(element).HasFlags(ProjectPerItemFlags.MilestoneUnlocked);
if (gui.BuildFactorioObjectButton(element, 3f, display: MilestoneDisplay.None, bgColor: unlocked ? SchemeColor.Primary : SchemeColor.None))
{
foreach (var cur in settings.milestones)
if (!unlocked)
{
grid.Next();
var unlocked = settings.Flags(cur).HasFlags(ProjectPerItemFlags.MilestoneUnlocked);
if (gui.BuildFactorioObjectButton(cur, 3f, MilestoneDisplay.None, unlocked ? SchemeColor.Primary : SchemeColor.None))
var massUnlock = Milestones.Instance.milestoneResult[element];
var subIndex = 0;
settings.SetFlag(element, ProjectPerItemFlags.MilestoneUnlocked, true);
foreach (var milestone in settings.milestones)
{
if (!unlocked)
{
var massUnlock = Milestones.Instance.milestoneResult[cur];
var subIndex = 0;
settings.SetFlag(cur, ProjectPerItemFlags.MilestoneUnlocked, true);
foreach (var milestone in settings.milestones)
{
subIndex++;
if ((massUnlock & (1ul << subIndex)) != 0)
settings.SetFlag(milestone, ProjectPerItemFlags.MilestoneUnlocked, true);
}
}
else
{
settings.SetFlag(cur, ProjectPerItemFlags.MilestoneUnlocked, false);
}
subIndex++;
if ((massUnlock & (1ul << subIndex)) != 0)
settings.SetFlag(milestone, ProjectPerItemFlags.MilestoneUnlocked, true);
}
if (unlocked && gui.isBuilding)
gui.DrawIcon(gui.lastRect, Icon.Check, SchemeColor.Error);
}
else
{
settings.SetFlag(element, ProjectPerItemFlags.MilestoneUnlocked, false);
}
}
if (unlocked && gui.isBuilding)
gui.DrawIcon(gui.lastRect, Icon.Check, SchemeColor.Error);

}

}

public class MilestonesPanel : PseudoScreen
{
public static readonly MilestonesPanel Instance = new MilestonesPanel();
Expand All @@ -53,14 +55,14 @@ public override void Build(ImGui gui)
gui.AllocateSpacing(2f);
MilestonesWidget.Instance.Build(gui);
gui.AllocateSpacing(2f);
gui.BuildText("For your convinience, YAFC will show objects you DON'T have access to based on this selection", wrap:true);
gui.BuildText("For your convenience, YAFC will show objects you DON'T have access to based on this selection", wrap: true);
gui.BuildText("These are called 'Milestones'. By default all science packs are added as milestones, but this does not have to be this way! " +
"You can define your own milestones: Any item, recipe, entity or technology may be added as a milestone. For example you can add advanced " +
"electronic circuits as a milestone, and YAFC will display everything that is locked behind those circuits", wrap: true);
using (gui.EnterRow())
{
if (gui.BuildButton("Edit milestones", SchemeColor.Grey))
MilestonesEditor.Show();
MilestonesEditor.Show();
if (gui.RemainingRow().BuildButton("Done"))
Close();
}
Expand Down
108 changes: 108 additions & 0 deletions YAFCmodel.Tests/Analysis/Milestones.cs
shpaass marked this conversation as resolved.
Show resolved Hide resolved
Original file line number Diff line number Diff line change
@@ -0,0 +1,108 @@
using System.Reflection;
using System.Collections.Generic;
using Xunit;

namespace YAFC.Model.Tests
{
public class MilestonesTests
{
private static Milestones setupMilestones(ulong result, ulong mask, out FactorioObject factorioObj)
{
factorioObj = new Technology();
var milestoneResult = new Mapping<FactorioObject, ulong>(new FactorioIdRange<FactorioObject>(0, 1, new List<FactorioObject>() {
factorioObj
}));
milestoneResult[factorioObj] = result;

var milestones = new Milestones()
{
milestoneResult = milestoneResult,
currentMilestones = new FactorioObject[] { factorioObj }
};

var milestonesType = typeof(Milestones);
var milestonesLockedMask = milestonesType.GetProperty("lockedMask");
milestonesLockedMask.SetValue(milestones, mask);

return milestones;
}

[Theory]
[InlineData(0, 1, false)]
[InlineData(1, 0, false)]
[InlineData(1, 1, true)]
[InlineData(3, 1, true)]
[InlineData(1, 3, true)]
public void IsAccessibleWithCurrentMilestones_WhenGivenMilestones_ShouldReturnCorrectValue(ulong result, ulong mask, bool expectedResult)
{
var milestones = setupMilestones(result, mask, out FactorioObject factorioObj);

Assert.Equal(expectedResult, milestones.IsAccessibleWithCurrentMilestones(0));
Assert.Equal(expectedResult, milestones.IsAccessibleWithCurrentMilestones(factorioObj));
}

[Theory]
[InlineData(1, 1, true)]
[InlineData(3, 3, false)]
[InlineData(15, 15, false)] // Triggers last return
[InlineData(16, 16, false)] // Triggers last return
[InlineData(17, 17, false)] // Caught by 'bit 0 check', otherwise last return would return true
public void IsAccessibleAtNextMilestone_WhenGivenMilestones_ShouldReturnCorrectValue(ulong result, ulong mask, bool expectedResult)
{
var milestones = setupMilestones(result, mask, out FactorioObject factorioObj);

Assert.Equal(expectedResult, milestones.IsAccessibleAtNextMilestone(factorioObj));
}

[Theory]
[InlineData(false, ~0ul)] // all bits set (nothing gets masked)
[InlineData(true, ~0ul & ~2ul)] // all bits set, except bit 1 (for reasons not bit 0, even if the FIRST milestone has its flag set?!)
public void GetLockedMaskFromProject_ShouldCalculateMask(bool unlocked, ulong expectedResult)
{
var milestonesType = typeof(Milestones);
var getLockedMaskFromProject = milestonesType.GetMethod("GetLockedMaskFromProject", BindingFlags.NonPublic | BindingFlags.Instance);
var projectField = milestonesType.GetField("project", BindingFlags.NonPublic | BindingFlags.Instance);

var milestones = setupMilestones(0, 0, out FactorioObject factorioObj);

var project = new Project();
if (unlocked)
{
// Can't use SetFlag() as it uses the Undo system, which requires SDL
var flags = project.settings.itemFlags;
flags[factorioObj] = ProjectPerItemFlags.MilestoneUnlocked;
var projectType = typeof(ProjectSettings);
var itemFlagsField = projectType.GetField("<itemFlags>k__BackingField", BindingFlags.NonPublic | BindingFlags.Instance);
itemFlagsField.SetValue(project.settings, flags);
}


projectField.SetValue(milestones, project);

getLockedMaskFromProject.Invoke(milestones, null);

Assert.Equal(expectedResult, milestones.lockedMask);
}

[Theory]
[InlineData(1, 0, true, false)] // HighestBitSet() - 1, so bit 0 is never in range...
[InlineData(2, 0, true, true)] // mask is ignored -> true
[InlineData(2, 0, false, false)] // mask is active -> false
[InlineData(2, 2, true, true)]
[InlineData(2, 2, false, true)] // mask is active and overlaps -> true
[InlineData(4, 0, true, false)] // HighestBitSet() too large...
public void GetHighest_WhenGivenMilestones_ShouldReturnCorrectValue(ulong result, ulong mask, bool all, bool expectObject)
{
var milestones = setupMilestones(result, mask, out FactorioObject factorioObj);

if (expectObject)
{
Assert.Equal(factorioObj, milestones.GetHighest(factorioObj, all));
}
else
{
Assert.Null(milestones.GetHighest(factorioObj, all));
}
}
}
}
21 changes: 21 additions & 0 deletions YAFCmodel.Tests/YAFCmodel.Tests.csproj
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
<Project Sdk="Microsoft.NET.Sdk">

<PropertyGroup>
<TargetFramework>netcoreapp6.0</TargetFramework>
</PropertyGroup>

<ItemGroup>
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.4.0" />
<PackageReference Include="xunit" Version="2.4.2" />
<PackageReference Include="xunit.runner.visualstudio" Version="2.4.5">
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
<PrivateAssets>all</PrivateAssets>
</PackageReference>
</ItemGroup>

<ItemGroup>
<ProjectReference Include="..\YAFCmodel\YAFCmodel.csproj" />
<ProjectReference Include="..\YAFCparser\YAFCparser.csproj" />
</ItemGroup>

</Project>
1 change: 1 addition & 0 deletions YAFCmodel/Analysis/Milestones.cs
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ public bool IsAccessibleAtNextMilestone(FactorioObject obj)
return true;
if ((milestoneMask & 1) != 0)
return false;
// TODO Always returns false -> milestoneMask is a power of 2 + 1 always has bit 0 set, as x pow 2 sets one (high) bit, so the + 1 adds bit 0, which is detected by (milestoneMask & 1) != 0
return ((milestoneMask - 1) & (milestoneMask - 2)) == 0; // milestoneMask is a power of 2 + 1
}

Expand Down
4 changes: 2 additions & 2 deletions YAFCmodel/Data/Database.cs
Original file line number Diff line number Diff line change
Expand Up @@ -79,7 +79,7 @@ public class FactorioIdRange<T> where T : FactorioObject
public int count { get; }
public T[] all { get; }

internal FactorioIdRange(int start, int end, List<FactorioObject> source)
public FactorioIdRange(int start, int end, List<FactorioObject> source)
{
this.start = start;
count = end-start;
Expand Down Expand Up @@ -110,7 +110,7 @@ public Mapping<T, TValue> CreateMapping<TValue>(Func<T, TValue> mapFunc)
private readonly int offset;
private readonly TValue[] data;
private readonly FactorioIdRange<TKey> source;
internal Mapping(FactorioIdRange<TKey> source)
public Mapping(FactorioIdRange<TKey> source)
{
this.source = source;
data = new TValue[source.count];
Expand Down