Compare commits
5
Commits
241a753879
..
main
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
e4e7c9fa0d | ||
|
|
2034dbab40 | ||
|
|
1507f8b8c6 | ||
|
|
4eb7494d08 | ||
|
|
b1d4094f78 |
@@ -7,6 +7,15 @@
|
|||||||
objects = {
|
objects = {
|
||||||
|
|
||||||
/* Begin PBXBuildFile section */
|
/* Begin PBXBuildFile section */
|
||||||
|
7619A1543055ABA300E6F277 /* RerollPickerView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7619A1533055ABA300E6F277 /* RerollPickerView.swift */; };
|
||||||
|
7619A1563055ABAF00E6F277 /* HitCalculatorView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7619A1553055ABAF00E6F277 /* HitCalculatorView.swift */; };
|
||||||
|
7619A1583055ABB400E6F277 /* WoundCalculatorView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7619A1573055ABB400E6F277 /* WoundCalculatorView.swift */; };
|
||||||
|
7619A15A3055ABB600E6F277 /* ArmourCalculatorView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7619A1593055ABB600E6F277 /* ArmourCalculatorView.swift */; };
|
||||||
|
7619A15C3055AF2400E6F277 /* StatePickerView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7619A15B3055AF2400E6F277 /* StatePickerView.swift */; };
|
||||||
|
7619A15E3055AF2A00E6F277 /* RollSummaryView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7619A15D3055AF2A00E6F277 /* RollSummaryView.swift */; };
|
||||||
|
7619A1623055AF3300E6F277 /* DiePoolView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7619A1613055AF3300E6F277 /* DiePoolView.swift */; };
|
||||||
|
7619A1643055AF3900E6F277 /* InfoAreaView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7619A1633055AF3900E6F277 /* InfoAreaView.swift */; };
|
||||||
|
7619A1663055AF3D00E6F277 /* ButtonView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7619A1653055AF3D00E6F277 /* ButtonView.swift */; };
|
||||||
7692E8432BD55CDF005BDD76 /* PrivacyInfo.xcprivacy in Resources */ = {isa = PBXBuildFile; fileRef = 7692E8422BD55CDE005BDD76 /* PrivacyInfo.xcprivacy */; };
|
7692E8432BD55CDF005BDD76 /* PrivacyInfo.xcprivacy in Resources */ = {isa = PBXBuildFile; fileRef = 7692E8422BD55CDE005BDD76 /* PrivacyInfo.xcprivacy */; };
|
||||||
76AB19823050A4B500BF7B9D /* DieHammerEngine in Frameworks */ = {isa = PBXBuildFile; productRef = 76AB19813050A4B500BF7B9D /* DieHammerEngine */; };
|
76AB19823050A4B500BF7B9D /* DieHammerEngine in Frameworks */ = {isa = PBXBuildFile; productRef = 76AB19813050A4B500BF7B9D /* DieHammerEngine */; };
|
||||||
980EF454285E2BAD002FE8A0 /* MainView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 980EF453285E2BAD002FE8A0 /* MainView.swift */; };
|
980EF454285E2BAD002FE8A0 /* MainView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 980EF453285E2BAD002FE8A0 /* MainView.swift */; };
|
||||||
@@ -18,7 +27,6 @@
|
|||||||
981AD0D927C30E32005A6492 /* Bunch_O__DiceUITests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 981AD0D827C30E32005A6492 /* Bunch_O__DiceUITests.swift */; };
|
981AD0D927C30E32005A6492 /* Bunch_O__DiceUITests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 981AD0D827C30E32005A6492 /* Bunch_O__DiceUITests.swift */; };
|
||||||
981AD0DB27C30E32005A6492 /* Bunch_O__DiceUITestsLaunchTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 981AD0DA27C30E32005A6492 /* Bunch_O__DiceUITestsLaunchTests.swift */; };
|
981AD0DB27C30E32005A6492 /* Bunch_O__DiceUITestsLaunchTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 981AD0DA27C30E32005A6492 /* Bunch_O__DiceUITestsLaunchTests.swift */; };
|
||||||
981AD0E927C31208005A6492 /* Dice.swift in Sources */ = {isa = PBXBuildFile; fileRef = 981AD0E827C31208005A6492 /* Dice.swift */; };
|
981AD0E927C31208005A6492 /* Dice.swift in Sources */ = {isa = PBXBuildFile; fileRef = 981AD0E827C31208005A6492 /* Dice.swift */; };
|
||||||
98225DE12A3527160019DE8E /* dice.swift in Sources */ = {isa = PBXBuildFile; fileRef = 98225DE02A3527160019DE8E /* dice.swift */; };
|
|
||||||
98325A2E2839BE4B0006DF4B /* RollState.swift in Sources */ = {isa = PBXBuildFile; fileRef = 98325A2D2839BE4B0006DF4B /* RollState.swift */; };
|
98325A2E2839BE4B0006DF4B /* RollState.swift in Sources */ = {isa = PBXBuildFile; fileRef = 98325A2D2839BE4B0006DF4B /* RollState.swift */; };
|
||||||
98745CA62870CC670031166A /* SettingsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 98745CA52870CC670031166A /* SettingsView.swift */; };
|
98745CA62870CC670031166A /* SettingsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 98745CA52870CC670031166A /* SettingsView.swift */; };
|
||||||
988B150F2854E9170082C2C5 /* HistoryView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 988B150E2854E9160082C2C5 /* HistoryView.swift */; };
|
988B150F2854E9170082C2C5 /* HistoryView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 988B150E2854E9160082C2C5 /* HistoryView.swift */; };
|
||||||
@@ -46,6 +54,15 @@
|
|||||||
/* End PBXContainerItemProxy section */
|
/* End PBXContainerItemProxy section */
|
||||||
|
|
||||||
/* Begin PBXFileReference section */
|
/* Begin PBXFileReference section */
|
||||||
|
7619A1533055ABA300E6F277 /* RerollPickerView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RerollPickerView.swift; sourceTree = "<group>"; };
|
||||||
|
7619A1553055ABAF00E6F277 /* HitCalculatorView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HitCalculatorView.swift; sourceTree = "<group>"; };
|
||||||
|
7619A1573055ABB400E6F277 /* WoundCalculatorView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WoundCalculatorView.swift; sourceTree = "<group>"; };
|
||||||
|
7619A1593055ABB600E6F277 /* ArmourCalculatorView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ArmourCalculatorView.swift; sourceTree = "<group>"; };
|
||||||
|
7619A15B3055AF2400E6F277 /* StatePickerView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StatePickerView.swift; sourceTree = "<group>"; };
|
||||||
|
7619A15D3055AF2A00E6F277 /* RollSummaryView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RollSummaryView.swift; sourceTree = "<group>"; };
|
||||||
|
7619A1613055AF3300E6F277 /* DiePoolView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DiePoolView.swift; sourceTree = "<group>"; };
|
||||||
|
7619A1633055AF3900E6F277 /* InfoAreaView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = InfoAreaView.swift; sourceTree = "<group>"; };
|
||||||
|
7619A1653055AF3D00E6F277 /* ButtonView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ButtonView.swift; sourceTree = "<group>"; };
|
||||||
768C2F3A304F04E6002FEA9A /* Goals.md */ = {isa = PBXFileReference; lastKnownFileType = net.daringfireball.markdown; path = Goals.md; sourceTree = "<group>"; };
|
768C2F3A304F04E6002FEA9A /* Goals.md */ = {isa = PBXFileReference; lastKnownFileType = net.daringfireball.markdown; path = Goals.md; sourceTree = "<group>"; };
|
||||||
7692E8422BD55CDE005BDD76 /* PrivacyInfo.xcprivacy */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xml; path = PrivacyInfo.xcprivacy; sourceTree = "<group>"; };
|
7692E8422BD55CDE005BDD76 /* PrivacyInfo.xcprivacy */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xml; path = PrivacyInfo.xcprivacy; sourceTree = "<group>"; };
|
||||||
980EF453285E2BAD002FE8A0 /* MainView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MainView.swift; sourceTree = "<group>"; };
|
980EF453285E2BAD002FE8A0 /* MainView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MainView.swift; sourceTree = "<group>"; };
|
||||||
@@ -60,7 +77,6 @@
|
|||||||
981AD0D827C30E32005A6492 /* Bunch_O__DiceUITests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Bunch_O__DiceUITests.swift; sourceTree = "<group>"; };
|
981AD0D827C30E32005A6492 /* Bunch_O__DiceUITests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Bunch_O__DiceUITests.swift; sourceTree = "<group>"; };
|
||||||
981AD0DA27C30E32005A6492 /* Bunch_O__DiceUITestsLaunchTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Bunch_O__DiceUITestsLaunchTests.swift; sourceTree = "<group>"; };
|
981AD0DA27C30E32005A6492 /* Bunch_O__DiceUITestsLaunchTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Bunch_O__DiceUITestsLaunchTests.swift; sourceTree = "<group>"; };
|
||||||
981AD0E827C31208005A6492 /* Dice.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Dice.swift; sourceTree = "<group>"; };
|
981AD0E827C31208005A6492 /* Dice.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Dice.swift; sourceTree = "<group>"; };
|
||||||
98225DE02A3527160019DE8E /* dice.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = dice.swift; sourceTree = "<group>"; };
|
|
||||||
98325A2D2839BE4B0006DF4B /* RollState.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RollState.swift; sourceTree = "<group>"; };
|
98325A2D2839BE4B0006DF4B /* RollState.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RollState.swift; sourceTree = "<group>"; };
|
||||||
98745CA52870CC670031166A /* SettingsView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SettingsView.swift; sourceTree = "<group>"; };
|
98745CA52870CC670031166A /* SettingsView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SettingsView.swift; sourceTree = "<group>"; };
|
||||||
988997FF285814AB00B07E09 /* DieHammer.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = DieHammer.plist; sourceTree = SOURCE_ROOT; };
|
988997FF285814AB00B07E09 /* DieHammer.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = DieHammer.plist; sourceTree = SOURCE_ROOT; };
|
||||||
@@ -97,6 +113,22 @@
|
|||||||
/* End PBXFrameworksBuildPhase section */
|
/* End PBXFrameworksBuildPhase section */
|
||||||
|
|
||||||
/* Begin PBXGroup section */
|
/* Begin PBXGroup section */
|
||||||
|
7619A1523055AA4500E6F277 /* Elements */ = {
|
||||||
|
isa = PBXGroup;
|
||||||
|
children = (
|
||||||
|
7619A1533055ABA300E6F277 /* RerollPickerView.swift */,
|
||||||
|
7619A1553055ABAF00E6F277 /* HitCalculatorView.swift */,
|
||||||
|
7619A1573055ABB400E6F277 /* WoundCalculatorView.swift */,
|
||||||
|
7619A1593055ABB600E6F277 /* ArmourCalculatorView.swift */,
|
||||||
|
7619A15B3055AF2400E6F277 /* StatePickerView.swift */,
|
||||||
|
7619A15D3055AF2A00E6F277 /* RollSummaryView.swift */,
|
||||||
|
7619A1613055AF3300E6F277 /* DiePoolView.swift */,
|
||||||
|
7619A1633055AF3900E6F277 /* InfoAreaView.swift */,
|
||||||
|
7619A1653055AF3D00E6F277 /* ButtonView.swift */,
|
||||||
|
);
|
||||||
|
path = Elements;
|
||||||
|
sourceTree = "<group>";
|
||||||
|
};
|
||||||
76AB19803050A4B500BF7B9D /* Frameworks */ = {
|
76AB19803050A4B500BF7B9D /* Frameworks */ = {
|
||||||
isa = PBXGroup;
|
isa = PBXGroup;
|
||||||
children = (
|
children = (
|
||||||
@@ -132,9 +164,6 @@
|
|||||||
7692E8422BD55CDE005BDD76 /* PrivacyInfo.xcprivacy */,
|
7692E8422BD55CDE005BDD76 /* PrivacyInfo.xcprivacy */,
|
||||||
988997FF285814AB00B07E09 /* DieHammer.plist */,
|
988997FF285814AB00B07E09 /* DieHammer.plist */,
|
||||||
981AD0BD27C30E31005A6492 /* DieHammerApp.swift */,
|
981AD0BD27C30E31005A6492 /* DieHammerApp.swift */,
|
||||||
981AD0BF27C30E31005A6492 /* ContentView.swift */,
|
|
||||||
988B1518285566AD0082C2C5 /* OnboardingView.swift */,
|
|
||||||
980EF453285E2BAD002FE8A0 /* MainView.swift */,
|
|
||||||
981AD0EC27C3356D005A6492 /* Views */,
|
981AD0EC27C3356D005A6492 /* Views */,
|
||||||
981AD0E727C311DA005A6492 /* Model */,
|
981AD0E727C311DA005A6492 /* Model */,
|
||||||
98E534D2285653E700D5ABB4 /* Utilities */,
|
98E534D2285653E700D5ABB4 /* Utilities */,
|
||||||
@@ -185,8 +214,11 @@
|
|||||||
981AD0EC27C3356D005A6492 /* Views */ = {
|
981AD0EC27C3356D005A6492 /* Views */ = {
|
||||||
isa = PBXGroup;
|
isa = PBXGroup;
|
||||||
children = (
|
children = (
|
||||||
|
7619A1523055AA4500E6F277 /* Elements */,
|
||||||
98D0CD192856E3850047DC1B /* Modals */,
|
98D0CD192856E3850047DC1B /* Modals */,
|
||||||
98225DE02A3527160019DE8E /* dice.swift */,
|
988B1518285566AD0082C2C5 /* OnboardingView.swift */,
|
||||||
|
981AD0BF27C30E31005A6492 /* ContentView.swift */,
|
||||||
|
980EF453285E2BAD002FE8A0 /* MainView.swift */,
|
||||||
);
|
);
|
||||||
path = Views;
|
path = Views;
|
||||||
sourceTree = "<group>";
|
sourceTree = "<group>";
|
||||||
@@ -344,17 +376,25 @@
|
|||||||
buildActionMask = 2147483647;
|
buildActionMask = 2147483647;
|
||||||
files = (
|
files = (
|
||||||
98E534D42856540A00D5ABB4 /* ViewUtils.swift in Sources */,
|
98E534D42856540A00D5ABB4 /* ViewUtils.swift in Sources */,
|
||||||
|
7619A1643055AF3900E6F277 /* InfoAreaView.swift in Sources */,
|
||||||
|
7619A15A3055ABB600E6F277 /* ArmourCalculatorView.swift in Sources */,
|
||||||
981AD0C027C30E31005A6492 /* ContentView.swift in Sources */,
|
981AD0C027C30E31005A6492 /* ContentView.swift in Sources */,
|
||||||
988B1519285566AD0082C2C5 /* OnboardingView.swift in Sources */,
|
988B1519285566AD0082C2C5 /* OnboardingView.swift in Sources */,
|
||||||
981AD0E927C31208005A6492 /* Dice.swift in Sources */,
|
981AD0E927C31208005A6492 /* Dice.swift in Sources */,
|
||||||
|
7619A1583055ABB400E6F277 /* WoundCalculatorView.swift in Sources */,
|
||||||
98F001D5284F86F1004F5140 /* AddToPool.swift in Sources */,
|
98F001D5284F86F1004F5140 /* AddToPool.swift in Sources */,
|
||||||
988B150F2854E9170082C2C5 /* HistoryView.swift in Sources */,
|
988B150F2854E9170082C2C5 /* HistoryView.swift in Sources */,
|
||||||
98225DE12A3527160019DE8E /* dice.swift in Sources */,
|
7619A1543055ABA300E6F277 /* RerollPickerView.swift in Sources */,
|
||||||
981AD0BE27C30E31005A6492 /* DieHammerApp.swift in Sources */,
|
981AD0BE27C30E31005A6492 /* DieHammerApp.swift in Sources */,
|
||||||
|
7619A15E3055AF2A00E6F277 /* RollSummaryView.swift in Sources */,
|
||||||
|
7619A1623055AF3300E6F277 /* DiePoolView.swift in Sources */,
|
||||||
|
7619A15C3055AF2400E6F277 /* StatePickerView.swift in Sources */,
|
||||||
980EF454285E2BAD002FE8A0 /* MainView.swift in Sources */,
|
980EF454285E2BAD002FE8A0 /* MainView.swift in Sources */,
|
||||||
|
7619A1563055ABAF00E6F277 /* HitCalculatorView.swift in Sources */,
|
||||||
98325A2E2839BE4B0006DF4B /* RollState.swift in Sources */,
|
98325A2E2839BE4B0006DF4B /* RollState.swift in Sources */,
|
||||||
988B1517285565610082C2C5 /* Onboarding.swift in Sources */,
|
988B1517285565610082C2C5 /* Onboarding.swift in Sources */,
|
||||||
98745CA62870CC670031166A /* SettingsView.swift in Sources */,
|
98745CA62870CC670031166A /* SettingsView.swift in Sources */,
|
||||||
|
7619A1663055AF3D00E6F277 /* ButtonView.swift in Sources */,
|
||||||
);
|
);
|
||||||
runOnlyForDeploymentPostprocessing = 0;
|
runOnlyForDeploymentPostprocessing = 0;
|
||||||
};
|
};
|
||||||
@@ -532,7 +572,7 @@
|
|||||||
INFOPLIST_KEY_UISupportedInterfaceOrientations = UIInterfaceOrientationPortrait;
|
INFOPLIST_KEY_UISupportedInterfaceOrientations = UIInterfaceOrientationPortrait;
|
||||||
INFOPLIST_KEY_UISupportedInterfaceOrientations_iPad = "UIInterfaceOrientationPortrait UIInterfaceOrientationPortraitUpsideDown UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight";
|
INFOPLIST_KEY_UISupportedInterfaceOrientations_iPad = "UIInterfaceOrientationPortrait UIInterfaceOrientationPortraitUpsideDown UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight";
|
||||||
INFOPLIST_KEY_UISupportedInterfaceOrientations_iPhone = UIInterfaceOrientationPortrait;
|
INFOPLIST_KEY_UISupportedInterfaceOrientations_iPhone = UIInterfaceOrientationPortrait;
|
||||||
IPHONEOS_DEPLOYMENT_TARGET = "$(RECOMMENDED_IPHONEOS_DEPLOYMENT_TARGET)";
|
IPHONEOS_DEPLOYMENT_TARGET = 26.6;
|
||||||
LD_RUNPATH_SEARCH_PATHS = (
|
LD_RUNPATH_SEARCH_PATHS = (
|
||||||
"$(inherited)",
|
"$(inherited)",
|
||||||
"@executable_path/Frameworks",
|
"@executable_path/Frameworks",
|
||||||
@@ -564,7 +604,7 @@
|
|||||||
INFOPLIST_KEY_UISupportedInterfaceOrientations = UIInterfaceOrientationPortrait;
|
INFOPLIST_KEY_UISupportedInterfaceOrientations = UIInterfaceOrientationPortrait;
|
||||||
INFOPLIST_KEY_UISupportedInterfaceOrientations_iPad = "UIInterfaceOrientationPortrait UIInterfaceOrientationPortraitUpsideDown UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight";
|
INFOPLIST_KEY_UISupportedInterfaceOrientations_iPad = "UIInterfaceOrientationPortrait UIInterfaceOrientationPortraitUpsideDown UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight";
|
||||||
INFOPLIST_KEY_UISupportedInterfaceOrientations_iPhone = UIInterfaceOrientationPortrait;
|
INFOPLIST_KEY_UISupportedInterfaceOrientations_iPhone = UIInterfaceOrientationPortrait;
|
||||||
IPHONEOS_DEPLOYMENT_TARGET = "$(RECOMMENDED_IPHONEOS_DEPLOYMENT_TARGET)";
|
IPHONEOS_DEPLOYMENT_TARGET = 26.6;
|
||||||
LD_RUNPATH_SEARCH_PATHS = (
|
LD_RUNPATH_SEARCH_PATHS = (
|
||||||
"$(inherited)",
|
"$(inherited)",
|
||||||
"@executable_path/Frameworks",
|
"@executable_path/Frameworks",
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
<?xml version="1.0" encoding="UTF-8"?>
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
<Scheme
|
<Scheme
|
||||||
LastUpgradeVersion = "2700"
|
LastUpgradeVersion = "2700"
|
||||||
version = "1.8">
|
version = "1.7">
|
||||||
<BuildAction
|
<BuildAction
|
||||||
parallelizeBuildables = "YES"
|
parallelizeBuildables = "YES"
|
||||||
buildImplicitDependencies = "YES"
|
buildImplicitDependencies = "YES"
|
||||||
@@ -27,11 +27,10 @@
|
|||||||
buildConfiguration = "Debug"
|
buildConfiguration = "Debug"
|
||||||
selectedDebuggerIdentifier = ""
|
selectedDebuggerIdentifier = ""
|
||||||
selectedLauncherIdentifier = "Xcode.IDEFoundation.Launcher.PosixSpawn"
|
selectedLauncherIdentifier = "Xcode.IDEFoundation.Launcher.PosixSpawn"
|
||||||
shouldUseLaunchSchemeArgsEnv = "YES"
|
shouldUseLaunchSchemeArgsEnv = "YES">
|
||||||
shouldAutocreateTestPlan = "NO">
|
|
||||||
<TestPlans>
|
<TestPlans>
|
||||||
<TestPlanReference
|
<TestPlanReference
|
||||||
reference = "container:Bunch O' Dice.xctestplan"
|
reference = "container:Bunch O' Dice.xctestplan"
|
||||||
default = "YES">
|
default = "YES">
|
||||||
</TestPlanReference>
|
</TestPlanReference>
|
||||||
</TestPlans>
|
</TestPlans>
|
||||||
|
|||||||
@@ -1,509 +0,0 @@
|
|||||||
//
|
|
||||||
// PortraitView.swift
|
|
||||||
// Bunch O' Dice
|
|
||||||
//
|
|
||||||
// Created by Rory Hinnen on 22/6/18.
|
|
||||||
//
|
|
||||||
|
|
||||||
import SwiftUI
|
|
||||||
|
|
||||||
struct MainView: View {
|
|
||||||
|
|
||||||
enum MenuView: Int {
|
|
||||||
case history, add, settings
|
|
||||||
|
|
||||||
func icon() -> Image {
|
|
||||||
switch self {
|
|
||||||
case .history:
|
|
||||||
return(Image(systemName: "folder"))
|
|
||||||
case .settings:
|
|
||||||
return(Image(systemName: "square.and.pencil"))
|
|
||||||
case .add:
|
|
||||||
return(Image(systemName: "folder.badge.plus"))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@ViewBuilder private func myCircle() -> some View {
|
|
||||||
Circle()
|
|
||||||
.fill(Color.gray)
|
|
||||||
.blur(radius: 2.0)
|
|
||||||
.frame(width: controlSize*2.5, height: controlSize*2.5, alignment: .center)
|
|
||||||
}
|
|
||||||
|
|
||||||
@ViewBuilder private func stateView() -> some View {
|
|
||||||
switch current.state {
|
|
||||||
case .wound:
|
|
||||||
woundCalculator(controlSize: controlSize)
|
|
||||||
case .armour:
|
|
||||||
armourCalculator(controlSize: controlSize)
|
|
||||||
default:
|
|
||||||
hitCalculator(controlSize: controlSize)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@ViewBuilder private func sheetContent() -> some View {
|
|
||||||
switch sheet.state {
|
|
||||||
case .settings:
|
|
||||||
SettingsView(isShowing: $sheet.isShowing)
|
|
||||||
case .history:
|
|
||||||
HistoryView(isShowing: $sheet.isShowing)
|
|
||||||
default:
|
|
||||||
EmptyView()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@ViewBuilder func buttons(color: CGColor, label: String, value: Int) -> some View {
|
|
||||||
let frameSize = 120.0
|
|
||||||
let fontSize = 35.0
|
|
||||||
let blurSize = 5.0
|
|
||||||
|
|
||||||
ZStack {
|
|
||||||
Circle()
|
|
||||||
.fill(Color(color))
|
|
||||||
.frame(width: frameSize, height: frameSize)
|
|
||||||
.blur(radius: blurSize)
|
|
||||||
VStack{
|
|
||||||
Text(String(value))
|
|
||||||
.fontWeight(.black)
|
|
||||||
Text(label)
|
|
||||||
.font(.system(size: 20))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
.foregroundColor(.primary)
|
|
||||||
.font(.system(size: fontSize))
|
|
||||||
}
|
|
||||||
|
|
||||||
//MARK: State Views
|
|
||||||
@ViewBuilder func armourCalculator(controlSize: Double) -> some View {
|
|
||||||
VStack {
|
|
||||||
HStack(alignment: .top) {
|
|
||||||
Spacer()
|
|
||||||
VStack {
|
|
||||||
Text("Armour")
|
|
||||||
Group {
|
|
||||||
Text("▲")
|
|
||||||
.gesture(TapGesture() .onEnded {
|
|
||||||
current.addPenetration()
|
|
||||||
})
|
|
||||||
Text(
|
|
||||||
(current.armourPenetration > 0 ?
|
|
||||||
"+" : "") +
|
|
||||||
String(current.armourPenetration))
|
|
||||||
Text("▼")
|
|
||||||
.gesture(TapGesture() .onEnded {
|
|
||||||
current.losePenetration()
|
|
||||||
})
|
|
||||||
}
|
|
||||||
.font(.system(size: controlSize))
|
|
||||||
Text("Penetration")
|
|
||||||
}
|
|
||||||
Spacer()
|
|
||||||
VStack {
|
|
||||||
Text("Invulnerable")
|
|
||||||
Toggle("", isOn: $current.invulnerable).labelsHidden()
|
|
||||||
.padding()
|
|
||||||
}
|
|
||||||
Spacer()
|
|
||||||
}
|
|
||||||
HStack {
|
|
||||||
Text("Reroll")
|
|
||||||
Picker("", selection: $current.saveRerollMode) {
|
|
||||||
ForEach(RerollMode.allCases, id: \.self) { mode in
|
|
||||||
Text(mode.label).tag(mode)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
.pickerStyle(.segmented)
|
|
||||||
Button("🎲") {
|
|
||||||
current.applyReroll()
|
|
||||||
}
|
|
||||||
.disabled(current.saveRerollMode == .none || current.saveHasRerolled)
|
|
||||||
.font(.system(size: controlSize))
|
|
||||||
}
|
|
||||||
.padding(.horizontal)
|
|
||||||
if current.mortalWoundsTotal > 0 {
|
|
||||||
HStack {
|
|
||||||
Text("Mortal wounds: \(current.mortalWoundsTotal) (bypassed saves)")
|
|
||||||
.fontWeight(.semibold)
|
|
||||||
.foregroundColor(.orange)
|
|
||||||
Spacer()
|
|
||||||
}
|
|
||||||
.font(.system(size: 18))
|
|
||||||
.padding(.horizontal)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
.padding()
|
|
||||||
}
|
|
||||||
|
|
||||||
@ViewBuilder func woundCalculator(controlSize: Double) -> some View {
|
|
||||||
VStack {
|
|
||||||
if current.autoWoundsCarried > 0 {
|
|
||||||
HStack {
|
|
||||||
Text("Auto-wounds carried: \(current.autoWoundsCarried) (skip wound roll)")
|
|
||||||
.fontWeight(.semibold)
|
|
||||||
.foregroundColor(.orange)
|
|
||||||
Spacer()
|
|
||||||
}
|
|
||||||
.font(.system(size: 18))
|
|
||||||
.padding(.horizontal)
|
|
||||||
}
|
|
||||||
HStack(alignment: .top) {
|
|
||||||
Spacer()
|
|
||||||
VStack {
|
|
||||||
Text("Attack")
|
|
||||||
Group {
|
|
||||||
Text("▲")
|
|
||||||
.gesture(TapGesture() .onEnded {
|
|
||||||
current.addStrength()
|
|
||||||
})
|
|
||||||
Text(String(current.strength))
|
|
||||||
Text("▼")
|
|
||||||
.gesture(TapGesture() .onEnded {
|
|
||||||
current.loseStrength()
|
|
||||||
})
|
|
||||||
}.font(.system(size: controlSize))
|
|
||||||
Text("Strength")
|
|
||||||
}
|
|
||||||
Spacer()
|
|
||||||
VStack {
|
|
||||||
Text("Toughness")
|
|
||||||
Group {
|
|
||||||
Text("▲")
|
|
||||||
.gesture(TapGesture() .onEnded {
|
|
||||||
current.addToughness()
|
|
||||||
})
|
|
||||||
Text(String(current.toughness))
|
|
||||||
Text("▼")
|
|
||||||
.gesture(TapGesture() .onEnded {
|
|
||||||
current.loseToughness()
|
|
||||||
})
|
|
||||||
}.font(.system(size: controlSize))
|
|
||||||
Text(" ")
|
|
||||||
}
|
|
||||||
Spacer()
|
|
||||||
VStack {
|
|
||||||
Text("Wound")
|
|
||||||
.multilineTextAlignment(.center)
|
|
||||||
Group {
|
|
||||||
Text("▲")
|
|
||||||
.gesture(TapGesture() .onEnded {
|
|
||||||
current.woundModifier = current.woundModifier.add()
|
|
||||||
})
|
|
||||||
Text(
|
|
||||||
(current.woundModifier.rawValue > 0 ?
|
|
||||||
"+" : "") +
|
|
||||||
String(current.woundModifier.rawValue))
|
|
||||||
Text("▼")
|
|
||||||
.gesture(TapGesture() .onEnded {
|
|
||||||
current.woundModifier = current.woundModifier.remove()
|
|
||||||
})
|
|
||||||
}.font(.system(size: controlSize))
|
|
||||||
Text("Modifier")
|
|
||||||
}
|
|
||||||
Spacer()
|
|
||||||
}
|
|
||||||
HStack {
|
|
||||||
Text("Reroll")
|
|
||||||
Picker("", selection: $current.woundRerollMode) {
|
|
||||||
ForEach(RerollMode.allCases, id: \.self) { mode in
|
|
||||||
Text(mode.label).tag(mode)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
.pickerStyle(.segmented)
|
|
||||||
Button("🎲") {
|
|
||||||
current.applyReroll()
|
|
||||||
}
|
|
||||||
.disabled(current.woundRerollMode == .none || current.woundHasRerolled)
|
|
||||||
.font(.system(size: controlSize))
|
|
||||||
}
|
|
||||||
.padding(.horizontal)
|
|
||||||
VStack(spacing: 8) {
|
|
||||||
HStack {
|
|
||||||
Toggle("Devastating Wounds", isOn: $current.devastatingWounds)
|
|
||||||
}
|
|
||||||
if current.devastatingWounds {
|
|
||||||
HStack {
|
|
||||||
Text("Mortals (6s): \(current.critWoundCount)")
|
|
||||||
.fontWeight(.semibold)
|
|
||||||
.foregroundColor(.orange)
|
|
||||||
Spacer()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
.font(.system(size: 18))
|
|
||||||
.padding(.horizontal)
|
|
||||||
}
|
|
||||||
.padding()
|
|
||||||
}
|
|
||||||
|
|
||||||
@ViewBuilder func hitCalculator(controlSize: Double) -> some View {
|
|
||||||
VStack {
|
|
||||||
HStack(alignment: .top) {
|
|
||||||
Spacer()
|
|
||||||
VStack {
|
|
||||||
Text("Hit")
|
|
||||||
.multilineTextAlignment(.center)
|
|
||||||
Group {
|
|
||||||
Text("▲")
|
|
||||||
.gesture(TapGesture() .onEnded {
|
|
||||||
current.modifier = current.modifier.add()
|
|
||||||
})
|
|
||||||
Text((current.modifier.rawValue > 0 ? "+" : "") +
|
|
||||||
String(current.modifier.rawValue))
|
|
||||||
Text("▼")
|
|
||||||
.gesture(TapGesture() .onEnded {
|
|
||||||
current.modifier = current.modifier.remove()
|
|
||||||
})
|
|
||||||
}
|
|
||||||
.font(.system(size: controlSize))
|
|
||||||
Text("Modifier")
|
|
||||||
}
|
|
||||||
.disabled(current.torrent || current.psychic)
|
|
||||||
.opacity((current.torrent || current.psychic) ? 0.4 : 1.0)
|
|
||||||
Spacer()
|
|
||||||
VStack {
|
|
||||||
Text("Blow Up")
|
|
||||||
VStack {
|
|
||||||
Button(action: { current.applyBlowup(face: .six) }) {
|
|
||||||
Image(systemName: "die.face.6")
|
|
||||||
.foregroundColor(.primary)
|
|
||||||
}
|
|
||||||
Button(action: { current.applyBlowup(face: .five) }) {
|
|
||||||
Image(systemName: "die.face.5")
|
|
||||||
.foregroundColor(.primary)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
.padding()
|
|
||||||
.font(.system(size: controlSize))
|
|
||||||
}
|
|
||||||
.disabled(current.blowupUsed || current.torrent)
|
|
||||||
.opacity((current.blowupUsed || current.torrent) ? 0.4 : 1.0)
|
|
||||||
Spacer()
|
|
||||||
}
|
|
||||||
HStack {
|
|
||||||
Text("Reroll")
|
|
||||||
Picker("", selection: $current.hitRerollMode) {
|
|
||||||
ForEach(RerollMode.allCases, id: \.self) { mode in
|
|
||||||
Text(mode.label).tag(mode)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
.pickerStyle(.segmented)
|
|
||||||
Button("🎲") {
|
|
||||||
current.applyReroll()
|
|
||||||
}
|
|
||||||
.disabled(current.hitRerollMode == .none || current.hitHasRerolled)
|
|
||||||
.font(.system(size: controlSize))
|
|
||||||
}
|
|
||||||
.padding(.horizontal)
|
|
||||||
.disabled(current.torrent)
|
|
||||||
.opacity(current.torrent ? 0.4 : 1.0)
|
|
||||||
HStack {
|
|
||||||
Toggle("Torrent", isOn: $current.torrent)
|
|
||||||
Spacer()
|
|
||||||
Toggle("Psychic", isOn: $current.psychic)
|
|
||||||
.disabled(current.torrent)
|
|
||||||
.opacity(current.torrent ? 0.4 : 1.0)
|
|
||||||
}
|
|
||||||
.padding(.horizontal)
|
|
||||||
VStack(spacing: 8) {
|
|
||||||
HStack {
|
|
||||||
Toggle("Lethal Hits", isOn: $current.lethalHits)
|
|
||||||
Spacer()
|
|
||||||
Toggle("Sust. Hits", isOn: $current.sustainedHits)
|
|
||||||
}
|
|
||||||
if current.sustainedHits {
|
|
||||||
HStack {
|
|
||||||
Text("X:")
|
|
||||||
Spacer()
|
|
||||||
Text("▲")
|
|
||||||
.gesture(TapGesture().onEnded {
|
|
||||||
if current.sustainedHitsX < 6 { current.sustainedHitsX += 1 }
|
|
||||||
})
|
|
||||||
Text(String(current.sustainedHitsX))
|
|
||||||
.frame(minWidth: 30, alignment: .center)
|
|
||||||
Text("▼")
|
|
||||||
.gesture(TapGesture().onEnded {
|
|
||||||
if current.sustainedHitsX > 1 { current.sustainedHitsX -= 1 }
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
HStack {
|
|
||||||
Toggle("Anti-X", isOn: $current.antiX)
|
|
||||||
if current.antiX {
|
|
||||||
Spacer()
|
|
||||||
Text("▲")
|
|
||||||
.gesture(TapGesture().onEnded {
|
|
||||||
if current.antiXThreshold < 6 { current.antiXThreshold += 1 }
|
|
||||||
})
|
|
||||||
Text("\(current.antiXThreshold)+")
|
|
||||||
.frame(minWidth: 34, alignment: .center)
|
|
||||||
Text("▼")
|
|
||||||
.gesture(TapGesture().onEnded {
|
|
||||||
if current.antiXThreshold > 2 { current.antiXThreshold -= 1 }
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if current.lethalHits || current.sustainedHits || current.antiX {
|
|
||||||
HStack {
|
|
||||||
if current.lethalHits || current.sustainedHits {
|
|
||||||
Text("Crits (6s): \(current.critHitCount)")
|
|
||||||
.fontWeight(.semibold)
|
|
||||||
}
|
|
||||||
Spacer()
|
|
||||||
if current.antiX {
|
|
||||||
Text("Anti-X (\(current.antiXThreshold)+): \(current.antiXCount)")
|
|
||||||
.fontWeight(.semibold)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
.foregroundColor(.orange)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
.font(.system(size: 18))
|
|
||||||
.padding(.horizontal)
|
|
||||||
}
|
|
||||||
.padding()
|
|
||||||
}
|
|
||||||
|
|
||||||
//MARK: die views
|
|
||||||
@ViewBuilder func die(whichDie: Dice.DiceType) -> some View {
|
|
||||||
let size = 22.0
|
|
||||||
let displayColorHit = (whichDie.rawValue >= current.cutoff.rawValue) ? Color.red : Color.primary
|
|
||||||
let displayColorArmor = (whichDie.rawValue <= current.cutoff.rawValue) ? Color.red : Color.primary
|
|
||||||
let displayColorArmorText = (whichDie.rawValue < current.cutoff.rawValue) ? Color.red : Color.primary
|
|
||||||
|
|
||||||
VStack(spacing: 2) {
|
|
||||||
Text(String(current.dice.value(which: whichDie)))
|
|
||||||
.foregroundColor(current.state == .armour ?
|
|
||||||
displayColorArmorText : displayColorHit)
|
|
||||||
.frame(minWidth: 30, alignment: .center)
|
|
||||||
Image(systemName: "die.face." + String(whichDie.rawValue) + ".fill")
|
|
||||||
.foregroundColor(current.state == .armour ?
|
|
||||||
displayColorArmor : displayColorHit)
|
|
||||||
}
|
|
||||||
.font(.system(size: size))
|
|
||||||
.padding(4)
|
|
||||||
.frame(maxWidth: .infinity)
|
|
||||||
.gesture(TapGesture().onEnded {
|
|
||||||
if current.state == .hit {
|
|
||||||
if whichDie.rawValue != 1 {
|
|
||||||
current.hitCutoff = whichDie }
|
|
||||||
} else if current.state == .armour {
|
|
||||||
current.armourSave = Int(whichDie.rawValue)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
@ViewBuilder func diePool() -> some View {
|
|
||||||
HStack(spacing: 0) {
|
|
||||||
ForEach(Dice.DiceType.allCases, id: \.rawValue) { i in
|
|
||||||
die(whichDie: i)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
//MARK: Var declarations
|
|
||||||
@EnvironmentObject var current: Roll
|
|
||||||
@ObservedObject var sheet = SettingsSheet()
|
|
||||||
@State var showFillView = false
|
|
||||||
|
|
||||||
let padSize = 60.0
|
|
||||||
let controlSize = 30.0
|
|
||||||
let red = CGColor(red: 1.0, green: 0.3, blue: 0.3, alpha: 1.0)
|
|
||||||
let blue = CGColor(red: 0.3, green: 0.3, blue: 1.0, alpha: 1.0)
|
|
||||||
|
|
||||||
var body: some View {
|
|
||||||
VStack{
|
|
||||||
HStack{
|
|
||||||
Button(action: {current.state = .hit}) {
|
|
||||||
ZStack{
|
|
||||||
if current.state == .hit {myCircle()}
|
|
||||||
Text(RollState.hit.print())}}
|
|
||||||
Button(action: {current.state = .wound}) {
|
|
||||||
ZStack{
|
|
||||||
if current.state == .wound {myCircle()}
|
|
||||||
Text(RollState.wound.print())}}
|
|
||||||
Button(action: {current.state = .armour}) {
|
|
||||||
ZStack{
|
|
||||||
if current.state == .armour {myCircle()}
|
|
||||||
Text(RollState.armour.print())}}
|
|
||||||
}.font(.system(size: 60))
|
|
||||||
VStack(spacing: 12) {
|
|
||||||
HStack(spacing: 12) {
|
|
||||||
Button(action: { showFillView.toggle() }) {
|
|
||||||
VStack(spacing: 4) {
|
|
||||||
Text("Dice Pool")
|
|
||||||
.font(.caption)
|
|
||||||
Text(String(current.total))
|
|
||||||
.font(.system(size: 28, weight: .black))
|
|
||||||
}
|
|
||||||
.frame(maxWidth: .infinity)
|
|
||||||
.padding(.vertical, 12)
|
|
||||||
.background(Color.white)
|
|
||||||
.clipShape(RoundedRectangle(cornerRadius: 10))
|
|
||||||
.foregroundColor(.black)
|
|
||||||
}
|
|
||||||
.sheet(isPresented: $showFillView,
|
|
||||||
onDismiss: { current.resetPool() }) {
|
|
||||||
VStack {
|
|
||||||
AddToPool()
|
|
||||||
Button("Accept", action: {showFillView.toggle()})
|
|
||||||
.padding(20)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Button(action: current.incrementState) {
|
|
||||||
VStack(spacing: 4) {
|
|
||||||
Text(current.state.text())
|
|
||||||
.font(.caption)
|
|
||||||
Text(String(current.result))
|
|
||||||
.font(.system(size: 28, weight: .black))
|
|
||||||
}
|
|
||||||
.frame(maxWidth: .infinity)
|
|
||||||
.padding(.vertical, 12)
|
|
||||||
.background(Color.white)
|
|
||||||
.clipShape(RoundedRectangle(cornerRadius: 10))
|
|
||||||
.foregroundColor(.black)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
.padding()
|
|
||||||
.background(Color.black)
|
|
||||||
.clipShape(RoundedRectangle(cornerRadius: 14))
|
|
||||||
diePool()
|
|
||||||
stateView()
|
|
||||||
}.padding()
|
|
||||||
}
|
|
||||||
.toolbar {
|
|
||||||
ToolbarItemGroup(placement: .bottomBar) {
|
|
||||||
HStack {
|
|
||||||
Button(action: {self.sheet.state = .history}) {
|
|
||||||
MenuView.history.icon()}
|
|
||||||
Button(action: {current.history.insert(Event(total: current.result, pool: current.dice, state: current.state), at: 0)}) {
|
|
||||||
MenuView.add.icon()}
|
|
||||||
Button(action: {self.sheet.state = .settings}) {
|
|
||||||
MenuView.settings.icon()}
|
|
||||||
}
|
|
||||||
.font(.system(size: controlSize))
|
|
||||||
.foregroundColor(.primary)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
.sheet(isPresented: $sheet.isShowing, content: sheetContent)}
|
|
||||||
}
|
|
||||||
|
|
||||||
#Preview("Hit") {
|
|
||||||
MainView().environmentObject(Roll())
|
|
||||||
}
|
|
||||||
|
|
||||||
#Preview("Wound") {
|
|
||||||
MainView().environmentObject({
|
|
||||||
let r = Roll()
|
|
||||||
r.state = .wound
|
|
||||||
return r
|
|
||||||
}())
|
|
||||||
}
|
|
||||||
|
|
||||||
#Preview("Armour") {
|
|
||||||
MainView().environmentObject({
|
|
||||||
let r = Roll()
|
|
||||||
r.state = .armour
|
|
||||||
return r
|
|
||||||
}())
|
|
||||||
}
|
|
||||||
@@ -151,6 +151,11 @@ class Roll: ObservableObject {
|
|||||||
@Published var woundHasRerolled = false
|
@Published var woundHasRerolled = false
|
||||||
@Published var saveHasRerolled = false
|
@Published var saveHasRerolled = false
|
||||||
|
|
||||||
|
// Snapshot of dice before the current phase's reroll was applied.
|
||||||
|
// Restored when the user switches reroll modes, so the new mode
|
||||||
|
// always applies to the original un-rerolled results.
|
||||||
|
@Published var preRerollDice: Dice? = nil
|
||||||
|
|
||||||
@Published var blowupUsed = false
|
@Published var blowupUsed = false
|
||||||
@Published var torrent = false
|
@Published var torrent = false
|
||||||
@Published var psychic = false
|
@Published var psychic = false
|
||||||
@@ -181,7 +186,7 @@ class Roll: ObservableObject {
|
|||||||
case .wound:
|
case .wound:
|
||||||
return wound
|
return wound
|
||||||
case .armour:
|
case .armour:
|
||||||
return save
|
return save + mortalWoundsTotal
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -209,17 +214,7 @@ class Roll: ObservableObject {
|
|||||||
case .hit:
|
case .hit:
|
||||||
return hitCutoff
|
return hitCutoff
|
||||||
case .wound:
|
case .wound:
|
||||||
if Float(self.strength) / Float(self.toughness) >= 2.0 {
|
return Dice.DiceType(rawValue: woundCutoff) ?? .four
|
||||||
return .two
|
|
||||||
} else if Float(self.strength) / Float(self.toughness) > 1.0 {
|
|
||||||
return .three
|
|
||||||
} else if Float(self.strength) / Float(self.toughness) == 1 {
|
|
||||||
return .four
|
|
||||||
} else if Float(self.strength) / Float(self.toughness) <= 0.5 {
|
|
||||||
return .six
|
|
||||||
} else {
|
|
||||||
return .five
|
|
||||||
}
|
|
||||||
case .armour:
|
case .armour:
|
||||||
return Dice.DiceType(rawValue: self.armourSave) ?? .one
|
return Dice.DiceType(rawValue: self.armourSave) ?? .one
|
||||||
}
|
}
|
||||||
@@ -376,6 +371,7 @@ class Roll: ObservableObject {
|
|||||||
self.total = max(0, hit - autoWoundsCarried) + sustained
|
self.total = max(0, hit - autoWoundsCarried) + sustained
|
||||||
self.state = .wound
|
self.state = .wound
|
||||||
woundHasRerolled = false
|
woundHasRerolled = false
|
||||||
|
preRerollDice = nil
|
||||||
case .wound:
|
case .wound:
|
||||||
let mortals = critWoundCount
|
let mortals = critWoundCount
|
||||||
mortalWoundsTotal = mortals
|
mortalWoundsTotal = mortals
|
||||||
@@ -383,10 +379,12 @@ class Roll: ObservableObject {
|
|||||||
autoWoundsCarried = 0
|
autoWoundsCarried = 0
|
||||||
self.state = .armour
|
self.state = .armour
|
||||||
saveHasRerolled = false
|
saveHasRerolled = false
|
||||||
|
preRerollDice = nil
|
||||||
case .armour:
|
case .armour:
|
||||||
self.total = save
|
self.total = save + mortalWoundsTotal
|
||||||
self.state = .hit
|
self.state = .hit
|
||||||
hitHasRerolled = false
|
hitHasRerolled = false
|
||||||
|
preRerollDice = nil
|
||||||
blowupUsed = false
|
blowupUsed = false
|
||||||
mortalWoundsTotal = 0
|
mortalWoundsTotal = 0
|
||||||
}
|
}
|
||||||
@@ -397,6 +395,7 @@ class Roll: ObservableObject {
|
|||||||
func resetPool() {
|
func resetPool() {
|
||||||
dice.roll(howMany: total)
|
dice.roll(howMany: total)
|
||||||
hitHasRerolled = false
|
hitHasRerolled = false
|
||||||
|
preRerollDice = nil
|
||||||
blowupUsed = false
|
blowupUsed = false
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -414,8 +413,6 @@ class Roll: ObservableObject {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func applyReroll() {
|
func applyReroll() {
|
||||||
guard !currentHasRerolled else { return }
|
|
||||||
|
|
||||||
let mode: RerollMode
|
let mode: RerollMode
|
||||||
let cutoff: Int
|
let cutoff: Int
|
||||||
|
|
||||||
@@ -431,9 +428,19 @@ class Roll: ObservableObject {
|
|||||||
cutoff = invulnerable ? armourSave : (armourSave + armourPenetration)
|
cutoff = invulnerable ? armourSave : (armourSave + armourPenetration)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
guard mode != .none else { return }
|
||||||
|
|
||||||
|
// If a reroll was already applied this phase, restore the original dice first
|
||||||
|
// so the new mode always operates on the un-rerolled results.
|
||||||
|
if let snapshot = preRerollDice {
|
||||||
|
dice = snapshot
|
||||||
|
} else {
|
||||||
|
preRerollDice = dice
|
||||||
|
}
|
||||||
|
|
||||||
switch mode {
|
switch mode {
|
||||||
case .none:
|
case .none:
|
||||||
return
|
break
|
||||||
case .ones:
|
case .ones:
|
||||||
dice.reroll(dice: .one)
|
dice.reroll(dice: .one)
|
||||||
case .failed:
|
case .failed:
|
||||||
@@ -449,6 +456,18 @@ class Roll: ObservableObject {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Restores dice to pre-reroll state when the user selects None.
|
||||||
|
func undoReroll() {
|
||||||
|
guard let snapshot = preRerollDice else { return }
|
||||||
|
dice = snapshot
|
||||||
|
preRerollDice = nil
|
||||||
|
switch state {
|
||||||
|
case .hit: hitHasRerolled = false
|
||||||
|
case .wound: woundHasRerolled = false
|
||||||
|
case .armour: saveHasRerolled = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func add(howMuch: Int) {
|
func add(howMuch: Int) {
|
||||||
var newTotal: Int = 0
|
var newTotal: Int = 0
|
||||||
newTotal = self.total + howMuch
|
newTotal = self.total + howMuch
|
||||||
|
|||||||
@@ -31,3 +31,53 @@ extension CaseIterable where Self: Equatable {
|
|||||||
self = allCases[nextIndex == allCases.endIndex ? allCases.startIndex : nextIndex]
|
self = allCases[nextIndex == allCases.endIndex ? allCases.startIndex : nextIndex]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
extension EnvironmentValues {
|
||||||
|
@Entry var dieColorSelected: Color = .brown
|
||||||
|
@Entry var dieColorPrimary: Color = .cyan
|
||||||
|
}
|
||||||
|
|
||||||
|
extension View {
|
||||||
|
func disabledWithOpacity(_ condition: Bool) -> some View {
|
||||||
|
self.disabled(condition).opacity(condition ? 0.4 : 1.0)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
struct ToggleChip: View {
|
||||||
|
let label: String
|
||||||
|
@Binding var isOn: Bool
|
||||||
|
|
||||||
|
var body: some View {
|
||||||
|
Button(action: { isOn.toggle() }) {
|
||||||
|
Text(label)
|
||||||
|
.fontWeight(isOn ? .semibold : .regular)
|
||||||
|
.padding(.horizontal, 12)
|
||||||
|
.padding(.vertical, 6)
|
||||||
|
.foregroundColor(isOn ? Color(UIColor.systemBackground) : .primary)
|
||||||
|
.background(isOn ? Color.primary : Color.clear)
|
||||||
|
.clipShape(Capsule())
|
||||||
|
.overlay(Capsule().stroke(Color.primary, lineWidth: 1))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
struct StatStepper: View {
|
||||||
|
let top: String
|
||||||
|
let value: String
|
||||||
|
let bottom: String
|
||||||
|
let increment: () -> Void
|
||||||
|
let decrement: () -> Void
|
||||||
|
|
||||||
|
var body: some View {
|
||||||
|
VStack {
|
||||||
|
Text(top)
|
||||||
|
Group {
|
||||||
|
Text("▲").gesture(TapGesture().onEnded { increment() })
|
||||||
|
Text(value)
|
||||||
|
Text("▼").gesture(TapGesture().onEnded { decrement() })
|
||||||
|
}
|
||||||
|
.font(.system(size: 30))
|
||||||
|
Text(bottom)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -0,0 +1,39 @@
|
|||||||
|
//
|
||||||
|
// ArmourCalculatorView.swift
|
||||||
|
// Bunch O' Dice
|
||||||
|
//
|
||||||
|
|
||||||
|
import SwiftUI
|
||||||
|
|
||||||
|
struct ArmourCalculatorView: View {
|
||||||
|
@EnvironmentObject var current: Roll
|
||||||
|
|
||||||
|
var body: some View {
|
||||||
|
VStack {
|
||||||
|
RerollPickerView(mode: $current.saveRerollMode)
|
||||||
|
HStack(alignment: .top) {
|
||||||
|
Spacer()
|
||||||
|
StatStepper(
|
||||||
|
top: "Armour",
|
||||||
|
value: (current.armourPenetration > 0 ? "+" : "") + String(current.armourPenetration),
|
||||||
|
bottom: "Penetration",
|
||||||
|
increment: { current.addPenetration() },
|
||||||
|
decrement: { current.losePenetration() }
|
||||||
|
)
|
||||||
|
Spacer()
|
||||||
|
ToggleChip(label: "Invulnerable", isOn: $current.invulnerable)
|
||||||
|
Spacer()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.padding()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#Preview {
|
||||||
|
ArmourCalculatorView()
|
||||||
|
.environmentObject({
|
||||||
|
let r = Roll()
|
||||||
|
r.state = .armour
|
||||||
|
return r
|
||||||
|
}())
|
||||||
|
}
|
||||||
@@ -0,0 +1,41 @@
|
|||||||
|
//
|
||||||
|
// ButtonView.swift
|
||||||
|
// Bunch O' Dice
|
||||||
|
//
|
||||||
|
|
||||||
|
import SwiftUI
|
||||||
|
|
||||||
|
struct ButtonView: View {
|
||||||
|
let color: CGColor
|
||||||
|
let label: String
|
||||||
|
let value: Int
|
||||||
|
|
||||||
|
var body: some View {
|
||||||
|
let frameSize = 120.0
|
||||||
|
let fontSize = 35.0
|
||||||
|
let blurSize = 5.0
|
||||||
|
|
||||||
|
ZStack {
|
||||||
|
Circle()
|
||||||
|
.fill(Color(color))
|
||||||
|
.frame(width: frameSize, height: frameSize)
|
||||||
|
.blur(radius: blurSize)
|
||||||
|
VStack {
|
||||||
|
Text(String(value))
|
||||||
|
.fontWeight(.black)
|
||||||
|
Text(label)
|
||||||
|
.font(.system(size: 20))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.foregroundColor(.primary)
|
||||||
|
.font(.system(size: fontSize))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#Preview {
|
||||||
|
ButtonView(
|
||||||
|
color: CGColor(red: 1.0, green: 0.3, blue: 0.3, alpha: 1.0),
|
||||||
|
label: "Hits",
|
||||||
|
value: 12
|
||||||
|
)
|
||||||
|
}
|
||||||
@@ -0,0 +1,54 @@
|
|||||||
|
//
|
||||||
|
// DiePoolView.swift
|
||||||
|
// Bunch O' Dice
|
||||||
|
//
|
||||||
|
|
||||||
|
import SwiftUI
|
||||||
|
|
||||||
|
struct DiePoolView: View {
|
||||||
|
var body: some View {
|
||||||
|
HStack(spacing: 0) {
|
||||||
|
ForEach(Dice.DiceType.allCases, id: \.rawValue) { i in
|
||||||
|
DieView(whichDie: i)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private struct DieView: View {
|
||||||
|
let whichDie: Dice.DiceType
|
||||||
|
@EnvironmentObject var current: Roll
|
||||||
|
@Environment(\.dieColorSelected) var colorSelected
|
||||||
|
@Environment(\.dieColorPrimary) var colorPrimary
|
||||||
|
var body: some View {
|
||||||
|
let size = 42.0
|
||||||
|
let displayColorHit = (whichDie.rawValue >= current.cutoff.rawValue) ? colorSelected : colorPrimary
|
||||||
|
let displayColorArmor = (whichDie.rawValue <= current.cutoff.rawValue) ? colorSelected : colorPrimary
|
||||||
|
let displayColorArmorText = (whichDie.rawValue < current.cutoff.rawValue) ? colorSelected : colorPrimary
|
||||||
|
|
||||||
|
VStack(spacing: 2) {
|
||||||
|
Text(String(current.dice.value(which: whichDie)))
|
||||||
|
.foregroundColor(current.state == .armour ? displayColorArmorText : displayColorHit)
|
||||||
|
.frame(minWidth: 30, alignment: .center)
|
||||||
|
Image(systemName: "die.face." + String(whichDie.rawValue) + ".fill")
|
||||||
|
.foregroundColor(current.state == .armour ? displayColorArmor : displayColorHit)
|
||||||
|
}
|
||||||
|
.font(.system(size: size))
|
||||||
|
.padding(4)
|
||||||
|
.frame(maxWidth: .infinity)
|
||||||
|
.gesture(TapGesture().onEnded {
|
||||||
|
if current.state == .hit {
|
||||||
|
if whichDie.rawValue != 1 {
|
||||||
|
current.hitCutoff = whichDie
|
||||||
|
}
|
||||||
|
} else if current.state == .armour {
|
||||||
|
current.armourSave = Int(whichDie.rawValue)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#Preview {
|
||||||
|
DiePoolView()
|
||||||
|
.environmentObject(Roll())
|
||||||
|
}
|
||||||
@@ -0,0 +1,100 @@
|
|||||||
|
//
|
||||||
|
// HitCalculatorView.swift
|
||||||
|
// Bunch O' Dice
|
||||||
|
//
|
||||||
|
|
||||||
|
import SwiftUI
|
||||||
|
|
||||||
|
struct HitCalculatorView: View {
|
||||||
|
@EnvironmentObject var current: Roll
|
||||||
|
private let controlSize = 30.0
|
||||||
|
|
||||||
|
var body: some View {
|
||||||
|
VStack {
|
||||||
|
RerollPickerView(mode: $current.hitRerollMode)
|
||||||
|
.disabledWithOpacity(current.torrent)
|
||||||
|
HStack(alignment: .top) {
|
||||||
|
Spacer()
|
||||||
|
StatStepper(
|
||||||
|
top: "Hit Modifier",
|
||||||
|
value: (current.modifier.rawValue > 0 ? "+" : "") + String(current.modifier.rawValue),
|
||||||
|
bottom: "",
|
||||||
|
increment: { current.modifier = current.modifier.add() },
|
||||||
|
decrement: { current.modifier = current.modifier.remove() }
|
||||||
|
)
|
||||||
|
.disabledWithOpacity(current.torrent || current.psychic)
|
||||||
|
Spacer()
|
||||||
|
VStack {
|
||||||
|
Text("Blow Up")
|
||||||
|
VStack {
|
||||||
|
Button(action: { current.applyBlowup(face: .six) }) {
|
||||||
|
Image(systemName: "die.face.6")
|
||||||
|
.foregroundColor(.primary)
|
||||||
|
}
|
||||||
|
Button(action: { current.applyBlowup(face: .five) }) {
|
||||||
|
Image(systemName: "die.face.5")
|
||||||
|
.foregroundColor(.primary)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.padding()
|
||||||
|
.font(.system(size: controlSize))
|
||||||
|
}
|
||||||
|
.disabledWithOpacity(current.blowupUsed || current.torrent)
|
||||||
|
Spacer()
|
||||||
|
}
|
||||||
|
|
||||||
|
HStack(spacing: 6) {
|
||||||
|
ToggleChip(label: "Torrent", isOn: $current.torrent)
|
||||||
|
Spacer()
|
||||||
|
ToggleChip(label: "Psychic", isOn: $current.psychic)
|
||||||
|
.disabledWithOpacity(current.torrent)
|
||||||
|
Spacer()
|
||||||
|
ToggleChip(label: "Lethal", isOn: $current.lethalHits)
|
||||||
|
}
|
||||||
|
.padding(.horizontal)
|
||||||
|
VStack(spacing: 8) {
|
||||||
|
HStack {
|
||||||
|
ToggleChip(label: "Sustained Hits", isOn: $current.sustainedHits)
|
||||||
|
Spacer()
|
||||||
|
if current.sustainedHits {
|
||||||
|
Text("X:")
|
||||||
|
Text("▲")
|
||||||
|
.gesture(TapGesture().onEnded {
|
||||||
|
if current.sustainedHitsX < 6 { current.sustainedHitsX += 1 }
|
||||||
|
})
|
||||||
|
Text(String(current.sustainedHitsX))
|
||||||
|
.frame(minWidth: 30, alignment: .center)
|
||||||
|
Text("▼")
|
||||||
|
.gesture(TapGesture().onEnded {
|
||||||
|
if current.sustainedHitsX > 1 { current.sustainedHitsX -= 1 }
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
HStack {
|
||||||
|
ToggleChip(label: "Anti-X", isOn: $current.antiX)
|
||||||
|
Spacer()
|
||||||
|
if current.antiX {
|
||||||
|
Text("▲")
|
||||||
|
.gesture(TapGesture().onEnded {
|
||||||
|
if current.antiXThreshold < 6 { current.antiXThreshold += 1 }
|
||||||
|
})
|
||||||
|
Text("\(current.antiXThreshold)+")
|
||||||
|
.frame(minWidth: 34, alignment: .center)
|
||||||
|
Text("▼")
|
||||||
|
.gesture(TapGesture().onEnded {
|
||||||
|
if current.antiXThreshold > 2 { current.antiXThreshold -= 1 }
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.font(.system(size: 18))
|
||||||
|
.padding(.horizontal)
|
||||||
|
}
|
||||||
|
.padding()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#Preview {
|
||||||
|
HitCalculatorView()
|
||||||
|
.environmentObject(Roll())
|
||||||
|
}
|
||||||
@@ -0,0 +1,49 @@
|
|||||||
|
//
|
||||||
|
// InfoAreaView.swift
|
||||||
|
// Bunch O' Dice
|
||||||
|
//
|
||||||
|
|
||||||
|
import SwiftUI
|
||||||
|
|
||||||
|
struct InfoAreaView: View {
|
||||||
|
@EnvironmentObject var current: Roll
|
||||||
|
|
||||||
|
var body: some View {
|
||||||
|
HStack() {
|
||||||
|
switch current.state {
|
||||||
|
case .hit:
|
||||||
|
if current.lethalHits || current.sustainedHits {
|
||||||
|
Text("Crits (6s): \(current.critHitCount)")
|
||||||
|
.fontWeight(.semibold)
|
||||||
|
.foregroundColor(.red)
|
||||||
|
}
|
||||||
|
if current.antiX {
|
||||||
|
Text("Anti-X (\(current.antiXThreshold)+): \(current.antiXCount)")
|
||||||
|
.fontWeight(.semibold)
|
||||||
|
.foregroundColor(.red)
|
||||||
|
}
|
||||||
|
case .wound:
|
||||||
|
if current.autoWoundsCarried > 0 {
|
||||||
|
Text("Auto-wounds carried: \(current.autoWoundsCarried) (skip wound roll)")
|
||||||
|
.fontWeight(.semibold)
|
||||||
|
.foregroundColor(.red)
|
||||||
|
}
|
||||||
|
if current.devastatingWounds {
|
||||||
|
Text("Mortal wounds: \(current.critWoundCount)")
|
||||||
|
.fontWeight(.semibold)
|
||||||
|
.foregroundColor(.red)
|
||||||
|
}
|
||||||
|
case .armour:
|
||||||
|
EmptyView()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.font(.system(size: 18))
|
||||||
|
.frame(maxWidth: .infinity, alignment: .leading)
|
||||||
|
.padding(.horizontal)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#Preview {
|
||||||
|
InfoAreaView()
|
||||||
|
.environmentObject(Roll())
|
||||||
|
}
|
||||||
@@ -0,0 +1,40 @@
|
|||||||
|
//
|
||||||
|
// RerollPickerView.swift
|
||||||
|
// Bunch O' Dice
|
||||||
|
//
|
||||||
|
|
||||||
|
import SwiftUI
|
||||||
|
|
||||||
|
struct RerollPickerView: View {
|
||||||
|
@EnvironmentObject var current: Roll
|
||||||
|
@Binding var mode: RerollMode
|
||||||
|
|
||||||
|
var body: some View {
|
||||||
|
let actionBinding = Binding<RerollMode>(
|
||||||
|
get: { mode },
|
||||||
|
set: { newValue in
|
||||||
|
mode = newValue
|
||||||
|
if newValue != .none {
|
||||||
|
current.applyReroll()
|
||||||
|
} else {
|
||||||
|
current.undoReroll()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
)
|
||||||
|
HStack(spacing: 8) {
|
||||||
|
Text("Reroll")
|
||||||
|
Picker("Reroll", selection: actionBinding) {
|
||||||
|
ForEach(RerollMode.allCases, id: \.self) { m in
|
||||||
|
Text(m.label).tag(m)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.pickerStyle(.segmented)
|
||||||
|
}
|
||||||
|
.padding(.horizontal)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#Preview {
|
||||||
|
RerollPickerView(mode: .constant(.none))
|
||||||
|
.environmentObject(Roll())
|
||||||
|
}
|
||||||
@@ -0,0 +1,73 @@
|
|||||||
|
//
|
||||||
|
// RollSummaryView.swift
|
||||||
|
// Bunch O' Dice
|
||||||
|
//
|
||||||
|
|
||||||
|
import SwiftUI
|
||||||
|
|
||||||
|
struct RollSummaryView: View {
|
||||||
|
@EnvironmentObject var current: Roll
|
||||||
|
@State private var showFillView = false
|
||||||
|
@Environment(\.dieColorSelected) var colorSelected
|
||||||
|
@Environment(\.dieColorPrimary) var colorPrimary
|
||||||
|
|
||||||
|
|
||||||
|
var body: some View {
|
||||||
|
VStack{
|
||||||
|
HStack(spacing: 12) {
|
||||||
|
VStack {
|
||||||
|
Text("Dice Pool")
|
||||||
|
.font(.caption)
|
||||||
|
Button(action: { showFillView.toggle() }) {
|
||||||
|
VStack(spacing: 4) {
|
||||||
|
Text(String(current.total))
|
||||||
|
.font(.system(size: 28, weight: .black))
|
||||||
|
}
|
||||||
|
.frame(maxWidth: .infinity)
|
||||||
|
.padding(.vertical, 12)
|
||||||
|
.clipShape(Circle())
|
||||||
|
.foregroundColor(.black)
|
||||||
|
.glassEffect(.clear.tint(colorPrimary))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.sheet(isPresented: $showFillView,
|
||||||
|
onDismiss: { current.resetPool() }) {
|
||||||
|
VStack {
|
||||||
|
AddToPool()
|
||||||
|
Button("Accept", action: { showFillView.toggle() })
|
||||||
|
.padding(20)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
VStack {
|
||||||
|
Text(current.state == .armour ? "Model Damage" : current.state.text() + "s")
|
||||||
|
.font(.caption)
|
||||||
|
Button(action: current.incrementState) {
|
||||||
|
VStack(spacing: 4) {
|
||||||
|
Text(String(current.result))
|
||||||
|
.font(.system(size: 28, weight: .black))
|
||||||
|
if current.state == .armour && current.mortalWoundsTotal > 0 {
|
||||||
|
Text("mortal: \(current.mortalWoundsTotal)")
|
||||||
|
.font(.caption)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.frame(maxWidth: .infinity)
|
||||||
|
.padding(.vertical, 12)
|
||||||
|
.clipShape(Circle())
|
||||||
|
.foregroundColor(.black)
|
||||||
|
.glassEffect(.clear.tint(colorSelected))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
DiePoolView()
|
||||||
|
}
|
||||||
|
.padding()
|
||||||
|
.frame(maxWidth: .infinity)
|
||||||
|
.background(Color.gray)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#Preview {
|
||||||
|
RollSummaryView()
|
||||||
|
.environmentObject(Roll())
|
||||||
|
}
|
||||||
@@ -0,0 +1,47 @@
|
|||||||
|
//
|
||||||
|
// StatePickerView.swift
|
||||||
|
// Bunch O' Dice
|
||||||
|
//
|
||||||
|
|
||||||
|
import SwiftUI
|
||||||
|
|
||||||
|
struct StatePickerView: View {
|
||||||
|
@EnvironmentObject var current: Roll
|
||||||
|
private let controlSize = 30.0
|
||||||
|
|
||||||
|
var body: some View {
|
||||||
|
HStack {
|
||||||
|
Button(action: { current.state = .hit }) {
|
||||||
|
ZStack {
|
||||||
|
if current.state == .hit { circle() }
|
||||||
|
Text(RollState.hit.print())
|
||||||
|
}
|
||||||
|
}.padding(10)
|
||||||
|
Button(action: { current.state = .wound }) {
|
||||||
|
ZStack {
|
||||||
|
if current.state == .wound { circle() }
|
||||||
|
Text(RollState.wound.print())
|
||||||
|
}
|
||||||
|
}.padding(10)
|
||||||
|
Button(action: { current.state = .armour }) {
|
||||||
|
ZStack {
|
||||||
|
if current.state == .armour { circle() }
|
||||||
|
Text(RollState.armour.print())
|
||||||
|
}
|
||||||
|
}.padding(10)
|
||||||
|
}
|
||||||
|
.font(.system(size: 60))
|
||||||
|
}
|
||||||
|
|
||||||
|
@ViewBuilder private func circle() -> some View {
|
||||||
|
Circle()
|
||||||
|
.fill(Color.gray)
|
||||||
|
.blur(radius: 2.0)
|
||||||
|
.frame(width: controlSize * 2.5, height: controlSize * 2.5, alignment: .center)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#Preview {
|
||||||
|
StatePickerView()
|
||||||
|
.environmentObject(Roll())
|
||||||
|
}
|
||||||
@@ -0,0 +1,58 @@
|
|||||||
|
//
|
||||||
|
// WoundCalculatorView.swift
|
||||||
|
// Bunch O' Dice
|
||||||
|
//
|
||||||
|
|
||||||
|
import SwiftUI
|
||||||
|
|
||||||
|
struct WoundCalculatorView: View {
|
||||||
|
@EnvironmentObject var current: Roll
|
||||||
|
|
||||||
|
var body: some View {
|
||||||
|
VStack {
|
||||||
|
RerollPickerView(mode: $current.woundRerollMode)
|
||||||
|
HStack(alignment: .top) {
|
||||||
|
Spacer()
|
||||||
|
StatStepper(
|
||||||
|
top: "Attack",
|
||||||
|
value: String(current.strength),
|
||||||
|
bottom: "Strength",
|
||||||
|
increment: { current.addStrength() },
|
||||||
|
decrement: { current.loseStrength() }
|
||||||
|
)
|
||||||
|
Spacer()
|
||||||
|
StatStepper(
|
||||||
|
top: "Toughness",
|
||||||
|
value: String(current.toughness),
|
||||||
|
bottom: " ",
|
||||||
|
increment: { current.addToughness() },
|
||||||
|
decrement: { current.loseToughness() }
|
||||||
|
)
|
||||||
|
Spacer()
|
||||||
|
StatStepper(
|
||||||
|
top: "Wound",
|
||||||
|
value: (current.woundModifier.rawValue > 0 ? "+" : "") + String(current.woundModifier.rawValue),
|
||||||
|
bottom: "Modifier",
|
||||||
|
increment: { current.woundModifier = current.woundModifier.add() },
|
||||||
|
decrement: { current.woundModifier = current.woundModifier.remove() }
|
||||||
|
)
|
||||||
|
Spacer()
|
||||||
|
}
|
||||||
|
HStack {
|
||||||
|
ToggleChip(label: "Devastating Wounds", isOn: $current.devastatingWounds)
|
||||||
|
}
|
||||||
|
.font(.system(size: 18))
|
||||||
|
.padding(.horizontal)
|
||||||
|
}
|
||||||
|
.padding()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#Preview {
|
||||||
|
WoundCalculatorView()
|
||||||
|
.environmentObject({
|
||||||
|
let r = Roll()
|
||||||
|
r.state = .wound
|
||||||
|
return r
|
||||||
|
}())
|
||||||
|
}
|
||||||
@@ -0,0 +1,88 @@
|
|||||||
|
//
|
||||||
|
// PortraitView.swift
|
||||||
|
// Bunch O' Dice
|
||||||
|
//
|
||||||
|
// Created by Rory Hinnen on 22/6/18.
|
||||||
|
//
|
||||||
|
|
||||||
|
import SwiftUI
|
||||||
|
|
||||||
|
struct MainView: View {
|
||||||
|
|
||||||
|
enum MenuView: Int {
|
||||||
|
case history, add, settings
|
||||||
|
|
||||||
|
func icon() -> Image {
|
||||||
|
switch self {
|
||||||
|
case .history: return Image(systemName: "folder")
|
||||||
|
case .settings: return Image(systemName: "square.and.pencil")
|
||||||
|
case .add: return Image(systemName: "folder.badge.plus")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@EnvironmentObject var current: Roll
|
||||||
|
@ObservedObject var sheet = SettingsSheet()
|
||||||
|
|
||||||
|
private let controlSize = 30.0
|
||||||
|
|
||||||
|
var body: some View {
|
||||||
|
VStack {
|
||||||
|
StatePickerView()
|
||||||
|
VStack(spacing: 8) {
|
||||||
|
RollSummaryView()
|
||||||
|
switch current.state {
|
||||||
|
case .wound: WoundCalculatorView()
|
||||||
|
case .armour: ArmourCalculatorView()
|
||||||
|
default: HitCalculatorView()
|
||||||
|
}
|
||||||
|
InfoAreaView()
|
||||||
|
Spacer()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.toolbar {
|
||||||
|
ToolbarItemGroup(placement: .bottomBar) {
|
||||||
|
HStack {
|
||||||
|
Button(action: { sheet.state = .history }) {
|
||||||
|
MenuView.history.icon()
|
||||||
|
}
|
||||||
|
Button(action: { current.history.insert(Event(total: current.result, pool: current.dice, state: current.state), at: 0) }) {
|
||||||
|
MenuView.add.icon()
|
||||||
|
}
|
||||||
|
Button(action: { sheet.state = .settings }) {
|
||||||
|
MenuView.settings.icon()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.font(.system(size: controlSize))
|
||||||
|
.foregroundColor(.primary)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.sheet(isPresented: $sheet.isShowing) {
|
||||||
|
switch sheet.state {
|
||||||
|
case .settings: SettingsView(isShowing: $sheet.isShowing)
|
||||||
|
case .history: HistoryView(isShowing: $sheet.isShowing)
|
||||||
|
default: EmptyView()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#Preview("Hit") {
|
||||||
|
MainView().environmentObject(Roll())
|
||||||
|
}
|
||||||
|
|
||||||
|
#Preview("Wound") {
|
||||||
|
MainView().environmentObject({
|
||||||
|
let r = Roll()
|
||||||
|
r.state = .wound
|
||||||
|
return r
|
||||||
|
}())
|
||||||
|
}
|
||||||
|
|
||||||
|
#Preview("Armour") {
|
||||||
|
MainView().environmentObject({
|
||||||
|
let r = Roll()
|
||||||
|
r.state = .armour
|
||||||
|
return r
|
||||||
|
}())
|
||||||
|
}
|
||||||
@@ -1,20 +0,0 @@
|
|||||||
//
|
|
||||||
// dice.swift
|
|
||||||
// Bunch O' Dice
|
|
||||||
//
|
|
||||||
// Created by Rory Hinnen on 6/10/23.
|
|
||||||
//
|
|
||||||
|
|
||||||
import SwiftUI
|
|
||||||
|
|
||||||
struct dice: View {
|
|
||||||
var body: some View {
|
|
||||||
Text(/*@START_MENU_TOKEN@*/"Hello, World!"/*@END_MENU_TOKEN@*/)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
struct dice_Previews: PreviewProvider {
|
|
||||||
static var previews: some View {
|
|
||||||
dice()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -227,20 +227,30 @@ struct RerollTests {
|
|||||||
#expect(roll.hitHasRerolled == true)
|
#expect(roll.hitHasRerolled == true)
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test("applyReroll: guard blocks second call")
|
@Test("applyReroll: re-applying restores snapshot and rerolls with new mode")
|
||||||
func applyRerollRespectsFlagAlreadySet() {
|
func applyRerollChangeModeRestoresSnapshot() {
|
||||||
let roll = Roll()
|
let roll = Roll()
|
||||||
roll.state = .hit
|
roll.state = .hit
|
||||||
|
roll.hitCutoff = .four
|
||||||
|
roll.hitRerollMode = .ones
|
||||||
|
roll.dice.one = 3; roll.dice.six = 2
|
||||||
|
roll.total = 5
|
||||||
|
|
||||||
|
// First reroll: reroll the 3 ones
|
||||||
|
roll.applyReroll()
|
||||||
|
#expect(roll.hitHasRerolled == true)
|
||||||
|
#expect(roll.preRerollDice != nil)
|
||||||
|
let afterFirst = roll.dice.one + roll.dice.two + roll.dice.three +
|
||||||
|
roll.dice.four + roll.dice.five + roll.dice.six
|
||||||
|
#expect(afterFirst == 5)
|
||||||
|
|
||||||
|
// Switch to 'all' mode — should restore snapshot then reroll all
|
||||||
roll.hitRerollMode = .all
|
roll.hitRerollMode = .all
|
||||||
roll.hitHasRerolled = true // simulate already used
|
roll.applyReroll()
|
||||||
roll.dice.one = 5 // known state
|
let afterSecond = roll.dice.one + roll.dice.two + roll.dice.three +
|
||||||
roll.applyReroll() // should be blocked by guard
|
roll.dice.four + roll.dice.five + roll.dice.six
|
||||||
#expect(roll.dice.one == 5)
|
#expect(afterSecond == 5)
|
||||||
#expect(roll.dice.two == 0)
|
#expect(roll.hitHasRerolled == true)
|
||||||
#expect(roll.dice.three == 0)
|
|
||||||
#expect(roll.dice.four == 0)
|
|
||||||
#expect(roll.dice.five == 0)
|
|
||||||
#expect(roll.dice.six == 0)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test("applyReroll: only sets flag for the active phase")
|
@Test("applyReroll: only sets flag for the active phase")
|
||||||
@@ -269,6 +279,36 @@ struct RerollTests {
|
|||||||
#expect(roll.dice.six >= 2) // dice above cutoff are untouched; rerolls may also produce sixes
|
#expect(roll.dice.six >= 2) // dice above cutoff are untouched; rerolls may also produce sixes
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test("undoReroll: restores dice and clears flag")
|
||||||
|
func undoRerollRestoresDice() {
|
||||||
|
let roll = Roll()
|
||||||
|
roll.state = .hit
|
||||||
|
roll.hitCutoff = .four
|
||||||
|
roll.hitRerollMode = .ones
|
||||||
|
roll.dice.one = 3; roll.dice.six = 2
|
||||||
|
roll.total = 5
|
||||||
|
|
||||||
|
roll.applyReroll()
|
||||||
|
#expect(roll.hitHasRerolled == true)
|
||||||
|
|
||||||
|
roll.undoReroll()
|
||||||
|
#expect(roll.hitHasRerolled == false)
|
||||||
|
#expect(roll.preRerollDice == nil)
|
||||||
|
// Dice should be restored to original state
|
||||||
|
#expect(roll.dice.one == 3)
|
||||||
|
#expect(roll.dice.six == 2)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test("undoReroll: no-op when no reroll has been applied")
|
||||||
|
func undoRerollNoop() {
|
||||||
|
let roll = Roll()
|
||||||
|
roll.state = .hit
|
||||||
|
roll.dice.one = 3
|
||||||
|
roll.undoReroll() // nothing to undo
|
||||||
|
#expect(roll.dice.one == 3)
|
||||||
|
#expect(roll.hitHasRerolled == false)
|
||||||
|
}
|
||||||
|
|
||||||
@Test("resetPool: clears hitHasRerolled flag")
|
@Test("resetPool: clears hitHasRerolled flag")
|
||||||
func resetPoolClearsFlag() {
|
func resetPoolClearsFlag() {
|
||||||
let roll = Roll()
|
let roll = Roll()
|
||||||
|
|||||||
Reference in New Issue
Block a user