/*
* 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_UNIT.CPP
//Description: AI - unit related functions
#include
#include
#include
#include
#include
//-------- Begin of function Nation::get_skilled_unit -------//
//
// skillId - the skill the selected unit should have
// raceId - the race the selected unit should have
// (0 for any races)
// actionNode - the ActionNode of the action that needs this skilled unit.
//
// return: skilledUnit - pointer to the skilled unit.
//
Unit* Nation::get_skilled_unit(int skillId, int raceId, ActionNode* actionNode)
{
//--------- get a skilled unit --------//
Unit* skilledUnit;
if(actionNode->unit_recno) // a unit has started training previously
{
skilledUnit = unit_array[actionNode->unit_recno];
}
else
{
char resultFlag;
int xLoc, yLoc;
//---- for harbor, we have to get the land region id. instead of the sea region id. ----//
if( actionNode->action_mode==ACTION_AI_BUILD_FIRM &&
actionNode->action_para==FIRM_HARBOR )
{
int rc=0;
for( yLoc=actionNode->action_y_loc ; yLocaction_y_loc+3 ; yLoc++ )
{
for( xLoc=actionNode->action_x_loc ; xLocaction_x_loc+3 ; xLoc++ )
{
if( region_array[ world.get_region_id(xLoc,yLoc) ]->region_type == REGION_LAND )
{
rc=1;
break;
}
}
if( rc )
break;
}
}
else
{
xLoc = actionNode->action_x_loc;
yLoc = actionNode->action_y_loc;
}
//-----------------------------------------//
skilledUnit = find_skilled_unit(skillId, raceId, xLoc, yLoc, resultFlag, actionNode->action_id);
if( !skilledUnit ) // skilled unit not found
return NULL;
}
//------ if the unit is still in training -----//
if( !skilledUnit->is_visible() )
{
actionNode->next_retry_date = info.game_date + TOTAL_TRAIN_DAYS + 1;
return NULL; // continue processing this action after this date, this is used when training a unit for construction
}
err_when( !skilledUnit->race_id );
return skilledUnit;
}
//-------- End of function Nation::get_skilled_unit -------//
//--------- Begin of function Nation::find_skilled_unit --------//
//
// skillId - the skill the selected unit should have
// raceId - the race the selected unit should have
// (0 for any races)
// destX, destY - location the unit move to
// resultFlag - describle how to find the skilled unit
// 0 - for unable to train unit,
// 1 - for existing skilled unit
// 2 - for unit hired from inn
// 3 - for training unit in town (training is required)
//
// [int] actionId - the action id. of the action which
// the unit should do after it has finished training.
//
// return the unit pointer pointed to the skilled unit
//
Unit* Nation::find_skilled_unit(int skillId, int raceId, short destX, short destY, char& resultFlag, int actionId)
{
//----- try to find an existing unit with the required skill -----//
Unit *skilledUnit = NULL;
Unit *unitPtr;
Firm *firmPtr;
short curDist, minDist=0x1000;
int destRegionId = world.get_region_id(destX, destY);
for(int i=unit_array.size(); i>0; i--)
{
if(unit_array.is_deleted(i))
continue;
unitPtr = unit_array[i];
if( unitPtr->nation_recno!=nation_recno || !unitPtr->race_id )
continue;
if( raceId && unitPtr->race_id != raceId )
continue;
//---- if this unit is on a mission ----//
if( unitPtr->home_camp_firm_recno )
continue;
if( unitPtr->region_id() != destRegionId )
continue;
//----- if this is a mobile unit ------//
if( unitPtr->is_visible() )
{
if( !unitPtr->is_ai_all_stop() )
continue;
if( unitPtr->skill.skill_id==skillId &&
unitPtr->cur_action!=SPRITE_ATTACK && !unitPtr->ai_action_id )
{
curDist = m.points_distance(unitPtr->next_x_loc(), unitPtr->next_y_loc(), destX, destY);
if(curDist < minDist)
{
skilledUnit = unitPtr;
minDist = curDist;
}
}
}
//------- if this is an overseer ------//
else if( skillId==SKILL_LEADING && unitPtr->unit_mode==UNIT_MODE_OVERSEE )
{
firmPtr = firm_array[unitPtr->unit_mode_para];
if( firmPtr->region_id != destRegionId )
continue;
if( firmPtr->firm_id == FIRM_CAMP )
{
//--- if this military camp is going to be closed, use this overseer ---//
if( firmPtr->should_close_flag )
{
firmPtr->mobilize_overseer();
skilledUnit = unitPtr; // pick this overseer
break;
}
}
}
else if( skillId==SKILL_CONSTRUCTION && unitPtr->unit_mode==UNIT_MODE_CONSTRUCT ) // the unit is a residental builder for repairing the firm
{
firmPtr = firm_array[unitPtr->unit_mode_para];
if( !firmPtr->under_construction ) // only if the unit is repairing instead of constructing the firm
{
if( firmPtr->set_builder(0) ) // return 1 if the builder is mobilized successfully, 0 if the builder was killed because of out of space on the map
{
skilledUnit = unitPtr;
break;
}
}
}
}
//---------------------------------------------------//
if(skilledUnit)
{
resultFlag = 1;
}
else
{
//--- if no existing skilled unit found, try to hire one from inns ---//
int unitRecno = hire_unit(skillId, raceId, destX, destY); // this function will try going with hiring units that are better than training your own ones
if( unitRecno )
{
skilledUnit = unit_array[unitRecno];
resultFlag = 2;
}
else //--- if still cannot get a skilled unit, train one ---//
{
int trainTownRecno;
if( train_unit(skillId, raceId, destX, destY, trainTownRecno, actionId) )
resultFlag = 3;
else
resultFlag = 0;
}
}
err_when(skilledUnit && !skilledUnit->is_visible());
err_when(skilledUnit && skilledUnit->rank_id==RANK_KING && (skillId!=SKILL_CONSTRUCTION && skillId!=SKILL_LEADING));
err_when(skilledUnit && (skilledUnit->cur_action==SPRITE_DIE || skilledUnit->action_mode==ACTION_DIE));
return skilledUnit;
}
//---------- End of function Nation::find_skilled_unit --------//
//--------- Begin of function Nation::hire_unit --------//
//
// importantRating - importance of hiring the unit.
//
int Nation::ai_should_hire_unit(int importanceRating)
{
if( !ai_inn_count ) // don't hire any body in the cash is too low
return 0;
return ai_should_spend(importanceRating + pref_hire_unit/5 - 10 ); // -10 to +10 depending on pref_hire_unit
}
//---------- End of function Nation::hire_unit --------//
//--------- Begin of function Nation::hire_unit --------//
//
// skillId - the skill the unit should have
// raceId - the race the selected unit should have
// (0 for any races)
// destX - the x location the unit will move to
// destY - the y location the unit will move to
//
// hire unit with specified skill from an inn
// return the unit pointer pointed to the skilled unit
//
// return: recno of the unit recruited.
//
int Nation::hire_unit(int skillId, int raceId, short destX, short destY)
{
if( !ai_should_hire_unit(20) ) // 20 - importance rating
return 0;
//-------------------------------------------//
FirmInn *firmInnPtr;
Firm *firmPtr;
InnUnit *innUnit;
Skill *innUnitSkill;
int i, j, innUnitCount, curRating, curFirmDist;
int bestRating=0, bestInnRecno=0, bestInnUnitId=0;
int destRegionId = world.get_region_id(destX, destY);
for(i=0; iregion_id != destRegionId )
continue;
firmInnPtr = firmPtr->cast_to_FirmInn();
innUnitCount=firmInnPtr->inn_unit_count;
if( !innUnitCount )
continue;
innUnit = firmInnPtr->inn_unit_array + innUnitCount - 1;
curFirmDist = m.points_distance(firmPtr->center_x, firmPtr->center_y, destX, destY);
//------- check units in the inn ---------//
for(j=innUnitCount; j>0; j--, innUnit--)
{
innUnitSkill = &(innUnit->skill);
if( innUnitSkill->skill_id==skillId &&
(!raceId || unit_res[innUnit->unit_id]->race_id == raceId) &&
cash >= innUnit->hire_cost )
{
//----------------------------------------------//
// evalute a unit on:
// -its race, whether it's the same as the nation's race
// -the inn's distance from the destination
// -the skill level of the unit.
//----------------------------------------------//
curRating = innUnitSkill->skill_level
- (100-100*curFirmDist/MAX_WORLD_X_LOC);
if( unit_res[innUnit->unit_id]->race_id == race_id )
curRating += 50;
if( curRating > bestRating )
{
bestRating = curRating;
bestInnRecno = firmInnPtr->firm_recno;
bestInnUnitId = j;
}
}
}
}
//----------------------------------------------------//
if( bestInnUnitId )
{
firmPtr = firm_array[bestInnRecno];
firmInnPtr = firmPtr->cast_to_FirmInn();
return firmInnPtr->hire(bestInnUnitId);
}
return 0;
}
//---------- End of function Nation::hire_unit --------//
//--------- Begin of function Nation::train_unit --------//
//
// skillId - the skill the unit should have
// raceId - the race the selected unit should have
// (0 for any races)
// destX - the x location the unit will move to
// destY - the y location the unit will move to
//
// trainTownRecno - the recno of the town where this unit is trained.
//
// [int] actionId - the action id. of the action which
// the unit should do after it has finished training.
//
// return: recno of the unit trained.
//
int Nation::train_unit(int skillId, int raceId, short destX, short destY, int& trainTownRecno, int actionId)
{
//----- locate the best town for training the unit -----//
int i;
Town *townPtr;
int curDist, curRating, bestRating=0;
int destRegionId = world.get_loc(destX, destY)->region_id;
trainTownRecno = 0;
for(i=0; ijobless_population || townPtr->train_unit_recno || // no jobless population or currently a unit is being trained
!townPtr->has_linked_own_camp )
{
continue;
}
if( townPtr->region_id != destRegionId )
continue;
//--------------------------------------//
curDist = m.points_distance(townPtr->center_x, townPtr->center_y, destX, destY);
curRating = 100-100*curDist/MAX_WORLD_X_LOC;
if( curRating > bestRating )
{
bestRating = curRating;
trainTownRecno = townPtr->town_recno;
}
}
if( !trainTownRecno )
return 0;
//---------- train the unit ------------//
townPtr = town_array[trainTownRecno];
if( !raceId )
raceId = townPtr->pick_random_race(1, 1); // 1-pick has job units also, 1-pick spy units
if( !raceId )
return 0;
int unitRecno = townPtr->recruit(skillId, raceId, COMMAND_AI);
if( !unitRecno )
return 0;
townPtr->train_unit_action_id = actionId; // set train_unit_action_id so the unit can immediately execute the action when he has finished training.
return unitRecno;
}
//---------- End of function Nation::train_unit --------//
//--------- Begin of function Nation::recruit_jobless_worker --------//
//
// destFirmPtr - the firm which the workers are recruited for.
// [int] preferedRaceId - the prefered race id.
//
// return: recno of the unit recruited.
//
int Nation::recruit_jobless_worker(Firm* destFirmPtr, int preferedRaceId)
{
#define MIN_AI_TOWN_POP 8
int needSpecificRace, raceId; // the race of the needed unit
if( preferedRaceId )
{
raceId = preferedRaceId;
needSpecificRace = 1;
}
else
{
if( destFirmPtr->firm_id == FIRM_BASE ) // for seat of power, the race must be specific
{
raceId = firm_res.get_build(destFirmPtr->firm_build_id)->race_id;
needSpecificRace = 1;
}
else
{
raceId = destFirmPtr->majority_race();
needSpecificRace = 0;
}
}
if( !raceId )
return 0;
//----- locate the best town for recruiting the unit -----//
Town *townPtr;
int curDist, curRating, bestRating=0, bestTownRecno=0;
for( int i=0; ination_recno != nation_recno );
if( !townPtr->jobless_population ) // no jobless population or currently a unit is being recruited
continue;
if( !townPtr->should_ai_migrate() ) // if the town is going to migrate, disregard the minimum population consideration
{
if( townPtr->population < MIN_AI_TOWN_POP ) // don't recruit workers if the population is low
continue;
}
if( !townPtr->has_linked_own_camp && townPtr->has_linked_enemy_camp ) // cannot recruit from this town if there are enemy camps but no own camps
continue;
if( townPtr->region_id != destFirmPtr->region_id )
continue;
//--- get the distance beteween town & the destination firm ---//
curDist = m.points_distance(townPtr->center_x, townPtr->center_y, destFirmPtr->center_x, destFirmPtr->center_y);
curRating = 100-100*curDist/MAX_WORLD_X_LOC;
//--- recruit units from non-base town first ------//
if( !townPtr->is_base_town )
curRating += 100;
//---- if the town has the race that the firm needs most ----//
if( townPtr->can_recruit(raceId) )
{
curRating += 50 + (int) townPtr->race_loyalty_array[raceId-1];
}
else
{
if( needSpecificRace ) // if the firm must need this race, don't consider the town if it doesn't have the race.
continue;
}
if( curRating > bestRating )
{
bestRating = curRating;
bestTownRecno = townPtr->town_recno;
}
}
if( !bestTownRecno )
return 0;
//---------- recruit the unit ------------//
townPtr = town_array[bestTownRecno];
if( townPtr->recruitable_race_pop(raceId, 1) == 0 )
{
err_when( needSpecificRace );
raceId = townPtr->pick_random_race(0, 1); // 0-pick jobless only, 1-pick spy units
if( !raceId )
return 0;
}
//--- if the chosen race is not recruitable, pick any recruitable race ---//
if( !townPtr->can_recruit(raceId) )
{
//---- if the loyalty is too low to recruit, grant the town people first ---//
if( cash > 0 && townPtr->accumulated_reward_penalty < 10 )
townPtr->reward(COMMAND_AI);
//---- if the loyalty is still too low, return now ----//
if( !townPtr->can_recruit(raceId) )
return 0;
}
return townPtr->recruit(-1, raceId, COMMAND_AI);
}
//---------- End of function Nation::recruit_jobless_worker --------//
//--------- Begin of function Nation::recruit_on_job_worker --------//
//
// Get skilled workers from existing firms who have labor more than
// it really needs.
//
// destFirmPtr - the firm which the workers are recruited for.
// [int] preferedRaceId - the prefered race id.
//
// return: recno of the unit recruited.
//
int Nation::recruit_on_job_worker(Firm* destFirmPtr, int preferedRaceId)
{
err_when( !firm_res[destFirmPtr->firm_id]->need_worker );
err_when( destFirmPtr->firm_id == FIRM_BASE ); // seat of power shouldn't call this function at all, as it doesn't handle the racial issue.
if( !preferedRaceId )
{
preferedRaceId = destFirmPtr->majority_race();
if( !preferedRaceId )
return 0;
}
//--- scan existing firms to see if any of them have excess workers ---//
int aiFirmCount; // reference vars for returning result
short* aiFirmArray = update_ai_firm_array(destFirmPtr->firm_id, 0, 0, aiFirmCount);
Firm* firmPtr, *bestFirmPtr=NULL;
int bestRating=0, curRating, curDistance;
int hasHuman;
Worker* workerPtr;
int i;
for( i=0 ; ifirm_id != destFirmPtr->firm_id );
if( firmPtr->firm_recno == destFirmPtr->firm_recno )
continue;
if( firmPtr->region_id != destFirmPtr->region_id )
continue;
if( !firmPtr->ai_has_excess_worker() )
continue;
//-----------------------------------//
curDistance = m.points_distance( firmPtr->center_x, firmPtr->center_y,
destFirmPtr->center_x, destFirmPtr->center_y );
curRating = 100 - 100 * curDistance / MAX_WORLD_X_LOC;
workerPtr = firmPtr->worker_array;
hasHuman = 0;
for( int j=0 ; jworker_count ; j++, workerPtr++ )
{
if( workerPtr->race_id )
hasHuman = 1;
if( workerPtr->race_id == preferedRaceId )
{
//---- can't recruit this unit if he lives in a foreign town ----//
if( workerPtr->town_recno &&
town_array[workerPtr->town_recno]->nation_recno != nation_recno )
{
continue;
}
//--------------------------//
curRating += 100;
break;
}
}
if( hasHuman && curRating > bestRating )
{
bestRating = curRating;
bestFirmPtr = firmPtr;
}
}
if( !bestFirmPtr )
return 0;
//------ mobilize a worker form the selected firm ----//
int workerId=0;
workerPtr = bestFirmPtr->worker_array;
for( i=1 ; i<=bestFirmPtr->worker_count ; i++, workerPtr++ )
{
//---- can't recruit this unit if he lives in a foreign town ----//
if( workerPtr->town_recno &&
town_array[workerPtr->town_recno]->nation_recno != nation_recno )
{
continue;
}
//--------------------------------//
if( workerPtr->race_id ) // if this is a human unit, take it first
workerId = i;
if( workerPtr->race_id == preferedRaceId ) // if we have a better one, take the better one
{
workerId = i;
break;
}
}
if( !workerId ) // this can happen if all the workers are foreign workers.
return 0;
return bestFirmPtr->mobilize_worker( workerId, COMMAND_AI );
}
//---------- End of function Nation::recruit_on_job_worker --------//