added modal reroll
This commit is contained in:
@@ -104,11 +104,19 @@ struct MainView: View {
|
|||||||
.padding()
|
.padding()
|
||||||
}
|
}
|
||||||
Spacer()
|
Spacer()
|
||||||
VStack{
|
VStack {
|
||||||
Text("Reroll")
|
Text("Reroll")
|
||||||
Button("🎲", action: {
|
Picker("", selection: $current.saveRerollMode) {
|
||||||
current.dice.roll(howMany: current.total)
|
ForEach(RerollMode.allCases, id: \.self) { mode in
|
||||||
})
|
Text(mode.label).tag(mode)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.pickerStyle(.segmented)
|
||||||
|
.frame(width: 180)
|
||||||
|
Button("🎲") {
|
||||||
|
current.applyReroll()
|
||||||
|
}
|
||||||
|
.disabled(current.saveRerollMode == .none || current.saveHasRerolled)
|
||||||
.padding()
|
.padding()
|
||||||
.font(.system(size: controlSize))
|
.font(.system(size: controlSize))
|
||||||
}
|
}
|
||||||
@@ -173,13 +181,21 @@ struct MainView: View {
|
|||||||
Text("Modifier")
|
Text("Modifier")
|
||||||
}
|
}
|
||||||
Spacer()
|
Spacer()
|
||||||
VStack{
|
VStack {
|
||||||
Text("Reroll")
|
Text("Reroll")
|
||||||
Button("🎲", action: {
|
Picker("", selection: $current.woundRerollMode) {
|
||||||
current.dice.roll(howMany: current.total)
|
ForEach(RerollMode.allCases, id: \.self) { mode in
|
||||||
})
|
Text(mode.label).tag(mode)
|
||||||
.padding()
|
}
|
||||||
.font(.system(size: controlSize))
|
}
|
||||||
|
.pickerStyle(.segmented)
|
||||||
|
.frame(width: 180)
|
||||||
|
Button("🎲") {
|
||||||
|
current.applyReroll()
|
||||||
|
}
|
||||||
|
.disabled(current.woundRerollMode == .none || current.woundHasRerolled)
|
||||||
|
.padding()
|
||||||
|
.font(.system(size: controlSize))
|
||||||
}
|
}
|
||||||
Spacer()
|
Spacer()
|
||||||
}.padding()
|
}.padding()
|
||||||
@@ -223,13 +239,21 @@ struct MainView: View {
|
|||||||
.font(.system(size: controlSize))
|
.font(.system(size: controlSize))
|
||||||
|
|
||||||
}
|
}
|
||||||
VStack{
|
VStack {
|
||||||
Text("Reroll")
|
Text("Reroll")
|
||||||
Button("🎲", action: {
|
Picker("", selection: $current.hitRerollMode) {
|
||||||
current.dice.roll(howMany: current.total)
|
ForEach(RerollMode.allCases, id: \.self) { mode in
|
||||||
})
|
Text(mode.label).tag(mode)
|
||||||
.padding()
|
}
|
||||||
.font(.system(size: controlSize))
|
}
|
||||||
|
.pickerStyle(.segmented)
|
||||||
|
.frame(width: 180)
|
||||||
|
Button("🎲") {
|
||||||
|
current.applyReroll()
|
||||||
|
}
|
||||||
|
.disabled(current.hitRerollMode == .none || current.hitHasRerolled)
|
||||||
|
.padding()
|
||||||
|
.font(.system(size: controlSize))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}.padding()
|
}.padding()
|
||||||
@@ -304,7 +328,7 @@ struct MainView: View {
|
|||||||
buttons(color: blue, label: "Pool", value: current.total)
|
buttons(color: blue, label: "Pool", value: current.total)
|
||||||
}
|
}
|
||||||
.sheet(isPresented: $showFillView,
|
.sheet(isPresented: $showFillView,
|
||||||
onDismiss: {current.dice.roll(howMany: current.total)}) {
|
onDismiss: { current.resetPool() }) {
|
||||||
VStack {
|
VStack {
|
||||||
AddToPool()
|
AddToPool()
|
||||||
Button("Accept", action: {showFillView.toggle()})
|
Button("Accept", action: {showFillView.toggle()})
|
||||||
|
|||||||
@@ -279,6 +279,25 @@ struct Dice {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
mutating func rerollBelow(cutoff: Int) {
|
||||||
|
var count = 0
|
||||||
|
for faceValue in 1..<cutoff {
|
||||||
|
guard let face = DiceType(rawValue: faceValue) else { continue }
|
||||||
|
count += value(which: face)
|
||||||
|
switch face {
|
||||||
|
case .one: one = 0
|
||||||
|
case .two: two = 0
|
||||||
|
case .three: three = 0
|
||||||
|
case .four: four = 0
|
||||||
|
case .five: five = 0
|
||||||
|
case .six: six = 0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
var rerolls = Dice()
|
||||||
|
rerolls.roll(howMany: count)
|
||||||
|
add(pool: rerolls)
|
||||||
|
}
|
||||||
|
|
||||||
mutating func blowup(dice: DiceType) {
|
mutating func blowup(dice: DiceType) {
|
||||||
var rolls = Dice()
|
var rolls = Dice()
|
||||||
if dice == .six {
|
if dice == .six {
|
||||||
@@ -291,4 +310,3 @@ struct Dice {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -78,6 +78,19 @@ enum Appearance: Int, CaseIterable, Identifiable {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
enum RerollMode: Int, CaseIterable {
|
||||||
|
case none, ones, failed, all
|
||||||
|
|
||||||
|
var label: String {
|
||||||
|
switch self {
|
||||||
|
case .none: return "None"
|
||||||
|
case .ones: return "1s"
|
||||||
|
case .failed: return "Failed"
|
||||||
|
case .all: return "All"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// MARK: Structs
|
// MARK: Structs
|
||||||
struct Event: Identifiable {
|
struct Event: Identifiable {
|
||||||
let id: UUID
|
let id: UUID
|
||||||
@@ -130,6 +143,14 @@ class Roll: ObservableObject {
|
|||||||
|
|
||||||
@Published var currentPage: String
|
@Published var currentPage: String
|
||||||
|
|
||||||
|
@Published var hitRerollMode = RerollMode.none
|
||||||
|
@Published var woundRerollMode = RerollMode.none
|
||||||
|
@Published var saveRerollMode = RerollMode.none
|
||||||
|
|
||||||
|
@Published var hitHasRerolled = false
|
||||||
|
@Published var woundHasRerolled = false
|
||||||
|
@Published var saveHasRerolled = false
|
||||||
|
|
||||||
// calculated variables
|
// calculated variables
|
||||||
var totalDigits: [Int] {
|
var totalDigits: [Int] {
|
||||||
get {
|
get {
|
||||||
@@ -209,6 +230,22 @@ class Roll: ObservableObject {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var currentRerollMode: RerollMode {
|
||||||
|
switch state {
|
||||||
|
case .hit: return hitRerollMode
|
||||||
|
case .wound: return woundRerollMode
|
||||||
|
case .armour: return saveRerollMode
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var currentHasRerolled: Bool {
|
||||||
|
switch state {
|
||||||
|
case .hit: return hitHasRerolled
|
||||||
|
case .wound: return woundHasRerolled
|
||||||
|
case .armour: return saveHasRerolled
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// init
|
// init
|
||||||
init() {
|
init() {
|
||||||
if !UserDefaults.standard.bool(forKey: "didLaunchBefore") {
|
if !UserDefaults.standard.bool(forKey: "didLaunchBefore") {
|
||||||
@@ -295,17 +332,61 @@ class Roll: ObservableObject {
|
|||||||
case .hit:
|
case .hit:
|
||||||
self.total = hit
|
self.total = hit
|
||||||
self.state = .wound
|
self.state = .wound
|
||||||
|
woundHasRerolled = false
|
||||||
case .wound:
|
case .wound:
|
||||||
self.total = wound
|
self.total = wound
|
||||||
self.state = .armour
|
self.state = .armour
|
||||||
|
saveHasRerolled = false
|
||||||
case .armour:
|
case .armour:
|
||||||
self.total = save
|
self.total = save
|
||||||
self.state = .hit
|
self.state = .hit
|
||||||
|
hitHasRerolled = false
|
||||||
}
|
}
|
||||||
self.dice.roll(howMany: self.total)
|
self.dice.roll(howMany: self.total)
|
||||||
}
|
}
|
||||||
|
|
||||||
// MARK: extra
|
// MARK: extra
|
||||||
|
func resetPool() {
|
||||||
|
dice.roll(howMany: total)
|
||||||
|
hitHasRerolled = false
|
||||||
|
}
|
||||||
|
|
||||||
|
func applyReroll() {
|
||||||
|
guard !currentHasRerolled else { return }
|
||||||
|
|
||||||
|
let mode: RerollMode
|
||||||
|
let cutoff: Int
|
||||||
|
|
||||||
|
switch state {
|
||||||
|
case .hit:
|
||||||
|
mode = hitRerollMode
|
||||||
|
cutoff = hitCutoff.rawValue
|
||||||
|
case .wound:
|
||||||
|
mode = woundRerollMode
|
||||||
|
cutoff = woundCutoff
|
||||||
|
case .armour:
|
||||||
|
mode = saveRerollMode
|
||||||
|
cutoff = invulnerable ? armourSave : (armourSave - armourPenetration)
|
||||||
|
}
|
||||||
|
|
||||||
|
switch mode {
|
||||||
|
case .none:
|
||||||
|
return
|
||||||
|
case .ones:
|
||||||
|
dice.reroll(dice: .one)
|
||||||
|
case .failed:
|
||||||
|
dice.rerollBelow(cutoff: cutoff)
|
||||||
|
case .all:
|
||||||
|
dice.roll(howMany: total)
|
||||||
|
}
|
||||||
|
|
||||||
|
switch state {
|
||||||
|
case .hit: hitHasRerolled = true
|
||||||
|
case .wound: woundHasRerolled = true
|
||||||
|
case .armour: saveHasRerolled = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func add(howMuch: Int) {
|
func add(howMuch: Int) {
|
||||||
var newTotal: Int = 0
|
var newTotal: Int = 0
|
||||||
newTotal = self.total + howMuch
|
newTotal = self.total + howMuch
|
||||||
|
|||||||
@@ -45,6 +45,7 @@
|
|||||||
/* End PBXContainerItemProxy section */
|
/* End PBXContainerItemProxy section */
|
||||||
|
|
||||||
/* Begin PBXFileReference section */
|
/* Begin PBXFileReference section */
|
||||||
|
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>"; };
|
||||||
981AD0BA27C30E31005A6492 /* Bunch O' Dice.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = "Bunch O' Dice.app"; sourceTree = BUILT_PRODUCTS_DIR; };
|
981AD0BA27C30E31005A6492 /* Bunch O' Dice.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = "Bunch O' Dice.app"; sourceTree = BUILT_PRODUCTS_DIR; };
|
||||||
@@ -97,6 +98,7 @@
|
|||||||
981AD0B127C30E31005A6492 = {
|
981AD0B127C30E31005A6492 = {
|
||||||
isa = PBXGroup;
|
isa = PBXGroup;
|
||||||
children = (
|
children = (
|
||||||
|
768C2F3A304F04E6002FEA9A /* Goals.md */,
|
||||||
981AD0BC27C30E31005A6492 /* Bunch-O--Dice */,
|
981AD0BC27C30E31005A6492 /* Bunch-O--Dice */,
|
||||||
981AD0CD27C30E32005A6492 /* Bunch O' DiceTests */,
|
981AD0CD27C30E32005A6492 /* Bunch O' DiceTests */,
|
||||||
981AD0D727C30E32005A6492 /* Bunch O' DiceUITests */,
|
981AD0D727C30E32005A6492 /* Bunch O' DiceUITests */,
|
||||||
|
|||||||
@@ -0,0 +1,10 @@
|
|||||||
|
- [ ] Update graphics to clarify roll context (to hit / to wound / armor save) #focus/professional
|
||||||
|
- [ ] Add stacking flat hit/wound modifier input, capped at net ±1 #focus/professional
|
||||||
|
- [ ] Add reroll mode selector (none / reroll 1s / reroll all failed / reroll all) #focus/professional
|
||||||
|
- [ ] Add critical-trigger layer keyed off unmodified natural 6s (Sustained Hits, Lethal Hits, Devastating Wounds, Anti-X) #focus/professional
|
||||||
|
- [ ] Add "ignore all modifiers" flag (Psychic) #focus/professional
|
||||||
|
- [ ] Add "auto-success, no roll" flag (Torrent) #focus/professional
|
||||||
|
- Android
|
||||||
|
- [ ] Android: set up official Swift-on-Android toolchain. #focus/professional #android
|
||||||
|
- [ ] Android: extract game logic (dice/modifier engine) into shared Swift library #focus/professional #android
|
||||||
|
- [ ] Android: rewrite UI natively in Jetpack Compose #focus/professional #android
|
||||||
Reference in New Issue
Block a user