/*
* 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 ---------//