From fa813f8f5ea9fdc0469584f7688010765e5f7970 Mon Sep 17 00:00:00 2001 From: SimonCeder <63475501+SimonCeder@users.noreply.github.com> Date: Fri, 24 Sep 2021 09:05:30 +0200 Subject: [PATCH] Quests fixes and additions (#5301) * enable all quests * implement more quests * weighting for quests * global quests * fixes * fixes * Update template.properties * general string * reviews --- .../assets/jsons/Civ V - Vanilla/Quests.json | 26 +- .../jsons/translations/template.properties | 2 + .../logic/civilization/CityStateFunctions.kt | 22 +- .../logic/civilization/CivilizationInfo.kt | 13 +- .../unciv/logic/civilization/QuestManager.kt | 234 ++++++++++++++++-- .../diplomacy/DiplomacyManager.kt | 1 + core/src/com/unciv/models/ruleset/Quest.kt | 9 + .../src/com/unciv/ui/trade/DiplomacyScreen.kt | 10 +- 8 files changed, 279 insertions(+), 38 deletions(-) diff --git a/android/assets/jsons/Civ V - Vanilla/Quests.json b/android/assets/jsons/Civ V - Vanilla/Quests.json index 8501acf407..f58ce17973 100644 --- a/android/assets/jsons/Civ V - Vanilla/Quests.json +++ b/android/assets/jsons/Civ V - Vanilla/Quests.json @@ -22,12 +22,12 @@ { "name": "Acquire Great Person", "description": "Great People can change the course of a Civilization! You will be rewarded for acquiring a new [greatPerson]." - },/* + }, { "name": "Conquer City State", - "description": "You will be rewarded for conquering the city state of [cityState]!", + "description": "It's time to erase the City State of [cityState] from the map. You will be greatly rewarded for conquering them!", "influence": 80 - },*/ + }, { "name": "Find Player", "description": "You have yet to discover where [civName] set up their cities. You will be rewarded for finding their territories.", @@ -36,18 +36,17 @@ { "name": "Find Natural Wonder", "description": "Send your best explorers on a quest to discover Natural Wonders. Nobody knows the location of [naturalWonder] yet." - } + }, /* G&K */ - /* { "name": "Give Gold", - "description": "We are suffering great poverty, and unless we receive a sum of [250] Gold, it's only a matter of time before we collapse.", + "description": "We are suffering great poverty after being robbed by [civName], and unless we receive a sum of Gold, it's only a matter of time before we collapse.", "influence": 20, "duration": 30 }, { "name": "Pledge to Protect", - "description": "Our peoples deserve to be protected by the likes of you. By signing a Protection Pedging, you'll confirm the bond that ties us.", + "description": "We need your protection to stop the aggressions of [civName]. By signing a Pledge of Protection, you'll confirm the bond that ties us.", "influence": 20, "duration": 30 }, @@ -66,7 +65,7 @@ "minimumCivs": 3 }, { - "name": "Contest Techs", + "name": "Contest Technologies", "description": "The civilization with the largest number of new Technologies researched will gain a reward.", "type": "Global", "duration": 30, @@ -74,7 +73,7 @@ }, { "name": "Invest", - "description": "Our people are rejoycing thanks to a tourism boom. For a certain amount of time, any Gold donation will yeld double the Influence.", + "description": "Our people are rejoicing thanks to a tourism boom. For a certain amount of time, any Gold donation will yield [50]% extra Influence.", "type": "Global", "influence": 0, "duration": 30, @@ -82,19 +81,18 @@ }, { "name": "Bully City State", - "description": "" + "description": "We are tired of the pretensions of [cityState]. If someone were to put them in their place by Demanding Tribute from them, they would be rewarded.", "duration": 30 }, { "name": "Denounce Civilization", - "description": "", + "description": "We have been forced to pay tribute to [civName]! We need you to tell the world of their ill deeds.", "duration": 30 }, { "name": "Spread Religion", - "description": "" - }, - */ + "description": "We have heard the tenets of [religionName] and are most curious. Will you send missionaries to teach us about your religion?" + } /* BNW */ /* { diff --git a/android/assets/jsons/translations/template.properties b/android/assets/jsons/translations/template.properties index f2ce5c56bc..85f202816f 100644 --- a/android/assets/jsons/translations/template.properties +++ b/android/assets/jsons/translations/template.properties @@ -134,6 +134,8 @@ Ally = [questName] (+[influenceAmount] influence) = [remainingTurns] turns remaining = +Current leader is [civInfo] with [amount] [stat] generated. = +Current leader is [civInfo] with [amount] Technologies discovered. = ## Diplomatic modifiers diff --git a/core/src/com/unciv/logic/civilization/CityStateFunctions.kt b/core/src/com/unciv/logic/civilization/CityStateFunctions.kt index eb0a3a6143..6f8917e1b3 100644 --- a/core/src/com/unciv/logic/civilization/CityStateFunctions.kt +++ b/core/src/com/unciv/logic/civilization/CityStateFunctions.kt @@ -19,7 +19,7 @@ import kotlin.math.pow /** Class containing city-state-specific functions */ class CityStateFunctions(val civInfo: CivilizationInfo) { - + /** Attempts to initialize the city state, returning true if successful. */ fun initCityState(ruleset: Ruleset, startingEra: String, unusedMajorCivs: Collection): Boolean { val cityStateType = ruleset.nations[civInfo.civName]?.cityStateType @@ -55,7 +55,7 @@ class CityStateFunctions(val civInfo: CivilizationInfo) { // Unique unit for militaristic city-states if (allPossibleBonuses.any { it.isOfType(UniqueType.CityStateMilitaryUnits) } || (fallback && cityStateType == CityStateType.Militaristic) // Fallback for badly defined Eras.json - ) { + ) { val possibleUnits = ruleset.units.values.filter { it.requiredTech != null && ruleset.eras[ruleset.technologies[it.requiredTech!!]!!.era()]!!.eraNumber > ruleset.eras[startingEra]!!.eraNumber // Not from the start era or before @@ -127,6 +127,10 @@ class CityStateFunctions(val civInfo: CivilizationInfo) { } for (unique in donorCiv.getMatchingUniques("Gifts of Gold to City-States generate []% more Influence")) influenceGained *= 1f + unique.params[0].toFloat() / 100f + + // Bonus due to "Invest" quests + influenceGained *= civInfo.questManager.getInvestmentMultiplier(donorCiv.civName) + influenceGained -= influenceGained % 5 if (influenceGained < 5f) influenceGained = 5f return influenceGained.toInt() @@ -137,6 +141,7 @@ class CityStateFunctions(val civInfo: CivilizationInfo) { donorCiv.addGold(-giftAmount) civInfo.addGold(giftAmount) civInfo.getDiplomacyManager(donorCiv).addInfluence(influenceGainedByGift(donorCiv, giftAmount).toFloat()) + civInfo.questManager.receivedGoldGift(donorCiv) } fun getProtectorCivs() : List { @@ -507,6 +512,13 @@ class CityStateFunctions(val civInfo: CivilizationInfo) { protector.popupAlerts.add(PopupAlert(AlertType.BulliedProtectedMinor, bully.civName + "@" + civInfo.civName)) // we need to pass both civs as argument, hence the horrible chimera } + + // Set a diplomatic flag so we remember for future quests (and not to give them any) + civInfo.getDiplomacyManager(bully).setFlag(DiplomacyFlags.Bullied, 20) + + // Notify all city states that we were bullied (for quests) + civInfo.gameInfo.getAliveCityStates() + .forEach { it.questManager.cityStateBullied(civInfo, bully) } } /** A city state was attacked. What are its protectors going to do about it??? Also checks for Wary */ @@ -531,6 +543,8 @@ class CityStateFunctions(val civInfo: CivilizationInfo) { continue if (!cityState.knows(attacker)) // Must have met continue + if (cityState.questManager.wantsDead(civInfo.civName)) // Must not want us dead + continue var probability: Int if (attacker.isMinorCivWarmonger()) { @@ -601,6 +615,10 @@ class CityStateFunctions(val civInfo: CivilizationInfo) { protector.addNotification("[${attacker.civName}] has destroyed [${civInfo.civName}], whom you had pledged to protect!", attacker.civName, NotificationIcon.Death, civInfo.civName) } + + // Notify all city states that we were killed (for quest completion) + civInfo.gameInfo.getAliveCityStates() + .forEach { it.questManager.cityStateConquered(civInfo, attacker) } } } \ No newline at end of file diff --git a/core/src/com/unciv/logic/civilization/CivilizationInfo.kt b/core/src/com/unciv/logic/civilization/CivilizationInfo.kt index 130139a970..d54c4e02a2 100644 --- a/core/src/com/unciv/logic/civilization/CivilizationInfo.kt +++ b/core/src/com/unciv/logic/civilization/CivilizationInfo.kt @@ -162,6 +162,9 @@ class CivilizationInfo { // For Aggressor, Warmonger status private var numMinorCivsAttacked = 0 + var totalCultureForContests = 0 + var totalFaithForContests = 0 + constructor() constructor(civName: String) { @@ -209,6 +212,8 @@ class CivilizationInfo { toReturn.hasEverOwnedOriginalCapital = hasEverOwnedOriginalCapital toReturn.passableImpassables.addAll(passableImpassables) toReturn.numMinorCivsAttacked = numMinorCivsAttacked + toReturn.totalCultureForContests = totalCultureForContests + toReturn.totalFaithForContests = totalFaithForContests return toReturn } @@ -730,6 +735,7 @@ class CivilizationInfo { val nextTurnStats = statsForNextTurn policies.endTurn(nextTurnStats.culture.toInt()) + totalCultureForContests += nextTurnStats.culture.toInt() if (isCityState()) questManager.endTurn() @@ -754,6 +760,7 @@ class CivilizationInfo { tech.endTurn(nextTurnStats.science.toInt()) religionManager.endTurn(nextTurnStats.faith.toInt()) + totalFaithForContests += nextTurnStats.faith.toInt() if (isMajorCiv()) greatPeople.addGreatPersonPoints(getGreatPersonPointsForNextTurn()) // City-states don't get great people! @@ -861,10 +868,12 @@ class CivilizationInfo { fun addStat(stat: Stat, amount: Int) { when (stat) { - Stat.Culture -> policies.addCulture(amount) + Stat.Culture -> { policies.addCulture(amount) + totalCultureForContests += amount } Stat.Science -> tech.addScience(amount) Stat.Gold -> addGold(amount) - Stat.Faith -> religionManager.storedFaith += amount + Stat.Faith -> { religionManager.storedFaith += amount + totalFaithForContests += amount } else -> {} // Food and Production wouldn't make sense to be added nationwide // Happiness cannot be added as it is recalculated again, use a unique instead diff --git a/core/src/com/unciv/logic/civilization/QuestManager.kt b/core/src/com/unciv/logic/civilization/QuestManager.kt index c1e7a2e468..796fb3b1fb 100644 --- a/core/src/com/unciv/logic/civilization/QuestManager.kt +++ b/core/src/com/unciv/logic/civilization/QuestManager.kt @@ -4,7 +4,8 @@ import com.badlogic.gdx.math.Vector2 import com.unciv.Constants import com.unciv.UncivGame import com.unciv.logic.GameInfo -import com.unciv.logic.map.BFS +import com.unciv.logic.civilization.diplomacy.DiplomacyFlags +import com.unciv.logic.civilization.diplomacy.DiplomaticStatus import com.unciv.logic.map.TileInfo import com.unciv.models.ruleset.Building import com.unciv.models.ruleset.Quest @@ -13,7 +14,9 @@ import com.unciv.models.ruleset.tile.ResourceType import com.unciv.models.ruleset.tile.TileResource import com.unciv.models.ruleset.unit.BaseUnit import com.unciv.models.translations.fillPlaceholders +import com.unciv.models.translations.getPlaceholderParameters import com.unciv.ui.utils.randomWeighted +import com.unciv.ui.utils.toPercent import kotlin.math.max import kotlin.random.Random @@ -55,6 +58,18 @@ class QuestManager { /** Returns true if [civInfo] have active quests for [challenger] */ fun haveQuestsFor(challenger: CivilizationInfo): Boolean = assignedQuests.any { it.assignee == challenger.civName } + /** Returns true if [civInfo] has asked anyone to conquer [target] */ + fun wantsDead(target: String): Boolean = assignedQuests.any { it.questName == QuestName.ConquerCityState.value && it.data1 == target } + + /** Returns the influence multiplier for [donor] from a Investment quest that [civInfo] might have (assumes only one) */ + fun getInvestmentMultiplier(donor: String): Float { + val investmentQuest = assignedQuests.firstOrNull { it.questName == QuestName.Invest.value && it.assignee == donor } + return if (investmentQuest == null) + 1f + else + investmentQuest.data1.toPercent() + } + fun clone(): QuestManager { val toReturn = QuestManager() toReturn.globalQuestCountdown = globalQuestCountdown @@ -247,6 +262,8 @@ class QuestManager { for (assignee in assignees) { + val playerReligion = civInfo.gameInfo.religions.values.firstOrNull { it.foundingCivName == assignee.civName && it.isMajorReligion() } + var data1 = "" var data2 = "" @@ -261,6 +278,17 @@ class QuestManager { QuestName.GreatPerson.value -> data1 = getGreatPersonForQuest(assignee)!!.name QuestName.FindPlayer.value -> data1 = getCivilizationToFindForQuest(assignee)!!.civName QuestName.FindNaturalWonder.value -> data1 = getNaturalWonderToFindForQuest(assignee)!! + QuestName.ConquerCityState.value -> data1 = getCityStateTarget(assignee)!!.civName + QuestName.BullyCityState.value -> data1 = getCityStateTarget(assignee)!!.civName + QuestName.PledgeToProtect.value -> data1 = getMostRecentBully()!! + QuestName.GiveGold.value -> data1 = getMostRecentBully()!! + QuestName.DenounceCiv.value -> data1 = getMostRecentBully()!! + QuestName.SpreadReligion.value -> { data1 = playerReligion!!.getReligionDisplayName() // For display + data2 = playerReligion.name } // To check completion + QuestName.ContestCulture.value -> data1 = assignee.totalCultureForContests.toString() + QuestName.ContestFaith.value -> data1 = assignee.totalFaithForContests.toString() + QuestName.ContestTech.value -> data1 = assignee.tech.getNumberOfTechsResearched().toString() + QuestName.Invest.value -> data1 = quest.description.getPlaceholderParameters().first() } val newQuest = AssignedQuest( @@ -294,22 +322,33 @@ class QuestManager { return false if (assignedQuests.any { it.assignee == challenger.civName && it.questName == quest.name }) return false + if (quest.isIndividual() && civInfo.getDiplomacyManager(challenger).hasFlag(DiplomacyFlags.Bullied)) + return false + + val mostRecentBully = getMostRecentBully() + val playerReligion = civInfo.gameInfo.religions.values.firstOrNull() { it.foundingCivName == challenger.civName && it.isMajorReligion() }?.name return when (quest.name) { QuestName.ClearBarbarianCamp.value -> getBarbarianEncampmentForQuest() != null - QuestName.Route.value -> { - if (challenger.cities.none() || !civInfo.hasEverBeenFriendWith(challenger) - || civInfo.isCapitalConnectedToCity(challenger.getCapital())) return false - - val bfs = BFS(civInfo.getCapital().getCenterTile()) { it.isLand && !it.isImpassible() } - bfs.stepUntilDestination(challenger.getCapital().getCenterTile()) - bfs.hasReachedTile(challenger.getCapital().getCenterTile()) - } - QuestName.ConnectResource.value -> civInfo.hasEverBeenFriendWith(challenger) && getResourceForQuest(challenger) != null - QuestName.ConstructWonder.value -> civInfo.hasEverBeenFriendWith(challenger) && getWonderToBuildForQuest(challenger) != null - QuestName.GreatPerson.value -> civInfo.hasEverBeenFriendWith(challenger) && getGreatPersonForQuest(challenger) != null - QuestName.FindPlayer.value -> civInfo.hasEverBeenFriendWith(challenger) && getCivilizationToFindForQuest(challenger) != null - QuestName.FindNaturalWonder.value -> civInfo.hasEverBeenFriendWith(challenger) && getNaturalWonderToFindForQuest(challenger) != null + QuestName.Route.value -> !challenger.cities.none() + && !civInfo.isCapitalConnectedToCity(challenger.getCapital()) + // Need to have a city within 7 tiles on the same continent + && challenger.cities.any { it.getCenterTile().aerialDistanceTo(civInfo.getCapital().getCenterTile()) <= 7 + && it.getCenterTile().getContinent() == civInfo.getCapital().getCenterTile().getContinent() } + QuestName.ConnectResource.value -> getResourceForQuest(challenger) != null + QuestName.ConstructWonder.value -> getWonderToBuildForQuest(challenger) != null + QuestName.GreatPerson.value -> getGreatPersonForQuest(challenger) != null + QuestName.FindPlayer.value -> getCivilizationToFindForQuest(challenger) != null + QuestName.FindNaturalWonder.value -> getNaturalWonderToFindForQuest(challenger) != null + QuestName.PledgeToProtect.value -> mostRecentBully != null && challenger !in civInfo.getProtectorCivs() + QuestName.GiveGold.value -> mostRecentBully != null + QuestName.DenounceCiv.value -> mostRecentBully != null && challenger.knows(mostRecentBully) + && !challenger.getDiplomacyManager(mostRecentBully).hasFlag(DiplomacyFlags.Denunciation) + && challenger.getDiplomacyManager(mostRecentBully).diplomaticStatus != DiplomaticStatus.War + && !( challenger.playerType == PlayerType.Human && civInfo.gameInfo.getCivilization(mostRecentBully).playerType == PlayerType.Human) + QuestName.SpreadReligion.value -> playerReligion != null && civInfo.getCapital().religion.getMajorityReligion()?.name != playerReligion + QuestName.ConquerCityState.value -> getCityStateTarget(challenger) != null && civInfo.cityStatePersonality != CityStatePersonality.Friendly + QuestName.BullyCityState.value -> getCityStateTarget(challenger) != null else -> true } } @@ -324,6 +363,9 @@ class QuestManager { QuestName.GreatPerson.value -> assignee.getCivGreatPeople().any { it.baseUnit.getReplacedUnit(civInfo.gameInfo.ruleSet).name == assignedQuest.data1 } QuestName.FindPlayer.value -> assignee.hasMetCivTerritory(civInfo.gameInfo.getCivilization(assignedQuest.data1)) QuestName.FindNaturalWonder.value -> assignee.naturalWonders.contains(assignedQuest.data1) + QuestName.PledgeToProtect.value -> assignee in civInfo.getProtectorCivs() + QuestName.DenounceCiv.value -> assignee.getDiplomacyManager(assignedQuest.data1).hasFlag(DiplomacyFlags.Denunciation) + QuestName.SpreadReligion.value -> civInfo.getCapital().religion.getMajorityReligion() == civInfo.gameInfo.religions[assignedQuest.data2] else -> false } } @@ -335,6 +377,9 @@ class QuestManager { QuestName.ClearBarbarianCamp.value -> civInfo.gameInfo.tileMap[assignedQuest.data1.toInt(), assignedQuest.data2.toInt()].improvement != Constants.barbarianEncampment QuestName.ConstructWonder.value -> civInfo.gameInfo.getCities().any { it.civInfo != assignee && it.cityConstructions.isBuilt(assignedQuest.data1) } QuestName.FindPlayer.value -> civInfo.gameInfo.getCivilization(assignedQuest.data1).isDefeated() + QuestName.ConquerCityState.value -> civInfo.gameInfo.getCivilization(assignedQuest.data1).isDefeated() + QuestName.BullyCityState.value -> civInfo.gameInfo.getCivilization(assignedQuest.data1).isDefeated() + QuestName.DenounceCiv.value -> civInfo.gameInfo.getCivilization(assignedQuest.data1).isDefeated() else -> false } } @@ -358,14 +403,29 @@ class QuestManager { /** Returns the score for the [assignedQuest] */ private fun getScoreForQuest(assignedQuest: AssignedQuest): Int { - @Suppress("UNUSED_VARIABLE") // This is a work in progress val assignee = civInfo.gameInfo.getCivilization(assignedQuest.assignee) return when (assignedQuest.questName) { - // Waiting for contest quests + QuestName.ContestCulture.value -> assignee.totalCultureForContests - assignedQuest.data1.toInt() + QuestName.ContestFaith.value -> assignee.totalFaithForContests - assignedQuest.data1.toInt() + QuestName.ContestTech.value -> assignee.tech.getNumberOfTechsResearched() - assignedQuest.data1.toInt() else -> 0 } } + /** Returns a string with the leading civ and their score for [questName] */ + fun getLeaderStringForQuest(questName: String): String { + val leadingQuest = assignedQuests.filter { it.questName == questName }.maxByOrNull { getScoreForQuest(it) } + if (leadingQuest == null) + return "" + + return when (questName){ + QuestName.ContestCulture.value -> "Current leader is ${leadingQuest.assignee} with ${getScoreForQuest(leadingQuest)} [Culture] generated." + QuestName.ContestFaith.value -> "Current leader is ${leadingQuest.assignee} with ${getScoreForQuest(leadingQuest)} [Faith] generated." + QuestName.ContestTech.value -> "Current leader is ${leadingQuest.assignee} with ${getScoreForQuest(leadingQuest)} Technologies discovered." + else -> "" + } + } + /** * Gets notified a barbarian camp in [location] has been cleared by [civInfo]. * Since [QuestName.ClearBarbarianCamp] is a global quest, it could have been assigned to @@ -383,6 +443,59 @@ class QuestManager { assignedQuests.removeAll(matchingQuests) } + /** + * Gets notified the city state [cityState] was just conquered by [attacker]. + */ + fun cityStateConquered(cityState: CivilizationInfo, attacker: CivilizationInfo) { + val matchingQuests = assignedQuests.asSequence() + .filter { it.questName == QuestName.ConquerCityState.value } + .filter { it.data1 == cityState.civName && it.assignee == attacker.civName} + + for (quest in matchingQuests) + giveReward(quest) + + assignedQuests.removeAll(matchingQuests) + } + + /** + * Gets notified the city state [cityState] was just bullied by [bully]. + */ + fun cityStateBullied(cityState: CivilizationInfo, bully: CivilizationInfo) { + val matchingQuests = assignedQuests.asSequence() + .filter { it.questName == QuestName.BullyCityState.value } + .filter { it.data1 == cityState.civName && it.assignee == bully.civName} + + for (quest in matchingQuests) + giveReward(quest) + + assignedQuests.removeAll(matchingQuests) + + // What idiots haha oh wait that's us + if (civInfo == cityState) { + // Revoke most quest types from the bully + val revokedQuests = assignedQuests.asSequence() + .filter { it.isIndividual() || it.questName == QuestName.Invest.value } + assignedQuests.removeAll(revokedQuests) + if (revokedQuests.count() > 0) + bully.addNotification("[${civInfo.civName}] cancelled the quests they had given you because you demanded tribute from them.", + DiplomacyAction(civInfo.civName), civInfo.civName, "OtherIcons/Quest") + } + } + + /** + * Gets notified when given gold by [donorCiv]. + */ + fun receivedGoldGift(donorCiv: CivilizationInfo) { + val matchingQuests = assignedQuests.asSequence() + .filter { it.questName == QuestName.GiveGold.value } + .filter { it.assignee == donorCiv.civName} + + for (quest in matchingQuests) + giveReward(quest) + + assignedQuests.removeAll(matchingQuests) + } + /** * Returns the weight of the [questName], depends on city state trait and personality */ @@ -440,6 +553,56 @@ class QuestManager { if (trait == CityStateType.Militaristic) weight *= 3f } + QuestName.GiveGold.value -> { + when (trait) { + CityStateType.Militaristic -> weight *= 2f + CityStateType.Mercantile -> weight *= 3.5f + else -> weight *= 3f + } + } + QuestName.PledgeToProtect.value -> { + when (trait) { + CityStateType.Militaristic -> weight *= 2f + CityStateType.Cultured -> weight *= 3.5f + else -> weight *= 3f + } + } + QuestName.BullyCityState.value -> { + when (personality) { + CityStatePersonality.Hostile -> weight *= 2f + CityStatePersonality.Irrational -> weight *= 1.5f + CityStatePersonality.Friendly -> weight *= .3f + } + } + QuestName.DenounceCiv.value -> { + when (trait) { + CityStateType.Religious -> weight *= 2.5f + CityStateType.Maritime -> weight *= 2f + else -> weight *= 1.5f + } + } + QuestName.SpreadReligion.value -> { + if (trait == CityStateType.Religious) + weight *= 3f + } + QuestName.ContestCulture.value -> { + if (trait == CityStateType.Cultured) + weight *= 2f + } + QuestName.ContestFaith.value -> { + when (trait) { + CityStateType.Religious -> weight *= 2f + else -> weight *= .5f + } + } + QuestName.ContestTech.value -> { + if (trait == CityStateType.Religious) + weight *= .5f + } + QuestName.Invest.value -> { + if (trait == CityStateType.Mercantile) + weight *= 1.5f + } } return weight } @@ -486,11 +649,21 @@ class QuestManager { } private fun getWonderToBuildForQuest(challenger: CivilizationInfo): Building? { + val startingEra = civInfo.gameInfo.ruleSet.eras[civInfo.gameInfo.gameParameters.startingEra]!! val wonders = civInfo.gameInfo.ruleSet.buildings.values .filter { building -> - building.isWonder && - (building.requiredTech == null || challenger.tech.isResearched(building.requiredTech!!)) && - civInfo.gameInfo.getCities().none { it.cityConstructions.isBuilt(building.name) } + // Buildable wonder + building.isWonder + && (building.requiredTech == null || challenger.tech.isResearched(building.requiredTech!!)) + && civInfo.gameInfo.getCities().none { it.cityConstructions.isBuilt(building.name) } + // Can't be disabled + && building.name !in startingEra.startingObsoleteWonders + && (civInfo.gameInfo.gameParameters.religionEnabled || !building.hasUnique("Hidden when religion is disabled")) + // Can't be more than 25% built anywhere + && civInfo.gameInfo.getCities().none { + it.cityConstructions.getWorkDone(building.name) * 3 > it.cityConstructions.getRemainingWork(building.name) } + // Can't be a unique wonder + && building.uniqueTo == null } if (wonders.isNotEmpty()) @@ -545,6 +718,29 @@ class QuestManager { return null } + + /** + * Returns a city-state [CivilizationInfo] that [civInfo] wants to target for hostile quests + */ + private fun getCityStateTarget(challenger: CivilizationInfo): CivilizationInfo? { + val closestProximity = civInfo.gameInfo.getAliveCityStates() + .mapNotNull { civInfo.proximity[it.civName] }.filter { it != Proximity.None }.minByOrNull { it.ordinal } + + if (closestProximity == null || closestProximity == Proximity.Distant) // None close enough + return null + + val validTargets = civInfo.getKnownCivs().filter { it.isCityState() && challenger.knows(it) + && civInfo.proximity[it.civName] == closestProximity } + + return validTargets.randomOrNull() + } + + /** Returns a [CivilizationInfo] of the civ that most recently bullied [civInfo]. + * Note: forgets after 20 turns has passed! */ + private fun getMostRecentBully(): String? { + val bullies = civInfo.diplomacy.values.filter { it.hasFlag(DiplomacyFlags.Bullied)} + return bullies.maxByOrNull { it.getFlag(DiplomacyFlags.Bullied) }?.otherCivName + } //endregion } diff --git a/core/src/com/unciv/logic/civilization/diplomacy/DiplomacyManager.kt b/core/src/com/unciv/logic/civilization/diplomacy/DiplomacyManager.kt index b1ecfb46fb..456212ff87 100644 --- a/core/src/com/unciv/logic/civilization/diplomacy/DiplomacyManager.kt +++ b/core/src/com/unciv/logic/civilization/diplomacy/DiplomacyManager.kt @@ -51,6 +51,7 @@ enum class DiplomacyFlags { RememberSidedWithProtectedMinor, Denunciation, WaryOf, + Bullied, } enum class DiplomaticModifiers { diff --git a/core/src/com/unciv/models/ruleset/Quest.kt b/core/src/com/unciv/models/ruleset/Quest.kt index be97dfa02a..5bf20b83a5 100644 --- a/core/src/com/unciv/models/ruleset/Quest.kt +++ b/core/src/com/unciv/models/ruleset/Quest.kt @@ -11,6 +11,15 @@ enum class QuestName(val value: String) { ConquerCityState("Conquer City State"), FindPlayer("Find Player"), FindNaturalWonder("Find Natural Wonder"), + GiveGold("Give Gold"), + PledgeToProtect("Pledge to Protect"), + ContestCulture("Contest Culture"), + ContestFaith("Contest Faith"), + ContestTech("Contest Technologies"), + Invest("Invest"), + BullyCityState("Bully City State"), + DenounceCiv("Denounce Civilization"), + SpreadReligion("Spread Religion"), None("") } diff --git a/core/src/com/unciv/ui/trade/DiplomacyScreen.kt b/core/src/com/unciv/ui/trade/DiplomacyScreen.kt index 3d66cfaac5..f681025f8e 100644 --- a/core/src/com/unciv/ui/trade/DiplomacyScreen.kt +++ b/core/src/com/unciv/ui/trade/DiplomacyScreen.kt @@ -541,7 +541,10 @@ class DiplomacyScreen(val viewingCiv:CivilizationInfo):CameraStageBaseScreen() { val quest: Quest = viewingCiv.gameInfo.ruleSet.quests[assignedQuest.questName]!! val remainingTurns: Int = assignedQuest.getRemainingTurns() - val title = "[${quest.name}] (+[${quest.influence.toInt()}] influence)" + val title = if (quest.influence > 0) + "[${quest.name}] (+[${quest.influence.toInt()}] influence)" + else + quest.name val description = assignedQuest.getDescription() questTable.add(title.toLabel(fontSize = 24)).row() @@ -549,6 +552,11 @@ class DiplomacyScreen(val viewingCiv:CivilizationInfo):CameraStageBaseScreen() { .width(stage.width / 2).row() if (quest.duration > 0) questTable.add("[${remainingTurns}] turns remaining".toLabel()).row() + if (quest.isGlobal()) { + val leaderString = viewingCiv.gameInfo.getCivilization(assignedQuest.assigner).questManager.getLeaderStringForQuest(assignedQuest.questName) + if (leaderString != "") + questTable.add(leaderString.toLabel()).row() + } questTable.onClick { assignedQuest.onClickAction()