/* * 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_ACT.CPP //Description: AI - action progressing functions #include #include #include #include #include #include #include //------- Begin of function Nation::process_action --------// // // [int] priorityActionRecno - if this is given, this specific action will // be processed first. Otherwise it will process // actions in the array in a sequential order. // // [int] processActionMode - if this is given, only message of this type // will be processed and all messages in the queued of this // type will be processed. // // Note: priorityActionRecno and processActionMode couldn't be used at // the same time. // // return: 1 - all messages of the specific action mode are processed or all messages are processed // or the priority message has been processed. // int Nation::process_action(int priorityActionRecno, int processActionMode) { err_when( priorityActionRecno && processActionMode ); // #define MAX_PROCESS_ACTION_TIME 0.01 // maximum time given to processing actions (second) // unsigned expireTime = m.get_time() + (unsigned)(MAX_PROCESS_ACTION_TIME*1000); int actionRecno, rc, delFlag, doneFlag=0; int thisSessionProcessCount=0; // actions processed in this call session ActionNode* actionNode; int divider = 4-config.ai_aggressiveness; // the more nations there, the less process count int nationRecno = nation_recno; int maxSessionProcessCount = 70 / nation_array.nation_count / max(divider,1); for( actionRecno=1 ; actionRecno<=action_count() && (thisSessionProcessCount < maxSessionProcessCount || processActionMode) && !doneFlag ; // if processActionMode has been specific, then all messages in the queue of this type will be processed actionRecno++ ) { //----- priority action ------// if( priorityActionRecno ) { actionRecno = priorityActionRecno; doneFlag = 1; // mark it done, so if the function "continue" to the next loop, the function will end } actionNode = get_action(actionRecno); //----- if only process specific action mode -----// if( processActionMode && actionNode->action_mode != processActionMode ) continue; //--- if the AI action is about processing diplomatic message ---// if( actionNode->action_mode == ACTION_AI_PROCESS_TALK_MSG && processActionMode != ACTION_AI_PROCESS_TALK_MSG ) { if( m.random(10) > 0 ) // 1/10 chance of processing the diplomatic messages continue; } //----------------------------------------------// if( actionNode->processing_instance_count == actionNode->instance_count ) { //---------------------------------------------// // // If this action has been marked processing for over 6 months // and we still haven't received finishing notifications, // then there may be some accidents (or bugs) happened, and // we will need to delete the action. // //---------------------------------------------// if( info.game_date > actionNode->add_date + 30 * 6 ) { del_action(actionRecno); actionRecno--; // stay in this array position as the current one has been deleted, the following one replace the current one's position } continue; } err_when( actionNode->processing_instance_count > actionNode->instance_count ); if( info.game_date < actionNode->next_retry_date && !priorityActionRecno ) // priorityAction bypass retry date checking continue; if( actionNode->retry_count==0 ) // the actionNode may still exist even when retry_count==0, waiting for processed_count to reach processing_count continue; //-- there is an unprocessing action in this waiting node --// switch( actionNode->action_mode ) { case ACTION_AI_BUILD_FIRM: rc = ai_build_firm(actionNode); break; case ACTION_AI_ASSIGN_OVERSEER: rc = ai_assign_overseer(actionNode); break; case ACTION_AI_ASSIGN_CONSTRUCTION_WORKER: rc = ai_assign_construction_worker(actionNode); break; case ACTION_AI_ASSIGN_WORKER: rc = ai_assign_worker(actionNode); break; case ACTION_AI_ASSIGN_SPY: rc = ai_assign_spy(actionNode); break; case ACTION_AI_SCOUT: rc = ai_scout(actionNode); break; case ACTION_AI_SETTLE_TO_OTHER_TOWN: rc = ai_settle_to_other_town(actionNode); break; case ACTION_AI_PROCESS_TALK_MSG: rc = ai_process_talk_msg(actionNode); break; case ACTION_AI_SEA_TRAVEL: rc = ai_sea_travel(actionNode); break; case ACTION_AI_SEA_TRAVEL2: rc = ai_sea_travel2(actionNode); break; case ACTION_AI_SEA_TRAVEL3: rc = ai_sea_travel3(actionNode); break; } if( nation_array.is_deleted(nationRecno) ) // diplomatic option can result in surrendering return 0; thisSessionProcessCount++; //------ check the return result -------// delFlag = 0; if( rc==1 ) // the action has been processed, but not sure whether it is complete or not { actionNode->processing_instance_count++; //---------------------------------------------------// // for ACTION_DYNAMIC, the action is immediately // deleted when processing_instance_count == instance_count. //---------------------------------------------------// if( actionNode->action_type == ACTION_DYNAMIC ) { if( actionNode->processing_instance_count > actionNode->instance_count ) delFlag = 1; } } else if( rc==0 ) // action failed, retry { actionNode->next_retry_date = info.game_date + 7; // try again one week later if( --actionNode->retry_count==0 ) delFlag = 1; err_when( actionNode->retry_count < 0 ); } else if( rc== -1 ) // action failed, remove immediately if return -1 { actionNode->retry_count = 0; delFlag = 1; } //-----------------------------------------// if( delFlag && actionNode->processing_instance_count == actionNode->processed_instance_count ) // if processing_count > processed_count, do not remove this ActionNode, as there are some unit using this actionNode, when they finish or fail the action, processed_count will increase and processing_count will reach processed_count { del_action(actionRecno); actionRecno--; // stay in this array position as the current one has been deleted, the following one replace the current one's position } } return actionRecno > action_count() || doneFlag; } //---------- End of function Nation::process_action --------// //------- Begin of function Nation::process_action_id --------// // // Process a specific action. // int Nation::process_action_id(int actionId) { for( int i=action_count() ; i>0 ; i-- ) { if( get_action(i)->action_id == actionId ) { process_action(i); return 1; } } return 0; } //---------- End of function Nation::process_action_id --------// //------- Begin of function Nation::get_action_based_on_id --------// // // Return ActionNode for the given actionId. // ActionNode* Nation::get_action_based_on_id(int actionId) { for( int i=action_count() ; i>0 ; i-- ) { if( get_action(i)->action_id == actionId ) { return get_action(i); } } return 0; } //---------- End of function Nation::get_action_based_on_id --------// //--------- Begin of function Nation::add_action --------// // // xLoc, yLoc - location of the action // refXLoc, refYLoc - reference location (optional, not all action types need this) // actionMode - action mode // actionPara - action parameter // [int] instanceCount - no. of instances of this action should be carried out // (default: 1) // [int] unitRecno - recno of the unit responsible for this action // if not given, an appropriate unit will be found. // [int] actionPara2 - action para2 // [short*] groupUnitArray - array of unit recno in the group for the action // the no. of units in the array is stored in instance_count // (default: NULL) // // return: recno of the action added in action_array // 0 - if the action is not added as it is already in action_array. // int Nation::add_action(short xLoc, short yLoc, short refXLoc, short refYLoc, int actionMode, int actionPara, int instanceCount, int unitRecno, int actionPara2, short* groupUnitArray) { err_when( instanceCount < 1 ); //--- check if the action has been added already or not ---// if( is_action_exist(xLoc, yLoc, refXLoc, refYLoc, actionMode, actionPara, unitRecno) ) return 0; //---------- queue the action ----------// ActionNode actionNode; memset( &actionNode, 0, sizeof(ActionNode) ); actionNode.action_mode = actionMode; // what kind of action actionNode.action_para = actionPara; // parameter of the action actionNode.action_para2 = actionPara2; // parameter of the action actionNode.action_x_loc = xLoc; // location to act to actionNode.action_y_loc = yLoc; actionNode.ref_x_loc = refXLoc; // the refective location of this action make to actionNode.ref_y_loc = refYLoc; actionNode.retry_count = STD_ACTION_RETRY_COUNT; // number of term to wait before discarding this action actionNode.instance_count = instanceCount; // num of this action being processed in the waiting queue int immediateProcess=0; if( groupUnitArray ) { // the no. of units in the array is stored in instance_count err_when( instanceCount < 1 ); err_when( instanceCount > ActionNode::MAX_ACTION_GROUP_UNIT ); memcpy( actionNode.group_unit_array, groupUnitArray, instanceCount * sizeof(groupUnitArray[0]) ); immediateProcess = 1; // have to execute this command immediately as the unit in unit_array[] may change actionNode.retry_count = 1; // only try once as the unit in unit_array[] may change } if( unitRecno ) { //-- this may happen when the unit is a spy and has just changed cloak --// if( !nation_array[unit_array[unitRecno]->true_nation_recno()]->is_ai() && !nation_array[unit_array[unitRecno]->nation_recno]->is_ai() ) { return 0; } //-------------------------------------// actionNode.unit_recno = unitRecno; if( unit_array[unitRecno]->is_visible() ) { immediateProcess = 1; // have to execute this command immediately as the unit in unit_array[] may change actionNode.retry_count = 1; // only try once as the unit in unit_array[] may change } else //--- the unit is still being trained ---// { actionNode.next_retry_date = info.game_date + TOTAL_TRAIN_DAYS + 1; } } //-------- set action type ---------// actionNode.action_type = ACTION_FIXED; // default action type //------- link into action_array --------// return add_action( &actionNode, immediateProcess ); } //---------- End of function Nation::add_action --------// //--------- Begin of function Nation::add_action --------// // // actionNode - the action node to be added. // [int] immediateProcess - process this action immediately // int Nation::add_action(ActionNode* actionNode, int immediateProcess) { //----------- reset some vars -----------// actionNode->add_date = info.game_date; actionNode->action_id = ++last_action_id; actionNode->retry_count = STD_ACTION_RETRY_COUNT; actionNode->processing_instance_count = 0; actionNode->processed_instance_count = 0; //------- link into action_array --------// action_array.linkin( actionNode ); if( immediateProcess ) process_action( action_array.recno() ); return action_array.recno(); } //---------- End of function Nation::add_action --------// //--------- Begin of function Nation::del_action --------// // void Nation::del_action(int actionRecno) { action_array.linkout(actionRecno); } //---------- End of function Nation::del_action --------// //--------- Begin of function Nation::is_action_exist --------// // // actionXLoc, actionYLoc - action_?_loc in ActionNode to match with // refXLoc, refYLoc - ref_?_loc in ActionNode to match with // actionMode - action mode // actionPara - parameter of the action // [int] unitRecno - unit recno to match with, only useful for actions under processing. // [int] checkMode - 1-check actionXLoc & actionYLoc only // 2-check refXLoc & refYLoc only // 0-check both // (default: 0) // // return: >0 if the action recno of the existing action // ==0 if not exist // int Nation::is_action_exist(short actionXLoc, short actionYLoc, short refXLoc, short refYLoc, int actionMode, int actionPara, int unitRecno, int checkMode) { int i; ActionNode* actionNode; for( i=action_count() ; i>0 ; i-- ) { actionNode = get_action(i); if( actionNode->action_mode == actionMode && actionNode->action_para == actionPara ) { if( unitRecno && unitRecno != actionNode->unit_recno ) // it requests to match the unit recno and it is not matched here continue; if( refXLoc>=0 ) { if( checkMode==0 || checkMode==2 ) { if( actionNode->ref_x_loc==refXLoc && actionNode->ref_y_loc==refYLoc) return i; } } else { if( checkMode==0 || checkMode==1 ) { if( actionNode->action_x_loc==actionXLoc && actionNode->action_y_loc==actionYLoc ) return i; } } } } return 0; } //---------- End of function Nation::is_action_exist --------// //--------- Begin of function Nation::is_action_exist --------// // // Check if the an action of the specific mode and para // exists in the action_array. // // actionMode - action mode // actionPara - parameter of the action // [int] regionId - if this parameter is given, only // action with destination in this // region will be checked. // int Nation::is_action_exist(int actionMode, int actionPara, int regionId) { int i; ActionNode* actionNode; for( i=action_count() ; i>0 ; i-- ) { actionNode = get_action(i); if( actionNode->action_mode == actionMode && actionNode->action_para == actionPara ) { if( !regionId ) return 1; err_when( actionNode->action_x_loc < 0 || actionNode->action_y_loc < 0 || actionNode->action_x_loc >= MAX_WORLD_X_LOC || actionNode->action_y_loc >= MAX_WORLD_Y_LOC ); if( world.get_region_id(actionNode->action_x_loc, actionNode->action_y_loc) == regionId ) { return 1; } } } return 0; } //---------- End of function Nation::is_action_exist --------// //--------- Begin of function Nation::is_build_action_exist --------// // // Return 1 if there is already a firm queued for building with // a building location that is within the effective range // of the given position. // int Nation::is_build_action_exist(int firmId, int xLoc, int yLoc) { int i; ActionNode* actionNode; for( i=action_count() ; i>0 ; i-- ) { actionNode = get_action(i); if( actionNode->action_mode == ACTION_AI_BUILD_FIRM && actionNode->action_para == firmId ) { if( m.points_distance( actionNode->action_x_loc, actionNode->action_y_loc, xLoc, yLoc) <= EFFECTIVE_FIRM_TOWN_DISTANCE ) { return 1; } } } return 0; } //---------- End of function Nation::is_build_action_exist --------// //--------- Begin of function Nation::action_finished --------// // // The action under processing is finished successfully. // // aiActionId - the id. of the action to be marked finished. // [short] unitRecno - if this is given, the the unit's all action // will be stopped. // [int] actionFailure - whether the action is failed and called by // action_failure(). (default: 0) // void Nation::action_finished(WORD aiActionId, short unitRecno, int actionFailure) { //----- locate the ActionNode of this action ------// int actionRecno; ActionNode* actionNode; //----- try to match actions by unitRecno first ----// for( actionRecno=action_count() ; actionRecno>0 ; actionRecno-- ) { actionNode = get_action(actionRecno); if( aiActionId == actionNode->action_id ) break; } if( actionRecno==0 ) // not found { // if( sys.debug_session && !sys.signal_exit_flag ) // box.msg( "Error: action_finished() - entry not found." ); stop_unit_action(unitRecno); return; } //------------------------------------------------// // // In the above condition is true, that means this ship // unit has called this function once and the current // calling is a duplicated calling. // //------------------------------------------------// int shouldStop=1; if( actionNode->action_mode == ACTION_AI_SEA_TRAVEL ) // don't reset the unit's ai_action_id in ACTION_AI_SEA_TRAVEL mode as if we reset it, the ship will take new action and won't wait for the units to go aboard. { if( !unit_array.is_deleted(unitRecno) && unit_res[ unit_array[unitRecno]->unit_id ]->unit_class == UNIT_CLASS_SHIP ) { if( actionNode->action_para2 ) { return; } else { actionNode->action_para2 = unitRecno; shouldStop = 0; } } } //---------------------------------------------// // // Only handle ACTION_FIXED, for ACTION_DYNAMIC, // the action is immediately deleted when // processing_instance_count == instance_count. // //---------------------------------------------// if( actionNode->action_type != ACTION_FIXED ) { stop_unit_action(unitRecno); return; } //-------------------------------------------------// actionNode->processed_instance_count++; err_when( actionNode->processed_instance_count > actionNode->processing_instance_count ); err_when( actionNode->processed_instance_count > actionNode->instance_count ); //---- if all requested instances are processed ----// int allDoneFlag=0; if( actionNode->processed_instance_count >= actionNode->instance_count ) allDoneFlag = 1; //---- if the action is failed and all the outstanding units are finished, del the action ---// else if( actionNode->retry_count==0 && actionNode->processed_instance_count >= actionNode->processing_instance_count ) { allDoneFlag = 1; } //------- stop the AI actions of the unit -----// if( shouldStop ) stop_unit_action(unitRecno); //---- if the action is done, see if there needs to be a following action ----// if( allDoneFlag ) { auto_next_action(actionNode); del_action(actionRecno); } } //---------- End of function Nation::action_finished --------// //--------- Begin of function Nation::action_failure --------// // // It's basically the same as action_finish(). Now there isn't any // difference. // // aiActionId - the id. of the action to be marked finished. // [short] unitRecno - if this is given, the the unit's all action // will be stopped. // void Nation::action_failure(WORD aiActionId, short unitRecno) { //-- if the unit is a ship, ignore it as it will be called by stop2() when it stops, but hasn't yet finished its action --// /* Unit* unitPtr = unit_array[unitRecno]; if( unit_res[unitPtr->unit_id]->unit_class == UNIT_CLASS_SHIP ) { int actionMode = get_action_based_on_id(aiActionId)->action_mode; if( actionMode == ACTION_AI_SEA_TRAVEL || actionMode == ACTION_AI_SEA_TRAVEL2 ) { return; } } */ //------------------------------------------------// action_finished(aiActionId, unitRecno, 1); // 1 - action failure } //---------- End of function Nation::action_failure ---------// //--------- Begin of function Nation::stop_unit_action --------// void Nation::stop_unit_action(short unitRecno) { if( !unitRecno ) return; //------- stop the AI actions of the unit -----// // // It is possible that this is not an AI unit as // when a player spy cloaked as an enemy unit, // the AI will control it. // //---------------------------------------------// if( !unit_array.is_deleted(unitRecno) ) { Unit* unitPtr = unit_array[unitRecno]; unitPtr->ai_action_id = 0; //---- if the unit is a ship on the beach and it's mode isn't NO_EXTRA_MOVE, we couldn't call stop2() as that will cause bug ---// if(unitPtr->action_mode2==ACTION_SHIP_TO_BEACH) { UnitMarine *shipPtr = (UnitMarine*) unitPtr; err_when( unit_res[shipPtr->unit_id]->unit_class != UNIT_CLASS_SHIP ); if( shipPtr->extra_move_in_beach != NO_EXTRA_MOVE ) return; } //--------------------------------------------------// unitPtr->stop2(); unitPtr->reset_action_misc_para(); err_when(unitPtr->in_auto_defense_mode()); err_when(unitPtr->action_x_loc!=-1 || unitPtr->action_y_loc!=-1); err_when(unitPtr->action_mode!=ACTION_STOP); } } //---------- End of function Nation::stop_unit_action ---------// //--------- Begin of function Nation::auto_next_action --------// // // Automatically create a follow-up action to this action // if this action needs one. // void Nation::auto_next_action(ActionNode* actionNode) { int actionRecno=0; switch( actionNode->action_mode ) { case ACTION_AI_SEA_TRAVEL: actionNode->action_mode = ACTION_AI_SEA_TRAVEL2; actionNode->instance_count = 1; // only move one ship, it was previously set to the no. units to aboard the ship actionRecno = add_action(actionNode, 1); // 1-immediate process flag break; case ACTION_AI_SEA_TRAVEL2: actionNode->action_mode = ACTION_AI_SEA_TRAVEL3; actionRecno = add_action(actionNode, 1); // 1-immediate process flag break; } if( actionRecno ) process_action(actionRecno); } //---------- End of function Nation::auto_next_action ---------//