From 27a03bca8aea4f84ca4f997e986d21fb58a26ce5 Mon Sep 17 00:00:00 2001 From: Paul Pogonyshev Date: Sat, 21 May 2022 17:36:26 +0200 Subject: [PATCH 1/4] Implement a waiting command (#6884) --- .../logic/civilization/CivilizationInfo.kt | 32 ++++++++++++----- core/src/com/unciv/models/UnitAction.kt | 2 ++ .../com/unciv/ui/worldscreen/WorldScreen.kt | 34 +++++++++++-------- .../unciv/ui/worldscreen/unit/UnitActions.kt | 18 ++++++++-- 4 files changed, 62 insertions(+), 24 deletions(-) diff --git a/core/src/com/unciv/logic/civilization/CivilizationInfo.kt b/core/src/com/unciv/logic/civilization/CivilizationInfo.kt index 41c68105474be..39b49d4a67ff0 100644 --- a/core/src/com/unciv/logic/civilization/CivilizationInfo.kt +++ b/core/src/com/unciv/logic/civilization/CivilizationInfo.kt @@ -211,6 +211,12 @@ class CivilizationInfo { */ var attacksSinceTurnStart = ArrayList() + /** + * Queue of all civilization units that have their actions possibly pending. Internally + * might include e.g. non-idle units, so must be filtered before being given to any outside code. + * (Should be small enough that using ArrayList is fine.) + */ + private var dueUnits = ArrayList() var hasMovedAutomatedUnits = false @Transient @@ -264,6 +270,7 @@ class CivilizationInfo { toReturn.totalCultureForContests = totalCultureForContests toReturn.totalFaithForContests = totalFaithForContests toReturn.attacksSinceTurnStart = attacksSinceTurnStart.copy() + toReturn.dueUnits = ArrayList(dueUnits) toReturn.hasMovedAutomatedUnits = hasMovedAutomatedUnits return toReturn } @@ -453,6 +460,8 @@ class CivilizationInfo { newList.add(mapUnit) units = newList + dueUnits.add(mapUnit) + if (updateCivInfo) { // Not relevant when updating TileInfo transients, since some info of the civ itself isn't yet available, // and in any case it'll be updated once civ info transients are @@ -471,18 +480,23 @@ class CivilizationInfo { fun getIdleUnits() = getCivUnits().filter { it.isIdle() } - private fun getDueUnits() = getCivUnits().filter { it.due && it.isIdle() } + // Drop all units that are not really 'due' anymore. We do it here to avoid caring how and where it happened. + fun getDueUnits() = dueUnits.filter { it.due && !it.isDestroyed && it.isIdle() } fun shouldGoToDueUnit() = UncivGame.Current.settings.checkForDueUnits && getDueUnits().any() - fun getNextDueUnit(): MapUnit? { - val dueUnits = getDueUnits() - if (dueUnits.any()) { - val unit = dueUnits.first() - unit.due = false - return unit + fun getNextDueUnit() = getDueUnits().firstOrNull() + + fun cycleThroughDueUnits(): MapUnit? { + var realDueUnits = getDueUnits(); + if (realDueUnits.any()) { + var unit = realDueUnits.first(); + // We shift the unit to the back of the queue. However, the caller may clear its 'due' state if it wants. + dueUnits.remove(unit); + dueUnits.add(unit); + return unit; } - return null + else return null; } //endregion @@ -828,6 +842,8 @@ class CivilizationInfo { fun startTurn() { civConstructions.startTurn() attacksSinceTurnStart.clear() + dueUnits.clear() + dueUnits.addAll(getCivUnits()) updateStatsForNextTurn() // for things that change when turn passes e.g. golden age, city state influence // Do this after updateStatsForNextTurn but before cities.startTurn diff --git a/core/src/com/unciv/models/UnitAction.kt b/core/src/com/unciv/models/UnitAction.kt index bf64172ed941f..540db322f9d95 100644 --- a/core/src/com/unciv/models/UnitAction.kt +++ b/core/src/com/unciv/models/UnitAction.kt @@ -137,6 +137,8 @@ enum class UnitActionType( { ImageGetter.getImage("OtherIcons/DisbandUnit") }, KeyCharAndCode.DEL), GiftUnit("Gift unit", { ImageGetter.getImage("OtherIcons/Present") }, UncivSound.Silent), + Wait("Wait", + null, 'z', UncivSound.Silent), ShowAdditionalActions("Show more", { imageGetShowMore() }, KeyCharAndCode(Input.Keys.PAGE_DOWN)), HideAdditionalActions("Back", diff --git a/core/src/com/unciv/ui/worldscreen/WorldScreen.kt b/core/src/com/unciv/ui/worldscreen/WorldScreen.kt index ab049cd03826a..aef69c8b3a102 100644 --- a/core/src/com/unciv/ui/worldscreen/WorldScreen.kt +++ b/core/src/com/unciv/ui/worldscreen/WorldScreen.kt @@ -184,9 +184,11 @@ class WorldScreen(val gameInfo: GameInfo, val viewingCiv:CivilizationInfo) : Bas // Don't select unit and change selectedCiv when centering as spectator if (viewingCiv.isSpectator()) mapHolder.setCenterPosition(tileToCenterOn, immediately = true, selectUnit = false) - else + else { mapHolder.setCenterPosition(tileToCenterOn, immediately = true, selectUnit = true) - + if (viewingCiv.getNextDueUnit() == bottomUnitTable.selectedUnit) + viewingCiv.cycleThroughDueUnits() + } tutorialController.allTutorialsShowedCallback = { shouldUpdate = true } @@ -727,6 +729,21 @@ class WorldScreen(val gameInfo: GameInfo, val viewingCiv:CivilizationInfo) : Bas } } + fun switchToNextUnit() { + val nextDueUnit = viewingCiv.cycleThroughDueUnits() + if (nextDueUnit != null) { + mapHolder.setCenterPosition( + nextDueUnit.currentTile.position, + immediately = false, + selectUnit = false + ) + bottomUnitTable.selectUnit(nextDueUnit) + shouldUpdate = true + // Unless 'wait' action is chosen, the unit will not be considered due anymore. + nextDueUnit.due = false + } + } + private fun updateNextTurnButton(isSomethingOpen: Boolean) { nextTurnButton.update(isSomethingOpen, isPlayersTurn, waitingForAutosave, getNextTurnAction()) nextTurnButton.setPosition(stage.width - nextTurnButton.width - 10f, topBar.y - nextTurnButton.height - 10f) @@ -741,18 +758,7 @@ class WorldScreen(val gameInfo: GameInfo, val viewingCiv:CivilizationInfo) : Bas NextTurnAction("Waiting for other players...",Color.GRAY) {} viewingCiv.shouldGoToDueUnit() -> - NextTurnAction("Next unit", Color.LIGHT_GRAY) { - val nextDueUnit = viewingCiv.getNextDueUnit() - if (nextDueUnit != null) { - mapHolder.setCenterPosition( - nextDueUnit.currentTile.position, - immediately = false, - selectUnit = false - ) - bottomUnitTable.selectUnit(nextDueUnit) - shouldUpdate = true - } - } + NextTurnAction("Next unit", Color.LIGHT_GRAY) { switchToNextUnit() } viewingCiv.cities.any { it.cityConstructions.currentConstructionFromQueue == "" } -> NextTurnAction("Pick construction", Color.CORAL) { diff --git a/core/src/com/unciv/ui/worldscreen/unit/UnitActions.kt b/core/src/com/unciv/ui/worldscreen/unit/UnitActions.kt index 094eaf955c0db..71c2479801957 100644 --- a/core/src/com/unciv/ui/worldscreen/unit/UnitActions.kt +++ b/core/src/com/unciv/ui/worldscreen/unit/UnitActions.kt @@ -68,9 +68,10 @@ object UnitActions { addTriggerUniqueActions(unit, actionList) addAddInCapitalAction(unit, actionList, tile) - addToggleActionsAction(unit, actionList, unitTable) + addWaitAction(unit, actionList, worldScreen); + return actionList } @@ -832,4 +833,17 @@ object UnitActions { ) } -} \ No newline at end of file + private fun addWaitAction(unit: MapUnit, actionList: ArrayList, worldScreen: WorldScreen) { + // This is only for idle units. + if (!unit.isIdle()) return + // Don't add if there are no idle units we could switch to, + if (!worldScreen.viewingCiv.getDueUnits().any()) return + actionList += UnitAction( + type = UnitActionType.Wait, + action = { + unit.due = true + worldScreen.switchToNextUnit() + } + ) + } +} From 93d9fe9cc3a477491df271e57affe5f83f171095 Mon Sep 17 00:00:00 2001 From: Paul Pogonyshev Date: Sat, 21 May 2022 18:58:32 +0200 Subject: [PATCH 2/4] Resolve misc. issues with commit 27a03bca8 --- .../logic/civilization/CivilizationInfo.kt | 27 ++++++++++++++----- .../com/unciv/ui/worldscreen/WorldScreen.kt | 8 +++--- .../unciv/ui/worldscreen/unit/UnitActions.kt | 4 +-- 3 files changed, 25 insertions(+), 14 deletions(-) diff --git a/core/src/com/unciv/logic/civilization/CivilizationInfo.kt b/core/src/com/unciv/logic/civilization/CivilizationInfo.kt index 39b49d4a67ff0..6eb33714eec6b 100644 --- a/core/src/com/unciv/logic/civilization/CivilizationInfo.kt +++ b/core/src/com/unciv/logic/civilization/CivilizationInfo.kt @@ -216,7 +216,8 @@ class CivilizationInfo { * might include e.g. non-idle units, so must be filtered before being given to any outside code. * (Should be small enough that using ArrayList is fine.) */ - private var dueUnits = ArrayList() + @Transient + private var dueUnits = mutableListOf() var hasMovedAutomatedUnits = false @Transient @@ -270,7 +271,7 @@ class CivilizationInfo { toReturn.totalCultureForContests = totalCultureForContests toReturn.totalFaithForContests = totalFaithForContests toReturn.attacksSinceTurnStart = attacksSinceTurnStart.copy() - toReturn.dueUnits = ArrayList(dueUnits) + toReturn.dueUnits = dueUnits.toMutableList() toReturn.hasMovedAutomatedUnits = hasMovedAutomatedUnits return toReturn } @@ -460,6 +461,8 @@ class CivilizationInfo { newList.add(mapUnit) units = newList + // Make sure it is initialized. + getDueUnits() dueUnits.add(mapUnit) if (updateCivInfo) { @@ -474,6 +477,7 @@ class CivilizationInfo { val newList = ArrayList(units) newList.remove(mapUnit) units = newList + dueUnits.remove(mapUnit) updateStatsForNextTurn() // unit upkeep updateDetailedCivResources() } @@ -481,19 +485,32 @@ class CivilizationInfo { fun getIdleUnits() = getCivUnits().filter { it.isIdle() } // Drop all units that are not really 'due' anymore. We do it here to avoid caring how and where it happened. - fun getDueUnits() = dueUnits.filter { it.due && !it.isDestroyed && it.isIdle() } + // Internal side effect: if 'dueUnits' has never been initialized (new game, load game), do it here. + fun getDueUnits(): List { + if (dueUnits.none()) + dueUnits.addAll(units) + return dueUnits.filter { it.due && it.isIdle() } + } fun shouldGoToDueUnit() = UncivGame.Current.settings.checkForDueUnits && getDueUnits().any() + // Callers should consider if cycleThroughDueUnits() is not a better choice. fun getNextDueUnit() = getDueUnits().firstOrNull() - fun cycleThroughDueUnits(): MapUnit? { + fun cycleThroughDueUnits(unitToSkip: MapUnit?): MapUnit? { var realDueUnits = getDueUnits(); if (realDueUnits.any()) { var unit = realDueUnits.first(); // We shift the unit to the back of the queue. However, the caller may clear its 'due' state if it wants. dueUnits.remove(unit); dueUnits.add(unit); + + if (unit == unitToSkip && realDueUnits.size > 1) { + unit = realDueUnits[1]; + dueUnits.remove(unit); + dueUnits.add(unit); + } + return unit; } else return null; @@ -842,8 +859,6 @@ class CivilizationInfo { fun startTurn() { civConstructions.startTurn() attacksSinceTurnStart.clear() - dueUnits.clear() - dueUnits.addAll(getCivUnits()) updateStatsForNextTurn() // for things that change when turn passes e.g. golden age, city state influence // Do this after updateStatsForNextTurn but before cities.startTurn diff --git a/core/src/com/unciv/ui/worldscreen/WorldScreen.kt b/core/src/com/unciv/ui/worldscreen/WorldScreen.kt index aef69c8b3a102..540de4cc87262 100644 --- a/core/src/com/unciv/ui/worldscreen/WorldScreen.kt +++ b/core/src/com/unciv/ui/worldscreen/WorldScreen.kt @@ -184,11 +184,8 @@ class WorldScreen(val gameInfo: GameInfo, val viewingCiv:CivilizationInfo) : Bas // Don't select unit and change selectedCiv when centering as spectator if (viewingCiv.isSpectator()) mapHolder.setCenterPosition(tileToCenterOn, immediately = true, selectUnit = false) - else { + else mapHolder.setCenterPosition(tileToCenterOn, immediately = true, selectUnit = true) - if (viewingCiv.getNextDueUnit() == bottomUnitTable.selectedUnit) - viewingCiv.cycleThroughDueUnits() - } tutorialController.allTutorialsShowedCallback = { shouldUpdate = true } @@ -730,7 +727,8 @@ class WorldScreen(val gameInfo: GameInfo, val viewingCiv:CivilizationInfo) : Bas } fun switchToNextUnit() { - val nextDueUnit = viewingCiv.cycleThroughDueUnits() + // Try to select something new if we already have the next pending unit selected. + val nextDueUnit = viewingCiv.cycleThroughDueUnits(bottomUnitTable.selectedUnit) if (nextDueUnit != null) { mapHolder.setCenterPosition( nextDueUnit.currentTile.position, diff --git a/core/src/com/unciv/ui/worldscreen/unit/UnitActions.kt b/core/src/com/unciv/ui/worldscreen/unit/UnitActions.kt index 71c2479801957..5a0af1a15cea6 100644 --- a/core/src/com/unciv/ui/worldscreen/unit/UnitActions.kt +++ b/core/src/com/unciv/ui/worldscreen/unit/UnitActions.kt @@ -834,10 +834,8 @@ object UnitActions { } private fun addWaitAction(unit: MapUnit, actionList: ArrayList, worldScreen: WorldScreen) { - // This is only for idle units. if (!unit.isIdle()) return - // Don't add if there are no idle units we could switch to, - if (!worldScreen.viewingCiv.getDueUnits().any()) return + if (worldScreen.viewingCiv.getDueUnits().filter { it != unit }.none()) return actionList += UnitAction( type = UnitActionType.Wait, action = { From 2ca7ed154166e40cd119a316bd8589286317e022 Mon Sep 17 00:00:00 2001 From: Paul Pogonyshev Date: Mon, 23 May 2022 22:08:22 +0200 Subject: [PATCH 3/4] Resolve misc. issues with commit 93d9fe9cc --- .../logic/civilization/CivilizationInfo.kt | 75 ++++++++++--------- .../unciv/ui/worldscreen/unit/UnitActions.kt | 26 +++---- 2 files changed, 52 insertions(+), 49 deletions(-) diff --git a/core/src/com/unciv/logic/civilization/CivilizationInfo.kt b/core/src/com/unciv/logic/civilization/CivilizationInfo.kt index 6eb33714eec6b..39ad4a2d06575 100644 --- a/core/src/com/unciv/logic/civilization/CivilizationInfo.kt +++ b/core/src/com/unciv/logic/civilization/CivilizationInfo.kt @@ -74,6 +74,12 @@ class CivilizationInfo { @Transient private var units = listOf() + /** + * Index in the unit list above of the unit that is potentially due and is next up for button "Next unit". + */ + @Transient + private var nextPotentiallyDueAt = 0 + @Transient var viewableTiles = setOf() @@ -211,13 +217,6 @@ class CivilizationInfo { */ var attacksSinceTurnStart = ArrayList() - /** - * Queue of all civilization units that have their actions possibly pending. Internally - * might include e.g. non-idle units, so must be filtered before being given to any outside code. - * (Should be small enough that using ArrayList is fine.) - */ - @Transient - private var dueUnits = mutableListOf() var hasMovedAutomatedUnits = false @Transient @@ -271,7 +270,6 @@ class CivilizationInfo { toReturn.totalCultureForContests = totalCultureForContests toReturn.totalFaithForContests = totalFaithForContests toReturn.attacksSinceTurnStart = attacksSinceTurnStart.copy() - toReturn.dueUnits = dueUnits.toMutableList() toReturn.hasMovedAutomatedUnits = hasMovedAutomatedUnits return toReturn } @@ -456,14 +454,18 @@ class CivilizationInfo { fun getCivUnits(): Sequence = units.asSequence() fun getCivGreatPeople(): Sequence = getCivUnits().filter { mapUnit -> mapUnit.isGreatPerson() } + // Similar to getCivUnits(), but the returned list is rotated so that the + // 'nextPotentiallyDueAt' unit is first here. + private fun getCivUnitsStartingAtNexDue() = units.subList(nextPotentiallyDueAt, units.size) + units.subList(0, nextPotentiallyDueAt) + fun addUnit(mapUnit: MapUnit, updateCivInfo: Boolean = true) { - val newList = ArrayList(units) + // Since we create a new list anyway, also rearrange existing units so that + // 'nextPotentiallyDueAt' becomes 0. This way new units are always last to be due + // (can be changed as wanted, just have a predictable place). + var newList = ArrayList(getCivUnitsStartingAtNexDue()) newList.add(mapUnit) units = newList - - // Make sure it is initialized. - getDueUnits() - dueUnits.add(mapUnit) + nextPotentiallyDueAt = 0 if (updateCivInfo) { // Not relevant when updating TileInfo transients, since some info of the civ itself isn't yet available, @@ -474,46 +476,47 @@ class CivilizationInfo { } fun removeUnit(mapUnit: MapUnit) { - val newList = ArrayList(units) + // See comment in addUnit(). + var newList = ArrayList(getCivUnitsStartingAtNexDue()) newList.remove(mapUnit) units = newList - dueUnits.remove(mapUnit) + nextPotentiallyDueAt = 0 + updateStatsForNextTurn() // unit upkeep updateDetailedCivResources() } fun getIdleUnits() = getCivUnits().filter { it.isIdle() } - // Drop all units that are not really 'due' anymore. We do it here to avoid caring how and where it happened. - // Internal side effect: if 'dueUnits' has never been initialized (new game, load game), do it here. - fun getDueUnits(): List { - if (dueUnits.none()) - dueUnits.addAll(units) - return dueUnits.filter { it.due && it.isIdle() } - } + fun getDueUnits(): List = getCivUnitsStartingAtNexDue().filter { it.due && it.isIdle() } fun shouldGoToDueUnit() = UncivGame.Current.settings.checkForDueUnits && getDueUnits().any() // Callers should consider if cycleThroughDueUnits() is not a better choice. fun getNextDueUnit() = getDueUnits().firstOrNull() - fun cycleThroughDueUnits(unitToSkip: MapUnit?): MapUnit? { - var realDueUnits = getDueUnits(); - if (realDueUnits.any()) { - var unit = realDueUnits.first(); - // We shift the unit to the back of the queue. However, the caller may clear its 'due' state if it wants. - dueUnits.remove(unit); - dueUnits.add(unit); - - if (unit == unitToSkip && realDueUnits.size > 1) { - unit = realDueUnits[1]; - dueUnits.remove(unit); - dueUnits.add(unit); + // Return the next due unit, but preferably not 'unitToSkip': this is returned only if it is the only remaining due unit. + fun cycleThroughDueUnits(unitToSkip: MapUnit? = null): MapUnit? { + var returnAt = nextPotentiallyDueAt; + var fallbackAt = -1; + + do { + if (units[returnAt].due && units[returnAt].isIdle()) { + if (units[returnAt] != unitToSkip) { + nextPotentiallyDueAt = (returnAt + 1) % units.size + return units[returnAt] + } + else fallbackAt = returnAt } - return unit; + returnAt = (returnAt + 1) % units.size + } while (returnAt != nextPotentiallyDueAt) + + if (fallbackAt >= 0) { + nextPotentiallyDueAt = (fallbackAt + 1) % units.size + return units[fallbackAt] } - else return null; + else return null } //endregion diff --git a/core/src/com/unciv/ui/worldscreen/unit/UnitActions.kt b/core/src/com/unciv/ui/worldscreen/unit/UnitActions.kt index 5a0af1a15cea6..150b888f79f64 100644 --- a/core/src/com/unciv/ui/worldscreen/unit/UnitActions.kt +++ b/core/src/com/unciv/ui/worldscreen/unit/UnitActions.kt @@ -68,10 +68,10 @@ object UnitActions { addTriggerUniqueActions(unit, actionList) addAddInCapitalAction(unit, actionList, tile) - addToggleActionsAction(unit, actionList, unitTable) - addWaitAction(unit, actionList, worldScreen); + addToggleActionsAction(unit, actionList, unitTable) + return actionList } @@ -822,17 +822,6 @@ object UnitActions { } } - private fun addToggleActionsAction(unit: MapUnit, actionList: ArrayList, unitTable: UnitTable) { - actionList += UnitAction( - type = if (unit.showAdditionalActions) UnitActionType.HideAdditionalActions - else UnitActionType.ShowAdditionalActions, - action = { - unit.showAdditionalActions = !unit.showAdditionalActions - unitTable.update() - } - ) - } - private fun addWaitAction(unit: MapUnit, actionList: ArrayList, worldScreen: WorldScreen) { if (!unit.isIdle()) return if (worldScreen.viewingCiv.getDueUnits().filter { it != unit }.none()) return @@ -844,4 +833,15 @@ object UnitActions { } ) } + + private fun addToggleActionsAction(unit: MapUnit, actionList: ArrayList, unitTable: UnitTable) { + actionList += UnitAction( + type = if (unit.showAdditionalActions) UnitActionType.HideAdditionalActions + else UnitActionType.ShowAdditionalActions, + action = { + unit.showAdditionalActions = !unit.showAdditionalActions + unitTable.update() + } + ) + } } From bc733cd41fcbbffdc77db3bce212ba154a83f11f Mon Sep 17 00:00:00 2001 From: Paul Pogonyshev Date: Tue, 24 May 2022 19:22:51 +0200 Subject: [PATCH 4/4] Resolve misc. issues with commit 2ca7ed154 --- .../logic/civilization/CivilizationInfo.kt | 20 +++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/core/src/com/unciv/logic/civilization/CivilizationInfo.kt b/core/src/com/unciv/logic/civilization/CivilizationInfo.kt index 39ad4a2d06575..d3a1a392b13fd 100644 --- a/core/src/com/unciv/logic/civilization/CivilizationInfo.kt +++ b/core/src/com/unciv/logic/civilization/CivilizationInfo.kt @@ -456,13 +456,14 @@ class CivilizationInfo { // Similar to getCivUnits(), but the returned list is rotated so that the // 'nextPotentiallyDueAt' unit is first here. - private fun getCivUnitsStartingAtNexDue() = units.subList(nextPotentiallyDueAt, units.size) + units.subList(0, nextPotentiallyDueAt) + private fun getCivUnitsStartingAtNextDue(): Sequence = sequenceOf(units.subList(nextPotentiallyDueAt, units.size) + units.subList(0, nextPotentiallyDueAt)).flatten() fun addUnit(mapUnit: MapUnit, updateCivInfo: Boolean = true) { - // Since we create a new list anyway, also rearrange existing units so that + // Since we create a new list anyway (otherwise some concurrent modification + // exception will happen), also rearrange existing units so that // 'nextPotentiallyDueAt' becomes 0. This way new units are always last to be due // (can be changed as wanted, just have a predictable place). - var newList = ArrayList(getCivUnitsStartingAtNexDue()) + var newList = getCivUnitsStartingAtNextDue().toMutableList() newList.add(mapUnit) units = newList nextPotentiallyDueAt = 0 @@ -477,7 +478,7 @@ class CivilizationInfo { fun removeUnit(mapUnit: MapUnit) { // See comment in addUnit(). - var newList = ArrayList(getCivUnitsStartingAtNexDue()) + var newList = getCivUnitsStartingAtNextDue().toMutableList() newList.remove(mapUnit) units = newList nextPotentiallyDueAt = 0 @@ -488,17 +489,16 @@ class CivilizationInfo { fun getIdleUnits() = getCivUnits().filter { it.isIdle() } - fun getDueUnits(): List = getCivUnitsStartingAtNexDue().filter { it.due && it.isIdle() } + fun getDueUnits(): Sequence = getCivUnitsStartingAtNextDue().filter { it.due && it.isIdle() } fun shouldGoToDueUnit() = UncivGame.Current.settings.checkForDueUnits && getDueUnits().any() - // Callers should consider if cycleThroughDueUnits() is not a better choice. - fun getNextDueUnit() = getDueUnits().firstOrNull() - // Return the next due unit, but preferably not 'unitToSkip': this is returned only if it is the only remaining due unit. fun cycleThroughDueUnits(unitToSkip: MapUnit? = null): MapUnit? { - var returnAt = nextPotentiallyDueAt; - var fallbackAt = -1; + if (units.none()) return null + + var returnAt = nextPotentiallyDueAt + var fallbackAt = -1 do { if (units[returnAt].due && units[returnAt].isIdle()) {