mirror of
https://github.com/yairm210/Unciv.git
synced 2025-02-10 19:09:06 +07:00
City-states personalities (#3259)
* City State Personality * Introduced 4 personalities for city states: Friendly, Neutral, Hostile and Irrational. * Influence recovery and degrade depends on city state personality. * Quests assignement dependant on Personality and Trait * Personality localization strings
This commit is contained in:
parent
8292b848b7
commit
b5a32e64ae
@ -138,6 +138,11 @@ Maritime =
|
||||
Mercantile =
|
||||
Militaristic =
|
||||
Type: =
|
||||
Friendly =
|
||||
Neutral =
|
||||
Hostile =
|
||||
Irrational =
|
||||
Personality: =
|
||||
Influence: =
|
||||
Reach 30 for friendship. =
|
||||
Reach highest influence above 60 for alliance. =
|
||||
|
@ -2,6 +2,7 @@ package com.unciv.logic
|
||||
|
||||
import com.badlogic.gdx.math.Vector2
|
||||
import com.unciv.Constants
|
||||
import com.unciv.logic.civilization.CityStatePersonality
|
||||
import com.unciv.logic.civilization.CivilizationInfo
|
||||
import com.unciv.logic.map.*
|
||||
import com.unciv.logic.map.mapgenerator.MapGenerator
|
||||
@ -135,6 +136,7 @@ object GameStarter {
|
||||
|
||||
for (cityStateName in availableCityStatesNames.take(newGameParameters.numberOfCityStates)) {
|
||||
val civ = CivilizationInfo(cityStateName)
|
||||
civ.cityStatePersonality = CityStatePersonality.values().random()
|
||||
gameInfo.civilizations.add(civ)
|
||||
for(tech in ruleset.technologies.values.filter { it.uniques.contains("Starting tech") })
|
||||
civ.tech.techsResearched.add(tech.name) // can't be .addTechnology because the civInfo isn't assigned yet
|
||||
|
@ -111,7 +111,7 @@ object NextTurnAutomation {
|
||||
private fun useGold(civInfo: CivilizationInfo) {
|
||||
if (civInfo.victoryType() == VictoryType.Cultural) {
|
||||
for (cityState in civInfo.getKnownCivs()
|
||||
.filter { it.isCityState() && it.getCityStateType() == CityStateType.Cultured }) {
|
||||
.filter { it.isCityState() && it.cityStateType == CityStateType.Cultured }) {
|
||||
val diploManager = cityState.getDiplomacyManager(civInfo)
|
||||
if (diploManager.influence < 40) { // we want to gain influence with them
|
||||
tryGainInfluence(civInfo, cityState)
|
||||
@ -122,7 +122,7 @@ object NextTurnAutomation {
|
||||
|
||||
if (civInfo.getHappiness() < 5) {
|
||||
for (cityState in civInfo.getKnownCivs()
|
||||
.filter { it.isCityState() && it.getCityStateType() == CityStateType.Mercantile }) {
|
||||
.filter { it.isCityState() && it.cityStateType == CityStateType.Mercantile }) {
|
||||
val diploManager = cityState.getDiplomacyManager(civInfo)
|
||||
if (diploManager.influence < 40) { // we want to gain influence with them
|
||||
tryGainInfluence(civInfo, cityState)
|
||||
|
@ -126,7 +126,7 @@ class CityStats {
|
||||
val stats = Stats()
|
||||
|
||||
for (otherCiv in cityInfo.civInfo.getKnownCivs()) {
|
||||
if (otherCiv.isCityState() && otherCiv.getCityStateType() == CityStateType.Maritime
|
||||
if (otherCiv.isCityState() && otherCiv.cityStateType == CityStateType.Maritime
|
||||
&& otherCiv.getDiplomacyManager(cityInfo.civInfo).relationshipLevel() >= RelationshipLevel.Friend) {
|
||||
if (cityInfo.isCapital()) stats.food += 3
|
||||
else stats.food += 1
|
||||
|
@ -1,8 +1,15 @@
|
||||
package com.unciv.logic.civilization
|
||||
|
||||
enum class CityStateType{
|
||||
enum class CityStateType {
|
||||
Cultured,
|
||||
Maritime,
|
||||
Mercantile,
|
||||
Militaristic
|
||||
}
|
||||
|
||||
enum class CityStatePersonality {
|
||||
Friendly,
|
||||
Neutral,
|
||||
Hostile,
|
||||
Irrational
|
||||
}
|
@ -85,7 +85,7 @@ class CivInfoStats(val civInfo: CivilizationInfo){
|
||||
|
||||
//City-States culture bonus
|
||||
for (otherCiv in civInfo.getKnownCivs()) {
|
||||
if (otherCiv.isCityState() && otherCiv.getCityStateType() == CityStateType.Cultured
|
||||
if (otherCiv.isCityState() && otherCiv.cityStateType == CityStateType.Cultured
|
||||
&& otherCiv.getDiplomacyManager(civInfo.civName).relationshipLevel() >= RelationshipLevel.Friend) {
|
||||
val cultureBonus = Stats()
|
||||
var culture = 3f * (civInfo.getEraNumber() + 1)
|
||||
@ -153,7 +153,7 @@ class CivInfoStats(val civInfo: CivilizationInfo){
|
||||
|
||||
//From city-states
|
||||
for (otherCiv in civInfo.getKnownCivs()) {
|
||||
if (otherCiv.isCityState() && otherCiv.getCityStateType() == CityStateType.Mercantile
|
||||
if (otherCiv.isCityState() && otherCiv.cityStateType == CityStateType.Mercantile
|
||||
&& otherCiv.getDiplomacyManager(civInfo).relationshipLevel() >= RelationshipLevel.Friend) {
|
||||
if (statMap.containsKey("City-States"))
|
||||
statMap["City-States"] = statMap["City-States"]!! + 3f
|
||||
|
@ -109,6 +109,7 @@ class CivilizationInfo {
|
||||
toReturn.popupAlerts.addAll(popupAlerts)
|
||||
toReturn.tradeRequests.addAll(tradeRequests)
|
||||
toReturn.naturalWonders.addAll(naturalWonders)
|
||||
toReturn.cityStatePersonality = cityStatePersonality
|
||||
return toReturn
|
||||
}
|
||||
|
||||
@ -117,7 +118,6 @@ class CivilizationInfo {
|
||||
if (isPlayerCivilization()) return gameInfo.getDifficulty()
|
||||
return gameInfo.ruleSet.difficulties["Chieftain"]!!
|
||||
}
|
||||
|
||||
fun getDiplomacyManager(civInfo: CivilizationInfo) = getDiplomacyManager(civInfo.civName)
|
||||
fun getDiplomacyManager(civName: String) = diplomacy[civName]!!
|
||||
/** Returns only undefeated civs, aka the ones we care about */
|
||||
@ -134,7 +134,8 @@ class CivilizationInfo {
|
||||
fun isBarbarian() = nation.isBarbarian()
|
||||
fun isSpectator() = nation.isSpectator()
|
||||
fun isCityState(): Boolean = nation.isCityState()
|
||||
fun getCityStateType(): CityStateType = nation.cityStateType!!
|
||||
val cityStateType: CityStateType get() = nation.cityStateType!!
|
||||
var cityStatePersonality: CityStatePersonality = CityStatePersonality.Neutral
|
||||
fun isMajorCiv() = nation.isMajorCiv()
|
||||
fun isAlive(): Boolean = !isDefeated()
|
||||
fun hasEverBeenFriendWith(otherCiv: CivilizationInfo): Boolean = getDiplomacyManager(otherCiv).everBeenFriends()
|
||||
|
@ -14,6 +14,7 @@ import com.unciv.models.ruleset.tile.TileResource
|
||||
import com.unciv.models.ruleset.unit.BaseUnit
|
||||
import com.unciv.models.translations.equalsPlaceholderText
|
||||
import com.unciv.models.translations.fillPlaceholders
|
||||
import com.unciv.ui.utils.randomWeighted
|
||||
import kotlin.math.max
|
||||
import kotlin.random.Random
|
||||
|
||||
@ -150,10 +151,10 @@ class QuestManager {
|
||||
if (numberValidMajorCivs >= quest.minimumCivs)
|
||||
assignableQuests.add(quest)
|
||||
}
|
||||
val weights = assignableQuests.map { getQuestWeight(it.name) }
|
||||
|
||||
//TODO: quest probabilities should change based on City State personality and traits
|
||||
if (assignableQuests.isNotEmpty()) {
|
||||
val quest = assignableQuests.random()
|
||||
val quest = assignableQuests.randomWeighted(weights)
|
||||
val assignees = civInfo.gameInfo.getAliveMajorCivs().filter { !it.isAtWarWith(civInfo) && isQuestValid(quest, it) }
|
||||
|
||||
assignNewQuest(quest, assignees)
|
||||
@ -172,13 +173,14 @@ class QuestManager {
|
||||
return
|
||||
|
||||
val assignableQuests = civInfo.gameInfo.ruleSet.quests.values.filter { it.isIndividual() && isQuestValid(it, challenger) }
|
||||
//TODO: quest probabilities should change based on City State personality and traits
|
||||
if (assignableQuests.isNotEmpty()) {
|
||||
val weights = assignableQuests.map { getQuestWeight(it.name) }
|
||||
|
||||
val quest = assignableQuests.random()
|
||||
if (assignableQuests.isNotEmpty()) {
|
||||
val quest = assignableQuests.randomWeighted(weights)
|
||||
val assignees = arrayListOf(challenger)
|
||||
|
||||
assignNewQuest(quest, assignees)
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -365,6 +367,67 @@ class QuestManager {
|
||||
assignedQuests.removeAll(matchingQuests)
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the weight of the [questName], depends on city state trait and personality
|
||||
*/
|
||||
private fun getQuestWeight(questName: String): Float {
|
||||
var weight = 1f
|
||||
val trait = civInfo.cityStateType
|
||||
val personality = civInfo.cityStatePersonality
|
||||
when (questName) {
|
||||
QuestName.Route.value -> {
|
||||
when (personality) {
|
||||
CityStatePersonality.Friendly -> weight *= 2f
|
||||
CityStatePersonality.Hostile -> weight *= .2f
|
||||
}
|
||||
when (trait) {
|
||||
CityStateType.Maritime -> weight *= 1.2f
|
||||
CityStateType.Mercantile -> weight *= 1.5f
|
||||
}
|
||||
}
|
||||
QuestName.ConnectResource.value -> {
|
||||
when (trait) {
|
||||
CityStateType.Maritime -> weight *= 2f
|
||||
CityStateType.Mercantile -> weight *= 3f
|
||||
}
|
||||
}
|
||||
QuestName.ConstructWonder.value -> {
|
||||
if (trait == CityStateType.Cultured)
|
||||
weight *= 3f
|
||||
}
|
||||
QuestName.GreatPerson.value -> {
|
||||
if (trait == CityStateType.Cultured)
|
||||
weight *= 3f
|
||||
}
|
||||
QuestName.ConquerCityState.value -> {
|
||||
if (trait == CityStateType.Militaristic)
|
||||
weight *= 2f
|
||||
when (personality) {
|
||||
CityStatePersonality.Hostile -> weight *= 2f
|
||||
CityStatePersonality.Neutral -> weight *= .4f
|
||||
}
|
||||
}
|
||||
QuestName.FindPlayer.value -> {
|
||||
when (trait) {
|
||||
CityStateType.Maritime -> weight *= 3f
|
||||
CityStateType.Mercantile -> weight *= 2f
|
||||
}
|
||||
}
|
||||
QuestName.FindNaturalWonder.value -> {
|
||||
if (trait == CityStateType.Militaristic)
|
||||
weight *= .5f
|
||||
if (personality == CityStatePersonality.Hostile)
|
||||
weight *= .3f
|
||||
}
|
||||
QuestName.ClearBarbarianCamp.value -> {
|
||||
weight *= 3f
|
||||
if (trait == CityStateType.Militaristic)
|
||||
weight *= 3f
|
||||
}
|
||||
}
|
||||
return weight
|
||||
}
|
||||
|
||||
//region get-quest-target
|
||||
/**
|
||||
* Returns a random [TileInfo] containing a Barbarian encampment within 8 tiles of [civInfo]
|
||||
|
@ -166,20 +166,55 @@ class DiplomacyManager() {
|
||||
return otherCivDiplomacy().getTurnsToRelationshipChange()
|
||||
|
||||
if (civInfo.isCityState() && !otherCiv().isCityState()) {
|
||||
val dropPerTurn = getCityStateInfluenceDegradeRate()
|
||||
when {
|
||||
relationshipLevel() >= RelationshipLevel.Ally -> return ceil((influence - 60f) / dropPerTurn).toInt() + 1
|
||||
relationshipLevel() >= RelationshipLevel.Friend -> return ceil((influence - 30f) / dropPerTurn).toInt() + 1
|
||||
else -> return 0
|
||||
val dropPerTurn = getCityStateInfluenceDegrade()
|
||||
return when {
|
||||
relationshipLevel() >= RelationshipLevel.Ally -> ceil((influence - 60f) / dropPerTurn).toInt() + 1
|
||||
relationshipLevel() >= RelationshipLevel.Friend -> ceil((influence - 30f) / dropPerTurn).toInt() + 1
|
||||
else -> 0
|
||||
}
|
||||
}
|
||||
return 0
|
||||
}
|
||||
|
||||
fun getCityStateInfluenceDegradeRate(): Float {
|
||||
if(otherCiv().hasUnique("City-State Influence degrades at half rate"))
|
||||
return .5f
|
||||
else return 1f
|
||||
private fun getCityStateInfluenceDegrade(): Float {
|
||||
if (influence < restingPoint)
|
||||
return 0f
|
||||
|
||||
var decrement = when (civInfo.cityStatePersonality) {
|
||||
CityStatePersonality.Hostile -> 1.5f
|
||||
else -> 1f
|
||||
}
|
||||
|
||||
var modifier = when (civInfo.cityStatePersonality) {
|
||||
CityStatePersonality.Hostile -> 2f
|
||||
CityStatePersonality.Irrational -> 1.5f
|
||||
CityStatePersonality.Friendly -> .5f
|
||||
else -> 1f
|
||||
}
|
||||
|
||||
if (otherCiv().hasUnique("City-State Influence degrades at half rate"))
|
||||
modifier *= .5f
|
||||
|
||||
return max(0f, decrement) * max(0f, modifier)
|
||||
}
|
||||
|
||||
private fun getCityStateInfluenceRecovery(): Float {
|
||||
if (influence > restingPoint)
|
||||
return 0f
|
||||
|
||||
var increment = 1f
|
||||
|
||||
var modifier = when (civInfo.cityStatePersonality) {
|
||||
CityStatePersonality.Friendly -> 2f
|
||||
CityStatePersonality.Irrational -> 1.5f
|
||||
CityStatePersonality.Hostile -> .5f
|
||||
else -> 1f
|
||||
}
|
||||
|
||||
if (otherCiv().hasUnique("City-State Influence recovers at twice the normal rate"))
|
||||
modifier *= 2f
|
||||
|
||||
return max(0f, increment) * max(0f, modifier)
|
||||
}
|
||||
|
||||
fun canDeclareWar() = turnsToPeaceTreaty()==0 && diplomaticStatus != DiplomaticStatus.War
|
||||
@ -300,14 +335,13 @@ class DiplomacyManager() {
|
||||
private fun nextTurnCityStateInfluence() {
|
||||
val initialRelationshipLevel = relationshipLevel()
|
||||
|
||||
val increment = if (otherCiv().hasUnique("City-State Influence recovers at twice the normal rate")) 2f else 1f
|
||||
val decrement = getCityStateInfluenceDegradeRate()
|
||||
|
||||
if (influence > restingPoint)
|
||||
if (influence > restingPoint) {
|
||||
val decrement = getCityStateInfluenceDegrade()
|
||||
influence = max(restingPoint, influence - decrement)
|
||||
else if (influence < restingPoint)
|
||||
} else if (influence < restingPoint) {
|
||||
val increment = getCityStateInfluenceRecovery()
|
||||
influence = min(restingPoint, influence + increment)
|
||||
else influence = restingPoint
|
||||
}
|
||||
|
||||
if(!civInfo.isDefeated()) { // don't display city state relationship notifications when the city state is currently defeated
|
||||
val civCapitalLocation = if (civInfo.cities.isNotEmpty()) civInfo.getCapital().location else null
|
||||
@ -400,7 +434,7 @@ class DiplomacyManager() {
|
||||
if (!hasFlag(DiplomacyFlags.DeclarationOfFriendship))
|
||||
revertToZero(DiplomaticModifiers.DeclarationOfFriendship, 1 / 2f) //decreases slowly and will revert to full if it is declared later
|
||||
|
||||
if (otherCiv().isCityState() && otherCiv().getCityStateType() == CityStateType.Militaristic) {
|
||||
if (otherCiv().isCityState() && otherCiv().cityStateType == CityStateType.Militaristic) {
|
||||
if (relationshipLevel() < RelationshipLevel.Friend) {
|
||||
if (hasFlag(DiplomacyFlags.ProvideMilitaryUnit)) removeFlag(DiplomacyFlags.ProvideMilitaryUnit)
|
||||
} else {
|
||||
|
@ -94,14 +94,14 @@ class DiplomacyScreen(val viewingCiv:CivilizationInfo):CameraStageBaseScreen() {
|
||||
}
|
||||
|
||||
|
||||
|
||||
private fun getCityStateDiplomacyTable(otherCiv: CivilizationInfo): Table {
|
||||
val otherCivDiplomacyManager = otherCiv.getDiplomacyManager(viewingCiv)
|
||||
|
||||
val diplomacyTable = Table()
|
||||
diplomacyTable.defaults().pad(10f)
|
||||
diplomacyTable.add(otherCiv.getLeaderDisplayName().toLabel(fontSize = 24)).row()
|
||||
diplomacyTable.add(("Type: ".tr() + otherCiv.getCityStateType().toString().tr()).toLabel()).row()
|
||||
diplomacyTable.add("{Type: } {${otherCiv.cityStateType}}".toLabel()).row()
|
||||
diplomacyTable.add("{Personality: } {${otherCiv.cityStatePersonality}}".toLabel()).row()
|
||||
otherCiv.updateAllyCivForCityState()
|
||||
val ally = otherCiv.getAllyCiv()
|
||||
if (ally != "") {
|
||||
@ -120,7 +120,7 @@ class DiplomacyScreen(val viewingCiv:CivilizationInfo):CameraStageBaseScreen() {
|
||||
diplomacyTable.add(nextLevelString.toLabel()).row()
|
||||
}
|
||||
|
||||
val friendBonusText = when (otherCiv.getCityStateType()) {
|
||||
val friendBonusText = when (otherCiv.cityStateType) {
|
||||
CityStateType.Cultured -> ("Provides [" + (3 * (viewingCiv.getEraNumber() + 1)).toString() + "] culture at 30 Influence").tr()
|
||||
CityStateType.Maritime -> "Provides 3 food in capital and 1 food in other cities at 30 Influence".tr()
|
||||
CityStateType.Mercantile -> "Provides 3 happiness at 30 Influence".tr()
|
||||
|
@ -21,6 +21,7 @@ import com.unciv.models.UncivSound
|
||||
import com.unciv.models.translations.tr
|
||||
import com.unciv.ui.tutorials.TutorialController
|
||||
import kotlin.concurrent.thread
|
||||
import kotlin.random.Random
|
||||
|
||||
open class CameraStageBaseScreen : Screen {
|
||||
|
||||
@ -267,4 +268,21 @@ fun Label.setFontSize(size:Int): Label {
|
||||
style.font = Fonts.font
|
||||
style = style // because we need it to call the SetStyle function. Yuk, I know.
|
||||
return this.apply { setFontScale(size/ORIGINAL_FONT_SIZE) } // for chaining
|
||||
}
|
||||
|
||||
|
||||
fun <T> List<T>.randomWeighted(weights: List<Float>, random: Random = Random): T {
|
||||
if (this.isEmpty()) throw NoSuchElementException("Empty list.")
|
||||
if (this.size != weights.size) throw UnsupportedOperationException("Weights size does not match this list size.")
|
||||
|
||||
val totalWeight = weights.sum()
|
||||
val randDouble = random.nextDouble()
|
||||
var sum = 0f
|
||||
|
||||
for (i in weights.indices) {
|
||||
sum += weights[i] / totalWeight
|
||||
if (randDouble <= sum)
|
||||
return this[i]
|
||||
}
|
||||
return this.last()
|
||||
}
|
Loading…
Reference in New Issue
Block a user