/*
* Seven Kingdoms: Ancient Adversaries
*
* Copyright 1997,1998 Enlight Software Ltd.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see .
*
*/
//Filename : OAI_ATTK.CPP
//Description: AI - attacking
#include
#include
#include
#include
#include
#include
#include
//------ Declare static functions --------//
static int get_target_nation_recno(int targetXLoc, int targetYLoc);
static int sort_attack_camp_function( const void *a, const void *b );
//--------- Begin of function Nation::ai_attack_target --------//
//
// Think about attacking a specific target.
//
// targetXLoc, targetYLoc - location of the target
// targetCombatLevel - the combat level of the target, will
// only attack the target if the attacker's
// force is larger than it.
// [int] defenseMode - whether the attack is basically for
// defending against an attack
// (default: 0)
// [int] justMoveToFlag - whether just all move there and wait for
// the units to attack the enemies automatically
// (default: 0)
// [int] attackerMinCombatLevel - the minimum combat level of the attacker,
// do not send troops whose combat level
// is lower than this.
// (default: 0)
// [int] leadAttackCampRecno - if this is given, this camp will be
// included in the attacker list by passing
// checking on it.
// (default: 0)
// [int] useAllCamp - use all camps to attack even if defenseMode is 0
// (default: defenseMode, which is default to 0)
//
// return: 0 - no attack action
// >0 - the total combat level of attacking force.
//
int Nation::ai_attack_target(int targetXLoc, int targetYLoc, int targetCombatLevel, int defenseMode, int justMoveToFlag, int attackerMinCombatLevel, int leadAttackCampRecno, int useAllCamp)
{
/* // this will be called when the AI tries to capture the town and attack the town's defense.
#ifdef DEBUG //----- check for attacking own objects error ------//
{
int targetNationRecno = get_target_nation_recno(targetXLoc, targetYLoc);
if( targetNationRecno )
{
err_when( get_relation(targetNationRecno)->status >= NATION_FRIENDLY );
}
}
#endif
*/
//--- order nearby mobile units who are on their way to home camps to join this attack mission. ---//
if( defenseMode )
useAllCamp = 1;
if( defenseMode ) // only for defense mode, for attack mission, we should plan and organize it better
{
int originalTargetCombatLevel;
targetCombatLevel = ai_attack_order_nearby_mobile(targetXLoc, targetYLoc, targetCombatLevel);
if( targetCombatLevel < 0 ) // the mobile force alone can finish all the enemies
return originalTargetCombatLevel;
}
//--- try to send troop with maxTargetCombatLevel, and don't send troop if available combat level < minTargetCombatLevel ---//
int maxTargetCombatLevel = targetCombatLevel * (150+pref_force_projection/2) / 100; // 150% to 200%
int minTargetCombatLevel;
if( defenseMode )
minTargetCombatLevel = targetCombatLevel * (100-pref_military_courage/2) / 100; // 50% to 100%
else
minTargetCombatLevel = targetCombatLevel * (125+pref_force_projection/4) / 100; // 125% to 150%
//--- if the AI is already on an attack mission ---//
if( attack_camp_count )
return 0;
//---- first locate for camps that do not need to protect any towns ---//
#define MAX_SUITABLE_TOWN_CAMP 10 // no. of camps in a town
FirmCamp* firmCamp;
int i, j;
int targetRegionId = world.get_loc(targetXLoc, targetYLoc)->region_id;
err_when( targetXLoc < 0 || targetXLoc >= MAX_WORLD_X_LOC );
err_when( targetYLoc < 0 || targetYLoc >= MAX_WORLD_Y_LOC );
ai_attack_target_x_loc = targetXLoc;
ai_attack_target_y_loc = targetYLoc;
ai_attack_target_nation_recno = get_target_nation_recno(targetXLoc, targetYLoc);
attack_camp_count=0;
AttackCamp townCampArray[MAX_SUITABLE_TOWN_CAMP];
short townCampCount;
//------- if there is a pre-selected camp -------//
lead_attack_camp_recno = leadAttackCampRecno;
if( leadAttackCampRecno )
{
err_when( firm_array[leadAttackCampRecno]->nation_recno != nation_recno );
err_when( firm_array[leadAttackCampRecno]->firm_id != FIRM_CAMP );
attack_camp_array[attack_camp_count].firm_recno = leadAttackCampRecno;
attack_camp_array[attack_camp_count].combat_level = ((FirmCamp*)firm_array[leadAttackCampRecno])->total_combat_level();
err_when( attack_camp_array[attack_camp_count].combat_level < 0 );
attack_camp_count++;
}
//---- if the military courage is low or the king is injured, don't send the king out to battles ---//
Nation* ownNation = nation_array[nation_recno];
int kingFirmRecno=0;
if( king_unit_recno )
{
Unit* kingUnit = unit_array[king_unit_recno];
if( kingUnit->unit_mode == UNIT_MODE_OVERSEE )
{
Firm* kingFirm = firm_array[kingUnit->unit_mode_para];
int rc = 0;
if( ai_camp_count > 3 + (100-ownNation->pref_military_courage)/20 ) // don't use the king if we have other generals, the king won't be used if we have 3 to 8 camps. The higher the military courage is, the smaller will be the number of camps
rc = 1;
//--- if the military courage is low or the king is injured ---//
else if( kingUnit->hit_points < 230-ownNation->pref_military_courage ) // 130 to 230, if over 200, the king will not fight
rc = 1;
//--- if the King does have a full troop ----//
else if( kingFirm->worker_count < MAX_WORKER )
rc = 1;
//-------------------------------------------//
if( rc )
{
kingFirmRecno = kingUnit->unit_mode_para;
//--- if the king is very close to the target, ask him to attack also ---//
if( kingFirmRecno &&
kingUnit->hit_points >= 150-ownNation->pref_military_courage/4 ) // if the king is not heavily injured
{
firmCamp = (FirmCamp*) firm_array[kingFirmRecno];
if( firmCamp->worker_count == MAX_WORKER ) // the king shouldn't go out alone
{
if( m.points_distance(firmCamp->center_x, firmCamp->center_y,
targetXLoc, targetYLoc) <= EFFECTIVE_FIRM_TOWN_DISTANCE )
{
kingFirmRecno = 0;
}
}
}
}
}
}
//--------- locate for camps that are not linked to towns ---------//
int rc;
for( i=0 ; ifirm_id != FIRM_CAMP );
if( firmCamp->region_id != targetRegionId )
continue;
if( !firmCamp->overseer_recno || !firmCamp->worker_count )
continue;
if( firmCamp->patrol_unit_count > 0 ) // if there are units patrolling out
continue;
if( firmCamp->ai_capture_town_recno ) // the base is trying to capture an independent town
continue;
if( firmCamp->is_attack_camp )
continue;
if( firmCamp->firm_recno == kingFirmRecno )
continue;
//---- don't order this camp if the overseer is injured ----//
Unit* overseerUnit = unit_array[firmCamp->overseer_recno];
if( overseerUnit->hit_points < overseerUnit->max_hit_points &&
overseerUnit->hit_points < 100-ownNation->pref_military_courage/2 ) // 50 to 100
{
continue;
}
//----------------------------------------------------------//
if( attackerMinCombatLevel )
{
if( firmCamp->average_combat_level() < attackerMinCombatLevel )
continue;
}
//-------------------------------------//
//
// Add this camp if:
// 1. we are in defense mode, and have to get all the forces available to defend against the attack.
// 2. this camp isn't linked to any of our towns.
//
//-------------------------------------//
if( useAllCamp )
rc = 1;
else
{
rc = firmCamp->linked_town_count==0; // don't use this camp as it may be in the process of capturing an indepdendent town or an enemy town
/*
for( int j=firmCamp->linked_town_count-1 ; j>=0 ; j-- )
{
if( town_array[firmCamp->linked_town_array[j]]->nation_recno == nation_recno )
break;
}
rc = j<0; // j<0 means not linked to any of our towns.
*/
}
if( rc )
{
//--- if this camp into the list of suitable attacker firm ---//
if( attack_camp_count < MAX_SUITABLE_ATTACK_CAMP )
{
err_when( firmCamp->nation_recno != nation_recno );
attack_camp_array[attack_camp_count].firm_recno = firmCamp->firm_recno;
attack_camp_array[attack_camp_count].combat_level = firmCamp->total_combat_level();
err_when( attack_camp_array[attack_camp_count].combat_level < 0 );
attack_camp_count++;
}
}
}
//---- locate for camps that are extra for protecting towns (there are basic ones doing the protection job only) ----//
int totalCombatLevel, protectionNeeded;
Town* townPtr;
Firm* firmPtr;
if( !useAllCamp ) // in defense mode, every camp has been already counted
{
for( i=0 ; iregion_id != targetRegionId )
continue;
err_when( townPtr->nation_recno != nation_recno );
//----- calculate the protection needed for this town ----//
protectionNeeded = townPtr->protection_needed();
townCampCount =0;
totalCombatLevel=0;
for( j=townPtr->linked_firm_count-1 ; j>=0 ; j-- )
{
firmPtr = firm_array[ townPtr->linked_firm_array[j] ];
if( firmPtr->nation_recno != nation_recno )
continue;
if( firmPtr->firm_recno == kingFirmRecno )
continue;
//----- if this is a camp, add combat level points -----//
if( firmPtr->firm_id == FIRM_CAMP )
{
if( !firmPtr->overseer_recno && !firmPtr->worker_count )
continue;
firmCamp = (FirmCamp*) firmPtr;
if( firmCamp->patrol_unit_count > 0 ) // if there are units patrolling out
continue;
if( firmCamp->ai_capture_town_recno ) // the base is trying to capture an independent town
continue;
if( firmCamp->is_attack_camp )
continue;
totalCombatLevel += firmCamp->total_combat_level();
if( townCampCount < MAX_SUITABLE_TOWN_CAMP )
{
err_when( firmCamp->nation_recno != nation_recno );
townCampArray[townCampCount].firm_recno = firmCamp->firm_recno;
townCampArray[townCampCount].combat_level = firmCamp->total_combat_level();
err_when( townCampArray[townCampCount].combat_level < 0 );
townCampCount++;
}
}
//--- if this is a civilian firm, add needed protection points ---//
else
{
if( firmPtr->firm_id == FIRM_MARKET )
protectionNeeded += ((FirmMarket*)firmPtr)->stock_value_index();
else
protectionNeeded += (int) firmPtr->productivity;
}
}
//--- see if the current combat level is larger than the protection needed ---//
if( totalCombatLevel > protectionNeeded )
{
//--- see if the protection is still enough if we put one of the camps into the upcoming battle ---//
for( int j=0 ; j protectionNeeded )
{
//--- if so, add this camp to the suitable camp list ---//
if( attack_camp_count < MAX_SUITABLE_ATTACK_CAMP )
{
//--- this camp can be linked to a town previously processed already (in this case, two towns linked to the same camp) ---//
int k;
for( k=0 ; knation_recno != nation_recno );
attack_camp_array[attack_camp_count] = townCampArray[j];
attack_camp_count++;
totalCombatLevel -= townCampArray[j].combat_level; // reduce it from the total combat level as its combat level has just been used, and is no longer available
}
}
}
}
}
}
}
//---- now we get all suitable camps in the list, it's time to attack ---//
//----- think about which ones in the list should be used -----//
//--- first calculate the total combat level of these camps ---//
totalCombatLevel = 0;
for( i=0 ; ination_recno != nation_recno );
attack_camp_array[i].distance = m.points_distance( firmPtr->center_x, firmPtr->center_y,
targetXLoc, targetYLoc );
err_when( attack_camp_array[i].distance < 0 );
}
//---- now sort the camps based on their distances & combat levels ----//
qsort( &attack_camp_array, attack_camp_count, sizeof(attack_camp_array[0]), sort_attack_camp_function );
//----- now take out the lowest rating ones -----//
for( i=attack_camp_count-1 ; i>=0 ; i-- )
{
if( totalCombatLevel - attack_camp_array[i].combat_level > maxTargetCombatLevel )
{
totalCombatLevel -= attack_camp_array[i].combat_level;
attack_camp_count--;
}
}
err_when( attack_camp_count < 0 );
//------- synchronize the attack date for different camps ----//
ai_attack_target_sync();
ai_attack_target_execute(!justMoveToFlag);
return totalCombatLevel;
}
//---------- End of function Nation::ai_attack_target --------//
//--------- Begin of function Nation::ai_attack_order_nearby_mobile --------//
//
// Order nearby mobile units who are on their way to home camps to
// join this attack mission.
//
// targetXLoc, targetYLoc - location of the target
// targetCombatLevel - the combat level of the target, will
// only attack the target if the attacker's
// force is larger than it.
//
// return: the remaining target combat level of the target
// after ordering the mobile units to deal with some of them.
//
int Nation::ai_attack_order_nearby_mobile(int targetXLoc, int targetYLoc, int targetCombatLevel)
{
int scanRange = 15+pref_military_development/20; // 15 to 20
int xOffset, yOffset;
int xLoc, yLoc;
int targetRegionId = world.get_region_id(targetXLoc, targetYLoc);
Location* locPtr;
for( int i=2 ; iregion_id != targetRegionId )
continue;
if( !locPtr->has_unit(UNIT_LAND) )
continue;
//----- if there is a unit on the location ------//
int unitRecno = locPtr->unit_recno(UNIT_LAND);
if( unit_array.is_deleted(unitRecno) ) // the unit is dying
continue;
Unit* unitPtr = unit_array[unitRecno];
//--- if if this is our own military unit ----//
if( unitPtr->nation_recno != nation_recno ||
unitPtr->skill.skill_id != SKILL_LEADING )
{
continue;
}
//--------- if this unit is injured ----------//
if( unitPtr->hit_points <
unitPtr->max_hit_points * (150-pref_military_courage/2) / 200 )
{
continue;
}
//---- only if this is not assigned to an action ---//
if( unitPtr->ai_action_id )
continue;
//---- if this unit is stop or assigning to a firm ----//
if( unitPtr->action_mode2 == ACTION_STOP ||
unitPtr->action_mode2 == ACTION_ASSIGN_TO_FIRM )
{
//-------- set should_attack on the target to 1 --------//
enable_should_attack_on_target(targetXLoc, targetYLoc);
//---------- attack now -----------//
unitPtr->attack_unit(targetXLoc, targetYLoc);
targetCombatLevel -= (int) unitPtr->hit_points; // reduce the target combat level
if( targetCombatLevel <= 0 )
break;
}
}
return targetCombatLevel;
}
//--------- End of function Nation::ai_attack_order_nearby_mobile --------//
//
//--------- Begin of function Nation::ai_attack_target_sync --------//
//
// Synchronize the timing of attacking a target. Camps that are further
// away from the target will move first while camps that are closer
// to the target will move later.
//
void Nation::ai_attack_target_sync()
{
//---- find the distance of the camp that is farest to the target ----//
int maxDistance=0;
int i;
for( i=0 ; i maxDistance )
maxDistance = attack_camp_array[i].distance;
}
int maxTravelDays = sprite_res[ unit_res[UNIT_NORMAN]->sprite_id ]->travel_days(maxDistance);
//------ set the date which the troop should start moving -----//
int travelDays;
for( i=0 ; ifirm_id != FIRM_CAMP );
err_when( firmPtr->nation_recno != nation_recno );
((FirmCamp*)firmPtr)->is_attack_camp = 1;
}
}
//---------- End of function Nation::ai_attack_target_sync --------//
//--------- Begin of function Nation::ai_attack_target_execute --------//
//
// Synchronize the timing of attacking a target. Camps that are further
// away from the target will move first while camps that are closer
// to the target will move later.
//
// directAttack - whether directly attack the target or
// just move close to the target.
//
void Nation::ai_attack_target_execute(int directAttack)
{
FirmCamp* firmCamp;
int firmRecno;
err_when( ai_attack_target_x_loc < 0 || ai_attack_target_x_loc >= MAX_WORLD_X_LOC );
err_when( ai_attack_target_y_loc < 0 || ai_attack_target_y_loc >= MAX_WORLD_Y_LOC );
//---- if the target no longer exist -----//
if( ai_attack_target_nation_recno != get_target_nation_recno(ai_attack_target_x_loc, ai_attack_target_y_loc) )
{
reset_ai_attack_target();
}
//----------------------------------------//
for( int i=attack_camp_count-1 ; i>=0 ; i-- )
{
//----- if it's still not the date to move to attack ----//
if( info.game_date < attack_camp_array[i].patrol_date )
continue;
//-------------------------------------------------------//
firmRecno = attack_camp_array[i].firm_recno;
firmCamp = (FirmCamp*) firm_array[firmRecno];
if( firmCamp->overseer_recno || firmCamp->worker_count )
{
//--- if this is the lead attack camp, don't mobilize the overseer ---//
if( lead_attack_camp_recno == firmRecno )
firmCamp->patrol_all_soldier(); // don't mobilize the overseer
else
firmCamp->patrol(); // mobilize the overseer and the soldiers
//----------------------------------------//
if( firmCamp->patrol_unit_count > 0 ) // there could be chances that there are no some for mobilizing the units
{
//------- declare war with the target nation -------//
if( ai_attack_target_nation_recno )
talk_res.ai_send_talk_msg(ai_attack_target_nation_recno, nation_recno, TALK_DECLARE_WAR);
//--- in defense mode, just move close to the target, the unit will start attacking themselves as their relationship is hostile already ---//
if( !directAttack )
{
unit_array.move_to(ai_attack_target_x_loc, ai_attack_target_y_loc, 0, firmCamp->patrol_unit_array,
firmCamp->patrol_unit_count, COMMAND_AI);
}
else
{
//-------- set should_attack on the target to 1 --------//
enable_should_attack_on_target(ai_attack_target_x_loc, ai_attack_target_y_loc);
//---------- attack now -----------//
// ##### patch begin Gilbert 5/8 ######//
unit_array.attack(ai_attack_target_x_loc, ai_attack_target_y_loc, 0, firmCamp->patrol_unit_array,
firmCamp->patrol_unit_count, COMMAND_AI, 0);
// ##### patch end Gilbert 5/8 ######//
}
}
}
//--------- reset FirmCamp::is_attack_camp ---------//
firmCamp->is_attack_camp = 0;
//------- remove this from attack_camp_array -------//
m.del_array_rec(attack_camp_array, attack_camp_count, sizeof(AttackCamp), i+1 );
attack_camp_count--;
}
}
//---------- End of function Nation::ai_attack_target_execute --------//
//--------- Begin of function Nation::reset_ai_attack_target --------//
//
void Nation::reset_ai_attack_target()
{
//------ reset all is_attack_camp -------//
for( int i=0 ; ifirm_id != FIRM_CAMP ||
firmPtr->nation_recno != nation_recno );
((FirmCamp*)firmPtr)->is_attack_camp = 0;
}
//--------------------------------------//
attack_camp_count = 0;
}
//---------- End of function Nation::reset_ai_attack_target --------//
//--------- Begin of function Nation::enable_should_attack_on_target --------//
//
void Nation::enable_should_attack_on_target(int targetXLoc, int targetYLoc)
{
//------ set should attack to 1 --------//
int targetNationRecno = 0;
Location* locPtr = world.get_loc(targetXLoc, targetYLoc);
if( locPtr->has_unit(UNIT_LAND) )
targetNationRecno = unit_array[ locPtr->unit_recno(UNIT_LAND) ]->nation_recno;
else if( locPtr->is_firm() )
targetNationRecno = firm_array[locPtr->firm_recno()]->nation_recno;
else if( locPtr->is_town() )
targetNationRecno = town_array[locPtr->town_recno()]->nation_recno;
if( targetNationRecno )
{
set_relation_should_attack(targetNationRecno, 1, COMMAND_AI);
}
}
//--------- End of function Nation::enable_should_attack_on_target --------//
//--------- Begin of static function get_target_nation_recno --------//
//
// Return the nation recno of the target.
//
static int get_target_nation_recno(int targetXLoc, int targetYLoc)
{
Location* locPtr = world.get_loc(targetXLoc, targetYLoc);
if( locPtr->is_firm() )
{
return firm_array[locPtr->firm_recno()]->nation_recno;
}
else if( locPtr->is_town() )
{
return town_array[locPtr->town_recno()]->nation_recno;
}
else if( locPtr->has_unit(UNIT_LAND) )
{
return unit_array[locPtr->unit_recno(UNIT_LAND)]->nation_recno;
}
return 0;
}
//---------- End of static function get_target_nation_recno --------//
//------ Begin of function sort_attack_camp_function ------//
//
static int sort_attack_camp_function( const void *a, const void *b )
{
int ratingA = ((AttackCamp*)a)->combat_level - ((AttackCamp*)a)->distance;
int ratingB = ((AttackCamp*)b)->combat_level - ((AttackCamp*)b)->distance;
return ratingB - ratingA;
}
//------- End of function sort_attack_camp_function ------//
//--------- Begin of function Nation::think_secret_attack --------//
//
// Think about secret assault plans.
//
int Nation::think_secret_attack()
{
//--- never secret attack if its peacefulness >= 80 ---//
if( pref_peacefulness >= 80 )
return 0;
//--- don't try to get new enemies if we already have many ---//
int totalEnemyMilitary = total_enemy_military();
if( totalEnemyMilitary > 20+pref_military_courage-pref_peacefulness )
return 0;
//---------------------------------------------//
int curRating=0, bestRating=0, bestNationRecno=0;
int ourMilitary = military_rank_rating();
int relationStatus, tradeRating;
Nation* nationPtr;
NationRelation* nationRelation;
for( int i=1 ; i<=nation_array.size() ; i++ )
{
if( nation_array.is_deleted(i) || nation_recno == i )
continue;
nationPtr = nation_array[i];
nationRelation = get_relation(i);
relationStatus = nationRelation->status;
//---- if the secret attack flag is not enabled yet ----//
if( !nationRelation->ai_secret_attack )
{
//---- if we have a friendly treaty with this nation ----//
if( relationStatus == NATION_FRIENDLY )
{
if( totalEnemyMilitary > 0 ) // do not attack if we still have enemies
continue;
}
//-------- never attacks an ally ---------//
else if( relationStatus == NATION_ALLIANCE )
{
continue;
}
//---- don't attack if we have a big trade volume with the nation ---//
tradeRating = trade_rating(i)/2 + // existing trade
ai_trade_with_rating(i)/2; // possible trade
if( tradeRating > (50-pref_trading_tendency/2) ) // 0 to 50, 0 if trade tendency is 100, it is 0
{
continue;
}
}
//--------- calculate the rating ----------//
curRating = (ourMilitary - nationPtr->military_rank_rating()) * 2
+ (overall_rank_rating() - 50) // if <50 negative, if >50 positive
- tradeRating*2
- get_relation(i)->ai_relation_level/2
- pref_peacefulness/2;
//------- if aggressiveness config is medium or high ----//
if( !nationPtr->is_ai() ) // more aggressive towards human players
{
switch( config.ai_aggressiveness )
{
case OPTION_MODERATE:
curRating += 100;
break;
case OPTION_HIGH:
curRating += 300;
break;
case OPTION_VERY_HIGH:
curRating += 500;
break;
}
}
//----- if the secret attack is already on -----//
if( nationRelation->ai_secret_attack )
{
//--- cancel secret attack if the situation has changed ---//
if( curRating < 0 )
{
nationRelation->ai_secret_attack = 0;
continue;
}
}
//--------- compare ratings -----------//
if( curRating > bestRating )
{
bestRating = curRating;
bestNationRecno = i;
}
}
//-------------------------------//
if( bestNationRecno )
{
get_relation(bestNationRecno)->ai_secret_attack = 1;
return 1;
}
return 0;
}
//---------- End of function Nation::think_secret_attack --------//