fixed #3066, crash in chooseMilitaryUnit and some great people actions (#3099)

* fixed KotlinNullPointerException crash in chooseMilitaryUnit
random() is not to be used in predicate
* GodmodeCheckbox is not lockable and unchecked by default
* no great people actions if no movement points left
* unique "Can start an []-turn golden age" now has parameter and 8-turn golden ages will last 8 turns instead of 10
golden age can be started if unit is on own territory (even embarked)
* "Golden Age length increased by [50]%" - now has parameter
* tweaked changed fort and terrain defence bonuses
fort can be built on forest and jungle (vegetation will not be removed)
any open flat land gives 10%  penalty
marsh gives 15% penalty
only top terrain counts, improvement bonus will be added to that
flatland + fort = 40%
hill + fort = 75%
hill = 25%
forest/jungle on flatland = 25%
forest/jungle on hill = 25%
forest on flat + fort = 75%
forest on hill + fort = 75%
forest on hill + citadel = 125%
fixed 20% penalty for attacking over river - will be displayed if unit is standing on the other side of river
"Amphibious" unique removes this penalty
This commit is contained in:
HadeanLake
2020-09-05 20:32:27 +03:00
committed by GitHub
parent ca36084e8b
commit 8ac3a88cec
13 changed files with 103 additions and 90 deletions

View File

@ -396,7 +396,7 @@
"happiness": 4, "happiness": 4,
"greatPersonPoints": {"production": 1}, "greatPersonPoints": {"production": 1},
"isWonder": true, "isWonder": true,
"uniques": ["Golden Age length increases +50%"], "uniques": ["Golden Age length increased by [50]%"],
"requiredTech": "Civil Service", "requiredTech": "Civil Service",
"quote": "'The katun is established at Chichen Itza. The settlement of the Itza shall take place there. The quetzal shall come, the green bird shall come. Ah Kantenal shall come. It is the word of God. The Itza shall come.' - The Books of Chilam Balam" "quote": "'The katun is established at Chichen Itza. The settlement of the Itza shall take place there. The quetzal shall come, the green bird shall come. Ah Kantenal shall come. It is the word of God. The Itza shall come.' - The Books of Chilam Balam"
}, },

View File

@ -441,7 +441,7 @@
"outerColor": [153,5,3], "outerColor": [153,5,3],
"innerColor": [244,232,54], "innerColor": [244,232,54],
"uniqueName": "Achaemenid Legacy", "uniqueName": "Achaemenid Legacy",
"uniques": ["Golden Age length increases +50%", "+1 Movement for all units during Golden Age", "+10% Strength for all units during Golden Age"], "uniques": ["Golden Age length increased by [50]%", "+1 Movement for all units during Golden Age", "+10% Strength for all units during Golden Age"],
"cities": ["Persepolis","Parsagadae","Susa","Ecbatana","Tarsus","Gordium","Bactra","Sardis","Ergili","Dariushkabir", "cities": ["Persepolis","Parsagadae","Susa","Ecbatana","Tarsus","Gordium","Bactra","Sardis","Ergili","Dariushkabir",
"Ghulaman","Zohak","Istakhr","Jinjan","Borazjan","Herat","Dakyanus","Bampur","Turengtepe","Rey","Shiraz", "Ghulaman","Zohak","Istakhr","Jinjan","Borazjan","Herat","Dakyanus","Bampur","Turengtepe","Rey","Shiraz",
"Thuspa","Hasanlu","Gabae","Merv","Behistun","Kandahar","Altintepe","Bunyan","Charsadda","Uratyube", "Thuspa","Hasanlu","Gabae","Merv","Behistun","Kandahar","Altintepe","Bunyan","Charsadda","Uratyube",

View File

@ -439,7 +439,7 @@
{ {
"name": "Freedom Complete", "name": "Freedom Complete",
"effect": "Tile yield from great improvement +100%, golden ages increase by 50%", "effect": "Tile yield from great improvement +100%, golden ages increase by 50%",
"uniques": ["Tile yield from Great Improvements +100%", "Golden Age length increases +50%"] "uniques": ["Tile yield from Great Improvements +100%", "Golden Age length increased by [50]%"]
} }
] ]
}, },

View File

@ -20,6 +20,7 @@
"type": "Land", "type": "Land",
"food": 2, "food": 2,
"movementCost": 1, "movementCost": 1,
"defenceBonus": -0.1,
"RGB": [109,139,53] "RGB": [109,139,53]
}, },
{ {
@ -28,6 +29,7 @@
"food": 1, "food": 1,
"production": 1, "production": 1,
"movementCost": 1, "movementCost": 1,
"defenceBonus": -0.1,
"RGB": [200,208,161] "RGB": [200,208,161]
}, },
{ {
@ -107,7 +109,7 @@
"food": -1, "food": -1,
"movementCost": 3, "movementCost": 3,
"unbuildable": true, "unbuildable": true,
"defenceBonus": -0.1, "defenceBonus": -0.15,
"occursOn": ["Grassland"] "occursOn": ["Grassland"]
}, },
{ {

View File

@ -76,7 +76,7 @@
// Military improvement // Military improvement
{ {
"name": "Fort", "name": "Fort",
"terrainsCanBeBuiltOn": ["Plains","Grassland","Desert","Hill","Tundra","Snow"], "terrainsCanBeBuiltOn": ["Plains","Grassland","Desert","Hill","Tundra","Snow","Forest","Jungle"],
"turnsToBuild": 6, "turnsToBuild": 6,
"techRequired": "Engineering", "techRequired": "Engineering",
"uniques": ["Gives a defensive bonus of [50]%", "Can be built outside your borders"] "uniques": ["Gives a defensive bonus of [50]%", "Can be built outside your borders"]

View File

@ -1293,7 +1293,7 @@
{ {
"name": "Great Artist", "name": "Great Artist",
"unitType": "Civilian", "unitType": "Civilian",
"uniques": ["Can start an 8-turn golden age", "Can construct [Landmark]", "Great Person - [Culture]", "Unbuildable"], "uniques": ["Can start an [8]-turn golden age", "Can construct [Landmark]", "Great Person - [Culture]", "Unbuildable"],
"movement": 2 "movement": 2
}, },
{ {
@ -1318,7 +1318,7 @@
{ {
"name": "Great General", "name": "Great General",
"unitType": "Civilian", "unitType": "Civilian",
"uniques": ["Can start an 8-turn golden age", "Bonus for units in 2 tile radius 15%", "Can construct [Citadel]", "uniques": ["Can start an [8]-turn golden age", "Bonus for units in 2 tile radius 15%", "Can construct [Citadel]",
"Great Person - [War]", "Unbuildable"], "Great Person - [War]", "Unbuildable"],
"movement": 2 "movement": 2
}, },
@ -1327,7 +1327,7 @@
"unitType": "Civilian", "unitType": "Civilian",
"uniqueTo": "Mongolia", "uniqueTo": "Mongolia",
"replaces": "Great General", "replaces": "Great General",
"uniques": ["Can start an 8-turn golden age","Bonus for units in 2 tile radius 15%", "uniques": ["Can start an [8]-turn golden age","Bonus for units in 2 tile radius 15%",
"Heal adjacent units for an additional 15 HP per turn", "Can construct [Citadel]", "Great Person - [War]", "Unbuildable"], "Heal adjacent units for an additional 15 HP per turn", "Can construct [Citadel]", "Great Person - [War]", "Unbuildable"],
"movement": 5 "movement": 5
} }

View File

@ -80,7 +80,8 @@ object Automation {
else { // randomize type of unit and take the most expensive of its kind else { // randomize type of unit and take the most expensive of its kind
val availableTypes = militaryUnits.map { it.unitType }.distinct().filterNot { it == UnitType.Scout }.toList() val availableTypes = militaryUnits.map { it.unitType }.distinct().filterNot { it == UnitType.Scout }.toList()
if (availableTypes.isEmpty()) return null if (availableTypes.isEmpty()) return null
chosenUnit = militaryUnits.filter { it.unitType == availableTypes.random() }.maxBy { it.cost }!! val randomType = availableTypes.random()
chosenUnit = militaryUnits.filter { it.unitType == randomType }.maxBy { it.cost }!!
} }
return chosenUnit.name return chosenUnit.name
} }

View File

@ -137,9 +137,9 @@ object BattleDamage {
} }
if (numberOfAttackersSurroundingDefender > 1) if (numberOfAttackersSurroundingDefender > 1)
modifiers["Flanking"] = 0.1f * (numberOfAttackersSurroundingDefender - 1) //https://www.carlsguides.com/strategy/civilization5/war/combatbonuses.php modifiers["Flanking"] = 0.1f * (numberOfAttackersSurroundingDefender - 1) //https://www.carlsguides.com/strategy/civilization5/war/combatbonuses.php
if (attacker.getTile().aerialDistanceTo(defender.getTile()) == 1 && attacker.getTile().isConnectedByRiver(defender.getTile())
if (tileToAttackFrom != null && tileToAttackFrom.isConnectedByRiver(defender.getTile())) { && !attacker.unit.hasUnique("Amphibious")) {
if (!tileToAttackFrom.hasConnection(attacker.getCivInfo()) // meaning, the tiles are not road-connected for this civ if (!attacker.getTile().hasConnection(attacker.getCivInfo()) // meaning, the tiles are not road-connected for this civ
|| !defender.getTile().hasConnection(attacker.getCivInfo()) || !defender.getTile().hasConnection(attacker.getCivInfo())
|| !attacker.getCivInfo().tech.roadsConnectAcrossRivers) { || !attacker.getCivInfo().tech.roadsConnectAcrossRivers) {
modifiers["Across river"] = -0.2f modifiers["Across river"] = -0.2f

View File

@ -24,10 +24,15 @@ class GoldenAgeManager{
return ((500 + numberOfGoldenAges * 250) * (1 + civInfo.cities.size / 100.0)).toInt() //https://forums.civfanatics.com/resources/complete-guide-to-happiness-vanilla.25584/ return ((500 + numberOfGoldenAges * 250) * (1 + civInfo.cities.size / 100.0)).toInt() //https://forums.civfanatics.com/resources/complete-guide-to-happiness-vanilla.25584/
} }
fun enterGoldenAge() { fun enterGoldenAge(unmodifiedNumberOfTurns: Int = 10) {
var turnsToGoldenAge = 10.0 var turnsToGoldenAge = unmodifiedNumberOfTurns.toFloat()
// as of 3.10.7 This is to be deprecated and converted to "Golden Age length increased by []%" - keeping it here to that mods with this can still work for now
for(unique in civInfo.getMatchingUniques("Golden Age length increases +50%")) for(unique in civInfo.getMatchingUniques("Golden Age length increases +50%"))
turnsToGoldenAge *= 1.5 turnsToGoldenAge *= 1.5f
for(unique in civInfo.getMatchingUniques("Golden Age length increased by []%"))
turnsToGoldenAge *= (unique.params[0].toFloat()/100 + 1)
turnsToGoldenAge *= civInfo.gameInfo.gameParameters.gameSpeed.modifier turnsToGoldenAge *= civInfo.gameInfo.gameParameters.gameSpeed.modifier
turnsLeftForCurrentGoldenAge += turnsToGoldenAge.toInt() turnsLeftForCurrentGoldenAge += turnsToGoldenAge.toInt()
civInfo.addNotification("You have entered a Golden Age!", null, Color.GOLD) civInfo.addNotification("You have entered a Golden Age!", null, Color.GOLD)

View File

@ -330,8 +330,7 @@ open class TileInfo {
tileMap.getTilesAtDistance(position, distance) tileMap.getTilesAtDistance(position, distance)
fun getDefensiveBonus(): Float { fun getDefensiveBonus(): Float {
var bonus = getBaseTerrain().defenceBonus var bonus = getLastTerrain().defenceBonus
if (terrainFeature != null) bonus += getTerrainFeature()!!.defenceBonus
val tileImprovement = getTileImprovement() val tileImprovement = getTileImprovement()
if (tileImprovement != null) { if (tileImprovement != null) {
for (unique in tileImprovement.uniqueObjects) for (unique in tileImprovement.uniqueObjects)

View File

@ -56,10 +56,10 @@ class GameOptionsTable(val previousScreen: IPreviousScreen, val updatePlayerPick
pack() pack()
} }
private fun Table.addCheckbox(text: String, initialState: Boolean, onChange: (newValue: Boolean) -> Unit) { private fun Table.addCheckbox(text: String, initialState: Boolean, lockable: Boolean = true, onChange: (newValue: Boolean) -> Unit) {
val checkbox = CheckBox(text.tr(), CameraStageBaseScreen.skin) val checkbox = CheckBox(text.tr(), CameraStageBaseScreen.skin)
checkbox.isChecked = initialState checkbox.isChecked = initialState
checkbox.isDisabled = locked checkbox.isDisabled = lockable && locked
checkbox.onChange { onChange(checkbox.isChecked) } checkbox.onChange { onChange(checkbox.isChecked) }
add(checkbox).colspan(2).left().row() add(checkbox).colspan(2).left().row()
} }
@ -77,7 +77,7 @@ class GameOptionsTable(val previousScreen: IPreviousScreen, val updatePlayerPick
{ gameParameters.nuclearWeaponsEnabled = it } { gameParameters.nuclearWeaponsEnabled = it }
private fun Table.addGodmodeCheckbox() = private fun Table.addGodmodeCheckbox() =
addCheckbox("Scenario Editor", gameParameters.godMode) addCheckbox("Scenario Editor", gameParameters.godMode, lockable = false)
{ gameParameters.godMode = it } { gameParameters.godMode = it }

View File

@ -95,9 +95,7 @@ class MapOptionsTable(val newGameScreen: NewGameScreen): Table() {
mapParameters.type = MapType.custom mapParameters.type = MapType.custom
mapParameters.name = mapFileSelectBox.selected.toString() mapParameters.name = mapFileSelectBox.selected.toString()
mapTypeSpecificTable.add(savedMapOptionsTable) mapTypeSpecificTable.add(savedMapOptionsTable)
newGameScreen.gameSetupInfo.gameParameters.godMode = false
newGameScreen.unlockTables() newGameScreen.unlockTables()
newGameScreen.updateTables()
} else if (mapTypeSelectBox.selected.value == MapType.scenarioMap) { } else if (mapTypeSelectBox.selected.value == MapType.scenarioMap) {
mapParameters.type = MapType.scenarioMap mapParameters.type = MapType.scenarioMap
mapParameters.name = scenarioMapSelectBox.selected.toString() mapParameters.name = scenarioMapSelectBox.selected.toString()
@ -108,7 +106,6 @@ class MapOptionsTable(val newGameScreen: NewGameScreen): Table() {
newGameScreen.updateRuleset() newGameScreen.updateRuleset()
// update PlayerTable and GameOptionsTable // update PlayerTable and GameOptionsTable
newGameScreen.lockTables() newGameScreen.lockTables()
newGameScreen.updateTables()
} else if(mapTypeSelectBox.selected.value == MapType.scenario){ } else if(mapTypeSelectBox.selected.value == MapType.scenario){
selectSavedGameAsScenario(scenarioSelectBox.selected.fileHandle) selectSavedGameAsScenario(scenarioSelectBox.selected.fileHandle)
mapTypeSpecificTable.add(scenarioOptionsTable) mapTypeSpecificTable.add(scenarioOptionsTable)
@ -116,11 +113,11 @@ class MapOptionsTable(val newGameScreen: NewGameScreen): Table() {
} else { // generated map } else { // generated map
mapParameters.name = "" mapParameters.name = ""
mapParameters.type = generatedMapOptionsTable.mapTypeSelectBox.selected.value mapParameters.type = generatedMapOptionsTable.mapTypeSelectBox.selected.value
newGameScreen.gameSetupInfo.gameParameters.godMode = false
mapTypeSpecificTable.add(generatedMapOptionsTable) mapTypeSpecificTable.add(generatedMapOptionsTable)
newGameScreen.unlockTables() newGameScreen.unlockTables()
newGameScreen.updateTables()
} }
newGameScreen.gameSetupInfo.gameParameters.godMode = false
newGameScreen.updateTables()
} }
// activate once, so when we had a file map before we'll have the right things set for another one // activate once, so when we had a file map before we'll have the right things set for another one
@ -145,7 +142,10 @@ class MapOptionsTable(val newGameScreen: NewGameScreen): Table() {
} }
mapFileSelectBox.items = mapFiles mapFileSelectBox.items = mapFiles
val selectedItem = mapFiles.firstOrNull { it.fileHandle.name()==mapParameters.name } val selectedItem = mapFiles.firstOrNull { it.fileHandle.name()==mapParameters.name }
if (selectedItem != null) mapFileSelectBox.selected = selectedItem if (selectedItem != null) {
mapFileSelectBox.selected = selectedItem
newGameScreen.gameSetupInfo.mapFile = mapFileSelectBox.selected.fileHandle
}
else if (!mapFiles.isEmpty) { else if (!mapFiles.isEmpty) {
mapFileSelectBox.selected = mapFiles.first() mapFileSelectBox.selected = mapFiles.first()
newGameScreen.gameSetupInfo.mapFile = mapFileSelectBox.selected.fileHandle newGameScreen.gameSetupInfo.mapFile = mapFileSelectBox.selected.fileHandle

View File

@ -25,8 +25,6 @@ import com.unciv.ui.worldscreen.WorldScreen
object UnitActions { object UnitActions {
const val CAN_UNDERTAKE = "Can undertake"
fun getUnitActions(unit: MapUnit, worldScreen: WorldScreen): List<UnitAction> { fun getUnitActions(unit: MapUnit, worldScreen: WorldScreen): List<UnitAction> {
val tile = unit.getTile() val tile = unit.getTile()
val unitTable = worldScreen.bottomUnitTable val unitTable = worldScreen.bottomUnitTable
@ -295,7 +293,8 @@ object UnitActions {
private fun addGreatPersonActions(unit: MapUnit, actionList: ArrayList<UnitAction>, tile: TileInfo) { private fun addGreatPersonActions(unit: MapUnit, actionList: ArrayList<UnitAction>, tile: TileInfo) {
if (unit.hasUnique("Can hurry technology research") && !unit.isEmbarked()) { if (unit.currentMovement > 0) for (unique in unit.getUniques()) when (unique.placeholderText) {
"Can hurry technology research" -> {
actionList += UnitAction( actionList += UnitAction(
type = UnitActionType.HurryResearch, type = UnitActionType.HurryResearch,
uncivSound = UncivSound.Chimes, uncivSound = UncivSound.Chimes,
@ -303,22 +302,32 @@ object UnitActions {
unit.civInfo.tech.hurryResearch() unit.civInfo.tech.hurryResearch()
addGoldPerGreatPersonUsage(unit.civInfo) addGoldPerGreatPersonUsage(unit.civInfo)
unit.destroy() unit.destroy()
}.takeIf { unit.civInfo.tech.currentTechnologyName() != null && unit.currentMovement > 0 }) }.takeIf { unit.civInfo.tech.currentTechnologyName() != null })
} }
"Can start an []-turn golden age" -> {
if (unit.hasUnique("Can start an 8-turn golden age") && !unit.isEmbarked()) { val turnsToGoldenAge = unique.params[0].toInt()
actionList += UnitAction( actionList += UnitAction(
type = UnitActionType.StartGoldenAge, type = UnitActionType.StartGoldenAge,
uncivSound = UncivSound.Chimes, uncivSound = UncivSound.Chimes,
action = { action = {
unit.civInfo.goldenAges.enterGoldenAge() unit.civInfo.goldenAges.enterGoldenAge(turnsToGoldenAge)
addGoldPerGreatPersonUsage(unit.civInfo) addGoldPerGreatPersonUsage(unit.civInfo)
unit.destroy() unit.destroy()
}.takeIf { unit.currentMovement > 0 }) }.takeIf { unit.currentTile.getOwner() != null && unit.currentTile.getOwner() == unit.civInfo })
} }
// As of 3.10.7 This is to be deprecated and converted to "Can start an []-turn golden age" - keeping it here to that mods with this can still work for now
if (unit.hasUnique("Can speed up construction of a wonder") && !unit.isEmbarked()) { "Can start an 8-turn golden age" -> {
val canHurryWonder = if (unit.currentMovement == 0f || !tile.isCityCenter()) false actionList += UnitAction(
type = UnitActionType.StartGoldenAge,
uncivSound = UncivSound.Chimes,
action = {
unit.civInfo.goldenAges.enterGoldenAge(8)
addGoldPerGreatPersonUsage(unit.civInfo)
unit.destroy()
}.takeIf { unit.currentTile.getOwner() != null && unit.currentTile.getOwner() == unit.civInfo })
}
"Can speed up construction of a wonder" -> {
val canHurryWonder = if (!tile.isCityCenter()) false
else { else {
val currentConstruction = tile.getCity()!!.cityConstructions.getCurrentConstruction() val currentConstruction = tile.getCity()!!.cityConstructions.getCurrentConstruction()
if (currentConstruction !is Building) false if (currentConstruction !is Building) false
@ -336,30 +345,27 @@ object UnitActions {
unit.destroy() unit.destroy()
}.takeIf { canHurryWonder }) }.takeIf { canHurryWonder })
} }
"Can undertake a trade mission with City-State, giving a large sum of gold and [] Influence" -> {
if (unit.hasUnique("Can undertake a trade mission with City-State, giving a large sum of gold and [30] Influence")
&& !unit.isEmbarked()) {
val canConductTradeMission = tile.owningCity?.civInfo?.isCityState() == true val canConductTradeMission = tile.owningCity?.civInfo?.isCityState() == true
&& tile.owningCity?.civInfo?.isAtWarWith(unit.civInfo) == false && tile.owningCity?.civInfo?.isAtWarWith(unit.civInfo) == false
&& unit.currentMovement > 0 val influenceEarned = unique.params[0].toInt()
actionList += UnitAction( actionList += UnitAction(
type = UnitActionType.ConductTradeMission, type = UnitActionType.ConductTradeMission,
uncivSound = UncivSound.Chimes, uncivSound = UncivSound.Chimes,
action = { action = {
// http://civilization.wikia.com/wiki/Great_Merchant_(Civ5) // http://civilization.wikia.com/wiki/Great_Merchant_(Civ5)
var goldEarned = (350 + 50 * unit.civInfo.getEraNumber()) * unit.civInfo.gameInfo.gameParameters.gameSpeed.modifier var goldEarned = ((350 + 50 * unit.civInfo.getEraNumber()) * unit.civInfo.gameInfo.gameParameters.gameSpeed.modifier).toInt()
if (unit.civInfo.hasUnique("Double gold from Great Merchant trade missions")) if (unit.civInfo.hasUnique("Double gold from Great Merchant trade missions"))
goldEarned *= 2 goldEarned *= 2
unit.civInfo.gold += goldEarned.toInt() unit.civInfo.gold += goldEarned
val relevantUnique = unit.getUniques().first { it.text.startsWith(CAN_UNDERTAKE) }
val influenceEarned = Regex("\\d+").find(relevantUnique.text)!!.value.toInt()
tile.owningCity!!.civInfo.getDiplomacyManager(unit.civInfo).influence += influenceEarned tile.owningCity!!.civInfo.getDiplomacyManager(unit.civInfo).influence += influenceEarned
unit.civInfo.addNotification("Your trade mission to [${tile.owningCity!!.civInfo}] has earned you [${goldEarned.toInt()}] gold and [$influenceEarned] influence!", null, Color.GOLD) unit.civInfo.addNotification("Your trade mission to [${tile.owningCity!!.civInfo}] has earned you [${goldEarned}] gold and [$influenceEarned] influence!", null, Color.GOLD)
addGoldPerGreatPersonUsage(unit.civInfo) addGoldPerGreatPersonUsage(unit.civInfo)
unit.destroy() unit.destroy()
}.takeIf { canConductTradeMission }) }.takeIf { canConductTradeMission })
} }
} }
}
fun getImprovementConstructionActions(unit: MapUnit, tile: TileInfo): ArrayList<UnitAction> { fun getImprovementConstructionActions(unit: MapUnit, tile: TileInfo): ArrayList<UnitAction> {