City State Barbarian Invasion and War with Major pseudo-quests (#5454)

* barbarian invasion event

* war with major pseudo-quest

* include latecomers

* diplomacy screen

* more notifications

* fixes

* reviews
This commit is contained in:
SimonCeder 2021-10-16 19:34:47 +02:00 committed by GitHub
parent 551e6e1d54
commit ac422d25cb
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
7 changed files with 198 additions and 2 deletions

View File

@ -205,6 +205,17 @@ We have married into the ruling family of [civName], bringing them under our con
You have broken your Pledge to Protect [civName]! =
City-States grow wary of your aggression. The resting point for Influence has decreased by [amount] for [civName]. =
[cityState] is being attacked by [civName] and asks all major civilizations to help them out by gifting them military units. =
[cityState] is being invaded by Barbarians! Destroy Barbarians near their territory to earn Influence. =
[cityState] is grateful that you killed a Barbarian that was threatening them! =
[cityState] is being attacked by [civName]! Kill [amount] of the attacker's military units and they will be immensely grateful. =
[cityState] is deeply grateful for your assistance in the war against [civName]! =
[cityState] no longer needs your assistance against [civName]. =
War against [civName] =
We need you to help us defend against [civName]. Killing [amount] of their military units would slow their offensive. =
Currently you have killed [amount] of their military units. =
You need to find them first! =
Cultured =
Maritime =
Mercantile =

View File

@ -173,6 +173,11 @@ object Battle {
}
}
}
// CS war with major pseudo-quest
for (cityState in UncivGame.Current.gameInfo.getAliveCityStates()) {
cityState.questManager.militaryUnitKilledBy(civUnit.getCivInfo(), defeatedUnit.getCivInfo())
}
}
private fun tryCaptureUnit(attacker: MapUnitCombatant, defender: MapUnitCombatant): Boolean {

View File

@ -473,7 +473,7 @@ class CityStateFunctions(val civInfo: CivilizationInfo) {
return
}
private fun getNumThreateningBarbarians(): Int {
fun getNumThreateningBarbarians(): Int {
if (civInfo.gameInfo.gameParameters.noBarbarians) return 0
val barbarianCiv = civInfo.gameInfo.civilizations.firstOrNull { it.isBarbarian() }
?: return 0
@ -482,11 +482,17 @@ class CityStateFunctions(val civInfo: CivilizationInfo) {
fun threateningBarbarianKilledBy(otherCiv: CivilizationInfo) {
val diplomacy = civInfo.getDiplomacyManager(otherCiv)
if (diplomacy.diplomaticStatus == DiplomaticStatus.War) return // No reward for enemies
diplomacy.addInfluence(12f)
if (diplomacy.hasFlag(DiplomacyFlags.AngerFreeIntrusion))
diplomacy.setFlag(DiplomacyFlags.AngerFreeIntrusion, diplomacy.getFlag(DiplomacyFlags.AngerFreeIntrusion) + 5)
else
diplomacy.setFlag(DiplomacyFlags.AngerFreeIntrusion, 5)
otherCiv.addNotification("[${civInfo.civName}] is grateful that you killed a Barbarian that was threatening them!",
DiplomacyAction(civInfo.civName), civInfo.civName)
}
/** A city state was bullied. What are its protectors going to do about it??? */
@ -593,6 +599,10 @@ class CityStateFunctions(val civInfo: CivilizationInfo) {
protector.popupAlerts.add(PopupAlert(AlertType.AttackedProtectedMinor,
attacker.civName + "@" + civInfo.civName)) // we need to pass both civs as argument, hence the horrible chimera
}
// Set up war with major pseudo-quest
civInfo.questManager.wasAttackedBy(attacker)
civInfo.getDiplomacyManager(attacker).setFlag(DiplomacyFlags.RecentlyAttacked, 2) // Reminder to ask for unit gifts in 2 turns
}
/** A city state was destroyed. Its protectors are going to be upset! */
@ -621,4 +631,18 @@ class CityStateFunctions(val civInfo: CivilizationInfo) {
.forEach { it.questManager.cityStateConquered(civInfo, attacker) }
}
/** Asks all met majors that haven't yet declared wor on [attacker] to at least give some units */
fun askForUnitGifts(attacker: CivilizationInfo) {
if (attacker.isDefeated() || civInfo.isDefeated()) // nevermind, someone died
return
if (civInfo.cities.isEmpty()) // Can't receive units with no cities
return
for (thirdCiv in civInfo.getKnownCivs().filter {
it != attacker && it.isAlive() && it.knows(attacker) && !it.isAtWarWith(attacker) }) {
thirdCiv.addNotification("[${civInfo.civName}] is being attacked by [${attacker.civName}] and asks all major civilizations to help them out by gifting them military units.",
civInfo.getCapital().location, civInfo.civName, "OtherIcons/Present")
}
}
}

View File

@ -508,6 +508,8 @@ class CivilizationInfo {
}
for ((key, value) in giftAmount)
otherCiv.addStat(key, value.toInt())
questManager.justMet(otherCiv) // Include them in war with major pseudo-quest
}
fun discoverNaturalWonder(naturalWonderName: String) {
@ -891,6 +893,7 @@ class CivilizationInfo {
fun getTurnsTillNextDiplomaticVote() = flagsCountdown[CivFlags.TurnsTillNextDiplomaticVote.name]
fun getRecentBullyingCountdown() = flagsCountdown[CivFlags.RecentlyBullied.name]
fun getTurnsTillCallForBarbHelp() = flagsCountdown[CivFlags.TurnsTillCallForBarbHelp.name]
fun mayVoteForDiplomaticVictory() =
getTurnsTillNextDiplomaticVote() == 0
@ -1184,6 +1187,7 @@ class CivilizationInfo {
fun getFreeTechForCityState() {
cityStateFunctions.getFreeTechForCityState()
}
fun getNumThreateningBarbarians() = cityStateFunctions.getNumThreateningBarbarians()
fun threateningBarbarianKilledBy(otherCiv: CivilizationInfo) {
cityStateFunctions.threateningBarbarianKilledBy(otherCiv)
}
@ -1214,5 +1218,6 @@ enum class CivFlags {
TurnsTillNextDiplomaticVote,
ShowDiplomaticVotingResults,
ShouldResetDiplomaticVotes,
RecentlyBullied
RecentlyBullied,
TurnsTillCallForBarbHelp,
}

View File

@ -56,6 +56,12 @@ class QuestManager {
/** Number of turns left before this city state can start a new individual quest */
private var individualQuestCountdown: HashMap<String, Int> = HashMap()
/** Target number of units to kill for this war, for war with major pseudo-quest */
private var unitsToKillForCiv: HashMap<String, Int> = HashMap()
/** For this attacker, number of units killed by each civ */
private var unitsKilledFromCiv: HashMap<String, HashMap<String, Int>> = HashMap()
/** Returns true if [civInfo] have active quests for [challenger] */
fun haveQuestsFor(challenger: CivilizationInfo): Boolean = assignedQuests.any { it.assignee == challenger.civName }
@ -76,6 +82,11 @@ class QuestManager {
toReturn.globalQuestCountdown = globalQuestCountdown
toReturn.individualQuestCountdown.putAll(individualQuestCountdown)
toReturn.assignedQuests.addAll(assignedQuests)
toReturn.unitsToKillForCiv.putAll(unitsToKillForCiv)
for ((attacker, unitsKilled) in unitsKilledFromCiv) {
toReturn.unitsKilledFromCiv[attacker] = HashMap()
toReturn.unitsKilledFromCiv[attacker]!!.putAll(unitsKilled)
}
return toReturn
}
@ -105,6 +116,9 @@ class QuestManager {
tryStartNewGlobalQuest()
tryStartNewIndividualQuests()
tryBarbarianInvasion()
tryEndWarWithMajorQuests()
}
private fun decrementQuestCountdowns() {
@ -201,6 +215,22 @@ class QuestManager {
}
}
private fun tryBarbarianInvasion() {
if ((civInfo.getTurnsTillCallForBarbHelp() == null || civInfo.getTurnsTillCallForBarbHelp() == 0)
&& civInfo.getNumThreateningBarbarians() >= 2) {
for (otherCiv in civInfo.getKnownCivs().filter {
it.isMajorCiv()
&& it.isAlive()
&& !it.isAtWarWith(civInfo)
&& it.getProximity(civInfo) <= Proximity.Far }) {
otherCiv.addNotification("[${civInfo.civName}] is being invaded by Barbarians! Destroy Barbarians near their territory to earn Influence.",
LocationAction(listOf(civInfo.getCapital().location)), civInfo.civName, NotificationIcon.War)
}
civInfo.addFlag(CivFlags.TurnsTillCallForBarbHelp.name, 30)
}
}
private fun handleGlobalQuests() {
val globalQuestsExpired = assignedQuests.filter { it.isGlobal() && it.isExpired() }.map { it.questName }.distinct()
for (globalQuestName in globalQuestsExpired)
@ -484,6 +514,100 @@ class QuestManager {
}
}
/** Gets notified when we are attacked, for war with major pseudo-quest */
fun wasAttackedBy(attacker: CivilizationInfo) {
// Set target number units to kill
val totalMilitaryUnits = attacker.getCivUnits().count { !it.isCivilian() }
val unitsToKill = max(3, totalMilitaryUnits / 4)
unitsToKillForCiv[attacker.civName] = unitsToKill
val location = if (civInfo.cities.isEmpty()) null
else civInfo.getCapital().location
// Ask for assistance
for (thirdCiv in civInfo.getKnownCivs().filter { it.isAlive() && !it.isAtWarWith(civInfo) }) {
if (location != null)
thirdCiv.addNotification("[${civInfo.civName}] is being attacked by [${attacker.civName}]! Kill [$unitsToKill] of the attacker's military units and they will be immensely grateful.",
location, civInfo.civName, "OtherIcons/Quest")
else thirdCiv.addNotification("[${civInfo.civName}] is being attacked by [${attacker.civName}]! Kill [$unitsToKill] of the attacker's military units and they will be immensely grateful.",
civInfo.civName, "OtherIcons/Quest")
}
}
/** Gets notified when [killed]'s military unit was killed by [killer], for war with major pseudo-quest */
fun militaryUnitKilledBy(killer: CivilizationInfo, killed: CivilizationInfo) {
if (!warWithMajorActive(killed)) return
// No credit if we're at war or haven't met
if (!civInfo.knows(killer) || civInfo.isAtWarWith(killer)) return
// Make the map if we haven't already
if (unitsKilledFromCiv[killed.civName] == null)
unitsKilledFromCiv[killed.civName] = HashMap()
// Update kill count
val updatedKillCount = 1 + (unitsKilledFromCiv[killed.civName]!![killer.civName] ?: 0)
unitsKilledFromCiv[killed.civName]!![killer.civName] = updatedKillCount
// Quest complete?
if (updatedKillCount >= unitsToKillForCiv[killed.civName]!!) {
killer.addNotification("[${civInfo.civName}] is deeply grateful for your assistance in the war against [${killed.civName}]!",
DiplomacyAction(civInfo.civName), civInfo.civName, "OtherIcons/Quest")
civInfo.getDiplomacyManager(killer).addInfluence(100f) // yikes
endWarWithMajorQuest(killed)
}
}
/** Called when a major civ meets the city-state for the first time. Mainly for war with major pseudo-quest. */
fun justMet(otherCiv: CivilizationInfo) {
val location = if (civInfo.cities.isEmpty()) null
else civInfo.getCapital().location
for ((attackerName, unitsToKill) in unitsToKillForCiv) {
if (location != null)
otherCiv.addNotification("[${civInfo.civName}] is being attacked by [$attackerName]! Kill [$unitsToKill] of the attacker's military units and they will be immensely grateful.",
location, civInfo.civName, "OtherIcons/Quest")
else otherCiv.addNotification("[${civInfo.civName}] is being attacked by [$attackerName]! Kill [$unitsToKill] of the attacker's military units and they will be immensely grateful.",
civInfo.civName, "OtherIcons/Quest")
}
}
/** Ends War with Major pseudo-quests that aren't relevant any longer */
private fun tryEndWarWithMajorQuests() {
for (attacker in unitsToKillForCiv.keys.map { civInfo.gameInfo.getCivilization(it) }) {
if (civInfo.isDefeated()
|| attacker.isDefeated()
|| !civInfo.isAtWarWith(attacker)) {
endWarWithMajorQuest(attacker)
}
}
}
private fun endWarWithMajorQuest(attacker: CivilizationInfo) {
for (thirdCiv in civInfo.getKnownCivs().filterNot { it.isDefeated() || it == attacker || it.isAtWarWith(civInfo) }) {
if (unitsKilledSoFar(attacker, thirdCiv) >= unitsToKill(attacker)) // Don't show the notification to the one who won the quest
continue
thirdCiv.addNotification("[${civInfo.civName}] no longer needs your assistance against [${attacker.civName}].",
DiplomacyAction(civInfo.civName), civInfo.civName, "OtherIcons/Quest")
}
unitsToKillForCiv.remove(attacker.civName)
unitsKilledFromCiv.remove(attacker.civName)
}
fun warWithMajorActive(target: CivilizationInfo): Boolean {
return unitsToKillForCiv.containsKey(target.civName)
}
fun unitsToKill(target: CivilizationInfo): Int {
return unitsToKillForCiv[target.civName] ?: 0
}
fun unitsKilledSoFar(target: CivilizationInfo, viewingCiv: CivilizationInfo): Int {
val killMap = unitsKilledFromCiv[target.civName] ?: return 0
return killMap[viewingCiv.civName] ?: 0
}
/**
* Gets notified when given gold by [donorCiv].
*/

View File

@ -52,6 +52,7 @@ enum class DiplomacyFlags {
Denunciation,
WaryOf,
Bullied,
RecentlyAttacked,
}
enum class DiplomaticModifiers {
@ -527,6 +528,9 @@ class DiplomacyManager() {
DiplomacyFlags.AgreedToNotSettleNearUs.name -> {
addModifier(DiplomaticModifiers.FulfilledPromiseToNotSettleCitiesNearUs, 10f)
}
DiplomacyFlags.RecentlyAttacked.name -> {
civInfo.cityStateFunctions.askForUnitGifts(otherCiv())
}
// These modifiers don't tick down normally, instead there is a threshold number of turns
DiplomacyFlags.RememberDestroyedProtectedMinor.name -> { // 125
removeModifier(DiplomaticModifiers.DestroyedProtectedMinor)

View File

@ -369,6 +369,11 @@ class DiplomacyScreen(val viewingCiv:CivilizationInfo): CameraStageBaseScreen()
diplomacyTable.addSeparator()
diplomacyTable.add(getQuestTable(assignedQuest)).row()
}
for (target in otherCiv.getKnownCivs().filter { otherCiv.questManager.warWithMajorActive(it) }) {
diplomacyTable.addSeparator()
diplomacyTable.add(getWarWithMajorTable(target, otherCiv)).row()
}
return diplomacyTable
}
@ -567,6 +572,24 @@ class DiplomacyScreen(val viewingCiv:CivilizationInfo): CameraStageBaseScreen()
return questTable
}
private fun getWarWithMajorTable(target: CivilizationInfo, otherCiv: CivilizationInfo): Table {
val warTable = Table()
warTable.defaults().pad(10f)
val title = "War against [${target.civName}]"
val description = "We need you to help us defend against [${target.civName}]. Killing [${otherCiv.questManager.unitsToKill(target)}] of their military units would slow their offensive."
val progress = if (viewingCiv.knows(target)) "Currently you have killed [${otherCiv.questManager.unitsKilledSoFar(target, viewingCiv)}] of their military units."
else "You need to find them first!"
warTable.add(title.toLabel(fontSize = 24)).row()
warTable.add(description.toLabel().apply { wrap = true; setAlignment(Align.center) })
.width(stage.width / 2).row()
warTable.add(progress.toLabel().apply { wrap = true; setAlignment(Align.center) })
.width(stage.width / 2).row()
return warTable
}
private fun getMajorCivDiplomacyTable(otherCiv: CivilizationInfo): Table {
val otherCivDiplomacyManager = otherCiv.getDiplomacyManager(viewingCiv)