From 3a5289bbeb6bb093199b50884c572c514c705ef6 Mon Sep 17 00:00:00 2001 From: "google-labs-jules[bot]" <161369871+google-labs-jules[bot]@users.noreply.github.com> Date: Fri, 1 May 2026 22:38:45 +0000 Subject: [PATCH 1/2] Refactor stall upgrade logic into StallUpgradeManager - Created StallUpgradeManager to centralize upgrade calculations. - Refactored MainViewModel.applyUpgrade to use the new manager. - Refactored StallDefinition.getUpgradeBenefit to use the new manager. - Unified scaling rules and milestone boosts according to STALL_STATS.md. - Improved rounding accuracy using roundToInt and roundToLong. Co-authored-by: candour <4670475+candour@users.noreply.github.com> --- .../java/com/messark/hawker/MainViewModel.kt | 275 +----------------- .../com/messark/hawker/registry/Registry.kt | 107 +------ .../hawker/utils/StallUpgradeManager.kt | 252 ++++++++++++++++ 3 files changed, 262 insertions(+), 372 deletions(-) create mode 100644 app/src/main/java/com/messark/hawker/utils/StallUpgradeManager.kt diff --git a/app/src/main/java/com/messark/hawker/MainViewModel.kt b/app/src/main/java/com/messark/hawker/MainViewModel.kt index 582329f..53370dc 100644 --- a/app/src/main/java/com/messark/hawker/MainViewModel.kt +++ b/app/src/main/java/com/messark/hawker/MainViewModel.kt @@ -1045,284 +1045,25 @@ class MainViewModel @JvmOverloads constructor( val tile = state.hexes[coord] ?: return@update state val stall = tile.stall ?: return@update state - val baseStall = _availableStalls.value.find { it.stallType == stall.stallType } ?: stall - val baseUpgradeCost = stall.getUpgradeCost() - val finalUpgradeCost = if (isSpecific) { - if (state.freeSpecificUpgrades > 0) 0 else baseUpgradeCost * 2 - } else { - baseUpgradeCost - } + val hasFreeUpgrade = state.freeSpecificUpgrades > 0 + val finalUpgradeCost = StallUpgradeManager.calculateUpgradeCost(stall, isSpecific, hasFreeUpgrade) if (state.gold >= finalUpgradeCost) { - val stallDef = StallRegistry.get(stall.stallType) - val mutableUpgrades = stall.upgrades.toMutableMap() - - var newDamage = stall.damage - var newRange = stall.range - var newFireRate = stall.fireRateMs - var newAoeRadius = stall.aoeRadius - var newEffectDuration = stall.effectDurationMs - var newFreezeDuration = stall.freezeDurationMs - var currentCategoryName = "" - - if (isSpecific && specificStat != null) { - currentCategoryName = specificStat - when (specificStat) { - "Damage" -> { - newDamage = Math.round(newDamage * 1.2f) - val newLevel = mutableUpgrades.getOrDefault("Damage", 0) + 1 - if (newLevel % 10 == 0) newDamage = Math.round(newDamage * 1.25f) - mutableUpgrades["Damage"] = newLevel - } - "Range" -> { - newRange += 0.5f - val newLevel = mutableUpgrades.getOrDefault("Range", 0) + 1 - if (newLevel % 10 == 0) newRange *= 1.25f - mutableUpgrades["Range"] = newLevel - } - "Rate", "Grab Rate" -> { - val rateReduction = when (stall.stallType) { - StallType.TRAY_RETURN_UNCLE -> 100L - StallType.CHICKEN_RICE -> 15L - StallType.DURIAN -> 50L - StallType.SATAY -> 25L - else -> (baseStall.fireRateMs * 0.1f).toLong() - } - val newLevel = mutableUpgrades.getOrDefault(specificStat, 0) + 1 - var potentialRate = stall.fireRateMs - rateReduction - if (newLevel % 10 == 0) potentialRate = Math.round(potentialRate * 0.75) - val floor = when (stall.stallType) { - StallType.TRAY_RETURN_UNCLE -> 10000L - StallType.CHICKEN_RICE -> 200L - StallType.DURIAN -> 1000L - StallType.SATAY -> 750L - else -> 50L - } - newFireRate = Math.max(floor, potentialRate) - mutableUpgrades[specificStat] = newLevel - if (stall.stallType == StallType.TRAY_RETURN_UNCLE) mutableUpgrades["Rate"] = newLevel - } - "Radius" -> { - newAoeRadius += 0.2f - val newLevel = mutableUpgrades.getOrDefault("Radius", 0) + 1 - if (newLevel % 10 == 0) newAoeRadius *= 1.25f - mutableUpgrades["Radius"] = newLevel - } - "Duration", "Cleaning Time" -> { - val newLevel = mutableUpgrades.getOrDefault(specificStat, 0) + 1 - var potentialDuration = if (stall.stallType == StallType.TRAY_RETURN_UNCLE) stall.effectDurationMs + 100L else stall.effectDurationMs + 500L - if (newLevel % 10 == 0) potentialDuration = Math.round(potentialDuration * 1.25) - val cap = if (stall.stallType == StallType.TRAY_RETURN_UNCLE) 4000L else Long.MAX_VALUE - newEffectDuration = Math.min(cap, potentialDuration) - mutableUpgrades[specificStat] = newLevel - mutableUpgrades["Duration"] = newLevel - } - "Effect" -> { - newFreezeDuration += 100L - val newLevel = mutableUpgrades.getOrDefault("Effect", 0) + 1 - if (newLevel % 10 == 0) newFreezeDuration = Math.round(newFreezeDuration * 1.25) - mutableUpgrades["Effect"] = newLevel - } - "Boost" -> { - newDamage += 20 - val newLevel = mutableUpgrades.getOrDefault("Boost", 0) + 1 - if (newLevel % 10 == 0) newDamage = Math.round(newDamage * 1.25f) - mutableUpgrades["Boost"] = newLevel - } - } + val statToUpgrade = if (isSpecific && specificStat != null) { + specificStat } else { - val upgradeCategories = mutableListOf(0, 1, 2).apply { shuffle() } - var applied = false - while (upgradeCategories.isNotEmpty() && !applied) { - val upgradeTypeIndex = upgradeCategories.removeAt(0) - when (upgradeTypeIndex) { - 0 -> { - if (stall.stallType == StallType.BAK_KUT_TEH) { - // Bak Kut Teh doesn't use standard Category 0 - continue - } - if (stall.stallType == StallType.TRAY_RETURN_UNCLE) { - if (kotlin.random.Random.nextBoolean()) { - currentCategoryName = "Grab Rate" - val rateReduction = 100L - val newLevel = mutableUpgrades.getOrDefault("Grab Rate", 0) + 1 - var potentialRate = stall.fireRateMs - rateReduction - if (newLevel % 10 == 0) potentialRate = Math.round(potentialRate * 0.75) - newFireRate = Math.max(10000L, potentialRate) - mutableUpgrades["Grab Rate"] = newLevel - mutableUpgrades["Rate"] = newLevel - } else { - currentCategoryName = "Cleaning Time" - val newLevel = mutableUpgrades.getOrDefault("Cleaning Time", 0) + 1 - var potentialDuration = stall.effectDurationMs + 100L - if (newLevel % 10 == 0) potentialDuration = Math.round(potentialDuration * 1.25) - newEffectDuration = Math.min(4000L, potentialDuration) - mutableUpgrades["Cleaning Time"] = newLevel - mutableUpgrades["Duration"] = newLevel - } - } else { - if (kotlin.random.Random.nextBoolean() && !stall.stallType.isUtility) { - currentCategoryName = "Damage" - newDamage = Math.round(newDamage * 1.15f) - val newLevel = mutableUpgrades.getOrDefault("Damage", 0) + 1 - if (newLevel % 10 == 0) newDamage = Math.round(newDamage * 1.25f) - mutableUpgrades["Damage"] = newLevel - } else { - currentCategoryName = "Range" - newRange += 0.5f - val newLevel = mutableUpgrades.getOrDefault("Range", 0) + 1 - if (newLevel % 10 == 0) newRange *= 1.25f - mutableUpgrades["Range"] = newLevel - } - } - applied = true - } - 1 -> { - if (stall.stallType == StallType.BAK_KUT_TEH) { - // Bak Kut Teh doesn't use standard Category 1 - continue - } - currentCategoryName = if (stall.stallType == StallType.TRAY_RETURN_UNCLE) "Grab Rate" else "Rate" - val rateReduction = when (stall.stallType) { - StallType.TRAY_RETURN_UNCLE -> 100L - StallType.CHICKEN_RICE -> 15L - StallType.DURIAN -> 50L - StallType.SATAY -> 25L - else -> (baseStall.fireRateMs * 0.1f).toLong() - } - val newLevel = mutableUpgrades.getOrDefault(currentCategoryName, 0) + 1 - var potentialRate = stall.fireRateMs - rateReduction - if (newLevel % 10 == 0) potentialRate = Math.round(potentialRate * 0.75) - val floor = when (stall.stallType) { - StallType.TRAY_RETURN_UNCLE -> 10000L - StallType.CHICKEN_RICE -> 200L - StallType.DURIAN -> 1000L - StallType.SATAY -> 750L - else -> 50L - } - newFireRate = Math.max(floor, potentialRate) - mutableUpgrades[currentCategoryName] = newLevel - if (stall.stallType == StallType.TRAY_RETURN_UNCLE) mutableUpgrades["Rate"] = newLevel - applied = true - } - 2 -> { - when (stall.stallType) { - StallType.SATAY, StallType.DURIAN -> { - currentCategoryName = "Radius" - newAoeRadius += 0.2f - val newLevel = mutableUpgrades.getOrDefault("Radius", 0) + 1 - if (newLevel % 10 == 0) newAoeRadius *= 1.25f - mutableUpgrades["Radius"] = newLevel - } - StallType.TEH_TARIK -> { - currentCategoryName = "Duration" - newEffectDuration += 500L - val newLevel = mutableUpgrades.getOrDefault("Duration", 0) + 1 - if (newLevel % 10 == 0) newEffectDuration = Math.round(newEffectDuration * 1.25) - mutableUpgrades["Duration"] = newLevel - } - StallType.ICE_KACHANG -> { - currentCategoryName = "Effect" - newFreezeDuration += 100L - val newLevel = mutableUpgrades.getOrDefault("Effect", 0) + 1 - if (newLevel % 10 == 0) newFreezeDuration = Math.round(newFreezeDuration * 1.25) - mutableUpgrades["Effect"] = newLevel - } - StallType.CHICKEN_RICE -> { - currentCategoryName = "Damage" - newDamage = Math.round(newDamage * 1.15f) - val newLevel = mutableUpgrades.getOrDefault("Damage", 0) + 1 - if (newLevel % 10 == 0) newDamage = Math.round(newDamage * 1.25f) - mutableUpgrades["Damage"] = newLevel - } - StallType.TRAY_RETURN_UNCLE -> { - currentCategoryName = "Cleaning Time" - val newLevel = mutableUpgrades.getOrDefault(currentCategoryName, 0) + 1 - var potentialDuration = stall.effectDurationMs + 100L - if (newLevel % 10 == 0) potentialDuration = Math.round(potentialDuration * 1.25) - newEffectDuration = Math.min(4000L, potentialDuration) - mutableUpgrades["Cleaning Time"] = newLevel - mutableUpgrades["Duration"] = newLevel - } - StallType.BAK_KUT_TEH -> { - currentCategoryName = "Boost" - newDamage += 20 - val newLevel = mutableUpgrades.getOrDefault("Boost", 0) + 1 - if (newLevel % 10 == 0) newDamage = Math.round(newDamage * 1.25f) - mutableUpgrades["Boost"] = newLevel - applied = true // Bak Kut Teh only has Boost category - } - else -> { - if (stall.stallType.isUtility) { - currentCategoryName = "Range" - newRange += 0.5f - val newLevel = mutableUpgrades.getOrDefault("Range", 0) + 1 - if (newLevel % 10 == 0) newRange *= 1.25f - mutableUpgrades["Range"] = newLevel - } - } - } - applied = true - } - } - } + StallUpgradeManager.getAvailableUpgradeStats(stall).random() } - var newPrefix = stall.legendaryPrefix - var newSuffix = stall.legendarySuffix - val newNamingCategories = stall.namingCategories.toMutableList() - - val levelOfUpgradedCat = mutableUpgrades[currentCategoryName] ?: 0 - if (levelOfUpgradedCat == 10 && !stall.namingCategories.contains(currentCategoryName)) { - val legendaryCat = when(currentCategoryName) { - "Grab Rate" -> "Rate" - "Cleaning Time" -> "Duration" - else -> currentCategoryName - } - if (stall.namingCategories.isEmpty()) { - newSuffix = LegendaryNames.getRandomSuffix(legendaryCat) - newNamingCategories.add(currentCategoryName) - } else if (stall.namingCategories.size == 1) { - newPrefix = LegendaryNames.getRandomPrefix(legendaryCat) - newNamingCategories.add(currentCategoryName) - } - } - - val newName = LegendaryNames.constructName(stall.baseName, newPrefix, newSuffix) - - var freeUpgradesLeft = state.freeSpecificUpgrades - var disabledWaves = stall.disabledWaves - if (isSpecific) { - if (freeUpgradesLeft > 0) { - freeUpgradesLeft -= 1 - } else { - disabledWaves += 1 - } - } - - val updatedStall = stall.copy( - name = newName, - damage = newDamage, - range = newRange, - fireRateMs = newFireRate, - aoeRadius = newAoeRadius, - effectDurationMs = newEffectDuration, - freezeDurationMs = newFreezeDuration, - upgradeCount = stall.upgradeCount + 1, - totalInvestment = stall.totalInvestment + finalUpgradeCost, - upgrades = mutableUpgrades, - legendaryPrefix = newPrefix, - legendarySuffix = newSuffix, - namingCategories = newNamingCategories, - disabledWaves = disabledWaves - ) + val updatedStall = StallUpgradeManager.applyUpgrade(stall, statToUpgrade, finalUpgradeCost, isSpecific) val newHexes = state.hexes.toMutableMap() newHexes[coord] = tile.copy(stall = updatedStall) + return@update state.copy( hexes = newHexes, gold = state.gold - finalUpgradeCost, - freeSpecificUpgrades = freeUpgradesLeft + freeSpecificUpgrades = if (isSpecific && hasFreeUpgrade) state.freeSpecificUpgrades - 1 else state.freeSpecificUpgrades ) } state diff --git a/app/src/main/java/com/messark/hawker/registry/Registry.kt b/app/src/main/java/com/messark/hawker/registry/Registry.kt index cb72f0a..f384c6d 100644 --- a/app/src/main/java/com/messark/hawker/registry/Registry.kt +++ b/app/src/main/java/com/messark/hawker/registry/Registry.kt @@ -3,6 +3,7 @@ package com.messark.hawker.registry import androidx.compose.ui.graphics.Color import androidx.compose.ui.unit.IntRect import com.messark.hawker.model.* +import com.messark.hawker.utils.StallUpgradeManager import java.util.* sealed class FireResult { @@ -144,111 +145,7 @@ data class StallDefinition( } fun getUpgradeBenefit(category: String, level: Int, baseStall: StallDefinition): String { - if (level <= 0) return "" - - return when (category) { - "Damage" -> { - var currentDamage = baseStall.damage.toFloat() - for (l in 1..level) { - currentDamage = Math.round(currentDamage * 1.25f).toFloat() - if (l % 10 == 0) { - currentDamage = Math.round(currentDamage * 1.25f).toFloat() - } - } - val percentage = if (baseStall.damage > 0) { - Math.round(((currentDamage - baseStall.damage) / baseStall.damage) * 100) - } else 0 - "+$percentage%" - } - "Grab Rate", "Rate" -> { - var currentRate = baseStall.fireRateMs - val rateReduction = when (baseStall.type) { - StallType.TRAY_RETURN_UNCLE -> 200L - StallType.CHICKEN_RICE -> 50L - StallType.DURIAN -> 100L - StallType.SATAY -> 50L - else -> (baseStall.fireRateMs * 0.1f).toLong() - } - val floor = when (baseStall.type) { - StallType.TRAY_RETURN_UNCLE -> 6000L - StallType.CHICKEN_RICE -> 200L - StallType.DURIAN -> 500L - StallType.SATAY -> 500L - else -> 50L - } - - for (l in 1..level) { - currentRate = Math.max(floor, currentRate - rateReduction) - if (l % 10 == 0) { - currentRate = Math.max(floor, Math.round(currentRate * 0.75)) - } - } - if (baseStall.type == StallType.TRAY_RETURN_UNCLE) { - "-${baseStall.fireRateMs - currentRate}ms" - } else { - val percentage = Math.round(((baseStall.fireRateMs - currentRate).toFloat() / baseStall.fireRateMs) * 100) - "+$percentage%" - } - } - "Range" -> { - var currentRange = baseStall.range - for (l in 1..level) { - currentRange += 0.5f - if (l % 10 == 0) { - currentRange *= 1.25f - } - } - "+${String.format("%.1f", currentRange - baseStall.range)}" - } - "Radius" -> { - var currentRadius = baseStall.aoeRadius - for (l in 1..level) { - currentRadius += 0.2f - if (l % 10 == 0) { - currentRadius *= 1.25f - } - } - "+${String.format("%.1f", currentRadius - baseStall.aoeRadius)}" - } - "Cleaning Time", "Duration" -> { - var currentDuration = baseStall.effectDurationMs - for (l in 1..level) { - if (baseStall.type == StallType.TRAY_RETURN_UNCLE) { - currentDuration = Math.min(4000L, currentDuration + 100) - } else { - currentDuration += 500 - } - if (l % 10 == 0) { - currentDuration = Math.round(currentDuration * 1.25f).toLong() - } - } - if (baseStall.type == StallType.TRAY_RETURN_UNCLE) { - currentDuration = Math.min(4000L, currentDuration) - } - "+${currentDuration - baseStall.effectDurationMs}ms" - } - "Effect" -> { - var currentEffect = baseStall.freezeDurationMs - for (l in 1..level) { - currentEffect += 100 - if (l % 10 == 0) { - currentEffect = Math.round(currentEffect * 1.25f).toLong() - } - } - "+${currentEffect - baseStall.freezeDurationMs}ms" - } - "Boost" -> { - var currentBoost = baseStall.damage.toFloat() - for (l in 1..level) { - currentBoost += 20f - if (l % 10 == 0) { - currentBoost = Math.round(currentBoost * 1.25f).toFloat() - } - } - "+${Math.round(currentBoost - baseStall.damage)}%" - } - else -> "" - } + return StallUpgradeManager.getBenefitString(category, level, baseStall) } } diff --git a/app/src/main/java/com/messark/hawker/utils/StallUpgradeManager.kt b/app/src/main/java/com/messark/hawker/utils/StallUpgradeManager.kt new file mode 100644 index 0000000..08670c8 --- /dev/null +++ b/app/src/main/java/com/messark/hawker/utils/StallUpgradeManager.kt @@ -0,0 +1,252 @@ +package com.messark.hawker.utils + +import com.messark.hawker.model.Stall +import com.messark.hawker.model.StallType +import com.messark.hawker.registry.StallDefinition +import com.messark.hawker.registry.StallRegistry +import kotlin.math.max +import kotlin.math.min +import kotlin.math.roundToInt +import kotlin.math.roundToLong + +object StallUpgradeManager { + + fun calculateUpgradeCost(stall: Stall, isSpecific: Boolean, hasFreeUpgrade: Boolean): Int { + val nextUpgradeIndex = stall.upgradeCount + 1 + val baseUpgradeCost = (stall.cost * (0.2f + nextUpgradeIndex * 0.1f)).roundToInt() + return if (isSpecific) { + if (hasFreeUpgrade) 0 else baseUpgradeCost * 2 + } else { + baseUpgradeCost + } + } + + fun getAvailableUpgradeStats(stall: Stall): List { + return when (stall.stallType) { + StallType.TRAY_RETURN_UNCLE -> listOf("Grab Rate", "Cleaning Time") + StallType.BAK_KUT_TEH -> listOf("Boost") + StallType.TEH_TARIK -> listOf("Range", "Rate", "Duration") + StallType.ICE_KACHANG -> listOf("Range", "Rate", "Effect") + StallType.SATAY, StallType.DURIAN -> listOf("Damage", "Range", "Rate", "Radius") + StallType.CHICKEN_RICE -> listOf("Damage", "Range", "Rate") + else -> if (stall.stallType.isUtility) listOf("Range", "Rate") else listOf("Damage", "Range", "Rate") + } + } + + fun applyUpgrade(stall: Stall, statName: String, upgradeCost: Int, isSpecific: Boolean): Stall { + val mutableUpgrades = stall.upgrades.toMutableMap() + val newLevel = mutableUpgrades.getOrDefault(statName, 0) + 1 + mutableUpgrades[statName] = newLevel + + var newDamage = stall.damage + var newRange = stall.range + var newFireRate = stall.fireRateMs + var newAoeRadius = stall.aoeRadius + var newEffectDuration = stall.effectDurationMs + var newFreezeDuration = stall.freezeDurationMs + var disabledWaves = stall.disabledWaves + + val isMilestone = newLevel % 10 == 0 + + when (statName) { + "Damage" -> { + if (stall.stallType == StallType.CHICKEN_RICE && stall.cost == 100) { + newDamage += 6 + } else { + newDamage = (newDamage * 1.15f).roundToInt() + } + if (isMilestone) newDamage = (newDamage * 1.25f).roundToInt() + } + "Range" -> { + newRange += 0.5f + if (isMilestone) newRange *= 1.25f + } + "Rate", "Grab Rate" -> { + val baseStall = StallRegistry.get(stall.stallType) + val rateReduction = when (stall.stallType) { + StallType.TRAY_RETURN_UNCLE -> 100L + StallType.CHICKEN_RICE -> 15L + StallType.DURIAN -> 50L + StallType.SATAY -> 25L + else -> (baseStall.fireRateMs * 0.1f).toLong() + } + var potentialRate = stall.fireRateMs - rateReduction + if (isMilestone) potentialRate = (potentialRate * 0.75).roundToLong() + + val floor = when (stall.stallType) { + StallType.TRAY_RETURN_UNCLE -> 10000L + StallType.CHICKEN_RICE -> 200L + StallType.DURIAN -> 1000L + StallType.SATAY -> 750L + else -> 50L + } + + if (stall.fireRateMs <= floor && statName == "Rate") { + newFireRate = stall.fireRateMs + } else { + newFireRate = max(floor, potentialRate) + } + + if (stall.stallType == StallType.TRAY_RETURN_UNCLE) mutableUpgrades["Rate"] = newLevel + } + "Radius" -> { + newAoeRadius += 0.2f + if (isMilestone) newAoeRadius *= 1.25f + } + "Duration", "Cleaning Time" -> { + val increment = if (stall.stallType == StallType.TRAY_RETURN_UNCLE) 100L else 500L + var potentialDuration = stall.effectDurationMs + increment + if (isMilestone) potentialDuration = (potentialDuration * 1.25).roundToLong() + + val cap = if (stall.stallType == StallType.TRAY_RETURN_UNCLE) 4000L else Long.MAX_VALUE + newEffectDuration = min(cap, potentialDuration) + mutableUpgrades["Duration"] = newLevel + } + "Effect" -> { + newFreezeDuration += 100L + if (isMilestone) newFreezeDuration = (newFreezeDuration * 1.25).roundToLong() + } + "Boost" -> { + newDamage += 20 + if (isMilestone) newDamage = (newDamage * 1.25f).roundToInt() + } + } + + // Legendary Naming + var newPrefix = stall.legendaryPrefix + var newSuffix = stall.legendarySuffix + val newNamingCategories = stall.namingCategories.toMutableList() + + if (newLevel == 10 && !stall.namingCategories.contains(statName)) { + val legendaryCat = when (statName) { + "Grab Rate" -> "Rate" + "Cleaning Time" -> "Duration" + else -> statName + } + if (stall.namingCategories.isEmpty()) { + newSuffix = LegendaryNames.getRandomSuffix(legendaryCat) + newNamingCategories.add(statName) + } else if (stall.namingCategories.size == 1) { + newPrefix = LegendaryNames.getRandomPrefix(legendaryCat) + newNamingCategories.add(statName) + } + } + val newName = LegendaryNames.constructName(stall.baseName, newPrefix, newSuffix) + + if (isSpecific && upgradeCost > 0) { + disabledWaves += 1 + } + + return stall.copy( + name = newName, + damage = newDamage, + range = newRange, + fireRateMs = newFireRate, + aoeRadius = newAoeRadius, + effectDurationMs = newEffectDuration, + freezeDurationMs = newFreezeDuration, + upgradeCount = stall.upgradeCount + 1, + totalInvestment = stall.totalInvestment + upgradeCost, + upgrades = mutableUpgrades, + legendaryPrefix = newPrefix, + legendarySuffix = newSuffix, + namingCategories = newNamingCategories, + disabledWaves = disabledWaves + ) + } + + fun getBenefitString(category: String, level: Int, baseStall: StallDefinition): String { + if (level <= 0) return "" + + return when (category) { + "Damage" -> { + var currentDamage = baseStall.damage.toFloat() + for (l in 1..level) { + if (baseStall.type == StallType.CHICKEN_RICE && baseStall.cost == 100) { + currentDamage += 6 + } else { + currentDamage *= 1.15f + } + if (l % 10 == 0) currentDamage *= 1.25f + } + val diff = currentDamage.roundToInt() - baseStall.damage + val percentage = if (baseStall.damage > 0) { + (diff.toFloat() / baseStall.damage * 100).roundToInt() + } else 0 + "+$percentage%" + } + "Grab Rate", "Rate" -> { + var currentRate = baseStall.fireRateMs + val rateReduction = when (baseStall.type) { + StallType.TRAY_RETURN_UNCLE -> 100L + StallType.CHICKEN_RICE -> 15L + StallType.DURIAN -> 50L + StallType.SATAY -> 25L + else -> (baseStall.fireRateMs * 0.1f).toLong() + } + val floor = when (baseStall.type) { + StallType.TRAY_RETURN_UNCLE -> 10000L + StallType.CHICKEN_RICE -> 200L + StallType.DURIAN -> 1000L + StallType.SATAY -> 750L + else -> 50L + } + + for (l in 1..level) { + currentRate = max(floor, currentRate - rateReduction) + if (l % 10 == 0) currentRate = max(floor, (currentRate * 0.75).roundToLong()) + } + if (baseStall.type == StallType.TRAY_RETURN_UNCLE) { + "-${baseStall.fireRateMs - currentRate}ms" + } else { + val percentage = ((baseStall.fireRateMs - currentRate).toFloat() / baseStall.fireRateMs * 100).roundToInt() + "+$percentage%" + } + } + "Range" -> { + var currentRange = baseStall.range + for (l in 1..level) { + currentRange += 0.5f + if (l % 10 == 0) currentRange *= 1.25f + } + "+${String.format("%.1f", currentRange - baseStall.range)}" + } + "Radius" -> { + var currentRadius = baseStall.aoeRadius + for (l in 1..level) { + currentRadius += 0.2f + if (l % 10 == 0) currentRadius *= 1.25f + } + "+${String.format("%.1f", currentRadius - baseStall.aoeRadius)}" + } + "Cleaning Time", "Duration" -> { + var currentDuration = baseStall.effectDurationMs + val increment = if (baseStall.type == StallType.TRAY_RETURN_UNCLE) 100L else 500L + val cap = if (baseStall.type == StallType.TRAY_RETURN_UNCLE) 4000L else Long.MAX_VALUE + + for (l in 1..level) { + currentDuration = min(cap, currentDuration + increment) + if (l % 10 == 0) currentDuration = min(cap, (currentDuration * 1.25).roundToLong()) + } + "+${currentDuration - baseStall.effectDurationMs}ms" + } + "Effect" -> { + var currentEffect = baseStall.freezeDurationMs + for (l in 1..level) { + currentEffect += 100 + if (l % 10 == 0) currentEffect = (currentEffect * 1.25).roundToLong() + } + "+${currentEffect - baseStall.freezeDurationMs}ms" + } + "Boost" -> { + var currentBoost = baseStall.damage.toFloat() + for (l in 1..level) { + currentBoost += 20f + if (l % 10 == 0) currentBoost *= 1.25f + } + "+${(currentBoost - baseStall.damage).roundToInt()}%" + } + else -> "" + } + } +} From 4409e4fd79259fc61ca091bb33fbef199e538910 Mon Sep 17 00:00:00 2001 From: "google-labs-jules[bot]" <161369871+google-labs-jules[bot]@users.noreply.github.com> Date: Sat, 2 May 2026 01:05:10 +0000 Subject: [PATCH 2/2] Refactor stall upgrade logic and fix Rate calculation inconsistency - Centralized upgrade logic in StallUpgradeManager.kt. - Unified scaling rules and milestone boosts. - Fixed inconsistent Rate benefit calculation in getBenefitString. - Added missing imports and improved rounding accuracy. Co-authored-by: candour <4670475+candour@users.noreply.github.com> --- app/src/main/java/com/messark/hawker/MainViewModel.kt | 1 + .../java/com/messark/hawker/utils/StallUpgradeManager.kt | 6 ++++-- 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/app/src/main/java/com/messark/hawker/MainViewModel.kt b/app/src/main/java/com/messark/hawker/MainViewModel.kt index 53370dc..1189b41 100644 --- a/app/src/main/java/com/messark/hawker/MainViewModel.kt +++ b/app/src/main/java/com/messark/hawker/MainViewModel.kt @@ -7,6 +7,7 @@ import androidx.lifecycle.viewModelScope import com.messark.hawker.model.* import com.messark.hawker.registry.* import com.messark.hawker.utils.* +import com.messark.hawker.utils.StallUpgradeManager import kotlinx.coroutines.* import kotlinx.coroutines.flow.* import java.time.Instant diff --git a/app/src/main/java/com/messark/hawker/utils/StallUpgradeManager.kt b/app/src/main/java/com/messark/hawker/utils/StallUpgradeManager.kt index 08670c8..9adbcdf 100644 --- a/app/src/main/java/com/messark/hawker/utils/StallUpgradeManager.kt +++ b/app/src/main/java/com/messark/hawker/utils/StallUpgradeManager.kt @@ -4,6 +4,7 @@ import com.messark.hawker.model.Stall import com.messark.hawker.model.StallType import com.messark.hawker.registry.StallDefinition import com.messark.hawker.registry.StallRegistry +import com.messark.hawker.utils.LegendaryNames import kotlin.math.max import kotlin.math.min import kotlin.math.roundToInt @@ -193,8 +194,9 @@ object StallUpgradeManager { } for (l in 1..level) { - currentRate = max(floor, currentRate - rateReduction) - if (l % 10 == 0) currentRate = max(floor, (currentRate * 0.75).roundToLong()) + var potentialRate = currentRate - rateReduction + if (l % 10 == 0) potentialRate = (potentialRate * 0.75).roundToLong() + currentRate = max(floor, potentialRate) } if (baseStall.type == StallType.TRAY_RETURN_UNCLE) { "-${baseStall.fireRateMs - currentRate}ms"