mirror of
https://github.com/yairm210/Unciv.git
synced 2025-07-15 02:09:21 +07:00
City state election rigging (#11577)
* Added city-state Elections * Added Elections notifications * Removed temporary rigging elections in Spy.kt * Modified votes from influence a little * Fixed rigging election turns * Fixed elections * Randomised city-state election days * Refactored geCapital * Refactored election random to use randomWeighted * Fixed getSkillModifier being private * Updated translations and fixed a notification for election rigging
This commit is contained in:
@ -1773,9 +1773,10 @@ Your spy [spyName] stole the Technology [techName] from [cityName]! =
|
||||
Your spy [spyName] was killed trying to steal Technology in [cityName]! =
|
||||
|
||||
# Rigging elections
|
||||
A spy from [civName] tried to rig elections and was found and killed in [cityName] by [spyName]! =
|
||||
Your spy [spyName] was killed trying to rig the election in [cityName]! =
|
||||
Your spy successfully rigged the election in [cityName]! =
|
||||
Your spy lost the election in [cityStateName] to [civName]! =
|
||||
The election in [cityStateName] were rigged by [civName]! =
|
||||
Your spy lost the election in [cityName]! =
|
||||
|
||||
# Spy fleeing city
|
||||
After the city of [cityName] was destroyed, your spy [spyName] has fled back to our hideout. =
|
||||
|
@ -29,7 +29,7 @@ class CityEspionageManager : IsPartOfGameInfoSerialization {
|
||||
return civInfo.espionageManager.spyList.any { it.getCityOrNull() == city }
|
||||
}
|
||||
|
||||
private fun getAllStationedSpies(): List<Spy> {
|
||||
fun getAllStationedSpies(): List<Spy> {
|
||||
return city.civ.gameInfo.civilizations.flatMap { it.espionageManager.getSpiesInCity(city) }
|
||||
}
|
||||
|
||||
|
@ -303,6 +303,7 @@ class Civilization : IsPartOfGameInfoSerialization {
|
||||
toReturn.hasMovedAutomatedUnits = hasMovedAutomatedUnits
|
||||
toReturn.statsHistory = statsHistory.clone()
|
||||
toReturn.resourceStockpiles = resourceStockpiles.clone()
|
||||
toReturn.cityStateTurnsUntilElection = cityStateTurnsUntilElection
|
||||
return toReturn
|
||||
}
|
||||
|
||||
@ -363,7 +364,7 @@ class Civilization : IsPartOfGameInfoSerialization {
|
||||
var cityStatePersonality: CityStatePersonality = CityStatePersonality.Neutral
|
||||
var cityStateResource: String? = null
|
||||
var cityStateUniqueUnit: String? = null // Unique unit for militaristic city state. Might still be null if there are no appropriate units
|
||||
|
||||
var cityStateTurnsUntilElection: Int = 0
|
||||
|
||||
fun hasMetCivTerritory(otherCiv: Civilization): Boolean =
|
||||
otherCiv.getCivTerritory().any { gameInfo.tileMap[it].isExplored(this) }
|
||||
|
@ -14,6 +14,8 @@ import com.unciv.logic.civilization.NotificationIcon
|
||||
import com.unciv.logic.civilization.PlayerType
|
||||
import com.unciv.logic.civilization.PopupAlert
|
||||
import com.unciv.logic.civilization.Proximity
|
||||
import com.unciv.models.Spy
|
||||
import com.unciv.models.SpyAction
|
||||
import com.unciv.models.ruleset.Ruleset
|
||||
import com.unciv.models.ruleset.nation.CityStateType
|
||||
import com.unciv.models.ruleset.tile.ResourceSupplyList
|
||||
@ -22,6 +24,7 @@ import com.unciv.models.ruleset.unique.Unique
|
||||
import com.unciv.models.ruleset.unique.UniqueType
|
||||
import com.unciv.models.ruleset.unit.BaseUnit
|
||||
import com.unciv.models.stats.Stat
|
||||
import com.unciv.ui.components.extensions.randomWeighted
|
||||
import com.unciv.ui.screens.victoryscreen.RankingType
|
||||
import kotlin.math.min
|
||||
import kotlin.math.pow
|
||||
@ -60,11 +63,63 @@ class CityStateFunctions(val civInfo: Civilization) {
|
||||
civInfo.cityStateUniqueUnit = possibleUnits.random().name
|
||||
}
|
||||
|
||||
// Set turns to elections to a random number so not every city-state has the same election date
|
||||
civInfo.cityStateTurnsUntilElection = Random.nextInt(15)
|
||||
|
||||
// TODO: Return false if attempting to put a religious city-state in a game without religion
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
fun nextTurnElections() {
|
||||
civInfo.cityStateTurnsUntilElection--
|
||||
val capital = civInfo.getCapital()
|
||||
if (civInfo.cityStateTurnsUntilElection <= 0) {
|
||||
if (capital == null) return
|
||||
civInfo.cityStateTurnsUntilElection = 15
|
||||
val spies= capital.espionage.getAllStationedSpies().filter { it.action == SpyAction.RiggingElections }
|
||||
if (spies.isEmpty()) return
|
||||
|
||||
fun getVotesFromSpy(spy: Spy?): Float {
|
||||
if (spy == null) return 20f
|
||||
var votes = (civInfo.getDiplomacyManager(spy.civInfo).influence / 2)
|
||||
votes += (spy.getSkillModifier() * spy.getEfficiencyModifier()).toFloat() // ranges from 30 to 90
|
||||
return votes
|
||||
}
|
||||
|
||||
val parties: MutableList<Spy?> = spies.toMutableList()
|
||||
parties.add(null) // Null spy is a neuteral party in the election
|
||||
val randomSeed = capital.location.x * capital.location.y + 123f * civInfo.gameInfo.turns
|
||||
val winner: Civilization? = parties.randomWeighted(Random(randomSeed.toInt())) { getVotesFromSpy(it) }?.civInfo
|
||||
|
||||
// There may be no winner, in that case all spies will loose 5 influence
|
||||
if (winner != null) {
|
||||
val allyCiv = civInfo.getAllyCiv()?.let { civInfo.gameInfo.getCivilization(it) }
|
||||
|
||||
// Winning civ gets influence and all others loose influence
|
||||
for (civ in civInfo.getKnownCivs().toList()) {
|
||||
val influence = if (civ == winner) 20f else -5f
|
||||
civInfo.getDiplomacyManager(civ).addInfluence(influence)
|
||||
if (civ == winner) {
|
||||
civ.addNotification("Your spy successfully rigged the election in [${civInfo.civName}]!", capital.location, NotificationCategory.Espionage, NotificationIcon.Spy)
|
||||
} else if (spies.any { it.civInfo == civ}) {
|
||||
civ.addNotification("Your spy lost the election in [${civInfo.civName}] to [${winner.civName}]!", capital.location, NotificationCategory.Espionage, NotificationIcon.Spy)
|
||||
} else if (civ == allyCiv) {
|
||||
// If the previous ally has no spy in the city then we should notify them
|
||||
allyCiv.addNotification("The election in [${civInfo.civName}] were rigged by [${winner.civName}]!", capital.location, NotificationCategory.Espionage, NotificationIcon.Spy)
|
||||
}
|
||||
}
|
||||
|
||||
} else {
|
||||
// No spy won the election, the civs that tried to rig the election loose influence
|
||||
for (spy in spies) {
|
||||
civInfo.getDiplomacyManager(spy.civInfo).addInfluence(-5f)
|
||||
spy.civInfo.addNotification("Your spy lost the election in [$capital]!", capital.location, NotificationCategory.Espionage, NotificationIcon.Spy)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun turnsForGreatPersonFromCityState(): Int = ((37 + Random.Default.nextInt(7)) * civInfo.gameInfo.speed.modifier).toInt()
|
||||
|
||||
/** Gain a random great person from the city state */
|
||||
|
@ -259,8 +259,10 @@ class TurnManager(val civInfo: Civilization) {
|
||||
civInfo.policies.endTurn(nextTurnStats.culture.toInt())
|
||||
civInfo.totalCultureForContests += nextTurnStats.culture.toInt()
|
||||
|
||||
if (civInfo.isCityState())
|
||||
if (civInfo.isCityState()) {
|
||||
civInfo.questManager.endTurn()
|
||||
civInfo.cityStateFunctions.nextTurnElections()
|
||||
}
|
||||
|
||||
// disband units until there are none left OR the gold values are normal
|
||||
if (!civInfo.isBarbarian() && civInfo.gold <= -200 && nextTurnStats.gold.toInt() < 0) {
|
||||
|
@ -22,7 +22,7 @@ enum class SpyAction(val displayString: String, val hasTurns: Boolean, internal
|
||||
EstablishNetwork("Establishing Network", true, false, true),
|
||||
Surveillance("Observing City", false, true),
|
||||
StealingTech("Stealing Tech", false, true, true),
|
||||
RiggingElections("Rigging Elections", true, true) {
|
||||
RiggingElections("Rigging Elections", false, true) {
|
||||
override fun isDoingWork(spy: Spy) = !spy.civInfo.isAtWarWith(spy.getCity().civ)
|
||||
},
|
||||
CounterIntelligence("Conducting Counter-intelligence", false, true) {
|
||||
@ -100,7 +100,7 @@ class Spy private constructor() : IsPartOfGameInfoSerialization {
|
||||
SpyAction.EstablishNetwork -> {
|
||||
val city = getCity() // This should never throw an exception, as going to the hideout sets your action to None.
|
||||
if (city.civ.isCityState())
|
||||
setAction(SpyAction.RiggingElections, 10)
|
||||
setAction(SpyAction.RiggingElections, getCity().civ.cityStateTurnsUntilElection - 1)
|
||||
else if (city.civ == civInfo)
|
||||
setAction(SpyAction.CounterIntelligence, 10)
|
||||
else
|
||||
@ -131,7 +131,9 @@ class Spy private constructor() : IsPartOfGameInfoSerialization {
|
||||
}
|
||||
}
|
||||
SpyAction.RiggingElections -> {
|
||||
rigElection()
|
||||
// No action done here
|
||||
// Handled in CityStateFunctions.nextTurnElections()
|
||||
turnsRemainingForAction = getCity().civ.cityStateTurnsUntilElection - 1
|
||||
}
|
||||
SpyAction.Dead -> {
|
||||
val oldSpyName = name
|
||||
@ -202,36 +204,6 @@ class Spy private constructor() : IsPartOfGameInfoSerialization {
|
||||
}
|
||||
}
|
||||
|
||||
private fun rigElection() {
|
||||
val city = getCity()
|
||||
val cityStateCiv = city.civ
|
||||
// TODO: Simple implementation, please implement this in the future. This is a guess.
|
||||
turnsRemainingForAction = 10
|
||||
|
||||
if (cityStateCiv.getAllyCiv() != null && cityStateCiv.getAllyCiv() != civInfo.civName) {
|
||||
val allyCiv = civInfo.gameInfo.getCivilization(cityStateCiv.getAllyCiv()!!)
|
||||
val defendingSpy = allyCiv.espionageManager.getSpyAssignedToCity(city)
|
||||
if (defendingSpy != null) {
|
||||
var spyResult = Random(randomSeed()).nextInt(120)
|
||||
spyResult -= getSkillModifier()
|
||||
spyResult += defendingSpy.getSkillModifier()
|
||||
if (spyResult > 100) {
|
||||
// The Spy was killed (use the notification without EspionageAction)
|
||||
allyCiv.addNotification("A spy from [${civInfo.civName}] tried to rig elections and was found and killed in [${city}] by [${defendingSpy.name}]!",
|
||||
city.location, NotificationCategory.Espionage, NotificationIcon.Spy)
|
||||
addNotification("Your spy [$name] was killed trying to rig the election in [$city]!")
|
||||
killSpy()
|
||||
defendingSpy.levelUpSpy()
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
// Starts at 10 influence and increases by 3 for each extra rank.
|
||||
cityStateCiv.getDiplomacyManager(civInfo).addInfluence(7f + rank * 3)
|
||||
civInfo.addNotification("Your spy successfully rigged the election in [$city]!", city.location,
|
||||
NotificationCategory.Espionage, NotificationIcon.Spy)
|
||||
}
|
||||
|
||||
fun moveTo(city: City?) {
|
||||
if (city == null) { // Moving to spy hideout
|
||||
location = null
|
||||
@ -285,7 +257,7 @@ class Spy private constructor() : IsPartOfGameInfoSerialization {
|
||||
* Or - chance range of best result is 0% (rank 1 vs rank 3 defender) to 30% (rank 3 vs no defender), range of worst is 53% to 3%, respectively.
|
||||
*/
|
||||
// Todo Moddable as some global and/or in-game-gainable Uniques?
|
||||
private fun getSkillModifier() = rank * 30
|
||||
fun getSkillModifier() = rank * 30
|
||||
|
||||
/**
|
||||
* Gets a friendly and enemy efficiency uniques for the spy at the location
|
||||
|
Reference in New Issue
Block a user