Skip to content

Commit

Permalink
Spy max rank can be modded (#11650)
Browse files Browse the repository at this point in the history
* Changed the spy level cap to be a mod constant

* Spy rank shows with more stars

* Reverted some temporary debug changes

* Changed Promotes all spies to accept an amount

* Change Promotes all spies [amount] time(s) to have parenthesis around the s

* Fixed problem with merging
  • Loading branch information
tuvus authored Jun 4, 2024
1 parent 9e6e157 commit ba0c5cf
Show file tree
Hide file tree
Showing 7 changed files with 53 additions and 35 deletions.
2 changes: 1 addition & 1 deletion android/assets/jsons/Civ V - Gods & Kings/Buildings.json
Original file line number Diff line number Diff line change
Expand Up @@ -988,7 +988,7 @@
"isNationalWonder": true,
"uniques": ["Hidden when espionage is disabled",
"New spies start with [1] level(s)",
"Promotes all spies",
"Promotes all spies [1] time(s)",
"Gain an extra spy", // Order is significant here
"[-15]% enemy spy effectiveness [in this city]",
"Only available <if [Police Station] is constructed in all [non-[Puppeted]] cities>",
Expand Down
1 change: 1 addition & 0 deletions android/assets/jsons/translations/template.properties
Original file line number Diff line number Diff line change
Expand Up @@ -1760,6 +1760,7 @@ Move =
After an unknown civilization entered the [eraName], we have recruited [spyName] as a spy! =
We have recruited [spyName] as a spy! =
Your spy [spyName] has leveled up! =
Your spy [spyName] has leveled up [amount] times! =
Your spy [spyName] cannot steal any more techs from [civName] as we've already researched all the technology they know! =
# Stealing Technology defending civ
Expand Down
2 changes: 2 additions & 0 deletions core/src/com/unciv/models/ModConstants.kt
Original file line number Diff line number Diff line change
Expand Up @@ -87,6 +87,8 @@ class ModConstants {

var workboatAutomationSearchMaxTiles = 20

var maxSpyLevel = 3

fun merge(other: ModConstants) {
for (field in this::class.java.declaredFields) {
if (field.modifiers and Modifier.STATIC != 0) continue
Expand Down
16 changes: 9 additions & 7 deletions core/src/com/unciv/models/Spy.kt
Original file line number Diff line number Diff line change
Expand Up @@ -230,9 +230,9 @@ class Spy private constructor() : IsPartOfGameInfoSerialization {
otherCiv.getDiplomacyManager(civInfo).addModifier(DiplomaticModifiers.SpiedOnUs, -15f)
}
}

fun canDoCoup(): Boolean = getCityOrNull() != null && getCity().civ.isCityState() && isSetUp() && getCity().civ.getAllyCiv() != civInfo.civName

/**
* Initiates a coup if this spies civ is not the ally of the city-state.
* The coup will only happen at the end of the Civ's turn for save scum reasons, so a play may not reload in multiplayer.
Expand Down Expand Up @@ -353,11 +353,13 @@ class Spy private constructor() : IsPartOfGameInfoSerialization {

fun getLocationName() = getCityOrNull()?.name ?: Constants.spyHideout

fun levelUpSpy() {
//TODO: Make the spy level cap dependent on some unique
if (rank >= 3) return
addNotification("Your spy [$name] has leveled up!")
rank++
fun levelUpSpy(amount: Int = 1) {
if (rank >= civInfo.gameInfo.ruleset.modOptions.constants.maxSpyLevel) return
val ranksToLevelUp = amount.coerceAtMost(civInfo.gameInfo.ruleset.modOptions.constants.maxSpyLevel - rank)

if (ranksToLevelUp == 1) addNotification("Your spy [$name] has leveled up!")
else addNotification("Your spy [$name] has leveled up [$ranksToLevelUp] times!")
rank += ranksToLevelUp
}

/** Zero-based modifier expressing shift of probabilities from Spy Rank
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -858,7 +858,7 @@ object UniqueTriggerActivation {
if (!civInfo.gameInfo.isEspionageEnabled()) return null

return {
civInfo.espionageManager.spyList.forEach { it.levelUpSpy() }
civInfo.espionageManager.spyList.forEach { it.levelUpSpy(unique.params[1].toInt()) }
true
}
}
Expand Down
2 changes: 1 addition & 1 deletion core/src/com/unciv/models/ruleset/unique/UniqueType.kt
Original file line number Diff line number Diff line change
Expand Up @@ -787,7 +787,7 @@ enum class UniqueType(
OneTimeRevealCrudeMap("From a randomly chosen tile [positiveAmount] tiles away from the ruins, reveal tiles up to [positiveAmount] tiles away with [positiveAmount]% chance", UniqueTarget.Ruins),
OneTimeGlobalAlert("Triggers the following global alert: [comment]", UniqueTarget.Triggerable), // used in Policy
OneTimeGlobalSpiesWhenEnteringEra("Every major Civilization gains a spy once a civilization enters this era", UniqueTarget.Era),
OneTimeSpiesLevelUp("Promotes all spies", UniqueTarget.Triggerable), // used in Policies, Buildings
OneTimeSpiesLevelUp("Promotes all spies [amount] time(s)", UniqueTarget.Triggerable), // used in Policies, Buildings
OneTimeGainSpy("Gain an extra spy", UniqueTarget.Triggerable), // used in Wonders

OneTimeUnitHeal("Heal this unit by [positiveAmount] HP", UniqueTarget.UnitTriggerable),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -85,7 +85,7 @@ class EspionageOverviewScreen(val civInfo: Civilization, val worldScreen: WorldS
spySelectionTable.add(spy.name.toLabel())
spySelectionTable.add(spy.rank.toLabel())
spySelectionTable.add(spy.getLocationName().toLabel())
val actionString = if (spy.action.showTurns && spy.turnsRemainingForAction != -1)
val actionString = if (spy.action.showTurns)
"[${spy.action.displayString}] ${spy.turnsRemainingForAction}${Fonts.turn}"
else spy.action.displayString
spySelectionTable.add(actionString.toLabel())
Expand All @@ -94,7 +94,7 @@ class EspionageOverviewScreen(val civInfo: Civilization, val worldScreen: WorldS
moveSpyButton.onClick {
onSpyClicked(moveSpyButton, spy)
}
moveSpyButton.onRightClick {
moveSpyButton.onRightClick {
onSpyRightClicked(spy)
}
if (!worldScreen.canChangeState || !spy.isAlive()) {
Expand Down Expand Up @@ -124,26 +124,26 @@ class EspionageOverviewScreen(val civInfo: Civilization, val worldScreen: WorldS
// Then add all cities

val sortedCities = civInfo.gameInfo.getCities()
.filter { civInfo.hasExplored(it.getCenterTile()) }
.sortedWith(
compareBy<City> {
it.civ != civInfo
}.thenBy {
it.civ.isCityState()
}.thenBy(collator) {
it.civ.civName.tr(hideIcons = true)
}.thenBy(collator) {
it.name.tr(hideIcons = true)
}
)
.filter { civInfo.hasExplored(it.getCenterTile()) }
.sortedWith(
compareBy<City> {
it.civ != civInfo
}.thenBy {
it.civ.isCityState()
}.thenBy(collator) {
it.civ.civName.tr(hideIcons = true)
}.thenBy(collator) {
it.name.tr(hideIcons = true)
}
)
for (city in sortedCities) {
addCityToSelectionTable(city)
}
}

private fun addCityToSelectionTable(city: City) {
citySelectionTable.add(ImageGetter.getNationPortrait(city.civ.nation, 30f))
.padLeft(20f)
.padLeft(20f)
val label = city.name.toLabel(hideIcons = true)
label.onClick {
worldScreen.game.popScreen() // If a detour to this screen (i.e. not directly from worldScreen) is made possible, use resetToWorldScreen instead
Expand All @@ -152,7 +152,7 @@ class EspionageOverviewScreen(val civInfo: Civilization, val worldScreen: WorldS
citySelectionTable.add(label).fill()
citySelectionTable.add(getSpyIcons(manager.getSpiesInCity(city)))

val spy = civInfo.espionageManager.getSpyAssignedToCity(city)
val spy = civInfo.espionageManager.getSpyAssignedToCity(city)
if (city.civ.isCityState() && spy != null && spy.canDoCoup()) {
val coupButton = CoupButton(city, spy.action == SpyAction.Coup)
citySelectionTable.add(coupButton)
Expand All @@ -164,20 +164,31 @@ class EspionageOverviewScreen(val civInfo: Civilization, val worldScreen: WorldS
}

private fun getSpyIcon(spy: Spy) = Table().apply {
add (ImageGetter.getImage("OtherIcons/Spy_White").apply {
add(ImageGetter.getImage("OtherIcons/Spy_White").apply {
color = Color.WHITE
}).size(30f)
val color = when(spy.rank) {
fun getColor(rank: Int): Color = when (rank) {
1 -> Color.BROWN
2 -> Color.LIGHT_GRAY
3 -> Color.GOLD
else -> return@apply
else -> Color.GOLD
}

// If we have 10 or more ranks, display them with a bigger star
if (spy.rank >= 10) {
val star = ImageGetter.getImage("OtherIcons/Star")
star.color = getColor(spy.rank / 10)
add(star).size(20f).pad(3f)
}

val color = getColor(spy.rank)
val starTable = Table()
repeat(spy.rank) {
// Create a grid of up to 9 stars
repeat(spy.rank % 10) {
val star = ImageGetter.getImage("OtherIcons/Star")
star.color = color
starTable.add(star).size(8f).pad(1f).row()
starTable.add(star).size(8f).pad(1f)
if (it % 3 == 2)
starTable.row()
}
add(starTable).center().padLeft(-4f)

Expand All @@ -199,12 +210,13 @@ class EspionageOverviewScreen(val civInfo: Civilization, val worldScreen: WorldS
}

private abstract inner class SpyCityActionButton : Button(SmallButtonStyle()) {
open fun setDirection(align: Int) { }
open fun setDirection(align: Int) {}
}

// city == null is interpreted as 'spy hideout'
private inner class MoveToCityButton(city: City?) : SpyCityActionButton() {
val arrow = ImageGetter.getArrowImage(Align.left)

init {
arrow.setSize(24f)
add(arrow).size(24f)
Expand Down Expand Up @@ -251,7 +263,7 @@ class EspionageOverviewScreen(val civInfo: Civilization, val worldScreen: WorldS
worldScreen.game.popScreen()
worldScreen.shouldUpdate = true
}

private fun resetSelection() {
selectedSpy = null
selectedSpyButton?.label?.setText("Move".tr())
Expand All @@ -262,6 +274,7 @@ class EspionageOverviewScreen(val civInfo: Civilization, val worldScreen: WorldS

private inner class CoupButton(city: City, isCurrentAction: Boolean) : SpyCityActionButton() {
val fist = ImageGetter.getStatIcon("Resistance")

init {
fist.setSize(24f)
add(fist).size(24f)
Expand All @@ -271,7 +284,7 @@ class EspionageOverviewScreen(val civInfo: Civilization, val worldScreen: WorldS
onClick {
val spy = selectedSpy!!
if (!isCurrentAction) {
ConfirmPopup(this@EspionageOverviewScreen,
ConfirmPopup(this@EspionageOverviewScreen,
"Do you want to stage a coup in [${city.civ.civName}] with a " +
"[${(selectedSpy!!.getCoupChanceOfSuccess(false) * 100f).toInt()}]% " +
"chance of success?", "Stage Coup") {
Expand Down

0 comments on commit ba0c5cf

Please sign in to comment.