AIR UNITS ARE GO!
Added interception and basic air unit AI
This commit is contained in:
parent
51ad40b8bf
commit
214b4880e4
9 changed files with 110 additions and 21 deletions
|
@ -304,6 +304,18 @@
|
||||||
Japanese:"「私たちの[ourUnit]を攻撃している間に敵[unit]が破壊された"
|
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":{
|
"An enemy [unit] was spotted near our territory":{
|
||||||
Italian:"Abbiamo avvistato un'unità nemica [unit] vicino al nostro territorio"
|
Italian:"Abbiamo avvistato un'unità nemica [unit] vicino al nostro territorio"
|
||||||
Russian:"Вражеский [unit] был замечен у нашей территории"
|
Russian:"Вражеский [unit] был замечен у нашей территории"
|
||||||
|
|
|
@ -960,15 +960,6 @@
|
||||||
French:"Porte-avions"
|
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":{
|
"Triplane":{
|
||||||
Italian:"Triplano"
|
Italian:"Triplano"
|
||||||
|
@ -980,6 +971,20 @@
|
||||||
French:"Triplan"
|
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)
|
////// Atomic units (not in game yet but will be, not super important but why not)
|
||||||
|
|
||||||
"Rocket Artillery":{
|
"Rocket Artillery":{
|
||||||
|
|
|
@ -838,10 +838,10 @@
|
||||||
hurryCostModifier:20,
|
hurryCostModifier:20,
|
||||||
attackSound:"shot"
|
attackSound:"shot"
|
||||||
},
|
},
|
||||||
/*
|
|
||||||
{
|
{
|
||||||
name:"Triplane",
|
name:"Triplane",
|
||||||
unitType:"AirFighter",
|
unitType:"Fighter",
|
||||||
movement:1,
|
movement:1,
|
||||||
strength:35,
|
strength:35,
|
||||||
rangedStrength:35,
|
rangedStrength:35,
|
||||||
|
@ -849,6 +849,8 @@
|
||||||
cost: 325,
|
cost: 325,
|
||||||
requiredTech:"Flight",
|
requiredTech:"Flight",
|
||||||
hurryCostModifier:20,
|
hurryCostModifier:20,
|
||||||
|
uniques:["[50]% chance to intercept air attacks","Bonus vs Bomber 150%",
|
||||||
|
"6 tiles in every direction always visible"]
|
||||||
attackSound:"shot"
|
attackSound:"shot"
|
||||||
},
|
},
|
||||||
|
|
||||||
|
|
|
@ -21,8 +21,8 @@ android {
|
||||||
applicationId "com.unciv.app"
|
applicationId "com.unciv.app"
|
||||||
minSdkVersion 14
|
minSdkVersion 14
|
||||||
targetSdkVersion 28
|
targetSdkVersion 28
|
||||||
versionCode 270
|
versionCode 271
|
||||||
versionName "2.17.15"
|
versionName "2.18.0"
|
||||||
}
|
}
|
||||||
|
|
||||||
// Had to add this crap for Travis to build, it wanted to sign the app
|
// Had to add this crap for Travis to build, it wanted to sign the app
|
||||||
|
|
|
@ -2,6 +2,7 @@ package com.unciv.logic.automation
|
||||||
|
|
||||||
import com.unciv.Constants
|
import com.unciv.Constants
|
||||||
import com.unciv.UnCivGame
|
import com.unciv.UnCivGame
|
||||||
|
import com.unciv.logic.battle.MapUnitCombatant
|
||||||
import com.unciv.logic.civilization.CivilizationInfo
|
import com.unciv.logic.civilization.CivilizationInfo
|
||||||
import com.unciv.logic.civilization.GreatPersonManager
|
import com.unciv.logic.civilization.GreatPersonManager
|
||||||
import com.unciv.logic.civilization.diplomacy.DiplomacyFlags
|
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
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
|
@ -33,6 +33,9 @@ class UnitAutomation{
|
||||||
if (unit.name == "Great General")
|
if (unit.name == "Great General")
|
||||||
return SpecificUnitAutomation().automateGreatGeneral(unit)
|
return SpecificUnitAutomation().automateGreatGeneral(unit)
|
||||||
|
|
||||||
|
if(unit.type==UnitType.Fighter)
|
||||||
|
return SpecificUnitAutomation().automateFighter(unit)
|
||||||
|
|
||||||
if(unit.name.startsWith("Great")
|
if(unit.name.startsWith("Great")
|
||||||
&& unit.name in GreatPersonManager().statToGreatPersonMapping.values){ // So "Great War Infantry" isn't caught here
|
&& unit.name in GreatPersonManager().statToGreatPersonMapping.values){ // So "Great War Infantry" isn't caught here
|
||||||
return SpecificUnitAutomation().automateGreatPerson(unit)
|
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 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 (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!
|
// if there is an attackable unit in the vicinity, attack!
|
||||||
|
@ -330,7 +333,7 @@ class UnitAutomation{
|
||||||
return false
|
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
|
if (!unit.type.isMelee() || !unit.type.isLandUnit() || !unit.isEmbarked()) return false
|
||||||
val attackableEnemiesNextTurn = getAttackableEnemies(unit, unitDistanceToTiles)
|
val attackableEnemiesNextTurn = getAttackableEnemies(unit, unitDistanceToTiles)
|
||||||
// Only take enemies we can fight without dying
|
// Only take enemies we can fight without dying
|
||||||
|
@ -349,7 +352,7 @@ class UnitAutomation{
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun tryAttackNearbyEnemy(unit: MapUnit): Boolean {
|
fun tryAttackNearbyEnemy(unit: MapUnit): Boolean {
|
||||||
val attackableEnemies = getAttackableEnemies(unit, unit.getDistanceToTiles())
|
val attackableEnemies = getAttackableEnemies(unit, unit.getDistanceToTiles())
|
||||||
// Only take enemies we can fight without dying
|
// Only take enemies we can fight without dying
|
||||||
.filter {
|
.filter {
|
||||||
|
|
|
@ -34,6 +34,11 @@ class Battle(val gameInfo:GameInfo) {
|
||||||
println(attacker.getCivInfo().civName+" "+attacker.getName()+" attacked "+defender.getCivInfo().civName+" "+defender.getName())
|
println(attacker.getCivInfo().civName+" "+attacker.getName()+" attacked "+defender.getCivInfo().civName+" "+defender.getName())
|
||||||
val attackedTile = defender.getTile()
|
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 damageToDefender = BattleDamage().calculateDamageToDefender(attacker,defender)
|
||||||
var damageToAttacker = BattleDamage().calculateDamageToAttacker(attacker,defender)
|
var damageToAttacker = BattleDamage().calculateDamageToAttacker(attacker,defender)
|
||||||
|
|
||||||
|
@ -287,4 +292,30 @@ class Battle(val gameInfo:GameInfo) {
|
||||||
capturedUnit.assignOwner(attacker.getCivInfo())
|
capturedUnit.assignOwner(attacker.getCivInfo())
|
||||||
capturedUnit.updateViewableTiles()
|
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
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -130,7 +130,9 @@ class MapUnit {
|
||||||
// we need to map all the places that this could change: Unit changes locations, owners, gets promotion?
|
// we need to map all the places that this could change: Unit changes locations, owners, gets promotion?
|
||||||
fun updateViewableTiles() {
|
fun updateViewableTiles() {
|
||||||
if(type.isAirUnit()){
|
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 {
|
else {
|
||||||
var visibilityRange = 2
|
var visibilityRange = 2
|
||||||
|
@ -559,5 +561,18 @@ class MapUnit {
|
||||||
civInfo.addUnit(this,updateCivInfo)
|
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
|
//endregion
|
||||||
}
|
}
|
|
@ -15,8 +15,8 @@ enum class UnitType{
|
||||||
WaterRanged,
|
WaterRanged,
|
||||||
WaterSubmarine,
|
WaterSubmarine,
|
||||||
|
|
||||||
AirFighter,
|
Fighter,
|
||||||
AirBomber;
|
Bomber;
|
||||||
|
|
||||||
fun isMelee(): Boolean {
|
fun isMelee(): Boolean {
|
||||||
return this == Melee
|
return this == Melee
|
||||||
|
@ -57,7 +57,7 @@ enum class UnitType{
|
||||||
}
|
}
|
||||||
|
|
||||||
fun isAirUnit():Boolean{
|
fun isAirUnit():Boolean{
|
||||||
return this==AirBomber
|
return this==Bomber
|
||||||
|| this==AirFighter
|
|| this==Fighter
|
||||||
}
|
}
|
||||||
}
|
}
|
Loading…
Reference in a new issue