diff --git a/core/src/com/unciv/Constants.kt b/core/src/com/unciv/Constants.kt index f1af11e7d5..aa43202f7b 100644 --- a/core/src/com/unciv/Constants.kt +++ b/core/src/com/unciv/Constants.kt @@ -12,6 +12,7 @@ class Constants{ const val hill = "Hill" const val coast = "Coast" const val plains = "Plains" + const val lakes = "Lakes" const val barbarianEncampment = "Barbarian encampment" const val ancientRuins = "Ancient ruins" diff --git a/core/src/com/unciv/logic/automation/UnitAutomation.kt b/core/src/com/unciv/logic/automation/UnitAutomation.kt index d58245152d..0265980b6d 100644 --- a/core/src/com/unciv/logic/automation/UnitAutomation.kt +++ b/core/src/com/unciv/logic/automation/UnitAutomation.kt @@ -162,12 +162,18 @@ class UnitAutomation{ } fun containsAttackableEnemy(tile: TileInfo, combatant: ICombatant): Boolean { - if(combatant is MapUnitCombatant){ + if(combatant is MapUnitCombatant) { if (combatant.unit.isEmbarked()) { if (tile.isWater) return false // can't attack water units while embarked, only land if (combatant.isRanged()) return false } - if (tile.isLand && combatant.unit.hasUnique("Can only attack water")) return false + if (combatant.unit.hasUnique("Can only attack water")) { + if (tile.isLand) return false + + // trying to attack lake-to-coast or vice versa + if ((tile.baseTerrain == Constants.lakes) != (combatant.getTile().baseTerrain == Constants.lakes)) + return false + } } val tileCombatant = Battle(combatant.getCivInfo().gameInfo).getMapCombatantOfTile(tile) diff --git a/core/src/com/unciv/logic/battle/Battle.kt b/core/src/com/unciv/logic/battle/Battle.kt index fb86f34ee4..55f032a9c6 100644 --- a/core/src/com/unciv/logic/battle/Battle.kt +++ b/core/src/com/unciv/logic/battle/Battle.kt @@ -33,7 +33,7 @@ class Battle(val gameInfo:GameInfo) { val attackedTile = defender.getTile() if(attacker is MapUnitCombatant && attacker.getUnitType().isAirUnit()){ - intercept(attacker,defender) + tryInterceptAirAttack(attacker,defender) if(attacker.isDefeated()) return } @@ -65,9 +65,9 @@ class Battle(val gameInfo:GameInfo) { postBattleAction(attacker,defender,attackedTile) } - private fun postBattleAction(attacker: ICombatant, defender: ICombatant, attackedTile:TileInfo){ + private fun postBattleAction(attacker: ICombatant, defender: ICombatant, attackedTile:TileInfo) { - if(attacker.getCivInfo()!=defender.getCivInfo()) { // If what happened was that a civilian unit was captures, that's dealt with in the CaptureCilvilianUnit function + if (attacker.getCivInfo() != defender.getCivInfo()) { // If what happened was that a civilian unit was captures, that's dealt with in the CaptureCilvilianUnit function val whatHappenedString = if (attacker !is CityCombatant && attacker.isDefeated()) " {was destroyed while attacking}" else " has " + (if (defender.isDefeated()) "destroyed" else "attacked") @@ -75,20 +75,20 @@ class Battle(val gameInfo:GameInfo) { if (attacker.getUnitType() == UnitType.City) "Enemy city [" + attacker.getName() + "]" else "An enemy [" + attacker.getName() + "]" val defenderString = - if (defender.getUnitType() == UnitType.City) " [" + defender.getName()+"]" - else " our [" + defender.getName()+"]" + if (defender.getUnitType() == UnitType.City) " [" + defender.getName() + "]" + else " our [" + defender.getName() + "]" val notificationString = attackerString + whatHappenedString + defenderString defender.getCivInfo().addNotification(notificationString, attackedTile.position, Color.RED) } // Units that heal when killing - if(defender.isDefeated() + if (defender.isDefeated() && defender is MapUnitCombatant - && attacker is MapUnitCombatant){ + && attacker is MapUnitCombatant) { val regex = Regex("""Heals \[(\d*)\] damage if it kills a unit"""") - for(unique in attacker.unit.getUniques()){ + for (unique in attacker.unit.getUniques()) { val match = regex.matchEntire(unique) - if(match==null) continue + if (match == null) continue val amountToHeal = match.groups[1]!!.value.toInt() attacker.unit.healBy(amountToHeal) } @@ -96,94 +96,93 @@ class Battle(val gameInfo:GameInfo) { // German unique - needs to be checked before we try to move to the enemy tile, since the encampment disappears after we move in - if(defender.isDefeated() && defender.getCivInfo().isBarbarian() - && attackedTile.improvement==Constants.barbarianEncampment - && attacker.getCivInfo().nation.unique== "67% chance to earn 25 Gold and recruit a Barbarian unit from a conquered encampment, -25% land units maintenance." - && Random().nextDouble() > 0.67){ + if (defender.isDefeated() && defender.getCivInfo().isBarbarian() + && attackedTile.improvement == Constants.barbarianEncampment + && attacker.getCivInfo().nation.unique == "67% chance to earn 25 Gold and recruit a Barbarian unit from a conquered encampment, -25% land units maintenance." + && Random().nextDouble() > 0.67) { attacker.getCivInfo().placeUnitNearTile(attackedTile.position, defender.getName()) attacker.getCivInfo().gold += 25 - attacker.getCivInfo().addNotification("A barbarian [${defender.getName()}] has joined us!",attackedTile.position, Color.RED) + attacker.getCivInfo().addNotification("A barbarian [${defender.getName()}] has joined us!", attackedTile.position, Color.RED) } // Similarly, Ottoman unique - if(defender.isDefeated() && defender.getUnitType().isWaterUnit() && attacker.isMelee() && attacker.getUnitType().isWaterUnit() - && attacker.getCivInfo().nation.unique== "Pay only one third the usual cost for naval unit maintenance. Melee naval units have a 1/3 chance to capture defeated naval units." - && Random().nextDouble() > 0.33){ + if (defender.isDefeated() && defender.getUnitType().isWaterUnit() && attacker.isMelee() && attacker.getUnitType().isWaterUnit() + && attacker.getCivInfo().nation.unique == "Pay only one third the usual cost for naval unit maintenance. Melee naval units have a 1/3 chance to capture defeated naval units." + && Random().nextDouble() > 0.33) { attacker.getCivInfo().placeUnitNearTile(attackedTile.position, defender.getName()) } // we're a melee unit and we destroyed\captured an enemy unit else if (attacker.isMelee() - && (defender.isDefeated() || defender.getCivInfo()==attacker.getCivInfo()) + && (defender.isDefeated() || defender.getCivInfo() == attacker.getCivInfo()) // This is so that if we attack e.g. a barbarian in enemy territory that we can't enter, we won't enter it && (attacker as MapUnitCombatant).unit.movement.canMoveTo(attackedTile)) { // we destroyed an enemy military unit and there was a civilian unit in the same tile as well - if(attackedTile.civilianUnit!=null && attackedTile.civilianUnit!!.civInfo != attacker.getCivInfo()) - captureCivilianUnit(attacker,MapUnitCombatant(attackedTile.civilianUnit!!)) + if (attackedTile.civilianUnit != null && attackedTile.civilianUnit!!.civInfo != attacker.getCivInfo()) + captureCivilianUnit(attacker, MapUnitCombatant(attackedTile.civilianUnit!!)) attacker.unit.movement.moveToTile(attackedTile) } - if(attacker is MapUnitCombatant) { + if (attacker is MapUnitCombatant) { val unit = attacker.unit if (unit.hasUnique("Can move after attacking") - || (unit.hasUnique("1 additional attack per turn") && unit.attacksThisTurn==0)){ + || (unit.hasUnique("1 additional attack per turn") && unit.attacksThisTurn == 0)) { // if it was a melee attack and we won, then the unit ALREADY got movement points deducted, // for the movement to the enemy's tile! // and if it's an air unit, it only has 1 movement anyway, so... - if(!attacker.getUnitType().isAirUnit() && !(attacker.getUnitType().isMelee() && defender.isDefeated())) + if (!attacker.getUnitType().isAirUnit() && !(attacker.getUnitType().isMelee() && defender.isDefeated())) unit.useMovementPoints(1f) - } - else unit.currentMovement = 0f - unit.attacksThisTurn+=1 - if(unit.isFortified() || unit.action == Constants.unitActionSleep) - attacker.unit.action=null // but not, for instance, if it's Set Up - then it should definitely keep the action! + } else unit.currentMovement = 0f + unit.attacksThisTurn += 1 + if (unit.isFortified() || unit.action == Constants.unitActionSleep) + attacker.unit.action = null // but not, for instance, if it's Set Up - then it should definitely keep the action! } else if (attacker is CityCombatant) { attacker.city.attackedThisTurn = true } - if(defender.isDefeated() + if (defender.isDefeated() && defender is CityCombatant && attacker.isMelee()) conquerCity(defender.city, attacker) - if(attacker.isMelee()){ - if(!defender.getUnitType().isCivilian()) // unit was not captured but actually attacked + if (attacker.isMelee()) { + if (!defender.getUnitType().isCivilian()) // unit was not captured but actually attacked { - addXp(attacker,5,defender) - addXp(defender,4,attacker) + addXp(attacker, 5, defender) + addXp(defender, 4, attacker) } - } - else{ // ranged attack - addXp(attacker,2,defender) - addXp(defender,2,attacker) + } else { // ranged attack + addXp(attacker, 2, defender) + addXp(defender, 2, attacker) } // Add culture when defeating a barbarian when Honor policy is adopted (can be either attacker or defender!) - fun tryGetCultureFromHonor(civUnit:ICombatant, barbarianUnit:ICombatant){ - if(barbarianUnit.isDefeated() && barbarianUnit is MapUnitCombatant - && barbarianUnit.getCivInfo().isBarbarian() - && civUnit.getCivInfo().policies.isAdopted("Honor")) - civUnit.getCivInfo().policies.storedCulture += - max(barbarianUnit.unit.baseUnit.strength,barbarianUnit.unit.baseUnit.rangedStrength) - } + tryGetCultureFromHonor(attacker, defender) + tryGetCultureFromHonor(defender, attacker) - tryGetCultureFromHonor(attacker,defender) - tryGetCultureFromHonor(defender,attacker) - - if(defender.isDefeated() && defender is MapUnitCombatant && !defender.getUnitType().isCivilian() + if (defender.isDefeated() && defender is MapUnitCombatant && !defender.getUnitType().isCivilian() && attacker.getCivInfo().policies.isAdopted("Honor Complete")) attacker.getCivInfo().gold += defender.unit.baseUnit.getProductionCost(attacker.getCivInfo()) / 10 - if(attacker is MapUnitCombatant && attacker.unit.action!=null && attacker.unit.action!!.startsWith("moveTo")) - attacker.unit.action=null + if (attacker is MapUnitCombatant && attacker.unit.action != null + && attacker.unit.action!!.startsWith("moveTo")) + attacker.unit.action = null + } + + private fun tryGetCultureFromHonor(civUnit:ICombatant, barbarianUnit:ICombatant){ + if(barbarianUnit.isDefeated() && barbarianUnit is MapUnitCombatant + && barbarianUnit.getCivInfo().isBarbarian() + && civUnit.getCivInfo().policies.isAdopted("Honor")) + civUnit.getCivInfo().policies.storedCulture += + max(barbarianUnit.unit.baseUnit.strength,barbarianUnit.unit.baseUnit.rangedStrength) } // XP! - fun addXp(thisCombatant:ICombatant, amount:Int, otherCombatant:ICombatant){ + private fun addXp(thisCombatant:ICombatant, amount:Int, otherCombatant:ICombatant){ if(thisCombatant !is MapUnitCombatant) return if(thisCombatant.unit.promotions.totalXpProduced() >= 30 && otherCombatant.getCivInfo().isBarbarian()) return @@ -211,7 +210,6 @@ class Battle(val gameInfo:GameInfo) { for(airUnit in airUnits.toList()) airUnit.destroy() } - if (attacker.getCivInfo().isPlayerCivilization()) attackerCiv.popupAlerts.add(PopupAlert(AlertType.CityConquered, city.name)) else city.puppetCity(attacker.getCivInfo()) @@ -224,12 +222,13 @@ class Battle(val gameInfo:GameInfo) { return null } - fun captureCivilianUnit(attacker: ICombatant, defender: ICombatant){ + private fun captureCivilianUnit(attacker: ICombatant, defender: ICombatant){ + // barbarians don't capture civilians, City-states don't capture settlers if(attacker.getCivInfo().isBarbarian() || (attacker.getCivInfo().isCityState() && defender.getName()==Constants.settler)){ defender.takeDamage(100) return - } // barbarians don't capture civilians! + } if (defender.getCivInfo().isDefeated()) {//Last settler captured defender.getCivInfo().destroy() @@ -245,14 +244,14 @@ class Battle(val gameInfo:GameInfo) { capturedUnit.updateViewableTiles() } - fun intercept(attacker:MapUnitCombatant, defender: ICombatant){ + private fun tryInterceptAirAttack(attacker:MapUnitCombatant, defender: ICombatant) { val attackedTile = defender.getTile() - for(interceptor in defender.getCivInfo().getCivUnits().filter { it.canIntercept(attackedTile) }){ - if(Random().nextFloat() > 100f/interceptor.interceptChance()) continue + for (interceptor in defender.getCivInfo().getCivUnits().filter { it.canIntercept(attackedTile) }) { + if (Random().nextFloat() > 100f / interceptor.interceptChance()) continue - var damage = BattleDamage().calculateDamageToDefender(MapUnitCombatant(interceptor),attacker) - damage += damage*interceptor.interceptDamagePercentBonus()/100 - if(attacker.unit.hasUnique("Reduces damage taken from interception by 50%")) damage/=2 + var damage = BattleDamage().calculateDamageToDefender(MapUnitCombatant(interceptor), attacker) + damage += damage * interceptor.interceptDamagePercentBonus() / 100 + if (attacker.unit.hasUnique("Reduces damage taken from interception by 50%")) damage /= 2 attacker.takeDamage(damage) interceptor.attacksThisTurn++ @@ -260,16 +259,19 @@ class Battle(val gameInfo:GameInfo) { val attackerName = attacker.getName() val interceptorName = interceptor.name - if(attacker.isDefeated()){ - attacker.getCivInfo().addNotification("Our [$attackerName] was destroyed by an intercepting [$interceptorName]", + if (attacker.isDefeated()) { + attacker.getCivInfo() + .addNotification("Our [$attackerName] was destroyed by an intercepting [$interceptorName]", Color.RED) - defender.getCivInfo().addNotification("Our [$interceptorName] intercepted and destroyed an enemy [$attackerName]", + defender.getCivInfo() + .addNotification("Our [$interceptorName] intercepted and destroyed an enemy [$attackerName]", interceptor.currentTile.position, Color.RED) - } - else{ - attacker.getCivInfo().addNotification("Our [$attackerName] was attacked by an intercepting [$interceptorName]", + } else { + attacker.getCivInfo() + .addNotification("Our [$attackerName] was attacked by an intercepting [$interceptorName]", Color.RED) - defender.getCivInfo().addNotification("Our [$interceptorName] intercepted and attacked an enemy [$attackerName]", + defender.getCivInfo() + .addNotification("Our [$interceptorName] intercepted and attacked an enemy [$attackerName]", interceptor.currentTile.position, Color.RED) } return diff --git a/core/src/com/unciv/logic/map/RandomMapGenerator.kt b/core/src/com/unciv/logic/map/RandomMapGenerator.kt index 2bac9ade0c..a23bbb1441 100644 --- a/core/src/com/unciv/logic/map/RandomMapGenerator.kt +++ b/core/src/com/unciv/logic/map/RandomMapGenerator.kt @@ -2,8 +2,6 @@ package com.unciv.logic.map import com.badlogic.gdx.math.Vector2 import com.unciv.Constants -import com.unciv.Constants.Companion.mountain -import com.unciv.Constants.Companion.ocean import com.unciv.logic.HexMath import com.unciv.models.gamebasics.GameBasics import com.unciv.models.gamebasics.tile.ResourceType @@ -123,7 +121,7 @@ class CelluarAutomataRandomMapGenerator(): SeedRandomMapGenerator() { val tile=TileInfo() tile.position=vector if (type == TerrainType.Land) tile.baseTerrain = "" - else tile.baseTerrain = ocean + else tile.baseTerrain = Constants.ocean return tile } @@ -152,7 +150,7 @@ class CelluarAutomataRandomMapGenerator(): SeedRandomMapGenerator() { if (tilesInArea.size <= 10) { for (vector in tilesInArea) { val tile = map[vector.toString()]!! - tile.baseTerrain = "Lakes" + tile.baseTerrain = Constants.lakes tile.setTransients() } } @@ -160,7 +158,7 @@ class CelluarAutomataRandomMapGenerator(): SeedRandomMapGenerator() { } //Coasts - for (tile in map.values.filter { it.baseTerrain == ocean }) { + for (tile in map.values.filter { it.baseTerrain == Constants.ocean }) { if (HexMath().getVectorsInDistance(tile.position,2).any { hasLandTile(map,it) }) { tile.baseTerrain = Constants.coast tile.setTransients() @@ -170,7 +168,7 @@ class CelluarAutomataRandomMapGenerator(): SeedRandomMapGenerator() { override fun randomizeTile(tileInfo: TileInfo, map: HashMap){ if(tileInfo.getBaseTerrain().type==TerrainType.Land && Math.random()<0.05f){ - tileInfo.baseTerrain = mountain + tileInfo.baseTerrain = Constants.mountain tileInfo.setTransients() } addRandomTerrainFeature(tileInfo) @@ -185,8 +183,8 @@ class CelluarAutomataRandomMapGenerator(): SeedRandomMapGenerator() { fun divideIntoAreas2(averageTilesPerArea: Int, waterPercent: Float, distance: Int, map: HashMap) { val areas = ArrayList() - val terrains = GameBasics.Terrains.values.filter { it.type === TerrainType.Land && it.name != "Lakes" - && it.name != mountain} + val terrains = GameBasics.Terrains.values.filter { it.type === TerrainType.Land && it.name != Constants.lakes + && it.name != Constants.mountain} while(map.values.any { it.baseTerrain=="" }) // the world could be split into lots off tiny islands, and every island deserves land types { @@ -196,7 +194,7 @@ class CelluarAutomataRandomMapGenerator(): SeedRandomMapGenerator() { for (i in 0 until numberOfSeeds) { var terrain = if (Math.random() > waterPercent) terrains.random().name - else ocean + else Constants.ocean val tile = emptyTiles.random() //change grassland to desert or tundra based on y @@ -204,7 +202,7 @@ class CelluarAutomataRandomMapGenerator(): SeedRandomMapGenerator() { if (terrain == "Grassland" || terrain == "Tundra") terrain = "Desert" } else if (abs(getLatitude(tile.position)) > maxLatitude * 0.7) { - if (terrain == "Grassland" || terrain == Constants.plains || terrain == "Desert" || terrain == ocean) { + if (terrain == "Grassland" || terrain == Constants.plains || terrain == "Desert" || terrain == Constants.ocean) { terrain = "Tundra" } } else { @@ -263,9 +261,9 @@ class PerlinNoiseRandomMapGenerator:SeedRandomMapGenerator(){ + Perlin.noise(vector.x*ratio*2,vector.y*ratio*2,mapRandomSeed)/2 + Perlin.noise(vector.x*ratio*4,vector.y*ratio*4,mapRandomSeed)/4 when { - height>0.8 -> tile.baseTerrain = mountain + height>0.8 -> tile.baseTerrain = Constants.mountain height>0 -> tile.baseTerrain = "" // we'll leave this to the area division - else -> tile.baseTerrain = ocean + else -> tile.baseTerrain = Constants.ocean } return tile } @@ -297,12 +295,12 @@ class AlexanderRandomMapGenerator:RandomMapGenerator(){ if(map[currentSpark]!!.baseTerrain==grassland){ for(tile in emptyTilesAroundSpark){ if(Math.random()() for(entry in map){ entry.value!!.position = entry.key - if(entry.value!!.baseTerrain==ocean + if(entry.value!!.baseTerrain==Constants.ocean && HexMath().getAdjacentVectors(entry.key).all { !map.containsKey(it) || map[it]!!.baseTerrain==grassland }) entry.value!!.baseTerrain=grassland @@ -364,7 +362,8 @@ open class SeedRandomMapGenerator : RandomMapGenerator() { open fun divideIntoAreas(averageTilesPerArea: Int, waterPercent: Float, map: HashMap) { val areas = ArrayList() - val terrains = GameBasics.Terrains.values.filter { it.type === TerrainType.Land && it.name != "Lakes" && it.name != mountain } + val terrains = GameBasics.Terrains.values + .filter { it.type === TerrainType.Land && it.name != Constants.lakes && it.name != Constants.mountain } while(map.values.any { it.baseTerrain=="" }) // the world could be split into lots off tiny islands, and every island deserves land types { @@ -373,7 +372,7 @@ open class SeedRandomMapGenerator : RandomMapGenerator() { for (i in 0 until numberOfSeeds) { val terrain = if (Math.random() > waterPercent) terrains.random().name - else ocean + else Constants.ocean val area = Area(terrain) val tile = emptyTiles.random() emptyTiles -= tile @@ -386,10 +385,10 @@ open class SeedRandomMapGenerator : RandomMapGenerator() { } - for (area in areas.filter { it.terrain == ocean && it.locations.size <= 10 }) { + for (area in areas.filter { it.terrain == Constants.ocean && it.locations.size <= 10 }) { // areas with 10 or less tiles are lakes. for (location in area.locations) - map[location]!!.baseTerrain = "Lakes" + map[location]!!.baseTerrain = Constants.lakes } } @@ -439,7 +438,7 @@ open class RandomMapGenerator { tileInfo.position = position val terrains = GameBasics.Terrains.values - val baseTerrain = terrains.filter { it.type === TerrainType.Land && it.name != "Lakes" }.random() + val baseTerrain = terrains.filter { it.type === TerrainType.Land }.random() tileInfo.baseTerrain = baseTerrain.name addRandomTerrainFeature(tileInfo) @@ -498,7 +497,7 @@ open class RandomMapGenerator { } open fun setWaterTiles(map: HashMap) { - for (tile in map.values.filter { it.baseTerrain == ocean }) { + for (tile in map.values.filter { it.baseTerrain == Constants.ocean }) { if (HexMath().getVectorsInDistance(tile.position,2).any { hasLandTile(map,it) }) { tile.baseTerrain = Constants.coast tile.setTransients() @@ -508,12 +507,12 @@ open class RandomMapGenerator { open fun randomizeTile(tileInfo: TileInfo, map: HashMap){ if(tileInfo.getBaseTerrain().type==TerrainType.Land && Math.random()<0.05f){ - tileInfo.baseTerrain = mountain + tileInfo.baseTerrain = Constants.mountain tileInfo.setTransients() } if(tileInfo.getBaseTerrain().type==TerrainType.Land && Math.random()<0.05f && HexMath().getVectorsInDistance(tileInfo.position,1).all { hasLandTile(map,it) }){ - tileInfo.baseTerrain = "Lakes" + tileInfo.baseTerrain = Constants.lakes tileInfo.setTransients() } addRandomTerrainFeature(tileInfo)