Use Events for the floating "Tutorials" (#11717)
* Split off reuses of CityStateIcons/Cultured to allow modding separately * Reposition floating tutorials in case the TopBar moved its buttons * Event definition, art and basic support * Split off Event rendering from AlertPopup * Support Event presentation modes and replace hardcoded floating tutorials * "Meet another civilization" art - can't find any better * Tweak TranslationFileWriter and some polishing
BIN
android/Images.Icons/OtherIcons/HiddenTutorialTask.png
Normal file
After Width: | Height: | Size: 6.6 KiB |
BIN
android/Images.Icons/OtherIcons/Score.png
Normal file
After Width: | Height: | Size: 6.6 KiB |
BIN
android/assets/ExtraImages/Tutorials/Conquer a city.png
Normal file
After Width: | Height: | Size: 26 KiB |
After Width: | Height: | Size: 33 KiB |
BIN
android/assets/ExtraImages/Tutorials/Create a trade route.png
Normal file
After Width: | Height: | Size: 40 KiB |
BIN
android/assets/ExtraImages/Tutorials/Enter city screen.png
Normal file
After Width: | Height: | Size: 45 KiB |
BIN
android/assets/ExtraImages/Tutorials/Found city.png
Normal file
After Width: | Height: | Size: 11 KiB |
After Width: | Height: | Size: 26 KiB |
BIN
android/assets/ExtraImages/Tutorials/Move an air unit.png
Normal file
After Width: | Height: | Size: 52 KiB |
BIN
android/assets/ExtraImages/Tutorials/Move unit.png
Normal file
After Width: | Height: | Size: 8.0 KiB |
BIN
android/assets/ExtraImages/Tutorials/Open the options table.png
Normal file
After Width: | Height: | Size: 13 KiB |
BIN
android/assets/ExtraImages/Tutorials/Pass a turn.png
Normal file
After Width: | Height: | Size: 26 KiB |
BIN
android/assets/ExtraImages/Tutorials/Pick construction.png
Normal file
After Width: | Height: | Size: 12 KiB |
BIN
android/assets/ExtraImages/Tutorials/Pick technology.png
Normal file
After Width: | Height: | Size: 22 KiB |
BIN
android/assets/ExtraImages/Tutorials/Reassign worked tiles.png
Normal file
After Width: | Height: | Size: 32 KiB |
After Width: | Height: | Size: 17 KiB |
@ -11,6 +11,20 @@ CityStateIcons/Cultured
|
|||||||
orig: 100, 100
|
orig: 100, 100
|
||||||
offset: 0, 0
|
offset: 0, 0
|
||||||
index: -1
|
index: -1
|
||||||
|
OtherIcons/HiddenTutorialTask
|
||||||
|
rotate: false
|
||||||
|
xy: 127, 1001
|
||||||
|
size: 100, 100
|
||||||
|
orig: 100, 100
|
||||||
|
offset: 0, 0
|
||||||
|
index: -1
|
||||||
|
OtherIcons/Score
|
||||||
|
rotate: false
|
||||||
|
xy: 127, 1001
|
||||||
|
size: 100, 100
|
||||||
|
orig: 100, 100
|
||||||
|
offset: 0, 0
|
||||||
|
index: -1
|
||||||
CityStateIcons/Maritime
|
CityStateIcons/Maritime
|
||||||
rotate: false
|
rotate: false
|
||||||
xy: 1819, 1821
|
xy: 1819, 1821
|
||||||
@ -431,27 +445,6 @@ StatIcons/Faith
|
|||||||
orig: 100, 100
|
orig: 100, 100
|
||||||
offset: 0, 0
|
offset: 0, 0
|
||||||
index: -1
|
index: -1
|
||||||
NotificationIcons/Loading
|
|
||||||
rotate: false
|
|
||||||
xy: 1063, 1820
|
|
||||||
size: 100, 100
|
|
||||||
orig: 100, 100
|
|
||||||
offset: 0, 0
|
|
||||||
index: -1
|
|
||||||
NotificationIcons/Working
|
|
||||||
rotate: false
|
|
||||||
xy: 1063, 1820
|
|
||||||
size: 100, 100
|
|
||||||
orig: 100, 100
|
|
||||||
offset: 0, 0
|
|
||||||
index: -1
|
|
||||||
OtherIcons/Loading
|
|
||||||
rotate: false
|
|
||||||
xy: 1063, 1820
|
|
||||||
size: 100, 100
|
|
||||||
orig: 100, 100
|
|
||||||
offset: 0, 0
|
|
||||||
index: -1
|
|
||||||
NotificationIcons/PickConstruction
|
NotificationIcons/PickConstruction
|
||||||
rotate: false
|
rotate: false
|
||||||
xy: 1555, 1497
|
xy: 1555, 1497
|
||||||
@ -739,6 +732,27 @@ OtherIcons/Load
|
|||||||
orig: 100, 100
|
orig: 100, 100
|
||||||
offset: 0, 0
|
offset: 0, 0
|
||||||
index: -1
|
index: -1
|
||||||
|
OtherIcons/Loading
|
||||||
|
rotate: false
|
||||||
|
xy: 1063, 1820
|
||||||
|
size: 100, 100
|
||||||
|
orig: 100, 100
|
||||||
|
offset: 0, 0
|
||||||
|
index: -1
|
||||||
|
NotificationIcons/Loading
|
||||||
|
rotate: false
|
||||||
|
xy: 1063, 1820
|
||||||
|
size: 100, 100
|
||||||
|
orig: 100, 100
|
||||||
|
offset: 0, 0
|
||||||
|
index: -1
|
||||||
|
NotificationIcons/Working
|
||||||
|
rotate: false
|
||||||
|
xy: 1063, 1820
|
||||||
|
size: 100, 100
|
||||||
|
orig: 100, 100
|
||||||
|
offset: 0, 0
|
||||||
|
index: -1
|
||||||
OtherIcons/LockSmall
|
OtherIcons/LockSmall
|
||||||
rotate: false
|
rotate: false
|
||||||
xy: 725, 869
|
xy: 725, 869
|
||||||
|
190
android/assets/jsons/Civ V - Gods & Kings/Events.json
Normal file
@ -0,0 +1,190 @@
|
|||||||
|
[
|
||||||
|
{
|
||||||
|
"name":"Tutorial Task: [Move unit]",
|
||||||
|
"presentation":"Floating",
|
||||||
|
"civilopediaText":[
|
||||||
|
{"text":"Move a unit!", "centered":true},
|
||||||
|
{"extraImage":"Tutorials/Move unit", "imageSize":140},
|
||||||
|
{"text":"Click on a unit → Click on a destination → Click the arrow popup."},
|
||||||
|
],
|
||||||
|
"uniques": [
|
||||||
|
"Only available <if tutorials are enabled> <when number of [Units] is greater than [0]>",
|
||||||
|
"Unavailable <if tutorial [Move unit] is completed>"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name":"Tutorial Task: [Found city]",
|
||||||
|
"presentation":"Floating",
|
||||||
|
"civilopediaText":[
|
||||||
|
{"text":"Found a city!", "centered":true},
|
||||||
|
{"extraImage":"Tutorials/Found city", "imageSize":140},
|
||||||
|
{"text":"Select the Settler → Click on 'Found city'."},
|
||||||
|
],
|
||||||
|
"uniques": [
|
||||||
|
"Only available <if tutorials are enabled> <when number of [Settler] is greater than [0]>",
|
||||||
|
"Unavailable <if tutorial [Found city] is completed>"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name":"Tutorial Task: [Enter city screen]",
|
||||||
|
"presentation":"Floating",
|
||||||
|
"civilopediaText":[
|
||||||
|
{"text":"Enter the city screen!", "centered":true},
|
||||||
|
{"extraImage":"Tutorials/Enter city screen", "imageSize":100},
|
||||||
|
{"text":"Click the city button twice."},
|
||||||
|
],
|
||||||
|
"uniques": [
|
||||||
|
"Only available <if tutorials are enabled> <when number of [Cities] is greater than [0]>",
|
||||||
|
"Unavailable <if tutorial [Enter city screen] is completed>"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name":"Tutorial Task: [Pick technology]",
|
||||||
|
"presentation":"Floating",
|
||||||
|
"civilopediaText":[
|
||||||
|
{"text":"Pick a technology to research!", "centered":true},
|
||||||
|
{"extraImage":"Tutorials/Pick technology", "imageSize":180},
|
||||||
|
{"text":"Click on the tech button → select technology → click 'Research' (bottom right)."},
|
||||||
|
],
|
||||||
|
"uniques": [
|
||||||
|
"Only available <if tutorials are enabled>",
|
||||||
|
"Unavailable <if tutorial [Pick technology] is completed>"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name":"Tutorial Task: [Pick construction]",
|
||||||
|
"presentation":"Floating",
|
||||||
|
"civilopediaText":[
|
||||||
|
{"text":"Pick a construction!", "centered":true},
|
||||||
|
{"extraImage":"Tutorials/Pick construction", "imageSize":120},
|
||||||
|
{"text":"Enter city screen → Click on a unit or building → click 'add to queue'."},
|
||||||
|
],
|
||||||
|
"uniques": [
|
||||||
|
"Only available <if tutorials are enabled> <when number of [Cities] is greater than [0]>",
|
||||||
|
"Unavailable <if tutorial [Pick construction] is completed>"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name":"Tutorial Task: [Pass a turn]",
|
||||||
|
"presentation":"Floating",
|
||||||
|
"civilopediaText":[
|
||||||
|
{"text":"Pass a turn!", "centered":true},
|
||||||
|
{"extraImage":"Tutorials/Pass a turn", "imageSize":180},
|
||||||
|
{"text":"Cycle through units with 'Next unit' → Click 'Next turn'."},
|
||||||
|
],
|
||||||
|
"uniques": [
|
||||||
|
"Only available <if tutorials are enabled> <when number of [turns] is less than [3]>",
|
||||||
|
"Unavailable <if tutorial [Pass a turn] is completed>"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name":"Tutorial Task: [Reassign worked tiles]",
|
||||||
|
"presentation":"Floating",
|
||||||
|
"civilopediaText":[
|
||||||
|
{"text":"Reassign worked tiles!", "centered":true},
|
||||||
|
{"extraImage":"Tutorials/Reassign worked tiles", "imageSize":140},
|
||||||
|
{"text":"Enter city screen → click the assigned tile to unassign → click an unassigned tile to assign population."},
|
||||||
|
],
|
||||||
|
"uniques": [
|
||||||
|
"Only available <if tutorials are enabled> <when number of [Cities] is greater than [0]>",
|
||||||
|
"Unavailable <if tutorial [Reassign worked tiles] is completed>"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name":"Tutorial Task: [Meet another civilization]",
|
||||||
|
"presentation":"Floating",
|
||||||
|
"civilopediaText":[
|
||||||
|
{"text":"Meet another civilization!", "centered": true},
|
||||||
|
{"extraImage":"Tutorials/Meet another civilization", "imageSize":160},
|
||||||
|
{"text":"Explore the map until you encounter another civilization!"},
|
||||||
|
],
|
||||||
|
"uniques": [
|
||||||
|
"Only available <if tutorials are enabled> <when number of [turns] is greater than [1]>",
|
||||||
|
"Unavailable <if tutorial [Meet another civilization] is completed>"
|
||||||
|
],
|
||||||
|
"choices": [
|
||||||
|
{
|
||||||
|
"text":"Got it",
|
||||||
|
"triggeredUniques": ["Mark tutorial [Meet another civilization] complete"],
|
||||||
|
},
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name":"Tutorial Task: [Open the options table]",
|
||||||
|
"presentation":"Floating",
|
||||||
|
"civilopediaText":[
|
||||||
|
{"text":"Open the options dialog!", "centered":true},
|
||||||
|
{"extraImage":"Tutorials/Open the options table", "imageSize":130},
|
||||||
|
{"text":"Click the menu button (top left) → click 'Options'."},
|
||||||
|
],
|
||||||
|
"uniques": [
|
||||||
|
"Only available <if tutorials are enabled>",
|
||||||
|
"Unavailable <if tutorial [Open the options table] is completed>"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name":"Tutorial Task: [Construct an improvement]",
|
||||||
|
"presentation":"Floating",
|
||||||
|
"civilopediaText":[
|
||||||
|
{"text":"Construct an improvement!", "centered":true},
|
||||||
|
{"extraImage":"Tutorials/Construct an improvement", "imageSize":150},
|
||||||
|
{"text":"Construct a Worker unit →> Move it to a Plains or Grassland tile → Click 'Construct improvement' → Choose the farm → Leave the worker there until it's finished."},
|
||||||
|
],
|
||||||
|
"uniques": [
|
||||||
|
"Only available <if tutorials are enabled> <when number of [Cities] is greater than [0]>",
|
||||||
|
"Unavailable <if tutorial [Construct an improvement] is completed>"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name":"Tutorial Task: [Create a trade route]",
|
||||||
|
"presentation":"Floating",
|
||||||
|
"civilopediaText":[
|
||||||
|
{"text":"Create a trade route!", "centered":true},
|
||||||
|
{"extraImage":"Tutorials/Create a trade route", "imageSize":120},
|
||||||
|
{"text":"Construct roads between your capital and another city. Or, automate your worker and let him get to that eventually."},
|
||||||
|
],
|
||||||
|
"uniques": [
|
||||||
|
"Only available <if tutorials are enabled> <when number of [Cities] is greater than [1]>",
|
||||||
|
"Unavailable <if tutorial [Create a trade route] is completed>"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name":"Tutorial Task: [Conquer a city]",
|
||||||
|
"presentation":"Floating",
|
||||||
|
"civilopediaText":[
|
||||||
|
{"text":"Conquer a city!", "centered":true},
|
||||||
|
{"extraImage":"Tutorials/Conquer a city", "imageSize":160},
|
||||||
|
{"text":"Bring an enemy city down to low health → Enter the city with a melee unit."},
|
||||||
|
],
|
||||||
|
"uniques": [
|
||||||
|
"Only available <if tutorials are enabled> <when at war>",
|
||||||
|
"Unavailable <if tutorial [Conquer a city] is completed>"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name":"Tutorial Task: [Move an air unit]",
|
||||||
|
"presentation":"Floating",
|
||||||
|
"civilopediaText":[
|
||||||
|
{"text":"Move an air unit!", "centered":true},
|
||||||
|
{"extraImage":"Tutorials/Move an air unit", "imageSize":140},
|
||||||
|
{"text":"Select an air unit →> select another city within range → Move the unit to the other city."},
|
||||||
|
],
|
||||||
|
"uniques": [
|
||||||
|
"Only available <if tutorials are enabled> <when number of [Air units] is greater than [0]>",
|
||||||
|
"Unavailable <if tutorial [Move an air unit] is completed>"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name":"Tutorial Task: [See your stats breakdown]",
|
||||||
|
"presentation":"Floating",
|
||||||
|
"civilopediaText":[
|
||||||
|
{"text":"See your stats breakdown!", "centered":true},
|
||||||
|
{"extraImage":"Tutorials/See your stats breakdown", "imageSize":140},
|
||||||
|
{"text":"Enter the Overview screen (top right corner) → Click on 'Stats'."},
|
||||||
|
],
|
||||||
|
"uniques": [
|
||||||
|
"Only available <if tutorials are enabled>",
|
||||||
|
"Unavailable <if tutorial [See your stats breakdown] is completed>"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
]
|
@ -4,27 +4,38 @@ import com.unciv.logic.civilization.Civilization
|
|||||||
import com.unciv.models.ruleset.unique.Conditionals
|
import com.unciv.models.ruleset.unique.Conditionals
|
||||||
import com.unciv.models.ruleset.unique.StateForConditionals
|
import com.unciv.models.ruleset.unique.StateForConditionals
|
||||||
import com.unciv.models.ruleset.unique.Unique
|
import com.unciv.models.ruleset.unique.Unique
|
||||||
|
import com.unciv.models.ruleset.unique.UniqueTarget
|
||||||
import com.unciv.models.ruleset.unique.UniqueTriggerActivation
|
import com.unciv.models.ruleset.unique.UniqueTriggerActivation
|
||||||
import com.unciv.models.stats.INamed
|
import com.unciv.models.ruleset.unique.UniqueType
|
||||||
import com.unciv.ui.components.input.KeyCharAndCode
|
import com.unciv.ui.components.input.KeyCharAndCode
|
||||||
import com.unciv.ui.screens.civilopediascreen.FormattedLine
|
import com.unciv.ui.screens.civilopediascreen.FormattedLine
|
||||||
import com.unciv.ui.screens.civilopediascreen.ICivilopediaText
|
import com.unciv.ui.screens.civilopediascreen.ICivilopediaText
|
||||||
|
|
||||||
|
|
||||||
class Event : INamed, ICivilopediaText {
|
class Event : RulesetObject() {
|
||||||
|
enum class Presentation { None, Alert, Floating }
|
||||||
override var name = ""
|
val presentation = Presentation.Alert
|
||||||
var text = ""
|
var text = ""
|
||||||
override var civilopediaText = listOf<FormattedLine>()
|
|
||||||
|
override fun getUniqueTarget() = UniqueTarget.Event
|
||||||
override fun makeLink() = "Event/$name"
|
override fun makeLink() = "Event/$name"
|
||||||
|
|
||||||
// todo: add unrepeatable events
|
// todo: add unrepeatable events
|
||||||
|
|
||||||
var choices = ArrayList<EventChoice>()
|
var choices = ArrayList<EventChoice>()
|
||||||
|
|
||||||
/** @return `null` when no choice passes the condition tests, so client code can easily bail using Elvis `?:`. */
|
/** @return `null` when no choice passes the condition tests, so client code can easily bail using Elvis `?:`.
|
||||||
fun getMatchingChoices(stateForConditionals: StateForConditionals) =
|
* An empty list is possible when the Event definition contains no choices and the event's conditions are fulfilled.
|
||||||
choices.filter { it.matchesConditions(stateForConditionals) }.ifEmpty { null }
|
*/
|
||||||
|
fun getMatchingChoices(stateForConditionals: StateForConditionals): Collection<EventChoice>? {
|
||||||
|
if (!isAvailable(stateForConditionals)) return null
|
||||||
|
if (choices.isEmpty()) return emptyList()
|
||||||
|
return choices.filter { it.matchesConditions(stateForConditionals) }.ifEmpty { null }
|
||||||
|
}
|
||||||
|
|
||||||
|
fun isAvailable(stateForConditionals: StateForConditionals) =
|
||||||
|
getMatchingUniques(UniqueType.OnlyAvailable, StateForConditionals.IgnoreConditionals).none { !it.conditionalsApply(stateForConditionals) } &&
|
||||||
|
getMatchingUniques(UniqueType.Unavailable, stateForConditionals).none()
|
||||||
}
|
}
|
||||||
|
|
||||||
class EventChoice : ICivilopediaText {
|
class EventChoice : ICivilopediaText {
|
||||||
@ -44,8 +55,10 @@ class EventChoice : ICivilopediaText {
|
|||||||
fun matchesConditions(stateForConditionals: StateForConditionals) =
|
fun matchesConditions(stateForConditionals: StateForConditionals) =
|
||||||
conditionObjects.all { Conditionals.conditionalApplies(null, it, stateForConditionals) }
|
conditionObjects.all { Conditionals.conditionalApplies(null, it, stateForConditionals) }
|
||||||
|
|
||||||
fun triggerChoice(civ: Civilization) {
|
fun triggerChoice(civ: Civilization): Boolean {
|
||||||
|
var success = false
|
||||||
for (unique in triggeredUniqueObjects)
|
for (unique in triggeredUniqueObjects)
|
||||||
UniqueTriggerActivation.triggerUnique(unique, civ)
|
if (UniqueTriggerActivation.triggerUnique(unique, civ)) success = true
|
||||||
|
return success
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,5 +1,6 @@
|
|||||||
package com.unciv.models.ruleset.unique
|
package com.unciv.models.ruleset.unique
|
||||||
|
|
||||||
|
import com.unciv.UncivGame
|
||||||
import com.unciv.logic.GameInfo
|
import com.unciv.logic.GameInfo
|
||||||
import com.unciv.logic.battle.CombatAction
|
import com.unciv.logic.battle.CombatAction
|
||||||
import com.unciv.logic.city.City
|
import com.unciv.logic.city.City
|
||||||
@ -103,6 +104,8 @@ object Conditionals {
|
|||||||
UniqueType.ConditionalEveryTurns -> checkOnGameInfo { turns % condition.params[0].toInt() == 0 }
|
UniqueType.ConditionalEveryTurns -> checkOnGameInfo { turns % condition.params[0].toInt() == 0 }
|
||||||
UniqueType.ConditionalBeforeTurns -> checkOnGameInfo { turns < condition.params[0].toInt() }
|
UniqueType.ConditionalBeforeTurns -> checkOnGameInfo { turns < condition.params[0].toInt() }
|
||||||
UniqueType.ConditionalAfterTurns -> checkOnGameInfo { turns >= condition.params[0].toInt() }
|
UniqueType.ConditionalAfterTurns -> checkOnGameInfo { turns >= condition.params[0].toInt() }
|
||||||
|
UniqueType.ConditionalTutorialsEnabled -> UncivGame.Current.settings.showTutorials
|
||||||
|
UniqueType.ConditionalTutorialCompleted -> condition.params[0] in UncivGame.Current.settings.tutorialTasksCompleted
|
||||||
|
|
||||||
UniqueType.ConditionalCivFilter -> checkOnCiv { matchesFilter(condition.params[0]) }
|
UniqueType.ConditionalCivFilter -> checkOnCiv { matchesFilter(condition.params[0]) }
|
||||||
UniqueType.ConditionalWar -> checkOnCiv { isAtWar() }
|
UniqueType.ConditionalWar -> checkOnCiv { isAtWar() }
|
||||||
|
@ -12,20 +12,27 @@ object Countables {
|
|||||||
|
|
||||||
val gameInfo = stateForConditionals.gameInfo ?: return null
|
val gameInfo = stateForConditionals.gameInfo ?: return null
|
||||||
|
|
||||||
if (countable == "year") return stateForConditionals.gameInfo!!.getYear(gameInfo.turns)
|
if (countable == "turns") return gameInfo.turns
|
||||||
|
if (countable == "year") return gameInfo.getYear(gameInfo.turns)
|
||||||
|
|
||||||
val civInfo = stateForConditionals.relevantCiv ?: return null
|
val civInfo = stateForConditionals.relevantCiv ?: return null
|
||||||
|
|
||||||
|
if (countable == "Cities") return civInfo.cities.size
|
||||||
|
if (countable == "Units") return civInfo.units.getCivUnitsSize()
|
||||||
|
if (countable == "Air units") return civInfo.units.getCivUnits().count { it.baseUnit.movesLikeAirUnits() }
|
||||||
|
|
||||||
if (gameInfo.ruleset.tileResources.containsKey(countable))
|
if (gameInfo.ruleset.tileResources.containsKey(countable))
|
||||||
return stateForConditionals.getResourceAmount(countable)
|
return stateForConditionals.getResourceAmount(countable)
|
||||||
|
|
||||||
if (countable in gameInfo.ruleset.units){
|
val unitTypeName = countable.removeSuffix(" units").removeSurrounding("[", "]")
|
||||||
return civInfo.units.getCivUnits().count { it.name == countable }
|
if (unitTypeName in gameInfo.ruleset.unitTypes)
|
||||||
}
|
return civInfo.units.getCivUnits().count { it.type.name == unitTypeName }
|
||||||
|
|
||||||
if (countable in gameInfo.ruleset.buildings){
|
if (countable in gameInfo.ruleset.units)
|
||||||
|
return civInfo.units.getCivUnits().count { it.name == countable }
|
||||||
|
|
||||||
|
if (countable in gameInfo.ruleset.buildings)
|
||||||
return civInfo.cities.count { it.cityConstructions.containsBuildingOrEquivalent(countable) }
|
return civInfo.cities.count { it.cityConstructions.containsBuildingOrEquivalent(countable) }
|
||||||
}
|
|
||||||
|
|
||||||
return null
|
return null
|
||||||
}
|
}
|
||||||
|
@ -65,7 +65,7 @@ enum class UniqueParameterType(
|
|||||||
Countable("countable", "1000", "This indicates a number or a numeric variable") {
|
Countable("countable", "1000", "This indicates a number or a numeric variable") {
|
||||||
// todo add more countables
|
// todo add more countables
|
||||||
private val knownValues = setOf(
|
private val knownValues = setOf(
|
||||||
"year"
|
"year", "turns", "Cities", "Units"
|
||||||
)
|
)
|
||||||
|
|
||||||
override fun isKnownValue(parameterText: String, ruleset: Ruleset) = when {
|
override fun isKnownValue(parameterText: String, ruleset: Ruleset) = when {
|
||||||
@ -74,6 +74,7 @@ enum class UniqueParameterType(
|
|||||||
Stat.isStat(parameterText) -> true
|
Stat.isStat(parameterText) -> true
|
||||||
parameterText in ruleset.tileResources -> true
|
parameterText in ruleset.tileResources -> true
|
||||||
parameterText in ruleset.units -> true
|
parameterText in ruleset.units -> true
|
||||||
|
parameterText.removeSuffix(" units").removeSurrounding("[", "]") in ruleset.unitTypes -> true
|
||||||
else -> parameterText in ruleset.buildings
|
else -> parameterText in ruleset.buildings
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
@ -58,6 +58,7 @@ enum class UniqueTarget(
|
|||||||
Tutorial,
|
Tutorial,
|
||||||
CityState(inheritsFrom = Global),
|
CityState(inheritsFrom = Global),
|
||||||
ModOptions,
|
ModOptions,
|
||||||
|
Event,
|
||||||
|
|
||||||
// Modifiers
|
// Modifiers
|
||||||
Conditional("Modifiers that can be added to other uniques to limit when they will be active", modifierType = ModifierType.Conditional),
|
Conditional("Modifiers that can be added to other uniques to limit when they will be active", modifierType = ModifierType.Conditional),
|
||||||
|
@ -23,6 +23,7 @@ import com.unciv.logic.map.mapunit.MapUnit
|
|||||||
import com.unciv.logic.map.tile.Tile
|
import com.unciv.logic.map.tile.Tile
|
||||||
import com.unciv.models.UpgradeUnitAction
|
import com.unciv.models.UpgradeUnitAction
|
||||||
import com.unciv.models.ruleset.BeliefType
|
import com.unciv.models.ruleset.BeliefType
|
||||||
|
import com.unciv.models.ruleset.Event
|
||||||
import com.unciv.models.ruleset.tile.TerrainType
|
import com.unciv.models.ruleset.tile.TerrainType
|
||||||
import com.unciv.models.stats.Stat
|
import com.unciv.models.stats.Stat
|
||||||
import com.unciv.models.stats.Stats
|
import com.unciv.models.stats.Stats
|
||||||
@ -115,11 +116,20 @@ object UniqueTriggerActivation {
|
|||||||
val event = ruleset.events[unique.params[0]] ?: return null
|
val event = ruleset.events[unique.params[0]] ?: return null
|
||||||
val choices = event.getMatchingChoices(stateForConditionals)
|
val choices = event.getMatchingChoices(stateForConditionals)
|
||||||
?: return null
|
?: return null
|
||||||
return {
|
if (civInfo.isAI() || event.presentation == Event.Presentation.None) return {
|
||||||
if (civInfo.isAI()) choices.random().triggerChoice(civInfo)
|
choices.randomOrNull()?.triggerChoice(civInfo) ?: false
|
||||||
else civInfo.popupAlerts.add(PopupAlert(AlertType.Event, event.name))
|
}
|
||||||
|
if (event.presentation == Event.Presentation.Alert) return {
|
||||||
|
civInfo.popupAlerts.add(PopupAlert(AlertType.Event, event.name))
|
||||||
true
|
true
|
||||||
}
|
}
|
||||||
|
// if (event.presentation == Event.Presentation.Floating) return { //todo: Park them in a Queue in GameInfo???
|
||||||
|
throw NotImplementedError("Event ${event.name} has presentation type ${event.presentation} which is not implemented for use via TriggerEvent")
|
||||||
|
}
|
||||||
|
|
||||||
|
UniqueType.MarkTutorialComplete -> return {
|
||||||
|
UncivGame.Current.settings.addCompletedTutorialTask(unique.params[0])
|
||||||
|
true
|
||||||
}
|
}
|
||||||
|
|
||||||
UniqueType.OneTimeFreeUnit -> {
|
UniqueType.OneTimeFreeUnit -> {
|
||||||
|
@ -276,15 +276,16 @@ enum class UniqueType(
|
|||||||
MaxNumberBuildable("Limited to [amount] per Civilization", UniqueTarget.Building, UniqueTarget.Unit),
|
MaxNumberBuildable("Limited to [amount] per Civilization", UniqueTarget.Building, UniqueTarget.Unit),
|
||||||
HiddenBeforeAmountPolicies("Hidden until [amount] social policy branches have been completed", UniqueTarget.Building, UniqueTarget.Unit),
|
HiddenBeforeAmountPolicies("Hidden until [amount] social policy branches have been completed", UniqueTarget.Building, UniqueTarget.Unit),
|
||||||
/** A special unique, as it only activates [RejectionReasonType] when it has conditionals that *do not* apply.
|
/** A special unique, as it only activates [RejectionReasonType] when it has conditionals that *do not* apply.
|
||||||
* Meant to be used together with conditionals, like "Buildable only <after adopting [policy]> <while the empire is happy>".
|
* Meant to be used together with conditionals, like `"Only available <after adopting [Piety]> <while the empire is happy>"`.
|
||||||
* Restricts Upgrade/Transform pathways.
|
* Restricts Upgrade/Transform pathways.
|
||||||
* @See [CanOnlyBeBuiltWhen]
|
* @See [CanOnlyBeBuiltWhen]
|
||||||
*/
|
*/
|
||||||
OnlyAvailable("Only available", UniqueTarget.Unit, UniqueTarget.Building, UniqueTarget.Improvement,
|
OnlyAvailable("Only available", UniqueTarget.Unit, UniqueTarget.Building, UniqueTarget.Improvement,
|
||||||
UniqueTarget.Policy, UniqueTarget.Tech, UniqueTarget.Promotion, UniqueTarget.Ruins, UniqueTarget.FollowerBelief, UniqueTarget.FounderBelief,
|
UniqueTarget.Policy, UniqueTarget.Tech, UniqueTarget.Promotion, UniqueTarget.Ruins,
|
||||||
|
UniqueTarget.FollowerBelief, UniqueTarget.FounderBelief, UniqueTarget.Event,
|
||||||
docDescription = "Meant to be used together with conditionals, like \"Only available <after adopting [policy]> <while the empire is happy>\". Only allows Building when ALL conditionals are met. Will also block Upgrade and Transform actions. See also CanOnlyBeBuiltWhen"),
|
docDescription = "Meant to be used together with conditionals, like \"Only available <after adopting [policy]> <while the empire is happy>\". Only allows Building when ALL conditionals are met. Will also block Upgrade and Transform actions. See also CanOnlyBeBuiltWhen"),
|
||||||
Unavailable("Unavailable", UniqueTarget.Unit, UniqueTarget.Building, UniqueTarget.Improvement,
|
Unavailable("Unavailable", UniqueTarget.Unit, UniqueTarget.Building, UniqueTarget.Improvement,
|
||||||
UniqueTarget.Policy, UniqueTarget.Tech, UniqueTarget.Promotion, UniqueTarget.Ruins,
|
UniqueTarget.Policy, UniqueTarget.Tech, UniqueTarget.Promotion, UniqueTarget.Ruins, UniqueTarget.Event,
|
||||||
docDescription = "Meant to be used together with conditionals, like \"Unavailable <after generating a Great Prophet>\"."),
|
docDescription = "Meant to be used together with conditionals, like \"Unavailable <after generating a Great Prophet>\"."),
|
||||||
|
|
||||||
ConvertFoodToProductionWhenConstructed("Excess Food converted to Production when under construction", UniqueTarget.Building, UniqueTarget.Unit),
|
ConvertFoodToProductionWhenConstructed("Excess Food converted to Production when under construction", UniqueTarget.Building, UniqueTarget.Unit),
|
||||||
@ -644,7 +645,8 @@ enum class UniqueType(
|
|||||||
ConditionalEveryTurns("every [positiveAmount] turns", UniqueTarget.Conditional),
|
ConditionalEveryTurns("every [positiveAmount] turns", UniqueTarget.Conditional),
|
||||||
ConditionalBeforeTurns("before [amount] turns", UniqueTarget.Conditional),
|
ConditionalBeforeTurns("before [amount] turns", UniqueTarget.Conditional),
|
||||||
ConditionalAfterTurns("after [amount] turns", UniqueTarget.Conditional),
|
ConditionalAfterTurns("after [amount] turns", UniqueTarget.Conditional),
|
||||||
|
ConditionalTutorialsEnabled("if tutorials are enabled", UniqueTarget.Conditional, flags = UniqueFlag.setOfHiddenToUsers), // Hidden as no translations needed for now
|
||||||
|
ConditionalTutorialCompleted("if tutorial [comment] is completed", UniqueTarget.Conditional, flags = UniqueFlag.setOfHiddenToUsers), // Hidden as no translations needed for now
|
||||||
|
|
||||||
/////// civ conditionals
|
/////// civ conditionals
|
||||||
ConditionalCivFilter("for [civFilter]", UniqueTarget.Conditional),
|
ConditionalCivFilter("for [civFilter]", UniqueTarget.Conditional),
|
||||||
@ -890,6 +892,7 @@ enum class UniqueType(
|
|||||||
AllowRazeHolyCity("Allow raze holy city", UniqueTarget.ModOptions, flags = UniqueFlag.setOfNoConditionals),
|
AllowRazeHolyCity("Allow raze holy city", UniqueTarget.ModOptions, flags = UniqueFlag.setOfNoConditionals),
|
||||||
|
|
||||||
SuppressWarnings("Suppress warning [validationWarning]", *UniqueTarget.CanIncludeSuppression, flags = UniqueFlag.setOfHiddenNoConditionals, docDescription = Suppression.uniqueDocDescription),
|
SuppressWarnings("Suppress warning [validationWarning]", *UniqueTarget.CanIncludeSuppression, flags = UniqueFlag.setOfHiddenNoConditionals, docDescription = Suppression.uniqueDocDescription),
|
||||||
|
MarkTutorialComplete("Mark tutorial [comment] complete", UniqueTarget.Event, flags = UniqueFlag.setOfHiddenNoConditionals),
|
||||||
|
|
||||||
// Declarative Mod compatibility (see [ModCompatibility]):
|
// Declarative Mod compatibility (see [ModCompatibility]):
|
||||||
// Note there is currently no display for these, but UniqueFlag.HiddenToUsers is not set.
|
// Note there is currently no display for these, but UniqueFlag.HiddenToUsers is not set.
|
||||||
|
@ -428,7 +428,7 @@ object TranslationFileWriter {
|
|||||||
// Promotion names are not uniques but since we did the "[unitName] ability"
|
// Promotion names are not uniques but since we did the "[unitName] ability"
|
||||||
// they need the "parameters" treatment too
|
// they need the "parameters" treatment too
|
||||||
// Same for victory milestones
|
// Same for victory milestones
|
||||||
(field.name == "uniques" || field.name == "promotions" || field.name == "milestones")
|
(field.name in fieldsToProcessParameters)
|
||||||
&& (fieldValue is java.util.AbstractCollection<*>) ->
|
&& (fieldValue is java.util.AbstractCollection<*>) ->
|
||||||
for (item in fieldValue)
|
for (item in fieldValue)
|
||||||
if (item is String) submitString(item, Unique(item)) else serializeElement(item!!)
|
if (item is String) submitString(item, Unique(item)) else serializeElement(item!!)
|
||||||
@ -464,6 +464,8 @@ object TranslationFileWriter {
|
|||||||
"RuinReward.uniques", "TerrainType.name",
|
"RuinReward.uniques", "TerrainType.name",
|
||||||
"CityStateType.friendBonusUniques", "CityStateType.allyBonusUniques",
|
"CityStateType.friendBonusUniques", "CityStateType.allyBonusUniques",
|
||||||
"Era.citySound",
|
"Era.citySound",
|
||||||
|
"keyShortcut",
|
||||||
|
"Event.name" // Presently not shown anywhere
|
||||||
)
|
)
|
||||||
|
|
||||||
/** Specifies Enums where the name property _is_ translatable, by Class name */
|
/** Specifies Enums where the name property _is_ translatable, by Class name */
|
||||||
@ -476,6 +478,11 @@ object TranslationFileWriter {
|
|||||||
UniqueParameterType.Comment
|
UniqueParameterType.Comment
|
||||||
)
|
)
|
||||||
|
|
||||||
|
private val fieldsToProcessParameters = setOf(
|
||||||
|
"uniques", "promotions", "milestones",
|
||||||
|
"triggeredUniques", "conditions"
|
||||||
|
)
|
||||||
|
|
||||||
private fun isFieldTypeRelevant(type: Class<*>) =
|
private fun isFieldTypeRelevant(type: Class<*>) =
|
||||||
type == String::class.java ||
|
type == String::class.java ||
|
||||||
type == java.util.ArrayList::class.java ||
|
type == java.util.ArrayList::class.java ||
|
||||||
|
@ -225,7 +225,7 @@ class StatsOverviewTab(
|
|||||||
private fun updateScoreTable() = scoreTable.apply {
|
private fun updateScoreTable() = scoreTable.apply {
|
||||||
clear()
|
clear()
|
||||||
val scoreHeader = Table()
|
val scoreHeader = Table()
|
||||||
val scoreIcon = ImageGetter.getImage("CityStateIcons/Cultured")
|
val scoreIcon = ImageGetter.getImage("OtherIcons/Score")
|
||||||
scoreIcon.color = Color.FIREBRICK
|
scoreIcon.color = Color.FIREBRICK
|
||||||
scoreHeader.add(scoreIcon).padRight(1f).size(Constants.headingFontSize.toFloat())
|
scoreHeader.add(scoreIcon).padRight(1f).size(Constants.headingFontSize.toFloat())
|
||||||
scoreHeader.add("Score".toLabel(fontSize = Constants.headingFontSize))
|
scoreHeader.add("Score".toLabel(fontSize = Constants.headingFontSize))
|
||||||
|
@ -10,7 +10,7 @@ enum class RankingType(
|
|||||||
val idForSerialization: String
|
val idForSerialization: String
|
||||||
) {
|
) {
|
||||||
// production, gold, happiness, and culture already have icons added when the line is `tr()`anslated
|
// production, gold, happiness, and culture already have icons added when the line is `tr()`anslated
|
||||||
Score({ ImageGetter.getImage("CityStateIcons/Cultured").apply { color = Color.FIREBRICK } }, "S"),
|
Score({ ImageGetter.getImage("OtherIcons/Score").apply { color = Color.FIREBRICK } }, "S"),
|
||||||
Population({ ImageGetter.getStatIcon("Population") }, "N"),
|
Population({ ImageGetter.getStatIcon("Population") }, "N"),
|
||||||
CropYield("Crop Yield", { ImageGetter.getStatIcon("Food") }, "C"),
|
CropYield("Crop Yield", { ImageGetter.getStatIcon("Food") }, "C"),
|
||||||
Production("P"),
|
Production("P"),
|
||||||
|
@ -18,26 +18,20 @@ import com.unciv.logic.civilization.PopupAlert
|
|||||||
import com.unciv.logic.civilization.diplomacy.DiplomacyFlags
|
import com.unciv.logic.civilization.diplomacy.DiplomacyFlags
|
||||||
import com.unciv.logic.civilization.diplomacy.DiplomaticModifiers
|
import com.unciv.logic.civilization.diplomacy.DiplomaticModifiers
|
||||||
import com.unciv.logic.civilization.diplomacy.RelationshipLevel
|
import com.unciv.logic.civilization.diplomacy.RelationshipLevel
|
||||||
import com.unciv.models.ruleset.EventChoice
|
|
||||||
import com.unciv.models.ruleset.unique.StateForConditionals
|
|
||||||
import com.unciv.models.ruleset.unique.UniqueType
|
import com.unciv.models.ruleset.unique.UniqueType
|
||||||
import com.unciv.models.translations.fillPlaceholders
|
import com.unciv.models.translations.fillPlaceholders
|
||||||
import com.unciv.models.translations.tr
|
import com.unciv.models.translations.tr
|
||||||
import com.unciv.ui.audio.MusicMood
|
import com.unciv.ui.audio.MusicMood
|
||||||
import com.unciv.ui.audio.MusicTrackChooserFlags
|
import com.unciv.ui.audio.MusicTrackChooserFlags
|
||||||
import com.unciv.ui.components.UncivTooltip.Companion.addTooltip
|
|
||||||
import com.unciv.ui.components.extensions.disable
|
import com.unciv.ui.components.extensions.disable
|
||||||
import com.unciv.ui.components.extensions.pad
|
import com.unciv.ui.components.extensions.pad
|
||||||
import com.unciv.ui.components.extensions.toLabel
|
import com.unciv.ui.components.extensions.toLabel
|
||||||
import com.unciv.ui.components.extensions.toTextButton
|
import com.unciv.ui.components.extensions.toTextButton
|
||||||
import com.unciv.ui.components.input.KeyCharAndCode
|
|
||||||
import com.unciv.ui.components.input.KeyboardBinding
|
import com.unciv.ui.components.input.KeyboardBinding
|
||||||
import com.unciv.ui.components.input.keyShortcuts
|
import com.unciv.ui.components.input.keyShortcuts
|
||||||
import com.unciv.ui.components.input.onActivation
|
import com.unciv.ui.components.input.onActivation
|
||||||
import com.unciv.ui.images.ImageGetter
|
import com.unciv.ui.images.ImageGetter
|
||||||
import com.unciv.ui.popups.Popup
|
import com.unciv.ui.popups.Popup
|
||||||
import com.unciv.ui.screens.civilopediascreen.FormattedLine
|
|
||||||
import com.unciv.ui.screens.civilopediascreen.MarkupRenderer
|
|
||||||
import com.unciv.ui.screens.diplomacyscreen.LeaderIntroTable
|
import com.unciv.ui.screens.diplomacyscreen.LeaderIntroTable
|
||||||
import com.unciv.ui.screens.victoryscreen.VictoryScreen
|
import com.unciv.ui.screens.victoryscreen.VictoryScreen
|
||||||
import java.util.EnumSet
|
import java.util.EnumSet
|
||||||
@ -507,18 +501,9 @@ class AlertPopup(
|
|||||||
/** Returns if event was triggered correctly */
|
/** Returns if event was triggered correctly */
|
||||||
private fun addEvent(): Boolean {
|
private fun addEvent(): Boolean {
|
||||||
val event = gameInfo.ruleset.events[popupAlert.value] ?: return false
|
val event = gameInfo.ruleset.events[popupAlert.value] ?: return false
|
||||||
|
val render = RenderEvent(event, worldScreen) { close() }
|
||||||
val stateForConditionals = StateForConditionals(gameInfo.currentPlayerCiv)
|
if (!render.isValid) return false
|
||||||
val choices = event.getMatchingChoices(stateForConditionals)
|
add(render).pad(0f).row()
|
||||||
?: return false
|
|
||||||
|
|
||||||
if (event.text.isNotEmpty())
|
|
||||||
addGoodSizedLabel(event.text)
|
|
||||||
if (event.civilopediaText.isNotEmpty()) {
|
|
||||||
add(event.renderCivilopediaText(stageWidth * 0.5f, ::openCivilopedia)).row()
|
|
||||||
}
|
|
||||||
|
|
||||||
for (choice in choices) addChoice(choice)
|
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -529,30 +514,4 @@ class AlertPopup(
|
|||||||
worldScreen.shouldUpdate = true
|
worldScreen.shouldUpdate = true
|
||||||
super.close()
|
super.close()
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun addChoice(choice: EventChoice) {
|
|
||||||
addSeparator()
|
|
||||||
|
|
||||||
val button = choice.text.toTextButton()
|
|
||||||
button.onActivation {
|
|
||||||
close()
|
|
||||||
choice.triggerChoice(gameInfo.currentPlayerCiv)
|
|
||||||
}
|
|
||||||
val key = KeyCharAndCode.parse(choice.keyShortcut)
|
|
||||||
if (key != KeyCharAndCode.UNKNOWN) {
|
|
||||||
button.keyShortcuts.add(key)
|
|
||||||
button.addTooltip(key)
|
|
||||||
}
|
|
||||||
add(button).row()
|
|
||||||
|
|
||||||
val lines = (
|
|
||||||
choice.civilopediaText.asSequence()
|
|
||||||
+ choice.triggeredUniqueObjects.asSequence()
|
|
||||||
.filterNot { it.isHiddenToUsers() }
|
|
||||||
.map { FormattedLine(it) }
|
|
||||||
).asIterable()
|
|
||||||
add(MarkupRenderer.render(lines, stageWidth * 0.5f, linkAction = ::openCivilopedia)).row()
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun openCivilopedia(link: String) = worldScreen.openCivilopedia(link)
|
|
||||||
}
|
}
|
||||||
|
78
core/src/com/unciv/ui/screens/worldscreen/RenderEvent.kt
Normal file
@ -0,0 +1,78 @@
|
|||||||
|
package com.unciv.ui.screens.worldscreen
|
||||||
|
|
||||||
|
import com.badlogic.gdx.scenes.scene2d.ui.Table
|
||||||
|
import com.badlogic.gdx.utils.Align
|
||||||
|
import com.unciv.models.ruleset.Event
|
||||||
|
import com.unciv.models.ruleset.EventChoice
|
||||||
|
import com.unciv.models.ruleset.unique.StateForConditionals
|
||||||
|
import com.unciv.ui.components.UncivTooltip.Companion.addTooltip
|
||||||
|
import com.unciv.ui.components.extensions.addSeparator
|
||||||
|
import com.unciv.ui.components.extensions.toTextButton
|
||||||
|
import com.unciv.ui.components.input.KeyCharAndCode
|
||||||
|
import com.unciv.ui.components.input.keyShortcuts
|
||||||
|
import com.unciv.ui.components.input.onActivation
|
||||||
|
import com.unciv.ui.components.widgets.WrappableLabel
|
||||||
|
import com.unciv.ui.screens.civilopediascreen.FormattedLine
|
||||||
|
import com.unciv.ui.screens.civilopediascreen.MarkupRenderer
|
||||||
|
|
||||||
|
/** Renders an [Event] for [AlertPopup] or a floating tutorial task on [WorldScreen] */
|
||||||
|
class RenderEvent(
|
||||||
|
event: Event,
|
||||||
|
val worldScreen: WorldScreen,
|
||||||
|
val onChoice: (EventChoice) -> Unit
|
||||||
|
) : Table() {
|
||||||
|
private val gameInfo get() = worldScreen.gameInfo
|
||||||
|
private val stageWidth get() = worldScreen.stage.width
|
||||||
|
|
||||||
|
val isValid: Boolean
|
||||||
|
|
||||||
|
//todo check generated translations
|
||||||
|
|
||||||
|
init {
|
||||||
|
defaults().fillX().center().pad(5f)
|
||||||
|
|
||||||
|
val stateForConditionals = StateForConditionals(gameInfo.currentPlayerCiv)
|
||||||
|
val choices = event.getMatchingChoices(stateForConditionals)
|
||||||
|
isValid = choices != null
|
||||||
|
if (isValid) {
|
||||||
|
if (event.text.isNotEmpty()) {
|
||||||
|
add(WrappableLabel(event.text, stageWidth * 0.5f).apply {
|
||||||
|
wrap = true
|
||||||
|
setAlignment(Align.center)
|
||||||
|
optimizePrefWidth()
|
||||||
|
}).row()
|
||||||
|
}
|
||||||
|
if (event.civilopediaText.isNotEmpty()) {
|
||||||
|
add(event.renderCivilopediaText(stageWidth * 0.5f, ::openCivilopedia)).row()
|
||||||
|
}
|
||||||
|
|
||||||
|
for (choice in choices!!) addChoice(choice)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun addChoice(choice: EventChoice) {
|
||||||
|
addSeparator()
|
||||||
|
|
||||||
|
val button = choice.text.toTextButton()
|
||||||
|
button.onActivation {
|
||||||
|
onChoice(choice)
|
||||||
|
choice.triggerChoice(gameInfo.currentPlayerCiv)
|
||||||
|
}
|
||||||
|
val key = KeyCharAndCode.parse(choice.keyShortcut)
|
||||||
|
if (key != KeyCharAndCode.UNKNOWN) {
|
||||||
|
button.keyShortcuts.add(key)
|
||||||
|
button.addTooltip(key)
|
||||||
|
}
|
||||||
|
add(button).row()
|
||||||
|
|
||||||
|
val lines = (
|
||||||
|
choice.civilopediaText.asSequence()
|
||||||
|
+ choice.triggeredUniqueObjects.asSequence()
|
||||||
|
.filterNot { it.isHiddenToUsers() }
|
||||||
|
.map { FormattedLine(it) }
|
||||||
|
).asIterable()
|
||||||
|
add(MarkupRenderer.render(lines, stageWidth * 0.5f, linkAction = ::openCivilopedia)).row()
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun openCivilopedia(link: String) = worldScreen.openCivilopedia(link)
|
||||||
|
}
|
@ -21,11 +21,12 @@ import com.unciv.logic.multiplayer.storage.MultiplayerAuthException
|
|||||||
import com.unciv.logic.trade.TradeEvaluation
|
import com.unciv.logic.trade.TradeEvaluation
|
||||||
import com.unciv.models.TutorialTrigger
|
import com.unciv.models.TutorialTrigger
|
||||||
import com.unciv.models.metadata.GameSetupInfo
|
import com.unciv.models.metadata.GameSetupInfo
|
||||||
|
import com.unciv.models.ruleset.Event
|
||||||
import com.unciv.models.ruleset.tile.ResourceType
|
import com.unciv.models.ruleset.tile.ResourceType
|
||||||
|
import com.unciv.models.ruleset.unique.StateForConditionals
|
||||||
import com.unciv.models.ruleset.unique.UniqueType
|
import com.unciv.models.ruleset.unique.UniqueType
|
||||||
import com.unciv.ui.components.extensions.centerX
|
import com.unciv.ui.components.extensions.centerX
|
||||||
import com.unciv.ui.components.extensions.darken
|
import com.unciv.ui.components.extensions.darken
|
||||||
import com.unciv.ui.components.extensions.toLabel
|
|
||||||
import com.unciv.ui.components.input.KeyShortcutDispatcherVeto
|
import com.unciv.ui.components.input.KeyShortcutDispatcherVeto
|
||||||
import com.unciv.ui.components.input.KeyboardBinding
|
import com.unciv.ui.components.input.KeyboardBinding
|
||||||
import com.unciv.ui.components.input.KeyboardPanningListener
|
import com.unciv.ui.components.input.KeyboardPanningListener
|
||||||
@ -122,6 +123,7 @@ class WorldScreen(
|
|||||||
private val tutorialTaskTable = Table().apply {
|
private val tutorialTaskTable = Table().apply {
|
||||||
background = skinStrings.getUiBackground("WorldScreen/TutorialTaskTable", tintColor = skinStrings.skinConfig.baseColor.darken(0.5f))
|
background = skinStrings.getUiBackground("WorldScreen/TutorialTaskTable", tintColor = skinStrings.skinConfig.baseColor.darken(0.5f))
|
||||||
}
|
}
|
||||||
|
private var tutorialTaskTableHash = 0
|
||||||
|
|
||||||
private var nextTurnUpdateJob: Job? = null
|
private var nextTurnUpdateJob: Job? = null
|
||||||
|
|
||||||
@ -149,10 +151,10 @@ class WorldScreen(
|
|||||||
stage.scrollFocus = mapHolder
|
stage.scrollFocus = mapHolder
|
||||||
stage.addActor(notificationsScroll) // very low in z-order, so we're free to let it extend _below_ tile info and minimap if we want
|
stage.addActor(notificationsScroll) // very low in z-order, so we're free to let it extend _below_ tile info and minimap if we want
|
||||||
stage.addActor(minimapWrapper)
|
stage.addActor(minimapWrapper)
|
||||||
|
stage.addActor(tutorialTaskTable) // behind topBar!
|
||||||
stage.addActor(topBar)
|
stage.addActor(topBar)
|
||||||
stage.addActor(statusButtons)
|
stage.addActor(statusButtons)
|
||||||
stage.addActor(techPolicyAndDiplomacy)
|
stage.addActor(techPolicyAndDiplomacy)
|
||||||
stage.addActor(tutorialTaskTable)
|
|
||||||
|
|
||||||
stage.addActor(zoomController)
|
stage.addActor(zoomController)
|
||||||
zoomController.isVisible = UncivGame.Current.settings.showZoomButtons
|
zoomController.isVisible = UncivGame.Current.settings.showZoomButtons
|
||||||
@ -401,6 +403,8 @@ class WorldScreen(
|
|||||||
else mapHolder.updateTiles(viewingCiv)
|
else mapHolder.updateTiles(viewingCiv)
|
||||||
|
|
||||||
topBar.update(selectedCiv)
|
topBar.update(selectedCiv)
|
||||||
|
if (tutorialTaskTable.isVisible)
|
||||||
|
tutorialTaskTable.y = topBar.getYForTutorialTask() - tutorialTaskTable.height
|
||||||
|
|
||||||
if (techPolicyAndDiplomacy.update())
|
if (techPolicyAndDiplomacy.update())
|
||||||
displayTutorial(TutorialTrigger.OtherCivEncountered)
|
displayTutorial(TutorialTrigger.OtherCivEncountered)
|
||||||
@ -450,50 +454,16 @@ class WorldScreen(
|
|||||||
zoomController.setPosition(stage.width - posZoomFromRight - 10f, 10f, Align.bottomRight)
|
zoomController.setPosition(stage.width - posZoomFromRight - 10f, 10f, Align.bottomRight)
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun getCurrentTutorialTask(): String {
|
private fun getCurrentTutorialTask(): Event? {
|
||||||
val completedTasks = game.settings.tutorialTasksCompleted
|
if (!game.settings.tutorialTasksCompleted.contains("Create a trade route")) {
|
||||||
if (!completedTasks.contains("Move unit"))
|
if (viewingCiv.cache.citiesConnectedToCapitalToMediums.any { it.key.civ == viewingCiv })
|
||||||
return "Move a unit!\nClick on a unit > Click on a destination > Click the arrow popup"
|
game.settings.addCompletedTutorialTask("Create a trade route")
|
||||||
if (!completedTasks.contains("Found city"))
|
}
|
||||||
return "Found a city!\nSelect the Settler (flag unit) > Click on 'Found city' (bottom-left corner)"
|
val stateForConditionals = StateForConditionals(viewingCiv)
|
||||||
if (!completedTasks.contains("Enter city screen"))
|
return gameInfo.ruleset.events.values.firstOrNull {
|
||||||
return "Enter the city screen!\nClick the city button twice"
|
it.presentation == Event.Presentation.Floating &&
|
||||||
if (!completedTasks.contains("Pick technology"))
|
it.isAvailable(stateForConditionals)
|
||||||
return "Pick a technology to research!\nClick on the tech button (greenish, top left) > " +
|
}
|
||||||
"\n select technology > click 'Research' (bottom right)"
|
|
||||||
if (!completedTasks.contains("Pick construction"))
|
|
||||||
return "Pick a construction!\nEnter city screen > Click on a unit or building (bottom left side) >" +
|
|
||||||
" \n click 'add to queue'"
|
|
||||||
if (!completedTasks.contains("Pass a turn"))
|
|
||||||
return "Pass a turn!\nCycle through units with 'Next unit' > Click 'Next turn'"
|
|
||||||
if (!completedTasks.contains("Reassign worked tiles"))
|
|
||||||
return "Reassign worked tiles!\nEnter city screen > click the assigned (green) tile to unassign > " +
|
|
||||||
"\n click an unassigned tile to assign population"
|
|
||||||
if (!completedTasks.contains("Meet another civilization"))
|
|
||||||
return "Meet another civilization!\nExplore the map until you encounter another civilization!"
|
|
||||||
if (!completedTasks.contains("Open the options table"))
|
|
||||||
return "Open the options table!\nClick the menu button (top left) > click 'Options'"
|
|
||||||
if (!completedTasks.contains("Construct an improvement"))
|
|
||||||
return "Construct an improvement!\nConstruct a Worker unit > Move to a Plains or Grassland tile > " +
|
|
||||||
"\n Click 'Construct improvement' (above the unit table, bottom left)" +
|
|
||||||
"\n > Choose the farm > \n Leave the worker there until it's finished"
|
|
||||||
if (!completedTasks.contains("Create a trade route")
|
|
||||||
&& viewingCiv.cache.citiesConnectedToCapitalToMediums.any { it.key.civ == viewingCiv })
|
|
||||||
game.settings.addCompletedTutorialTask("Create a trade route")
|
|
||||||
if (viewingCiv.cities.size > 1 && !completedTasks.contains("Create a trade route"))
|
|
||||||
return "Create a trade route!\nConstruct roads between your capital and another city" +
|
|
||||||
"\nOr, automate your worker and let him get to that eventually"
|
|
||||||
if (viewingCiv.isAtWar() && !completedTasks.contains("Conquer a city"))
|
|
||||||
return "Conquer a city!\nBring an enemy city down to low health > " +
|
|
||||||
"\nEnter the city with a melee unit"
|
|
||||||
if (viewingCiv.units.getCivUnits().any { it.baseUnit.movesLikeAirUnits() } && !completedTasks.contains("Move an air unit"))
|
|
||||||
return "Move an air unit!\nSelect an air unit > select another city within range > " +
|
|
||||||
"\nMove the unit to the other city"
|
|
||||||
if (!completedTasks.contains("See your stats breakdown"))
|
|
||||||
return "See your stats breakdown!\nEnter the Overview screen (top right corner) >" +
|
|
||||||
"\nClick on 'Stats'"
|
|
||||||
|
|
||||||
return ""
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun displayTutorialsOnUpdate() {
|
private fun displayTutorialsOnUpdate() {
|
||||||
@ -524,27 +494,38 @@ class WorldScreen(
|
|||||||
}
|
}
|
||||||
|
|
||||||
private fun displayTutorialTaskOnUpdate() {
|
private fun displayTutorialTaskOnUpdate() {
|
||||||
tutorialTaskTable.clear()
|
fun setInvisible() {
|
||||||
val tutorialTask = getCurrentTutorialTask()
|
|
||||||
if (tutorialTask == "" || !game.settings.showTutorials || viewingCiv.isDefeated()) {
|
|
||||||
tutorialTaskTable.isVisible = false
|
tutorialTaskTable.isVisible = false
|
||||||
return
|
tutorialTaskTable.clear()
|
||||||
|
tutorialTaskTableHash = 0
|
||||||
}
|
}
|
||||||
|
if (!game.settings.showTutorials || viewingCiv.isDefeated()) return setInvisible()
|
||||||
|
val tutorialTask = getCurrentTutorialTask() ?: return setInvisible()
|
||||||
|
|
||||||
tutorialTaskTable.isVisible = true
|
|
||||||
if (!UncivGame.Current.isTutorialTaskCollapsed) {
|
if (!UncivGame.Current.isTutorialTaskCollapsed) {
|
||||||
tutorialTaskTable.add(tutorialTask.toLabel()
|
val hash = tutorialTask.hashCode() // Default implementation is OK - we see the same instance or not
|
||||||
.apply { setAlignment(Align.center) }).pad(10f)
|
if (hash != tutorialTaskTableHash) {
|
||||||
|
val renderEvent = RenderEvent(tutorialTask, this) {
|
||||||
|
shouldUpdate = true
|
||||||
|
}
|
||||||
|
if (!renderEvent.isValid) return setInvisible()
|
||||||
|
tutorialTaskTable.clear()
|
||||||
|
tutorialTaskTable.add(renderEvent).pad(10f)
|
||||||
|
tutorialTaskTableHash = hash
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
tutorialTaskTable.add(ImageGetter.getImage("CityStateIcons/Cultured").apply { setSize(30f,30f) }).pad(5f)
|
tutorialTaskTable.clear()
|
||||||
|
tutorialTaskTable.add(ImageGetter.getImage("OtherIcons/HiddenTutorialTask").apply { setSize(30f,30f) }).pad(5f)
|
||||||
|
tutorialTaskTableHash = 0
|
||||||
}
|
}
|
||||||
tutorialTaskTable.pack()
|
tutorialTaskTable.pack()
|
||||||
tutorialTaskTable.centerX(stage)
|
tutorialTaskTable.centerX(stage)
|
||||||
tutorialTaskTable.y = topBar.y - tutorialTaskTable.height
|
tutorialTaskTable.y = topBar.getYForTutorialTask() - tutorialTaskTable.height
|
||||||
tutorialTaskTable.onClick {
|
tutorialTaskTable.onClick {
|
||||||
UncivGame.Current.isTutorialTaskCollapsed = !UncivGame.Current.isTutorialTaskCollapsed
|
UncivGame.Current.isTutorialTaskCollapsed = !UncivGame.Current.isTutorialTaskCollapsed
|
||||||
displayTutorialTaskOnUpdate()
|
displayTutorialTaskOnUpdate()
|
||||||
}
|
}
|
||||||
|
tutorialTaskTable.isVisible = true
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun updateSelectedCiv() {
|
private fun updateSelectedCiv() {
|
||||||
@ -794,7 +775,7 @@ class WorldScreen(
|
|||||||
shouldUpdate = true
|
shouldUpdate = true
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if (bottomUnitTable.selectedSpy != null) {
|
if (bottomUnitTable.selectedSpy != null) {
|
||||||
bottomUnitTable.selectSpy(null)
|
bottomUnitTable.selectSpy(null)
|
||||||
shouldUpdate = true
|
shouldUpdate = true
|
||||||
|
@ -67,6 +67,7 @@ class WorldScreenTopBar(internal val worldScreen: WorldScreen) : Table() {
|
|||||||
private val overviewButton = OverviewAndSupplyTable(worldScreen)
|
private val overviewButton = OverviewAndSupplyTable(worldScreen)
|
||||||
private val leftFiller: BackgroundActor
|
private val leftFiller: BackgroundActor
|
||||||
private val rightFiller: BackgroundActor
|
private val rightFiller: BackgroundActor
|
||||||
|
private var baseHeight = 0f
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
/** When the "fillers" are used, this is added to the required height, alleviating the "gap" problem a little. */
|
/** When the "fillers" are used, this is added to the required height, alleviating the "gap" problem a little. */
|
||||||
@ -100,6 +101,8 @@ class WorldScreenTopBar(internal val worldScreen: WorldScreen) : Table() {
|
|||||||
setLayoutEnabled(true)
|
setLayoutEnabled(true)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
internal fun getYForTutorialTask(): Float = y + height - baseHeight
|
||||||
|
|
||||||
/** Performs the layout tricks mentioned in the class Kdoc */
|
/** Performs the layout tricks mentioned in the class Kdoc */
|
||||||
private fun updateLayout() {
|
private fun updateLayout() {
|
||||||
val targetWidth = stage.width
|
val targetWidth = stage.width
|
||||||
@ -122,7 +125,7 @@ class WorldScreenTopBar(internal val worldScreen: WorldScreen) : Table() {
|
|||||||
add(resourceTable).colspan(3).growX().width(targetWidth).row()
|
add(resourceTable).colspan(3).growX().width(targetWidth).row()
|
||||||
layout() // force rowHeight calculation - validate is not enough - Table quirks
|
layout() // force rowHeight calculation - validate is not enough - Table quirks
|
||||||
val statsRowHeight = getRowHeight(0)
|
val statsRowHeight = getRowHeight(0)
|
||||||
val baseHeight = statsRowHeight + getRowHeight(1)
|
baseHeight = statsRowHeight + getRowHeight(1)
|
||||||
|
|
||||||
fun addFillers(fillerHeight: Float) {
|
fun addFillers(fillerHeight: Float) {
|
||||||
add(leftFiller).size(selectedCivWidth, fillerHeight + gapFillingExtraHeight)
|
add(leftFiller).size(selectedCivWidth, fillerHeight + gapFillingExtraHeight)
|
||||||
|
@ -745,6 +745,7 @@ Unless otherwise specified, all the following are from [the Noun Project](https:
|
|||||||
- [down](https://thenounproject.com/icon/down-39378/) by Cengiz SARI for Show unit destination
|
- [down](https://thenounproject.com/icon/down-39378/) by Cengiz SARI for Show unit destination
|
||||||
- [Cat](https://thenounproject.com/icon/cat-158942/) by Josi for Politics overview diagram legend
|
- [Cat](https://thenounproject.com/icon/cat-158942/) by Josi for Politics overview diagram legend
|
||||||
- [Bell](https://thenounproject.com/icon/bell-2054409) by Lyhn, transparency modified, for Notifications (overview, unhide button)
|
- [Bell](https://thenounproject.com/icon/bell-2054409) by Lyhn, transparency modified, for Notifications (overview, unhide button)
|
||||||
|
- [Galileo Donatello](https://en.wikipedia.org/wiki/File:Galileo_Donato.jpg) for the "Meet another civilization" tutorial: Public domain
|
||||||
|
|
||||||
### Main menu
|
### Main menu
|
||||||
|
|
||||||
|
@ -287,8 +287,10 @@ Allowed values are:
|
|||||||
Indicates *something that can be counted*, used both for comparisons and for multiplying uniques
|
Indicates *something that can be counted*, used both for comparisons and for multiplying uniques
|
||||||
|
|
||||||
Allowed values:
|
Allowed values:
|
||||||
- `year`
|
- `year`, `turns`
|
||||||
|
- `Cities`, `Units`, `Air units` - these count your total number
|
||||||
- Unit name (counts your existing units)
|
- Unit name (counts your existing units)
|
||||||
|
- `<unit type> units` (e.g. `Mounted units`) - counts your units by their type (this is not a filter, use the unitType verbatim)
|
||||||
- Building name (counts your existing buildings)
|
- Building name (counts your existing buildings)
|
||||||
- Stat name - gets the stat *reserve*, not the amount per turn (can be city stats or civilization stats, depending on where the unique is used)
|
- Stat name - gets the stat *reserve*, not the amount per turn (can be city stats or civilization stats, depending on where the unique is used)
|
||||||
- Resource name (can be city stats or civilization stats, depending on where the unique is used)
|
- Resource name (can be city stats or civilization stats, depending on where the unique is used)
|
||||||
|
@ -1007,11 +1007,11 @@ Simple unique parameters are explained by mouseover. Complex parameters are expl
|
|||||||
|
|
||||||
??? example "Only available"
|
??? example "Only available"
|
||||||
Meant to be used together with conditionals, like "Only available <after adopting [policy]> <while the empire is happy>". Only allows Building when ALL conditionals are met. Will also block Upgrade and Transform actions. See also CanOnlyBeBuiltWhen
|
Meant to be used together with conditionals, like "Only available <after adopting [policy]> <while the empire is happy>". Only allows Building when ALL conditionals are met. Will also block Upgrade and Transform actions. See also CanOnlyBeBuiltWhen
|
||||||
Applicable to: Tech, Policy, FounderBelief, FollowerBelief, Building, Unit, Promotion, Improvement, Ruins
|
Applicable to: Tech, Policy, FounderBelief, FollowerBelief, Building, Unit, Promotion, Improvement, Ruins, Event
|
||||||
|
|
||||||
??? example "Unavailable"
|
??? example "Unavailable"
|
||||||
Meant to be used together with conditionals, like "Unavailable <after generating a Great Prophet>".
|
Meant to be used together with conditionals, like "Unavailable <after generating a Great Prophet>".
|
||||||
Applicable to: Tech, Policy, Building, Unit, Promotion, Improvement, Ruins
|
Applicable to: Tech, Policy, Building, Unit, Promotion, Improvement, Ruins, Event
|
||||||
|
|
||||||
??? example "Cannot be hurried"
|
??? example "Cannot be hurried"
|
||||||
Applicable to: Tech, Building
|
Applicable to: Tech, Building
|
||||||
@ -1915,6 +1915,12 @@ Simple unique parameters are explained by mouseover. Complex parameters are expl
|
|||||||
|
|
||||||
Applicable to: ModOptions
|
Applicable to: ModOptions
|
||||||
|
|
||||||
|
## Event uniques
|
||||||
|
??? example "Mark tutorial [comment] complete"
|
||||||
|
Example: "Mark tutorial [comment] complete"
|
||||||
|
|
||||||
|
Applicable to: Event
|
||||||
|
|
||||||
## Conditional uniques
|
## Conditional uniques
|
||||||
!!! note ""
|
!!! note ""
|
||||||
|
|
||||||
@ -1945,6 +1951,14 @@ Simple unique parameters are explained by mouseover. Complex parameters are expl
|
|||||||
|
|
||||||
Applicable to: Conditional
|
Applicable to: Conditional
|
||||||
|
|
||||||
|
??? example "<if tutorials are enabled>"
|
||||||
|
Applicable to: Conditional
|
||||||
|
|
||||||
|
??? example "<if tutorial [comment] is completed>"
|
||||||
|
Example: "<if tutorial [comment] is completed>"
|
||||||
|
|
||||||
|
Applicable to: Conditional
|
||||||
|
|
||||||
??? example "<for [civFilter]>"
|
??? example "<for [civFilter]>"
|
||||||
Example: "<for [City-States]>"
|
Example: "<for [City-States]>"
|
||||||
|
|
||||||
|