/*
 * 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 <http://www.gnu.org/licenses/>.
 *
 */

//Filename    : OF_MONS.CPP
//Description : Fryhtan Lair

#include <OINFO.h>
#include <OVGA.h>
#include <vga_util.h>
#include <ODATE.h>
#include <OSTR.h>
#include <OSYS.h>
#include <OSITE.h>
#include <OFONT.h>
#include <OBUTTON.h>
#include <OPOWER.h>
#include <OTOWN.h>
#include <OU_MONS.h>
#include <OGAME.h>
#include <ONATION.h>
#include <OMONSRES.h>
#include <OF_MONS.h>
#include <locale.h>
#include <ConfigAdv.h>
#include "gettext.h"


//----------- Define constant ------------//

#define MONSTER_SOLDIER_COMBAT_LEVEL_DIVIDER		2
static char current_monster_action_mode;

static int fryhtan_attacks_per_six_months(int numOfLairs);
static int fryhtan_random_attack();
static void disp_worker_hit_points(int x1, int y1, int x2, int hitPoints, int maxHitPoints);

//--------- Begin of function FirmMonster::init_derived ---------//
//
void FirmMonster::init_derived()
{
	monster_general_count = 0;

	// ##### patch begin Gilbert 21/1 #######//
	// monster_king.monster_id = 0;
	memset( &monster_king, 0, sizeof(monster_king) );
	// ##### patch end Gilbert 21/1 #######//
	memset( monster_general_array, 0, sizeof(monster_general_array) );
	waiting_soldier_count = 0;

	defending_king_count = 0;
	defending_general_count = 0;
	defending_soldier_count = 0;

	monster_nation_relation = 0;

	//----------------------------------------//
	// Set monster agressiveness. It affects:
	//
	// -the number of defenders will be called out at one time.
	//----------------------------------------//

	defend_target_recno = 0;

	patrol_unit_count   = 0;

	//-- these vars must be initialized here instead of in FirmMonster::FirmMonster() for random seed sync during load game --//

	monster_aggressiveness = 20 + misc.random(50);		// 20 to 70
}
//----------- End of function FirmMonster::init_derived -----------//


//--------- Begin of function FirmMonster::~FirmMonster ---------//
//
FirmMonster::~FirmMonster()
{
	if( sys.signal_exit_flag )
		return;

	int goldAmount = 800 * (monster_res[monster_id]->level*30 + misc.random(50)) / 100;

	site_array.add_site( center_x, center_y, SITE_GOLD_COIN, goldAmount );
	site_array.ai_get_site_object();		// ask AI units to get the gold coins
}
//----------- End of function FirmMonster::~FirmMonster -----------//


//--------- Begin of function FirmMonster::deinit_derived ---------//
//
void FirmMonster::deinit_derived()
{
	if( sys.signal_exit_flag )
		return;

	//-------- mobilize all monsters in the firm --------//

	int loopCount=0;

	if( monster_king.monster_id )
		mobilize_king();

	while( monster_general_count > 0 )
	{
		if(!mobilize_general(1))
			break;

		err_when( loopCount++ > 100 );
	}

	clear_defense_mode();
}
//----------- End of function FirmMonster::deinit_derived -----------//


const char *monster_firm_name[] =
{
	N_("Deezboanz Lair"),
	N_("Rattus Lair"),
	N_("Broosken Lair"),
	N_("Haubudam Lair"),
	N_("Pfith Lair"),
	N_("Rokken Lair"),
	N_("Doink Lair"),
	N_("Wyrm Lair"),
	N_("Droog Lair"),
	N_("Ick Lair"),
	N_("Sauroid Lair"),
	N_("Karrotten Lair"),
	N_("Holgh Lair"),
};
//------- Begin of function FirmMonster::firm_name -----------//
//
char* FirmMonster::firm_name()
{
	static String str;

	str = _(monster_firm_name[monster_id-1]);

	return str;
}
//--------- End of function FirmMonster::firm_name -----------//


//--------- Begin of function FirmMonster::put_info ---------//
//
void FirmMonster::put_info(int refreshFlag)
{
	disp_basic_info(INFO_Y1, refreshFlag);

	if( should_show_info() )
		disp_monster_info(INFO_Y1+54, refreshFlag);
}
//----------- End of function FirmMonster::put_info -----------//


//--------- Begin of function FirmMonster::detect_info ---------//
//
int FirmMonster::detect_info()
{
	if( detect_basic_info() )
		return 1;

	if( !config.show_ai_info && nation_recno!=nation_array.player_recno )
		return 0;

	return 0;
}
//----------- End of function FirmMonster::detect_info -----------//


extern const char *monster_name_king[];
//--------- Begin of function FirmMonster::disp_monster_info ---------//
//
void FirmMonster::disp_monster_info(int dispY1, int refreshFlag)
{
	int x=INFO_X1+6, y=dispY1+4, x1=x+UNIT_LARGE_ICON_WIDTH+8;

	//---------------- paint the panel --------------//

	if( refreshFlag == INFO_REPAINT )
	{
		vga_util.d3_panel_up( INFO_X1, dispY1, INFO_X2, dispY1+UNIT_LARGE_ICON_WIDTH+6 );

		if( monster_king.monster_id )
		{
			MonsterInfo* monsterInfo = monster_res[monster_king.monster_id];
			UnitInfo* unitInfo = unit_res[monsterInfo->unit_id];

			font_san.put( x1, y+UNIT_LARGE_ICON_HEIGHT/2-6, _(monster_name_king[monster_king.monster_id-1]) );

			vga_front.put_bitmap(x, y, unitInfo->get_large_icon_ptr(RANK_KING));

			//------ display hit points bar --------//

			disp_worker_hit_points( x, y+UNIT_LARGE_ICON_HEIGHT, x+UNIT_LARGE_ICON_WIDTH, monster_king.hit_points, monster_king.max_hit_points );
		}
	}

	dispY1 = y+UNIT_LARGE_ICON_HEIGHT+12;

	//---------------- paint the ordo panel --------------//

	if( refreshFlag == INFO_REPAINT )
	{
		vga_util.d3_panel_up( INFO_X1, dispY1, INFO_X2, dispY1+(UNIT_LARGE_ICON_HEIGHT+4)*3+25 );
		font_san.center_put( INFO_X1, dispY1+1, INFO_X2, dispY1+21, _("Ordos") );
	}

	//------ display ordo composition -------//

	dispY1+=22;

	for( int i=0 ; i<MAX_MONSTER_GENERAL_IN_FIRM ; i++ )
	{
		x = INFO_X1+4+i%3*67;
		y = dispY1+i/3*(UNIT_LARGE_ICON_HEIGHT+6);

		if( i<monster_general_count )
		{
			MonsterInFirm* monsterPtr = &monster_general_array[i];
			MonsterInfo* monsterInfo = monster_res[monsterPtr->monster_id];
			UnitInfo* unitInfo = unit_res[monsterInfo->unit_id];
			if( refreshFlag==INFO_REPAINT )
				vga_front.put_bitmap(x, y, unitInfo->get_large_icon_ptr(RANK_GENERAL));

			//------ display hit points bar --------//

			disp_worker_hit_points( x, y+UNIT_LARGE_ICON_HEIGHT, x+UNIT_LARGE_ICON_WIDTH, monsterPtr->hit_points, monsterPtr->max_hit_points );

			//----- display ordo troop size ------//

			font_san.disp(x+UNIT_LARGE_ICON_WIDTH+5, y+UNIT_LARGE_ICON_HEIGHT/2-6, monsterPtr->soldier_count, 1, x+UNIT_LARGE_ICON_WIDTH+19);
		}
		else
		{
			vga_util.blt_buf( x, y, x+UNIT_LARGE_ICON_WIDTH+19, y+UNIT_LARGE_ICON_HEIGHT+6, 0 );
		}
	}
}
//----------- End of function FirmMonster::disp_monster_info -----------//


//--------- Begin of function FirmMonster::next_day ---------//
//
void FirmMonster::next_day()
{
	//-------- validate patrol unit --------//

	validate_patrol_unit();

	//---- the monster boss recruit new monsters ----//

	if( info.game_date%10 == firm_recno%10 )
		recruit_soldier();

	//----- monsters recover hit points -------//

	if( info.game_date%15 == firm_recno%15 )			// once a week
		recover_hit_points();

	//------ monster thinks about expansion -------//

	if( config.monster_type == OPTION_MONSTER_OFFENSIVE )
	{
		if( info.game_date%30 == firm_recno%30 && misc.random(3)==0 )
			recruit_general();
/*
		if( info.game_date%90 == firm_recno%90 )
			think_attack_neighbor();
*/
		//------ attack human towns and firms randomly -----//

		if( info.game_date > info.game_start_date + 1000 &&	// only start attacking 3 years after the game starts so the human can build up things
			info.game_date%30 == firm_recno%30 &&
			fryhtan_random_attack() )
		{
			think_attack_human();
		}

		//--------- think expansion ---------//

		if( info.game_date%180 == firm_recno%180 &&
			 misc.random( firm_res[FIRM_MONSTER]->total_firm_count*10 )==0 )		// it will expand slower when there are already a lot of the monster structures on the map
		{
			think_expansion();
		}
	}
}
//----------- End of function FirmMonster::next_day -----------//


//------- Begin of function fryhtan_attacks_per_six_months -------//
//
// Encodes the Fryhtan attack curve.
// Returns the average number of Fryhtan attacks in 6 months for the given number of lairs.
//
static int fryhtan_attacks_per_six_months(int numOfLairs)
{
	// This algorithm encodes the continuation of the following table:
	//
	// # Lairs     | 0   5   10  15  20  30  40  50  60  70  80  95  110 ...
	// -----------------------------------------------------------------------
	// # attacks   | 1   2   3   4   5   6   7   8   9   10  11  12  13  ...
	//
	//                      4 x +5            6 x +10                  8 x +15 
	//
	// The plateaus in this table are given by
	//   x0 = 0
	//   x1 = x0 + 4 * 5    = 20
	//   x2 = x1 + 6 * 10   = 80
	//   x3 = x2 + 8 * 15   = 200
	//   ...
	// 
	int attacks = 1;
	int plateauThreshold = 0;
	int plateauLength = 4;
	int plateau = 5;

	while (plateauThreshold < numOfLairs)
	{
		int currentPlateau = numOfLairs - plateauThreshold;
		plateauThreshold += plateauLength * plateau;
		attacks += MIN(currentPlateau, plateauThreshold)  / plateau;
		plateauLength += 2;
		plateau += 5;
	}
	return attacks;
}
//----------- End of function fryhtan_attacks_per_six_months -----------//


//------- Begin of function fryhtan_random_attack -------//
// The fryhtans attack chance based on monster_attack_divisor (original design)
// or fryhtans attack more as lairs increase (alternate option)
static int fryhtan_random_attack()
{
	if( config_adv.monster_alternate_attack_curve )
	{
		// roll of 1/(lairs*6)
		int chance = misc.random(firm_res[FIRM_MONSTER]->total_firm_count*6);
		// From the desired average number of attacks per 6 months, we want the attack chance per lair (which is per month):
		//   Lairs * P(Attack) * 6 = Desired      <=>    P(Attack) = Desired / 6 / Lairs
		// Note that Desired < (6 * Lairs). We can use: rand(6 * Lairs) < Desired to simulate this chance.
		return chance < fryhtan_attacks_per_six_months(firm_res[FIRM_MONSTER]->total_firm_count);
	}

	// original probability 1/6
	return misc.random(firm_res[FIRM_MONSTER]->total_firm_count*config_adv.monster_attack_divisor) == 0;
}
//----------- End of function fryhtan_random_attack -----------//


//------- Begin of function FirmMonster::recover_hit_points -------//
//
void FirmMonster::recover_hit_points()
{
   //------ recover the king's hit points -------//

	if( monster_king.hit_points < monster_king.max_hit_points )
		monster_king.hit_points++;

	//------ recover the generals' hit points -------//

	for( int i=0 ; i<monster_general_count ; i++ )
	{
		if( monster_general_array[i].hit_points < monster_general_array[i].max_hit_points )
			monster_general_array[i].hit_points++;
	}
}
//--------- End of function FirmMonster::recover_hit_points -------//


//--------- Begin of function FirmMonster::assign_unit ---------//
//
// Mobilized defender units are assigned back to the firm.
//
void FirmMonster::assign_unit(int unitRecno)
{
	Unit*		 unitPtr  = unit_array[unitRecno];
	UnitInfo* unitInfo = unit_res[unitPtr->unit_id];

	err_when( !unitInfo->is_monster );

	switch( unitPtr->rank_id )
	{
		case RANK_KING:
			set_king(unitPtr->get_monster_id(), unitPtr->skill.combat_level);
			break;

		case RANK_GENERAL:
			add_general(unitRecno);
			break;

		case RANK_SOLDIER:
			add_soldier(unitPtr->leader_unit_recno);
			break;
	}

	//--------- the unit disappear in firm -----//

	unit_array.disappear_in_firm(unitRecno);
}
//----------- End of function FirmMonster::assign_unit -----------//


//--------- Begin of function FirmMonster::set_king ---------//
//
// Set the monster king of this firm.
//
void FirmMonster::set_king(int monsterId, int combatLevel)
{
	monster_king.monster_id 	= monsterId;
	monster_king.set_combat_level(combatLevel);
	monster_king.hit_points 	= monster_king.max_hit_points;

	if( firm_array.selected_recno == firm_recno )
		sys.need_redraw_flag = 1;
}
//----------- End of function FirmMonster::set_king -----------//


//--------- Begin of function FirmMonster::add_general ---------//
//
// Add a general to the firm, this function is called when a
// mobilized monster general is assigned back to the firm.
//
void FirmMonster::add_general(int generalUnitRecno)
{
	if(monster_general_count>=MAX_MONSTER_GENERAL_IN_FIRM)
		return;

	Unit*		 unitPtr  = unit_array[generalUnitRecno];
	UnitInfo* unitInfo = unit_res[unitPtr->unit_id];

	MonsterInFirm* monsterInFirm = monster_general_array+monster_general_count;

	monsterInFirm->monster_id 	  = unitPtr->get_monster_id(); 	// contribution is used for storing the monster id. temporary
	monsterInFirm->set_combat_level(unitPtr->skill.combat_level);
	monsterInFirm->hit_points 	  = (int) unitPtr->hit_points;

	if( monsterInFirm->hit_points == 0 )		// 0.? will become 0 in (float) to (int) conversion
		monsterInFirm->hit_points = 1;

	monsterInFirm->soldier_monster_id = unitPtr->get_monster_soldier_id();		// total_reward is used for storing the soldier monster id temporarily
	monsterInFirm->soldier_count   	 = 0;

	monsterInFirm->mobile_unit_recno = generalUnitRecno; 	// unit recno of this monster when it is a mobile unit
																			// this is only used as a reference for soldiers to find their leaders
	monster_general_count++;

	//----- check if there are any soldiers waiting for this general ----//
	//
	// These are soldiers who follow the general to go out to defend
	// against the attack but then went back to the firm sooner
	// than the general does.
	//
	//-------------------------------------------------------------------//

	for( int i=0 ; i<waiting_soldier_count ; i++ )
	{
		//--- if this waiting soldier was led by this general ---//

		if( waiting_soldier_array[i] == generalUnitRecno )
		{
			monsterInFirm->soldier_count++;

			err_when( waiting_soldier_count > MAX_WAITING_SOLDIER );

			misc.del_array_rec(waiting_soldier_array, waiting_soldier_count, sizeof(waiting_soldier_array[0]), i+1);
			waiting_soldier_count--;
		}
	}

	if( firm_array.selected_recno == firm_recno )
		sys.need_redraw_flag = 1;
}
//----------- End of function FirmMonster::add_general -----------//


//--------- Begin of function FirmMonster::add_soldier ---------//
//
// <int> generalUnitRecno - the unit recno of the general leading
//									 this soldier.
//
void FirmMonster::add_soldier(int generalUnitRecno)
{
	//----- check if the soldier's leading general is here ----//

	for( int i=0 ; i<monster_general_count ; i++ )
	{
		if( monster_general_array[i].mobile_unit_recno == generalUnitRecno )
		{
			if(monster_general_array[i].soldier_count>=MAX_SOLDIER_PER_GENERAL)
				return;

			monster_general_array[i].soldier_count++;
			return;
		}
	}

	//---- if not, put the soldier into the waiting list ----//

	if( waiting_soldier_count < MAX_WAITING_SOLDIER )	// if the list is full, the unit disappear in air
	{
		waiting_soldier_array[waiting_soldier_count++] = generalUnitRecno;	// the soldier is waiting for this general.
	}
}
//----------- End of function FirmMonster::add_soldier -----------//


//--------- Begin of function FirmMonster::recruit_general ---------//
//
// Recruit general monsters.
//
// [int] soldierCount - the no. of soldiers to be created with this general.
//								(default: randomly from 1 to MAX_MONSTER_PER_GENERAL)
//
int FirmMonster::recruit_general(int soldierCount)
{
	err_when( monster_general_count < 0 || monster_general_count > MAX_MONSTER_GENERAL_IN_FIRM );

	if( monster_general_count >= MAX_MONSTER_GENERAL_IN_FIRM * monster_aggressiveness / 100 )
		return 0;

	if( !monster_king.monster_id )
		return 0;

	//---------- recruit the general now ----------//

	MonsterInFirm* monsterInFirm = monster_general_array+monster_general_count;

	int combatLevel = 40 + misc.random(30);		// 40 to 70

	monsterInFirm->monster_id 	  = monster_king.monster_id;
	monsterInFirm->set_combat_level(combatLevel);
	monsterInFirm->hit_points 	  = monsterInFirm->max_hit_points;

	monsterInFirm->soldier_monster_id = monster_king.monster_id;

	if( soldierCount >= 0 )
		monsterInFirm->soldier_count = soldierCount;
	else
		monsterInFirm->soldier_count = misc.random(MAX_SOLDIER_PER_GENERAL/2)+1;

	monster_general_count++;

	if( firm_array.selected_recno == firm_recno )
		sys.need_redraw_flag = 1;

	return 1;
}
//----------- End of function FirmMonster::recruit_general -----------//


//--------- Begin of function FirmMonster::recruit_soldier ---------//
//
// Recruit soldier monsters.
//
void FirmMonster::recruit_soldier()
{
	MonsterInFirm* monsterInFirm = monster_general_array;

	for( int i=0 ; i<monster_general_count ; i++, monsterInFirm++ )
	{
		if( monsterInFirm->soldier_count < MAX_SOLDIER_PER_GENERAL &&
			 misc.random(3) > 0 )		// 2/3 chance of recruiting a soldier
		{
			monsterInFirm->soldier_count++;
		}
	}
}
//----------- End of function FirmMonster::recruit_soldier -----------//


//--------- Begin of function FirmMonster::mobilize_king ---------//
//
// The king himself does not lead any soldiers.
//
int FirmMonster::mobilize_king()
{
	if( !mobilize_monster( monster_king.monster_id, RANK_KING, monster_king.combat_level, monster_king.hit_points ) )
		return 0;

	monster_king.monster_id = 0;

	if( firm_array.selected_recno == firm_recno )
		sys.need_redraw_flag = 1;

	return 1;
}
//----------- End of function FirmMonster::mobilize_king ---------//


//--------- Begin of function FirmMonster::mobilize_general ---------//
//
// Mobilize monster generals. Soldiers led by the general are also mobilized.
//
// <int> generalId 		 - id. of the general.
// [int] mobilizeSoldier - whether also mobilize soldiers this general commands. (default: 1)
//
// Return: <int> the no. of monsters have been mobilized.
//
int FirmMonster::mobilize_general(int generalId, int mobilizeSoldier)
{
	err_when( generalId < 1 || generalId > monster_general_count );

	MonsterInFirm* monsterInFirm = monster_general_array + generalId - 1;

	//------ mobilize the monster general ------//

	int generalUnitRecno = mobilize_monster( monsterInFirm->monster_id, RANK_GENERAL, monsterInFirm->combat_level, monsterInFirm->hit_points );

	if( !generalUnitRecno )
		return 0;

	Unit* generalUnit = unit_array[generalUnitRecno];
	generalUnit->team_id = unit_array.cur_team_id;
	generalUnit->set_monster_soldier_id(monsterInFirm->soldier_monster_id);

	int mobilizedCount = 1;

	patrol_unit_array[0] = generalUnitRecno;
	patrol_unit_count = 1;

	//------ mobilize soldiers commanded by the monster general ------//

	if( mobilizeSoldier )
	{
		for( int i=0 ; i<monsterInFirm->soldier_count ; i++ )
		{
			//--- the combat level of its soldiers ranges from 25% to 50% of the combat level of the general ---//

			int soldierCombatLevel = monsterInFirm->combat_level/MONSTER_SOLDIER_COMBAT_LEVEL_DIVIDER + misc.random(monsterInFirm->combat_level/MONSTER_SOLDIER_COMBAT_LEVEL_DIVIDER);

			int unitRecno = mobilize_monster( monsterInFirm->soldier_monster_id, RANK_SOLDIER, soldierCombatLevel );

			if( unitRecno )
			{
				unit_array[unitRecno]->team_id = unit_array.cur_team_id;
				unit_array[unitRecno]->leader_unit_recno = generalUnitRecno;
				mobilizedCount++;

				patrol_unit_array[patrol_unit_count++] = unitRecno;

				if(patrol_unit_count==MAX_SOLDIER_PER_GENERAL+1)
					break;
			}
			else
				break; // no space for init_sprite
		}
	}

	//------- set the team_info of the general -------//

	err_when( !generalUnit->team_info );

	for( int i=0 ; i<patrol_unit_count ; i++ )
		generalUnit->team_info->member_unit_array[i] = patrol_unit_array[i];

	generalUnit->team_info->member_count = patrol_unit_count;

	//---- delete the monster general record from the array ----//

	err_when( monster_general_count > MAX_MONSTER_GENERAL_IN_FIRM );

	misc.del_array_rec(monster_general_array, monster_general_count, sizeof(MonsterInFirm), generalId);

	monster_general_count--;
	unit_array.cur_team_id++;

	if( firm_array.selected_recno == firm_recno )
		sys.need_redraw_flag = 1;

	return mobilizedCount;
}
//----------- End of function FirmMonster::mobilize_general ---------//


//--------- Begin of function FirmMonster::mobilize_monster ---------//
//
// <int> monsterId   = id. of the monster to be mobilized
// <int> rankId		= rank id. of the monster. 
// <int> combatLevel = the combat level of the monster
// [int] hitPoints	= set the hit points of the monster to this
//							  (default: hit points of the monster is set to the MAX hit points when it is first created.)
//
// return: <int> the unit recno of the mobile unit created.
//
int FirmMonster::mobilize_monster(int monsterId, int rankId, int combatLevel, int hitPoints)
{
	MonsterInfo* monsterInfo = monster_res[monsterId];
	UnitInfo*    unitInfo    = unit_res[monsterInfo->unit_id];

	//------- locate a space first --------//

	int			xLoc=center_x, yLoc=center_y;
	SpriteInfo* spriteInfo = sprite_res[unitInfo->sprite_id];

	if( !world.locate_space( &xLoc, &yLoc, xLoc, yLoc, spriteInfo->loc_width, spriteInfo->loc_height, unitInfo->mobile_type ) )
		return 0;

	//---------- add the unit now -----------//

	int unitRecno = unit_array.add_unit( unitInfo->unit_id, 0,
						 rankId, 0, xLoc, yLoc );

	UnitMonster* monsterPtr = (UnitMonster*) unit_array[unitRecno];

	monsterPtr->set_mode(UNIT_MODE_MONSTER, firm_recno);
	monsterPtr->set_combat_level(combatLevel);
	monsterPtr->set_monster_id(monsterId);
	monsterPtr->set_monster_action_mode(current_monster_action_mode);

	if( hitPoints )
	{
		monsterPtr->hit_points = (float) hitPoints;
		err_when( hitPoints > monsterPtr->max_hit_points );
	}
	else
		monsterPtr->hit_points = monsterPtr->max_hit_points;

	//-----------------------------------------------------//
	// enable unit defend mode
	//-----------------------------------------------------//
	if(firm_recno) // 0 when firm is ready to be deleted
	{
		monsterPtr->stop2();
		monsterPtr->action_mode2 = ACTION_MONSTER_DEFEND_DETECT_TARGET;
		monsterPtr->action_para2 = MONSTER_DEFEND_DETECT_COUNT;

		monsterPtr->action_misc = ACTION_MISC_MONSTER_DEFEND_FIRM_RECNO;
		monsterPtr->action_misc_para = firm_recno;
	}

	return unitRecno;
}
//----------- End of function FirmMonster::mobilize_monster -----------//


//--------- Begin of function FirmMonster::being_attacked ---------//
//
// This function is called by Unit::hit_firm()
//
// <int> attackerUnitRecno - recno of the unit attacking this firm.
//
void FirmMonster::being_attacked(int attackerUnitRecno)
{
	int attackerNationRecno = unit_array[attackerUnitRecno]->nation_recno;

	//--- increase reputation of the nation that attacks monsters ---//

	if( attackerNationRecno )
	{
		nation_array[attackerNationRecno]->change_reputation(REPUTATION_INCREASE_PER_ATTACK_MONSTER);
		set_hostile_nation(attackerNationRecno);		// also set hostile with the nation
	}

	//------ MAX no. of defender it should call out -----//

	int maxDefender = MAX_MONSTER_IN_FIRM * monster_aggressiveness / 200;

	if( total_defender() >= maxDefender )	// only mobilize new ones when the MAX defender no. has been reached yet
		return;

	current_monster_action_mode = MONSTER_ACTION_DEFENSE;

	//---- mobilize monster general to defend against the attack ----//

	if( monster_general_count > 0 )
	{
		int mobilizedCount = mobilize_general( misc.random(monster_general_count)+1 );

		if(mobilizedCount)
		{
			defending_general_count++;
			defending_soldier_count += mobilizedCount-1;
		}
	}

	else if( monster_king.monster_id )
	{
		if( mobilize_king() )
			defending_king_count++;
	}

	defend_target_recno = attackerUnitRecno;
}
//----------- End of function FirmMonster::being_attacked -----------//


//-------- Begin of function FirmMonster::clear_defense_mode -------//
//
// This function is called when the firm monster is destroyed.
//
void FirmMonster::clear_defense_mode()
{
	//------------------------------------------------------------------//
	// change defense unit's to non-defense mode
	//------------------------------------------------------------------//

	Unit *unitPtr;

	for(int i=unit_array.size(); i>=1; --i)
	{
		if(unit_array.is_truly_deleted(i))
			continue;

		unitPtr = unit_array[i];

		//------ reset the monster's defense mode -----//

		if(unitPtr->in_monster_defend_mode() && unitPtr->action_misc==ACTION_MISC_MONSTER_DEFEND_FIRM_RECNO &&
			unitPtr->action_misc_para==firm_recno)
		{
			unitPtr->clear_monster_defend_mode();
			((UnitMonster*)unitPtr)->set_monster_action_mode(MONSTER_ACTION_STOP);
		}

		//--- if this unit belongs to this firm, reset its association with this firm ---//

		if( unitPtr->unit_mode == UNIT_MODE_MONSTER &&
			 unitPtr->unit_mode_para == firm_recno )
		{
			unitPtr->unit_mode_para = 0;
		}
	}
}
//----------- End of function FirmMonster::clear_defense_mode -----------//


//-------- Begin of function FirmMonster::reduce_defender_count -------//
//
// A defender unit moves back into the firm, reducing the defender count.
//
// <int> rankId - rank id. of the defender unit
//
void FirmMonster::reduce_defender_count(int rankId)
{
	switch(rankId)
	{
		case RANK_KING:
			defending_king_count--;
			if( defending_king_count < 0 )			//**BUGHERE
				defending_king_count = 0;
			err_when( defending_king_count < 0 );
			break;

		case RANK_GENERAL:
			defending_general_count--;
			if( defending_general_count < 0 )			//**BUGHERE
				defending_general_count = 0;
			err_when( defending_general_count < 0 );
			break;

		case RANK_SOLDIER:
			defending_soldier_count--;
			if( defending_soldier_count < 0 )			//**BUGHERE
				defending_soldier_count = 0;
			err_when( defending_soldier_count < 0 );
			break;
	}

	if( total_defender()==0 )
		monster_nation_relation = 0;
}
//------ End of function FirmMonster::reduce_defender_count ---------//


//-------- Begin of function MonsterInFirm::set_combat_level -------//

void MonsterInFirm::set_combat_level(int combatLevel)
{
	UnitInfo* unitInfo = unit_res[monster_res[monster_id]->unit_id];

	combat_level = combatLevel;

	max_hit_points = (int) unitInfo->hit_points * combatLevel / 100;
}
//--------- End of function MonsterInFirm::set_combat_level --------//


//-------- Begin of function FirmMonster::total_combat_level -------//
//
int FirmMonster::total_combat_level()
{
	MonsterInFirm* monsterInFirm = monster_general_array;
	int				totalCombatLevel=50;		// for the structure 

	for( int i=0 ; i<monster_general_count ; i++, monsterInFirm++ )
	{
		totalCombatLevel += monsterInFirm->hit_points +
			 (monsterInFirm->combat_level * 2 / MONSTER_SOLDIER_COMBAT_LEVEL_DIVIDER)			// *2 because total_combat_level() actually takes hit_points instead of combat_level
			 * 3 / 2 * monsterInFirm->soldier_count;		// *3/2 because the function use 100% + random(100%) for the combat level, so we take 150% as the average
	}

	return totalCombatLevel;
}
//------ End of function FirmMonster::total_combat_level ---------//


//-------- Begin of function FirmMonster::can_assign_monster -------//
//
// Return whether the given monster can be assigned to this firm.
//
// It checks if the type of the monster and the type of the monster
// structure are compatible.
//
int FirmMonster::can_assign_monster(int unitRecno)
{
	Unit* unitPtr = unit_array[unitRecno];

	return strcmp( firm_res.get_build(firm_build_id)->build_code,	// can assign if the build code are the same
			 monster_res[unitPtr->get_monster_id()]->firm_build_code ) == 0;
}
//------ End of function FirmMonster::can_assign_monster ---------//


//-------- Begin of function FirmMonster::set_hostile_nation -------//
void FirmMonster::set_hostile_nation(int nationRecno)
{
	if(nationRecno==0)
		return;

	err_when(nationRecno>7); // only 8 bits
	monster_nation_relation |= (0x1 << nationRecno);
}
//------ End of function FirmMonster::set_hostile_nation ---------//


//-------- Begin of function FirmMonster::reset_hostile_nation -------//
void FirmMonster::reset_hostile_nation(int nationRecno)
{
	if(nationRecno==0)
		return;

	err_when(nationRecno>7); // only 8 bits
	monster_nation_relation &= ~(0x1 << nationRecno);
}
//------ End of function FirmMonster::reset_hostile_nation ---------//


//-------- Begin of function FirmMonster::is_hostile_nation -------//
// return 1 for hostile nation
// return 0 otherwise
//
int FirmMonster::is_hostile_nation(int nationRecno)
{
	if(nationRecno==0)
		return 0;

	err_when(nationRecno>7); // only 8 bits
	return (monster_nation_relation & (0x1 << nationRecno));
}
//------ End of function FirmMonster::is_hostile_nation ---------//


//------- Begin of function FirmMonster::validate_patrol_unit ---------//
//
void FirmMonster::validate_patrol_unit()
{
	if(patrol_unit_count<=0)
		return;

	int 	unitRecno;
	Unit* unitPtr;

	for( int i=patrol_unit_count ; i>0 ; i-- )
	{
		unitRecno = patrol_unit_array[i-1];

		if( unit_array.is_deleted(unitRecno) ||
			 (unitPtr=unit_array[unitRecno])->is_visible()==0 )
		{
			err_when( patrol_unit_count > MAX_SOLDIER_PER_GENERAL+1 );

			misc.del_array_rec( patrol_unit_array, patrol_unit_count, sizeof(patrol_unit_array[0]), i );

			err_when( patrol_unit_count==0 );		// it's already 0

			patrol_unit_count--;
		}
	}

	err_when(patrol_unit_count<0);
	if(patrol_unit_count==0 && total_defender()==0)
		monster_nation_relation = 0;
	else if(patrol_unit_count<0)
		patrol_unit_count = 0;
}
//-------- End of function FirmMonster::validate_patrol_unit ---------//


//------- Begin of function FirmMonster::think_attack_neighbor -------//
//
int FirmMonster::think_attack_neighbor()
{
	//-- don't attack new target if some mobile monsters are already attacking somebody --//

	if( patrol_unit_count > 0 )
		return 0;

	//-------- only attack if we have enough generals ---------//

	int generalCount=0;

	MonsterInFirm* monsterInFirm = monster_general_array;
	int				totalCombatLevel=0;

	//--- count the number of generals commanding at least 5 soldiers ---//

	int i;
	for( i=0 ; i<monster_general_count ; i++, monsterInFirm++ )
	{
		if( monsterInFirm->soldier_count >= 5 )
			generalCount++;
	}

	if( generalCount<=1 )	// don't attack if there is only one general in the firm
		return 0;

	//------ look for neighbors to attack ------//

	int		 xOffset, yOffset;
	int		 xLoc, yLoc;
	int		 attackFlag=0;
	FirmInfo* firmInfo = firm_res[firm_id];
	Location* locPtr;

	int scanLocWidth  = MONSTER_ATTACK_NEIGHBOR_RANGE*2;
	int scanLocHeight = MONSTER_ATTACK_NEIGHBOR_RANGE*2;
	int scanLimit = scanLocWidth * scanLocHeight;
	short targetNation;

	for(i=firmInfo->loc_width*firmInfo->loc_height+1; i<=scanLimit; i++)
	{
		misc.cal_move_around_a_point(i, scanLocWidth, scanLocHeight, xOffset, yOffset);

		xLoc = center_x + xOffset;
		yLoc = center_y + yOffset;

		xLoc = MAX(0, xLoc);
		xLoc = MIN(MAX_WORLD_X_LOC-1, xLoc);

		yLoc = MAX(0, yLoc);
		yLoc = MIN(MAX_WORLD_Y_LOC-1, yLoc);

		locPtr = world.get_loc(xLoc, yLoc);

		if(locPtr->is_firm())
		{
			Firm *firmPtr = firm_array[locPtr->firm_recno()];
			if(firmPtr->nation_recno)
			{
				targetNation = firmPtr->nation_recno;
				attackFlag = 1;
				xLoc = firmPtr->loc_x1;
				yLoc = firmPtr->loc_y1;
				break;
			}
		}
		else if(locPtr->is_town())
		{
			Town *townPtr = town_array[locPtr->town_recno()];
			if(townPtr->nation_recno)
			{
				targetNation = townPtr->nation_recno;
				attackFlag = 1;
				xLoc = townPtr->loc_x1;
				yLoc = townPtr->loc_y1;
				break;
			}
		}
	}

	if( !attackFlag )
		return 0;

	//------- attack the civilian now --------//
	current_monster_action_mode = MONSTER_ACTION_ATTACK;

	mobilize_general( misc.random(monster_general_count)+1 );

	if( patrol_unit_count > 0 )
	{
		//### begin alex 16/9 ###//
		set_hostile_nation(targetNation);
		//#### end alex 16/9 ####//
		// ##### patch begin Gilbert 5/8 #######//
		unit_array.attack(xLoc, yLoc, 0, patrol_unit_array, patrol_unit_count, COMMAND_AI, 0);
		// ##### patch end Gilbert 5/8 #######//
		return 1;
	}
	else
		return 0;
}
//-------- End of function FirmMonster::think_attack_neighbor -------//


//-------- Begin of function FirmMonster::think_expansion -------//
//
int FirmMonster::think_expansion()
{
	#define MIN_GENERAL_EXPAND_NUM 3

	if(patrol_unit_count>0)
		return 0;

	if(!monster_king.monster_id || monster_general_count<MIN_GENERAL_EXPAND_NUM)
		return 0;

	//--- count the number of generals commanding 8 soldiers ----//

	int generalCount=0;

	MonsterInFirm* monsterInFirm = monster_general_array;

	for( int i=0 ; i<monster_general_count ; i++, monsterInFirm++ )
	{
		if( monsterInFirm->soldier_count == MAX_SOLDIER_PER_GENERAL )
			generalCount++;
	}

	if( generalCount < MIN_GENERAL_EXPAND_NUM )	// don't expand if the no. of generals is less than 3
		return 0;

	//------------- locate space to build monster firm randomly -------------//
	#define EXPAND_FIRM_DISTANCE	30
	#define FREE_SPACE_DISTANCE	3

	MonsterInfo* monsterInfo = monster_res[monster_king.monster_id];
   FirmInfo* firmInfo = firm_res[FIRM_MONSTER];
   char	teraMask = UnitRes::mobile_type_to_mask(UNIT_LAND);
   int	xLoc1 = MAX(0, loc_x1-EXPAND_FIRM_DISTANCE);
	int	yLoc1 = MAX(0, loc_y1-EXPAND_FIRM_DISTANCE);
	int	xLoc2 = MIN(MAX_WORLD_X_LOC-1, loc_x2+EXPAND_FIRM_DISTANCE);
	int	yLoc2 = MIN(MAX_WORLD_Y_LOC-1, loc_y2+EXPAND_FIRM_DISTANCE);

   if( !world.locate_space_random(xLoc1, yLoc1, xLoc2, yLoc2,
		 firmInfo->loc_width+FREE_SPACE_DISTANCE*2,
		 firmInfo->loc_height+FREE_SPACE_DISTANCE*2,   // leave at least 3 location space around the building
		 (xLoc2-xLoc1+1)*(yLoc2-yLoc1+1), 0, 1, teraMask) )
	{
		return 0;
	}

	monster_general_count--;
	//monsterInFirm = monster_general_array + monster_general_count;
	//unit_array.disappear_in_firm(monsterInFirm->mobile_unit_recno);

	return monsterInfo->build_firm_monster(xLoc1+FREE_SPACE_DISTANCE, yLoc1+FREE_SPACE_DISTANCE, 1);		//1-full hit points
}
//------ End of function FirmMonster::think_expansion ---------//


//------- Begin of function FirmMonster::think_attack_human -------//
//
int FirmMonster::think_attack_human()
{
	//-- don't attack new target if some mobile monsters are already attacking somebody --//

	if( patrol_unit_count > 0 )
		return 0;

	//-------- only attack if we have enough generals ---------//

	int generalCount=0;

	MonsterInFirm* monsterInFirm = monster_general_array;
	int				totalCombatLevel=0;

	//--- count the number of generals commanding at least 5 soldiers ---//

	int i;
	for( i=0 ; i<monster_general_count ; i++, monsterInFirm++ )
	{
		if( monsterInFirm->soldier_count >= 5 )
			generalCount++;
	}

	if( generalCount<=1 )	// don't attack if there is only one general in the firm
		return 0;

	//------ look for neighbors to attack ------//

	int   firmRecno, townRecno;
	int   targetXLoc= -1, targetYLoc, targetNationRecno=0;
	Firm* firmPtr;
	Town* townPtr;

	for(i=1 ; i<100 ; i++ )
	{
		//----- randomly pick a firm ------//

		firmRecno = misc.random(firm_array.size()) + 1;

		if( firm_array.is_deleted(firmRecno) )
			continue;

		firmPtr = firm_array[firmRecno];

		if( firmPtr->region_id == region_id )
		{
			targetXLoc = firmPtr->loc_x1;
			targetYLoc = firmPtr->loc_y1;
			targetNationRecno = firmPtr->nation_recno;
			break;
		}

		//----- randomly pick a town ------//

		townRecno = misc.random(town_array.size()) + 1;

		if( town_array.is_deleted(townRecno) )
			continue;

		townPtr = town_array[townRecno];

		if( townPtr->nation_recno && townPtr->region_id == region_id )
		{
			targetXLoc = townPtr->loc_x1;
			targetYLoc = townPtr->loc_y1;
			targetNationRecno = townPtr->nation_recno;
			break;
		}
	}

	if( targetXLoc == -1 )		// no target selected
		return 0;

	//------- attack the civilian now --------//

	current_monster_action_mode = MONSTER_ACTION_ATTACK;

	mobilize_general( misc.random(monster_general_count)+1 );

	if( patrol_unit_count > 0 )
	{
		set_hostile_nation(targetNationRecno);
		// ##### patch begin Gilbert 5/8 #######//
		unit_array.attack(targetXLoc, targetYLoc, 0, patrol_unit_array, patrol_unit_count, COMMAND_AI, 0);
		// ##### patch end Gilbert 5/8 #######//
		return 1;
	}
	else
		return 0;
}
//-------- End of function FirmMonster::think_attack_human -------//


//--------- Begin of function disp_worker_hit_points ---------//
//
static void disp_worker_hit_points(int x1, int y1, int x2, int hitPoints, int maxHitPoints)
{
	//------- determine the hit bar type -------//

	#define HIT_BAR_TYPE_COUNT  3

	int  hit_bar_color_array[HIT_BAR_TYPE_COUNT] = { 0xA8, 0xB4, 0xAC };
	int  hit_bar_max_array[HIT_BAR_TYPE_COUNT] 	= { 50, 100, 200 };
	char hitBarColor;

	for( int i=0 ; i<HIT_BAR_TYPE_COUNT ; i++ )
	{
		if( maxHitPoints <= hit_bar_max_array[i] || i==HIT_BAR_TYPE_COUNT-1 )
		{
			hitBarColor = hit_bar_color_array[i];
			break;
		}
	}

	//------- draw the hit points bar -------//

	enum { HIT_BAR_DARK_BORDER = 3,
			 HIT_BAR_BODY 		   = 1 };

	int barWidth = (x2-x1+1) * hitPoints / MAX(hitPoints, maxHitPoints);

	vga_front.bar( x1, y1, x1+barWidth-1, y1+1, hitBarColor + HIT_BAR_BODY );

	if( x1+barWidth <= x2 )
		vga_util.blt_buf( x1+barWidth, y1, x2, y1+1, 0 );

	y1+=2;

	vga_front.bar( x1, y1, x1+barWidth-1, y1, hitBarColor + HIT_BAR_DARK_BORDER );
	vga_front.bar( x1+barWidth, y1, x1+barWidth, y1, V_BLACK );

	if( x1+barWidth+1 <= x2 )
		vga_util.blt_buf( x1+barWidth+1, y1, x2, y1, 0 );

	y1++;

	vga_front.bar( x1+1, y1, x1+barWidth, y1, V_BLACK );

	if( x1+barWidth+1 <= x2 )
		vga_util.blt_buf( x1+barWidth+1, y1, x2, y1, 0 );
}
//----------- End of function disp_worker_hit_points -----------//
