AIR UNITS ARE GO!

Added interception and basic air unit AI
This commit is contained in:
Yair Morgenstern 2019-07-10 22:41:44 +03:00
parent 51ad40b8bf
commit 214b4880e4
9 changed files with 110 additions and 21 deletions

View file

@ -304,6 +304,18 @@
Japanese:"「私たちの[ourUnit]を攻撃している間に敵[unit]が破壊された"
}
"Our [attackerName] was destroyed by an intercepting [interceptorName]":{
}
"Our [interceptorName] intercepted and destroyed an enemy [attackerName]":{
}
"Our [$attackerName] was attacked by an intercepting [$interceptorName]":{
}
"Our [$interceptorName] intercepted and attacked an enemy [$attackerName]":{
}
"An enemy [unit] was spotted near our territory":{
Italian:"Abbiamo avvistato un'unità nemica [unit] vicino al nostro territorio"
Russian:"Вражеский [unit] был замечен у нашей территории"

View file

@ -960,15 +960,6 @@
French:"Porte-avions"
}
"Great War Bomber":{
Italian:"Bombardiere della Grande Guerra"
Romanian:"Bombardier din Marele Război"
Spanish:"Bombardero de la Gran Guerra"
Simplified_Chinese:"早期轰炸机"
Russian:"великий бомбардировщик0_0"
German:"Weltkriegsbomber"
French:"Bombardier de la grande guerre"
}
"Triplane":{
Italian:"Triplano"
@ -980,6 +971,20 @@
French:"Triplan"
}
"[percent]% chance to intercept air attacks":{
}
"Great War Bomber":{
Italian:"Bombardiere della Grande Guerra"
Romanian:"Bombardier din Marele Război"
Spanish:"Bombardero de la Gran Guerra"
Simplified_Chinese:"早期轰炸机"
Russian:"великий бомбардировщик0_0"
German:"Weltkriegsbomber"
French:"Bombardier de la grande guerre"
}
////// Atomic units (not in game yet but will be, not super important but why not)
"Rocket Artillery":{

View file

@ -838,10 +838,10 @@
hurryCostModifier:20,
attackSound:"shot"
},
/*
{
name:"Triplane",
unitType:"AirFighter",
unitType:"Fighter",
movement:1,
strength:35,
rangedStrength:35,
@ -849,6 +849,8 @@
cost: 325,
requiredTech:"Flight",
hurryCostModifier:20,
uniques:["[50]% chance to intercept air attacks","Bonus vs Bomber 150%",
"6 tiles in every direction always visible"]
attackSound:"shot"
},

View file

@ -21,8 +21,8 @@ android {
applicationId "com.unciv.app"
minSdkVersion 14
targetSdkVersion 28
versionCode 270
versionName "2.17.15"
versionCode 271
versionName "2.18.0"
}
// Had to add this crap for Travis to build, it wanted to sign the app

View file

@ -2,6 +2,7 @@ package com.unciv.logic.automation
import com.unciv.Constants
import com.unciv.UnCivGame
import com.unciv.logic.battle.MapUnitCombatant
import com.unciv.logic.civilization.CivilizationInfo
import com.unciv.logic.civilization.GreatPersonManager
import com.unciv.logic.civilization.diplomacy.DiplomacyFlags
@ -161,4 +162,24 @@ class SpecificUnitAutomation{
}
fun automateFighter(unit: MapUnit) {
val tilesInRange = unit.currentTile.getTilesInDistance(unit.getRange())
val enemyAirUnitsInRange = tilesInRange
.flatMap { it.airUnits }.filter { it.civInfo.isAtWarWith(unit.civInfo) }
if(enemyAirUnitsInRange.isNotEmpty()) return // we need to be on standby in case they attack
if(UnitAutomation().tryAttackNearbyEnemy(unit)) return
val reachableCities = tilesInRange
.filter { it.isCityCenter() && it.getOwner()==unit.civInfo && unit.canMoveTo(it)}
for(city in reachableCities){
if(city.getTilesInDistance(unit.getRange())
.any { UnitAutomation().containsAttackableEnemy(it,MapUnitCombatant(unit)) }) {
unit.moveToTile(city)
return
}
}
}
}

View file

@ -33,6 +33,9 @@ class UnitAutomation{
if (unit.name == "Great General")
return SpecificUnitAutomation().automateGreatGeneral(unit)
if(unit.type==UnitType.Fighter)
return SpecificUnitAutomation().automateFighter(unit)
if(unit.name.startsWith("Great")
&& unit.name in GreatPersonManager().statToGreatPersonMapping.values){ // So "Great War Infantry" isn't caught here
return SpecificUnitAutomation().automateGreatPerson(unit)
@ -61,7 +64,7 @@ class UnitAutomation{
// if a embarked melee unit can land and attack next turn, do not attack from water.
if (unit.type.isLandUnit() && unit.type.isMelee() && unit.isEmbarked()) {
if (tryLandUnitToAttackPosition(unit,unitDistanceToTiles)) return
if (tryDisembarkUnitToAttackPosition(unit,unitDistanceToTiles)) return
}
// if there is an attackable unit in the vicinity, attack!
@ -330,7 +333,7 @@ class UnitAutomation{
return false
}
private fun tryLandUnitToAttackPosition(unit: MapUnit, unitDistanceToTiles: HashMap<TileInfo, Float>): Boolean {
private fun tryDisembarkUnitToAttackPosition(unit: MapUnit, unitDistanceToTiles: HashMap<TileInfo, Float>): Boolean {
if (!unit.type.isMelee() || !unit.type.isLandUnit() || !unit.isEmbarked()) return false
val attackableEnemiesNextTurn = getAttackableEnemies(unit, unitDistanceToTiles)
// Only take enemies we can fight without dying
@ -349,7 +352,7 @@ class UnitAutomation{
return false
}
private fun tryAttackNearbyEnemy(unit: MapUnit): Boolean {
fun tryAttackNearbyEnemy(unit: MapUnit): Boolean {
val attackableEnemies = getAttackableEnemies(unit, unit.getDistanceToTiles())
// Only take enemies we can fight without dying
.filter {

View file

@ -34,6 +34,11 @@ class Battle(val gameInfo:GameInfo) {
println(attacker.getCivInfo().civName+" "+attacker.getName()+" attacked "+defender.getCivInfo().civName+" "+defender.getName())
val attackedTile = defender.getTile()
if(attacker is MapUnitCombatant && attacker.getUnitType().isAirUnit()){
intercept(attacker,defender)
if(attacker.isDefeated()) return
}
var damageToDefender = BattleDamage().calculateDamageToDefender(attacker,defender)
var damageToAttacker = BattleDamage().calculateDamageToAttacker(attacker,defender)
@ -287,4 +292,30 @@ class Battle(val gameInfo:GameInfo) {
capturedUnit.assignOwner(attacker.getCivInfo())
capturedUnit.updateViewableTiles()
}
fun intercept(attacker:MapUnitCombatant, defender: ICombatant){
val attackedTile = defender.getTile()
for(unit in defender.getCivInfo().getCivUnits().filter { it.canIntercept(attackedTile) }){
if(Random().nextFloat() > 100f/unit.interceptChance()) continue
val damage = BattleDamage().calculateDamageToDefender(MapUnitCombatant(unit),attacker)
attacker.takeDamage(damage)
val attackerName = attacker.getName()
val interceptorName = unit.name
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]",
unit.currentTile.position, Color.RED)
}
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]",
unit.currentTile.position, Color.RED)
}
return
}
}
}

View file

@ -130,7 +130,9 @@ class MapUnit {
// we need to map all the places that this could change: Unit changes locations, owners, gets promotion?
fun updateViewableTiles() {
if(type.isAirUnit()){
viewableTiles = getTile().getTilesInDistance(6) // it's that simple
if(hasUnique("6 tiles in every direction always visible"))
viewableTiles = getTile().getTilesInDistance(6) // it's that simple
else viewableTiles = listOf() // bomber units don't do recon
}
else {
var visibilityRange = 2
@ -559,5 +561,18 @@ class MapUnit {
civInfo.addUnit(this,updateCivInfo)
}
fun canIntercept(attackedTile: TileInfo): Boolean {
return interceptChance()!=0 && attacksThisTurn==0
&& currentTile.arialDistanceTo(attackedTile) <= getRange()
}
fun interceptChance():Int{
val interceptUnique = getUniques()
.firstOrNull { it.endsWith(" chance to intercept air attacks") }
if(interceptUnique==null) return 0
val percent = Regex("\\d+").find(interceptUnique)!!.value
return percent.toInt()
}
//endregion
}

View file

@ -15,8 +15,8 @@ enum class UnitType{
WaterRanged,
WaterSubmarine,
AirFighter,
AirBomber;
Fighter,
Bomber;
fun isMelee(): Boolean {
return this == Melee
@ -57,7 +57,7 @@ enum class UnitType{
}
fun isAirUnit():Boolean{
return this==AirBomber
|| this==AirFighter
return this==Bomber
|| this==Fighter
}
}