City states give gold when met; updates to city state gold gifts (#4435)

* City States now give gold when met

* City States can now receive different amounts of gold, and the amount of influence gained from gifts follows the base game

* Implemented requested changes

* Fixed tests
This commit is contained in:
Xander Lenstra 2021-07-10 20:51:11 +02:00 committed by GitHub
parent 5925489c0b
commit 253e62de72
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
8 changed files with 108 additions and 35 deletions

View File

@ -137,6 +137,7 @@ Provides [amountOfCulture] culture at 30 Influence =
Provides 3 food in capital and 1 food in other cities at 30 Influence =
Provides 3 happiness at 30 Influence =
Provides land units every 20 turns at 30 Influence =
Give a Gift =
Gift [giftAmount] gold (+[influenceAmount] influence) =
Relationship changes in another [turnsToRelationshipChange] turns =
Protected by =
@ -407,6 +408,8 @@ You have entered a Golden Age! =
[n] sources of [resourceName] revealed, e.g. near [cityName] =
A [greatPerson] has been born in [cityName]! =
We have encountered [civName]! =
[cityStateName] has given us [stats] as a token of goodwill for meeting us =
[cityStateName] has given us [stats] as we are the first major civ to meet them =
Cannot provide unit upkeep for [unitName] - unit has been disbanded! =
[cityName] has grown! =
[cityName] is starving! =

View File

@ -84,7 +84,7 @@ class CityInfoConquestFunctions(val city: CityInfo){
// How can you conquer a city but not know the civ you conquered it from?!
// I don't know either, but some of our players have managed this, and crashed their game!
if (!conqueringCiv.knows(oldCiv))
conqueringCiv.meetCivilization(oldCiv)
conqueringCiv.makeCivilizationsMeet(oldCiv)
oldCiv.getDiplomacyManager(conqueringCiv)
.addModifier(DiplomaticModifiers.CapturedOurCities, -aggroGenerated)
@ -145,7 +145,7 @@ class CityInfoConquestFunctions(val city: CityInfo){
// In order to get "plus points" in Diplomacy, you have to establish diplomatic relations if you haven't yet
if (!conqueringCiv.knows(foundingCiv))
conqueringCiv.meetCivilization(foundingCiv)
conqueringCiv.makeCivilizationsMeet(foundingCiv)
if (foundingCiv.isMajorCiv()) {
foundingCiv.getDiplomacyManager(conqueringCiv)

View File

@ -38,7 +38,7 @@ class CivInfoTransientUpdater(val civInfo: CivilizationInfo) {
for (entry in viewedCivs) {
val metCiv = entry.key
if (metCiv == civInfo || metCiv.isBarbarian() || civInfo.diplomacy.containsKey(metCiv.civName)) continue
civInfo.meetCivilization(metCiv)
civInfo.makeCivilizationsMeet(metCiv)
civInfo.addNotification("We have encountered [" + metCiv.civName + "]!", entry.value.position, metCiv.civName, NotificationIcon.Diplomacy)
metCiv.addNotification("We have encountered [" + civInfo.civName + "]!", entry.value.position, civInfo.civName, NotificationIcon.Diplomacy)
}

View File

@ -29,6 +29,7 @@ import kotlin.collections.ArrayList
import kotlin.collections.HashMap
import kotlin.math.roundToInt
import kotlin.math.min
import kotlin.math.pow
class CivilizationInfo {
@ -328,18 +329,40 @@ class CivilizationInfo {
return baseUnit
}
fun meetCivilization(otherCiv: CivilizationInfo) {
fun makeCivilizationsMeet(otherCiv: CivilizationInfo) {
meetCiv(otherCiv)
otherCiv.meetCiv(this)
}
private fun meetCiv(otherCiv: CivilizationInfo) {
diplomacy[otherCiv.civName] = DiplomacyManager(this, otherCiv.civName)
.apply { diplomaticStatus = DiplomaticStatus.Peace }
.apply { diplomaticStatus = DiplomaticStatus.Peace }
otherCiv.popupAlerts.add(PopupAlert(AlertType.FirstContact, civName))
otherCiv.diplomacy[civName] = DiplomacyManager(otherCiv, civName)
.apply { diplomaticStatus = DiplomaticStatus.Peace }
popupAlerts.add(PopupAlert(AlertType.FirstContact, otherCiv.civName))
if (isCurrentPlayer() || otherCiv.isCurrentPlayer())
if (isCurrentPlayer())
UncivGame.Current.settings.addCompletedTutorialTask("Meet another civilization")
if (!(isCityState() && otherCiv.isMajorCiv())) return
val cityStateLocation = if (cities.isEmpty()) null else getCapital().location
val giftAmount = Stats().add(Stat.Gold, 15f)
// Later, religious city-states will also gift gold, making this the better implementation
// For now, it might be overkill though.
var meetString = "[${civName}] has given us [${giftAmount}] as a token of goodwill for meeting us"
if (diplomacy.filter { it.value.otherCiv().isMajorCiv() }.count() == 1) {
giftAmount.timesInPlace(2f)
meetString = "[${civName}] has given us [${giftAmount}] as we are the first major civ to meet them"
}
if (cityStateLocation != null)
otherCiv.addNotification(meetString, cityStateLocation, NotificationIcon.Gold)
else
otherCiv.addNotification(meetString, NotificationIcon.Gold)
for (stat in giftAmount.toHashMap().filter { it.value != 0f })
otherCiv.addStat(stat.key, stat.value.toInt())
}
fun discoverNaturalWonder(naturalWonderName: String) {
@ -699,11 +722,32 @@ class CivilizationInfo {
diplomacyManager.otherCiv().tradeRequests.remove(tradeRequest) // it would be really weird to get a trade request from a dead civ
}
}
fun getResearchAgreementCost(): Int {
// https://forums.civfanatics.com/resources/research-agreements-bnw.25568/
val era = if (getEra() in gameInfo.ruleSet.eras) gameInfo.ruleSet.eras[getEra()]!! else Era()
return (era.researchAgreementCost * gameInfo.gameParameters.gameSpeed.modifier).toInt()
}
//////////////////////// Functions specific to City State civilizations ////////////////////////
fun influenceGainedByGift(cityState: CivilizationInfo, giftAmount: Int): Int {
var influenceGained = giftAmount / 10f
fun influenceGainedByGift(giftAmount: Int): Int {
// https://github.com/Gedemon/Civ5-DLL/blob/aa29e80751f541ae04858b6d2a2c7dcca454201e/CvGameCoreDLL_Expansion1/CvMinorCivAI.cpp
// line 8681 and below
var influenceGained = giftAmount.toFloat().pow(1.01f) / 9.8f
val gameProgressApproximate = min(gameInfo.turns / (400f * gameInfo.gameParameters.gameSpeed.modifier), 1f)
influenceGained *= 1 - (2/3) * gameProgressApproximate
influenceGained *= when (gameInfo.gameParameters.gameSpeed) {
GameSpeed.Quick -> 1.25f
GameSpeed.Standard -> 1f
GameSpeed.Epic -> 0.75f
GameSpeed.Marathon -> 0.67f
}
for (unique in getMatchingUniques("Gifts of Gold to City-States generate []% more Influence"))
influenceGained *= 1f + unique.params[0].toFloat() / 100f
influenceGained -= influenceGained % 5
if (influenceGained < 5f) influenceGained = 5f
return influenceGained.toInt()
}
@ -711,17 +755,11 @@ class CivilizationInfo {
if (!cityState.isCityState()) throw Exception("You can only gain influence with City-States!")
addGold(-giftAmount)
cityState.addGold(giftAmount)
cityState.getDiplomacyManager(this).influence += influenceGainedByGift(cityState, giftAmount)
cityState.getDiplomacyManager(this).influence += influenceGainedByGift(giftAmount)
cityState.updateAllyCivForCityState()
updateStatsForNextTurn()
}
fun getResearchAgreementCost(): Int {
// https://forums.civfanatics.com/resources/research-agreements-bnw.25568/
val era = if (getEra() in gameInfo.ruleSet.eras) gameInfo.ruleSet.eras[getEra()]!! else Era()
return (era.researchAgreementCost * gameInfo.gameParameters.gameSpeed.modifier).toInt()
}
fun gainMilitaryUnitFromCityState(otherCiv: CivilizationInfo) {
val cities = NextTurnAutomation.getClosestCities(this, otherCiv)
val city = cities.city1

View File

@ -107,7 +107,7 @@ class TradeLogic(val ourCivilization:CivilizationInfo, val otherCivilization: Ci
}
}
if (offer.type == TradeType.Introduction)
to.meetCivilization(to.gameInfo.getCivilization(offer.name))
to.makeCivilizationsMeet(to.gameInfo.getCivilization(offer.name))
if (offer.type == TradeType.WarDeclaration) {
val nameOfCivToDeclareWarOn = offer.name

View File

@ -29,7 +29,7 @@ class DiplomacyScreen(val viewingCiv:CivilizationInfo):CameraStageBaseScreen() {
private val leftSideTable = Table().apply { defaults().pad(10f) }
private val rightSideTable = Table()
private fun isNotPlayersTurn() = !UncivGame.Current.worldScreen.isPlayersTurn
init {
@ -92,8 +92,7 @@ class DiplomacyScreen(val viewingCiv:CivilizationInfo):CameraStageBaseScreen() {
return tradeTable
}
private fun getCityStateDiplomacyTable(otherCiv: CivilizationInfo): Table {
private fun getCityStateDiplomacyTableHeader(otherCiv: CivilizationInfo): Table {
val otherCivDiplomacyManager = otherCiv.getDiplomacyManager(viewingCiv)
val diplomacyTable = Table()
@ -144,21 +143,26 @@ class DiplomacyScreen(val viewingCiv:CivilizationInfo):CameraStageBaseScreen() {
friendBonusLabelColor = Color.GRAY
val friendBonusLabel = friendBonusText.toLabel(friendBonusLabelColor)
.apply { setAlignment(Align.center) }
.apply { setAlignment(Align.center) }
diplomacyTable.add(friendBonusLabel).row()
return diplomacyTable
}
private fun getCityStateDiplomacyTable(otherCiv: CivilizationInfo): Table {
val otherCivDiplomacyManager = otherCiv.getDiplomacyManager(viewingCiv)
val diplomacyTable = getCityStateDiplomacyTableHeader(otherCiv)
diplomacyTable.addSeparator()
val giftAmount = 250
val influenceAmount = viewingCiv.influenceGainedByGift(otherCiv, giftAmount)
val giftButton = "Gift [$giftAmount] gold (+[$influenceAmount] influence)".toTextButton()
giftButton.onClick {
viewingCiv.giveGoldGift(otherCiv, giftAmount)
updateRightSide(otherCiv)
val giveGoldButton = "Give a Gift".toTextButton()
giveGoldButton.onClick {
rightSideTable.clear()
rightSideTable.add(ScrollPane(getGoldGiftTable(otherCiv)))
}
diplomacyTable.add(giftButton).row()
if (viewingCiv.gold < giftAmount || isNotPlayersTurn()) giftButton.disable()
diplomacyTable.add(giveGoldButton).row()
if (otherCivDiplomacyManager.diplomaticStatus == DiplomaticStatus.Protector){
val revokeProtectionButton = "Revoke Protection".toTextButton()
revokeProtectionButton.onClick {
@ -216,6 +220,30 @@ class DiplomacyScreen(val viewingCiv:CivilizationInfo):CameraStageBaseScreen() {
return diplomacyTable
}
private fun getGoldGiftTable(otherCiv: CivilizationInfo): Table {
val diplomacyTable = getCityStateDiplomacyTableHeader(otherCiv)
diplomacyTable.addSeparator()
for (giftAmount in listOf(250, 500, 1000)) {
val influenceAmount = viewingCiv.influenceGainedByGift(giftAmount)
val giftButton = "Gift [$giftAmount] gold (+[$influenceAmount] influence)".toTextButton()
giftButton.onClick {
viewingCiv.giveGoldGift(otherCiv, giftAmount)
updateRightSide(otherCiv)
}
diplomacyTable.add(giftButton).row()
if (viewingCiv.gold < giftAmount || isNotPlayersTurn()) giftButton.disable()
}
val backButton = "Back".toTextButton()
backButton.onClick {
rightSideTable.clear()
rightSideTable.add(ScrollPane(getCityStateDiplomacyTable(otherCiv)))
}
diplomacyTable.add(backButton)
return diplomacyTable
}
private fun getQuestTable(assignedQuest: AssignedQuest): Table {
val questTable = Table()

View File

@ -518,7 +518,7 @@ object UnitActions {
val otherCiv = tile.getOwner()
if (otherCiv != null) {
// decrease relations for -10 pt/tile
if (!otherCiv.knows(unit.civInfo)) otherCiv.meetCivilization(unit.civInfo)
if (!otherCiv.knows(unit.civInfo)) otherCiv.makeCivilizationsMeet(unit.civInfo)
otherCiv.getDiplomacyManager(unit.civInfo).addModifier(DiplomaticModifiers.StealingTerritory, -10f)
civsToNotify.add(otherCiv)
}

View File

@ -2,6 +2,7 @@ package com.unciv.logic.civilization.diplomacy
import com.unciv.logic.GameInfo
import com.unciv.logic.civilization.CivilizationInfo
import com.unciv.models.ruleset.Nation
import com.unciv.testing.GdxTestRunner
import io.mockk.every
import io.mockk.mockk
@ -23,11 +24,14 @@ class DiplomacyManagerTests {
private fun meetByName(civilization: String, civilizationToMeet: String) {
civilizations.getValue(civilization)
.meetCivilization(civilizations.getValue(civilizationToMeet))
.makeCivilizationsMeet(civilizations.getValue(civilizationToMeet))
}
@Before
fun setup() {
// Add nations to test civilizations, as we need them to know that they are major civs
civilizations.values.forEach { it.nation = Nation() }
// Setup the GameInfo mock
every { mockGameInfo.getCivilization(capture(slot)) } answers { civilizations.getValue(slot.captured) }